1
votes

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.

2
Why not elsif on the second to better establish precedence? It may be that the first or last call sticks, so the order might not be clear at a glance.tadman
elsif does not work. users having both roles should have both permissions.Pascal

2 Answers

0
votes

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.

0
votes

The accepted answer is now 5 years old, and this functionality appears to be in CanCanCan as expected by the OP.

It did not work as expected in version 1.15, but does work in version 3.3, so you may need to upgrade your cancancan gem.