March 16, 2008

Scoped Access - stay DRY

Filed under: rails — Casper Fabricius @ 6:59 pm

One of my first hobby projects in Ruby on Rails, and for sure the longest living, is called Dinnerlyzer. It is a small, simple, pink (!) application that my girlfriend has used to plan our meals and the required shopping for several years. So far she is the only user, but a while ago I found myself wanting to add a user model and open up the application for multiple users. This meant that I had a lot of objects (recipes, ingredients, shopping lists, etc.) that needed to be associated with a user - the owner. It also meant that some objects should be shared between users (like the ingredients) while other should be private (like the shopping lists). This, in turn, meant that I stood with the prospect of adding a lot of filtering and setting of the correct user in my controllers.

Last week, I was hacking away at another hobby project with a couple of friends (one that will of course make us all zillionaires at some point in the future), where we found ourself wanting to be able to run the same application for multiple sites, but using the same physical backend. This meant the introduction of a Site object, associating almost all existing objects with a specific site, and - again - the prospect of adding a lot of filtering and setting of the correct site in the controllers.

In both cases I could have chosen to just go into the controllers and add the appropriate code to make sure all lookups and deletes was filtered by User or Site, respectively, and that all inserts and updates got these entities tagged on as well, but it just didn’t seem very DRY. It was too much work, it was not fun, and it was repeating myself.

My first impulse was to use observers. It is a Rails classic to use a UserObserver to make sure that fields such as creator_id or updater_id are set the currently active user. This is, however, done per class and not per controller, so if you want to differentiate, say, to filter on the owning user depending on which action has been invoked, you are generally out of luck. (Generally, because you can of course always hack your way to a solution.)

Then I asked Uncle Google for help, and - lo and behold - I found the Scoped Access plugin. Rails has had the with_scope method for a long time, but to my understanding it is not quite as powerful and DRY compared to this plugin. Let me give an example:

In Dinnerlyzer, I needed to restrict all access to recipes per user. I installed the Scoped Access plugin, and added this code to my RecipeController:

class RecipesController < ApplicationController
  scoped_access Recipe

  ..

 protected

  def method_scoping
    ScopedAccess::ClassScoping.new(Recipe, :user_id => current_user.id)
  end
end

The scoped_access call at the top ensures that the method method_scoping is called to return the appropriate scope for all requests on the Recipe object. Not just lookups, but also creation, editing and deletion.

In another controller, I wanted all users to be able to browse and view all ingredients in the application, but only to be able to edit and delete ingredients added by themselves. This was done as simple as:

  scoped_access Ingredient, :except => [:index, :show]

In the other application, we were able to employ a similar strategy, allowing us to very fast and in a quite DRY way to add these access restrictions to our controllers.

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.