Accessible Resources List: The DRY Solution for Controllers

by Andrey KoleshkoApril 18, 2013
This blog post explains how to achieve the DRY solution for controller's actions that return the list of scoped objects to avoid repetitive changes.

Imagine that we have a Trademarks controller with an index action. However, we shouldn’t get all the trademarks in this action. What we need instead is to get all the trademarks accessible by the current user only. This is a challenge we are going to solve.

There is a constraint here, though. You are not able to use the abilities defined by blocks (please, read this guide on how to define abilities). To learn more about the fetching records technique, please check out this documentation.

 

The sledgehammer

The simplest solution will be something looking like shown below.

class TrademarksController < ApplicationController
  def index
    @trademarks =Trademark.where :active => true
  end
end

One day we have to change our conditions for the selected trademarks.

class TrademarksController < ApplicationController
  def index
    @trademarks = Trademark.where :active => true, :owner_id => current_user.id
  end
end

Surely, we can define the scope of the Trademark model, but this will not save us from changes in the controller. We still have a chance to edit an index action in the feature. For example, we would have to combine two scopes there.

 

The DRY solution

There is a better solution that can help us to avoid these repetitive
tasks. This solution is achieved by installing the cancan gem. I hope you don’t hate this gem and can add it to the project.

So, the first step for this solution as you have already guessed will
be installing cancan. For this purpose, add cancan to the Gemfile and run the bundle install command in your terminal. Then, initialize with rails g cancan:ability.

Open the /app/models/ability.rb file and define the abilities. Below, you can see how to do it.

class Ability
  include CanCan::Ability

  def< initialize(user)
    user ||= User.new
   can :read, Trademark, :active =&gt; true, :owner_id =&gt; user.id
  end
end

Currently, we are at the last step. We have to refactor a controller. Use the accessible_by method provided by cancan as shown blow.

class TrademarksController &lt; ApplicationController
  def index
    @trademarks = Trademark.accessible_by(current_ability)
 end
end

Implementation of the accessible_by method is quite simple.

def accessible_by(ability, action = :index)
  ability.model_adapter(self action).database_records
end

It just fetches records from the database according to specified scopes in the abilities. Pay attention that you are able to pass any action here, not only the index action.

Here we are. The code in the controller’s action won’t be changed as often as it was before. I think, we managed to minimize the possibility of changes for this action.

 

Further reading

 

About the author

Andrey Koleshko is a Ruby developer at Altoros. He is majoring in delivering payment gateway solutions, as well as systems for billing, invoicing, subscriptions, bookkeeping, and accounting. Find him on GitHub.