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.
Hello, I'm Casper Fabricius. I have developed for the web for 10 years, and have been enjoying Ruby on Rails for the past 5.
My experience covers communities, shopping solutions, multi-language sites, heavy back-end lifting and a wide selection of more traditional websites. I like to integrate Ruby with Java and .NET through JRuby and IronRuby when it makes sense. I am passionate about test- and behavior-driven development, but at the same time I am pragmatic and believe in getting things done.
I live in Copenhagen, Denmark, where I work for a fantastic company: Podio. I do not currently take on freelance assignments.