Nathan Pena Nathan Pena - 1 month ago 18
Ruby Question

Assign Conflicting CanCan Roles

I am using the CanCan Gem and would like to assign a User two roles which conflict. I need a User to have two roles, one is the x role and the other is the y role. Role x allows users to create posts but not create articles. Role y allows users to create articles but not create posts.

My ability.rb file is like the following:

if user.role?(x)
can :manage, Post
cannot :manage, Article
end
if user.role?(y)
cannot :manage, Post
can :manage, Article
end


Currently if I assign a User both roles, the permissions for role Y will override the permissions for role x. I would like to allow Admins to stack roles so that if I want to add roles x and y to a User, that User will be able to manage Posts and Articles. So if the role has a can and another role has a cannot, the can permission overrides.

Answer

Shouldn't

if user.role?(x)
  can :manage, Post
end
if user.role?(y)
  can :manage, Article
end

be enough? cannot just removes previously granted permissions. But if you do not grant them, then you do not need to remove them.

See also https://github.com/CanCanCommunity/cancancan/wiki/Ability-Precedence

Here is a running example:

require 'cancan'

class Ability
  include CanCan::Ability
  attr_reader :user

  def initialize(user)
    @user = user

    if user.role?(:editor)
      can :edit, Post
    end

    if user.role?(:reader)
      can :read, Post
    end
  end
end

class Post
end

class User
  attr_reader :name
  def initialize(name, *roles)
    @name = name
    @roles = roles
  end

  def role?(role)
    @roles.include?(role)
  end
end

admin = User.new('admin', :reader, :editor)
user = User.new('user', :reader)
editor = User.new('editor', :editor)

admin_ability = Ability.new(admin)
user_ability = Ability.new(user)
editor_ability = Ability.new(editor)


def output(ability, permission)
  puts "#{ability.user.name} can #{permission} Post: #{ability.can?(permission, Post)}"
end

output(admin_ability, :edit)
output(user_ability, :edit)
output(editor_ability, :edit)
puts "--"
output(admin_ability, :read)
output(user_ability, :read)
output(editor_ability, :read)

The default is to NOT HAVE a permission. So if you only work in "additive" mode where you add permissions if a user has roles, you will never need to remove one of them.

:manage is the same as [:create, :edit, :update, :new, :destroy, :index, :show] and is only there for convenience. If you only want to add 6 of those permissions, you can add all 7 and then remove 1. But other than that I don't think you'll need cannot for your example.