Imagine that we have a Trademarks controller with an index action. But in this action, we shouldn’t get all trademarks. Instead, we have to get all active trademarks that are accessible to the current user only. This is the challenge I’m going to solve here.
The sledge-hammer
The simplest solution will be something like this:
class TrademarksController < ApplicationController
def index
@trademarks = Trademark.where :active => true
end
endOne day, we have to change our conditions for selecting trademarks:
class TrademarksController < ApplicationController
def index
@trademarks = Trademark.where :active => true, :owner_id => current_user.id
end
endOf course, we can define a scope on the Trademark model, but this will not save us from changes in the controller — we may still need to edit the index action in the future. For example, we might have to combine two scopes there.
DRY solution
There is a better solution that can help us avoid these repetitive tasks. It requires installing the cancan gem. I hope you don’t hate this gem and can add it to the project.
So the first step, as you have already guessed, is to install cancan — add it to the Gemfile and install it by running bundle install in your terminal. Initialize it with rails g cancan:ability.
Open the /app/models/ability.rb file and define the abilities there. Let’s do it:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
can :read, Trademark, :active => true, :owner_id => user.id
end
endNow we are at the last step. We have to refactor the controller — use the accessible_by method provided by cancan:
class TrademarksController < ApplicationController
def index
@trademarks = Trademark.accessible_by(current_ability)
end
endThe implementation of the accessible_by method is very simple:
def accessible_by(ability, action = :index)
ability.model_adapter(self, action).database_records
endIt just fetches records from the database according to the scopes specified in the abilities. Note that you can pass any action here, not only the index action as I did.
Here we are. The code in the controller action won’t change as often as before. I think we have reduced the need for changes to this action as much as possible.
There is one constraint here — you cannot use abilities defined with blocks (please read this doc about how to define abilities).
The documentation for the Fetching Records technique is here.