Skip to content

Latest commit

 

History

History
80 lines (54 loc) · 2.71 KB

define_abilities_best_practices.md

File metadata and controls

80 lines (54 loc) · 2.71 KB

Defining Abilities: Best Practices

Use hash conditions as much as possible

Although scopes are fine for fetching, they pose a problem when authorizing a discrete action.

For example, this declaration in Ability:

can :read, Article, Article.is_published

causes this CanCan::Error:

The can? and cannot? call cannot be used with a raw sql 'can' definition.
The checking code cannot be determined for :read #<Article ..>.

A better way to define the same is:

can :read, Article, is_published: true

Hash conditions are DRYer.

By using hashes instead of blocks for all actions, you won't have to worry about translating blocks used for member controller actions (:create, :destroy, :update) to equivalent blocks for collection actions (:index, :show)—which require hashes anyway!

Hash conditions are OR'd in SQL, giving you maximum flexibility.

Every time you define an ability with can, each can chains together with OR in the final SQL query for that model.

So if, in addition to the is_published condition above, we want to allow authors to see their drafts:

can :read, Article, author_id: @user.id, is_published: false

Then the final SQL would be:

SELECT `articles`.*
FROM   `articles`
WHERE  `articles`.`is_published` = 1
OR ( `articles`.`author_id` = 97 AND `articles`.`is_published` = 0 )

For complex object graphs, hash conditions accommodate joins easily.

See Hash of Conditions Chapter.

Give permissions, don't take them away

As suggested in this topic on Reddit you should, when possible, give increasing permissions to your users.

CanCanCan increases permissions: it starts by giving no permissions to nobody and then increases those permissions depending on the user.

A properly written ability.rb looks like that:

class Ability
  include CanCan::Ability

  def initialize(user)
    can :read, Post  # start by defining rules for all users, also not logged ones
    return unless user.present?
    can :manage, Post, user_id: user.id # if the user is logged in can manage it's own posts
    can :create, Comment # logged in users can also create comments
    return unless user.manager? # if the user is a manager we give additional permissions
    can :manage, Comment # like managing all comments in the website
    return unless user.admin?
    can :manage, :all # finally we give all remaining permissions only to the admins
  end
end

following this good practice will help you to keep your permissions clean and more readable.

The risk of giving wrong permissions to the wrong users is also decreased.