Keep Finders on Their Own Model
Moving the find calls out of the controller layer in your Rails application and into custom finders on the model is a strong step in the right direction of producing maintainable software.
For Example
Optimize(1)
class UsersController < ApplicationController
def index
@user = User.find(params[:id])
@members = @user.members.where(:active => true).limit(5).order("last_active_on DESC")
end
end
We know that we diligently move that scope chain into a method on the User model.
Now If we write code in Model then
Optimize(2)
class UsersController < ApplicationController
def index
@user = User.find(params[:id])
@recent_active_members = @user.find_recent_active_members
end
end
In User Model
class User < ActiveRecord::Base
has_many :members
def find_recent_active_members
memberships.where(:active=>true).limit(5).order("last_active_on DESC")
end
end
This is definitely an improvement. UsersController is now much thinner, and the method name reveals intent nicely. But you can do more...
By making use of the power of Active Record associations, you can trim up this example even further. You can define a finder on member, and you can then acess that finder through the User#members association, as follows:
Optimize(3)
class User < ActiveRecord::Base
has_many :members
def find_recent_active_members
member.find_recently_active
end
end
class Member < ActiveRecord::Base
belongs_to :user
def self.find_recently_active
where(:active=>true).limit(5).order("last_active_on DESC")
end
end
Now, This is much better. The application now honors the MVC boundaries and delegates domain model responsibilities cleanly. But you can do more...
You can make use of scopes here to produce a variety of finders on Membership that you can then chain together to get the results you're looking for:
Optimize(4)
class User < ActiveRecord::Base
has_many :members
def find_recent_active_members
members.only_active.order_by_activity.limit(5)
end
end
class Member < ActiveRecord::Base
belongs_to :user
scope :only_active, where(:active => true)
scope :order_by_activity, order('last_active_on DESC')
end
By making the re-factoring in this last step, you've generalized a lot of the code. Instead of only having a Member#find_recently_active method, you now have three class methods that you can mix and match to your heart's desire.
Anti Pattern
ReplyDelete