I'm using Rails 4.0.0, ruby 2.0.0p247, and CanCan 1.6.10.
How do I authorize users with CanCan based on their role in a join table (has_many :through)?
I have 3 models: User
, Group
, and GroupUser
.
Users and Groups are associated as has_many
through the GroupUser table. Each GroupUser also has a role
field, which can be 'editor' or 'owner.' One group can have multiple Users, each with different roles. Also, a User can have roles in multiple Groups.
I have the app setup with CanCan abilities, but instead of limiting access to only users with the correct role, it is authorizing everybody.
The models are setup as follows. Also, notice that Group has a method to return a list of its owners.
class User < ActiveRecord::Base
has_many :group_users
has_many :groups, through: :group_users
end
class Group < ActiveRecord::Base
has_many :group_users
has_many :users, through: :group_users
def owners
User.find(self.group_users.where(role: 'owner').map(&:user_id))
end
end
class GroupUser < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
The CanCan ability. Notice that it uses the owners
method on Group.
class Ability
include CanCan::Ability
def initialize(user)
can :update, Group do |group|
group.owners.include?(user)
end
end
end
The View is as follows. Here, users who shouldn't be able to see the link are still seeing it.
<ul class="groups">
<% @groups.each do |group| %>
<li>
<p><%= group.name %></p>
<% if can? :update, Group %>
<%= link_to "Edit", edit_group_path(group) %>
<% end %>
</li>
<% end %>
</ul>
Finally, the Controller action for the view is:
def index
@groups = current_user.groups
end
. . . One interesting thing is that even though it doesn't work during actual use, the following unit test passes:
test 'user can only update groups they own' do
ability_one = Ability.new(users(:one)) # Owner
ability_two = Ability.new(users(:two)) # Not-owner
assert ability_one.can?(:update, groups(:one))
assert ability_two.cannot?(:update, groups(:two))
end