Accessible Resources List: The DRY Solution for Controllers
DRYsolution 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 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.
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
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.
/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
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.
- 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.