Accessible Resources List: The DRY Solution for Controllers
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 => true, :owner_id => 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 < 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
- Things that I Hate as a Web Developer
- Visual Studio Code Really Surprised Me
- Spiderman’s Extenstion Methods
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.