I'm using Rails 5.2 with the cancancan gem.
rails g scaffold User first_name email:uniq
rails g scaffold Organization name:uniq
rails g scaffold Role name
rails g scaffold Membership user:references organization:references role:refences
user.rb
class User < ApplicationRecord
has_many :memberships
has_many :roles, through: :memberships
has_many :organizations, through: :memberships
end
membership.rb
class Membership < ApplicationRecord
belongs_to :role
belongs_to :organization
belongs_to :user
end
organization.rb
class Organization < ApplicationRecord
has_many :memberships
has_many :users, through: :memberships
end
role.rb
class Role < ApplicationRecord
has_many :memberships
has_many :users, through: :memberships
end
seeds.rb
admin = Role.create(name: 'Admin')
user = Role.create(name: 'User')
abc = Organization.create(name: 'Abc Inc.')
bob = User.create(first_name: 'Bob')
alice = User.create(first_name: 'Alice')
Membership.create(role: user, company: abc, role: user)
Membership.create(role: admin, company: abc, role: admin)
An admin should be able to manage all users and memberships of the company he/she is admin for. A user can only read all users and memberships of that company.
Here is my take on a cancancan configuration:
ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
user_role = Role.find_by_name('User')
admin_role = Role.find_by_name('Admin')
organizations_with_user_role = Organization.includes(:memberships).
where(memberships: {user_id: user.id, role_id: user_role.id})
organizations_with_admin_role = Organization.includes(:memberships).
where(memberships: {user_id: user.id, role_id: admin_role.id})
can :read, Organization, organizations_with_user_role
can :manage, Organization, organizations_with_admin_role
end
end
Then I try to run this code in a view:
<% if can? :read, organization %><%= link_to 'Show', organization %><% end %>
This results with an error page which says:
The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for :read #
I guess I'm tackling the problem from a totally wrong angle. How do I have to setup the ability.rb
to solve this problem?
From the documentation:
Almost anything that you can pass to a hash of conditions in Active Record will work here. The only exception is working with model ids. You can't pass in the model objects directly, you must pass in the ids.
can :manage, Project, group: { id: user.group_ids }
So try something like:
can :read, Organization, id: organizations_with_user_role.pluck(:id)
On a separate note, why are you using includes
instead of joins
? Your query can be simplified to (without the need for user_role = Role.find_by_name('User')
):
organizations_with_user_role = Organization.joins(memberships: :role).
where(memberships: {user_id: user.id}).where(roles: {name: 'User'})