02
Feb
08

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.


2 Responses to “Wrapping Conditional Access with with_scope”


  1. 1 Neil Wilson Feb 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.

  2. 2 benhughes Feb 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.

Leave a Reply




  • Ben Hughes

    I'm a freelance developer working with Ruby and other modern tools to build web applications, based currently out of Rochester, NY. I love to learn about new technologies and am always trying to achieve elegance and beauty through code.

    When I'm not writing software, I like to play tennis, dabble in jazz piano, and ponder economics. I'm a big fan of: world travel and cultures, jazz music, Korean food, coffee, and having interesting conversations.

  • Recommend Me
February 2008
M T W T F S S
« Jan   Mar »
 123
45678910
11121314151617
18192021222324
2526272829