Authorizing users on Rails 4 with CanCan based one roles in a join table

authorization, cancan, permissions, ruby-on-rails, ruby-on-rails-4

Solution

I think you should make `cancan` work with a real object here, passing the actual `group` object to the `can?` method instead of the `Group` class:

In the view:

<% if can? :update, group %>

Instead of:

<% if can? :update, Group %>

This way you will ask `cancan` if `current_user` is able to `:update` the actual `group`. Since no ability has been set for operations on the generic `Group` object, the second condition will always be true.

Problem

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 ```

Original source