Wrapping Conditional Access with with_scope
ActiveRecord::Base exposes a great way of keeping access control DRY. A very common paradigm in web application development is showing “all” of something to administrators but only only “active” of something to regular users/visitors. In non-ORM PHP you might do something like like this in each SQL statement:
$sql = "SELECT * FROM entries WHERE type = 1 "; $sql .= (!$is_admin ? "AND active = 1" : "");
This isn’t a very well-architected approach since this type of tacking onto the SQL string would have to be done in any statement that involved this table, for all of your “actions” like show, edit, and index. It tightly couples this particularly piece of add-on logic to your base code. In Rails we can scope out this functionality to a “scope_access” method in our controller like so:
class EntriesController < ApplicationController
around_filter :scope_access
# (Action Methods)
protected
def scope_access
unless current_user && current_user.is_admin?
Entry.send(:with_scope, :find => {:conditions => 'active = 1'}) do
yield
end
else
yield
end
end
end
We are declaring an around_filter which uses with_scope if the current user is an admin, otherwise it will do so without scope. This is an excellent example of how Rails allows you to architect your code to be very elegant. Note that with_method is now a protected method on ActiveRecord::Base hence our need to use ClassName.send.
The with_scope method is actually useful for many other purposes including running many but similar find statements. Aside from finders, this can actually scope attributes on create and more. I’d highly recommend you check it out in the Rails documentation and start making extensive use of it.


February 27th, 2008 at 11:43 am
with_scope is protected for a reason - people abuse it instead of creating associations properly using the modelling tools. It is a ‘code smell’ that suggests you are using a Rails anti-patterns.
“has_many :conditions” is your friend, or perhaps something like this:
http://weblog.jamisbuck.org/2007/1/9/extending-activerecord-associations
Try it between the User and Entry models and see what you come up with.
February 27th, 2008 at 2:07 pm
has_many :conditions is not a solution to this problem and I’m not sure you fully understood the problem my post is trying to solve. In my original code, forcing the requirement of active = 1 on ALL queries involving Entry within the controller is completely handled by the around filter and with_scope in a most AOP manner. I’m not looking for a elegant way of getting active only entries as such a solution would require explicitly saying “something.entries.active”. The above is completely transparent to the rest of the controller except of course if you are manually invoking find_by_conditions.
But you raise a point about with_scope being protected. Just as easily I could have defined a method in entry that handled this:
def self.scope_to_active self.send(:with_scope, :find => {:conditions => 'active = 1'}) do yield end end.. and then called it from the controller. This is probably a better solution. If you want to use this with multiple models you could even create a module and mix it in since the above code is generic so long as the model has an “active” attribute. You can then nest-scope multiple of these.