The addition of named_scope in Rails 2.1 has revealed several elegant approaches for modeling complex problem domains in ActiveRecord. One I came across recently while working on an app with a somewhat complex permissions system was a permission-based filtering mechanism. In this case I was dealing with permission for a given user to manage an “office”, while a user could be at one of three permission “levels”, one of which has specific office assignments (or it’s assumed all are manageable if user.can_manage_all_offices is true). Lot’s of necessary conditional logic there.
Now a normal approach to such a task to “show a list of offices that the user can manage” (for a drop-down for an interface perhaps) might be something like this:
# In controller:
if current_user.can_manage_company?
@offices = Office.find(:all)
elsif current_user.office_access_level? && current_user.can_manage_all_offices?
@offices.find(:all)
elsif current_user.office_access_level?
@offices.find(:all, :conditions => {:id => current_user.manageable_offices.map(&:id) })
else
@offices = []
end
But this approach starts at the user level and, using a lot of conditional logic baked right into places we don’t want, makes different calls to Office, which isn’t very DRY, and certainly not consistent with fat models skinny controllers. So I considered inverting this approach and instead starting with an office, and asking it what “is manageable by” a given user. Consider this alternative:
# In office.rb
named_scope :manageable_by, lambda {|user|
case
when user.can_manage_company? then {}
when user.office_access_level? && user.can_manage_all_offices? then {}
when user.office_access_level? then {:conditions => {:id => user.manageable_offices.map(&:id)}}
else {:conditions => "1 = 0"}
end
}
# Then in controller:
@offices = Office.manageable_by(current_user)
This seems much more elegant to me. I’m in general finding a lot of opportunities for inverting the way I designed something without named_scope to be more model-centric, so this approach helps further the design principle of “fat models, skinny controllers”. Sure you could do this before by defining your own methods and using with_scope, but named_scope just makes it all the more elegant.
Another advantage with using the named scope stuff is that you can chain scopes together. Let’s say that in another controller I want to do the same thing but instead restrict results to active offices only. I can create an “active” named scope that scopes :conditions => {:active => true}, then in the controller simply do this instead:
@offices = Office.manageable_by(current_user).active



cool beans
Legend, thanks for this hot tip.. saved me at least 10 lines of code!
Hey nice template by the way
I have the following code that is now working as expected. Do you have any ideas?
named_scope :by_company, lambda{{
case
when COMPANY_ID.nil? then false
else {:conditions => ['company_id = ?', COMPANY_ID]}
end
}}
Any help would be greatly appreciated.
Thanks,
Justin
Sorry, I forgot to give the error:
cet_named_scope.rb:13: odd number list for Hash
Line 13 is the ‘end’ of the case statement.
Try this:
named_scope :by_company, lambda { {:conditions => COMPANY_ID.nil? ? {} : {:company_id => COMPANY_ID} } }
However if COMPANY_ID really is a constant (not sure how or why you are using it this way…) you don’t really even need the lambda since lazy evaluation is unnecessary:
named_scope :by_company, {:conditions => COMPANY_ID.nil? ? {} : {:company_id => COMPANY_ID} }
Hi Ben,
Thanks, that worked perfect. I used:
named_scope :by_company, {:conditions => COMPANY_ID.nil? ? {} : {:company_id => COMPANY_ID} }
I see now where my syntax was wrong. I tried something like above, but kept getting errors.
I appreciate the help.
Regards,
Justin