madasahatta
madasahatta

Reputation: 43

defining abilities (CanCan) with has_many :through association

I am a beginner in rails and have been trawling through many suggested answers to this problem. I can't seem to translate the answers, I've seen to my situation. I'm hoping someone would be kind enough to shed some light on it. The 2 models, I am concerned with are User and Project. Each User can create a project, but each user can also manage a project. To allow for this, I have created a has_many :through relationship between User and Project, using a managing model to connect the two models.

user.rb

 has_many :managings, foreign_key: "manager_id", dependent: :destroy
 has_many :managed_projects, through: :managings

managing.rb

belongs_to :manager, class_name: "User"
belongs_to :managed_project, class_name: "Project"

project.rb

has_one :reverse_managing, foreign_key: "managed_project_id",
                                class_name:  "Managing",
                                dependent:   :destroy
has_one :manager, through: :reverse_managing, source: :manager

So, I have a role called 'Beginner'. Beginners can only read projects, with the exception of those who choose to be managers of a project. Managers should also be allowed to edit and update a project, which they are managing.

Below is my attempt at this in ability.rb

 user ||= User.new # guest user (not logged in)
  if user.role? :Admin
    can :manage, :all
  elsif user.role? :Author
    can :read, :all
    can [:create], [Project]
    can [:edit, :update], Project, :user_id => user.id
  elsif user.role? :Beginner
   can :read, :all

   can [:edit, :update], Project, :manager_id => user.id
  end

The last line doesn't appear to be working, as I am not seeing an edit link when I look at the project view of a beginner who is managing that project.

Here is the code that I use in the view:

<% if can? :update, project  %>
<%= link_to 'Edit', edit_project_path(project) %>
<% end %>

and this is what appears in projects_controller

def edit
  @project = Project.find(params[:id])
  authorize! :edit, @project
end

I will sleep a happy man if someone can help me on this

UPDATE

here is the role? method in User.rb

def role?(role)
  return !!self.roles.find_by_name(role.to_s.camelize)
end

UPDATE

I had the wrong role_id assigned to the user. After correcting this, I got an error message, when I tried to open the project view containing the edit link.

undefined method `manager_id' for #<Project:0xb387f0b0>

Upvotes: 3

Views: 3499

Answers (1)

abhishek
abhishek

Reputation: 1008

The problem is here:

can [:edit, :update], Project, :manager_id => user.id

cancan ability conditions must only use database columns (as per the cancan wiki). Here cancan expects manager_id to be a column in the Project model, which is actually is not the case. Your models look somewhat like this:

User [id, ...]
Project [id, ...]
Managings [manager_id, managed_project_id, ...]

manager_id is a field in Managings, and not in Project.

The fix is to use block conditions as described in another wiki page. I've not tested it, but I think the following will work -

can [:edit, :update], Project do |p|
 p.manager == user
end

Upvotes: 8

Related Questions