Search code examples
ruby-on-railsruby-on-rails-4devisecancancancancan

Rails 4 + CanCanCan: "undefined method `role?' for User"


This is a follow-up question on Rails 4: CanCanCan abilities with has_many :through association and I am restating the problem here since I believe context has slightly changed and after 4 updates, the code from the initial question is pretty different too.

I also checked other questions, like Undefined method 'role?' for User, but it did not solve my problem.

So, here we go: I have three models:

class User < ActiveRecord::Base
  has_many :administrations
  has_many :calendars, through: :administrations
end

class Calendar < ActiveRecord::Base
  has_many :administrations
  has_many :users, through: :administrations
end

class Administration < ActiveRecord::Base
  belongs_to :user
  belongs_to :calendar
end

For a given calendar, a user has a role, which is defined in the administration join model (in a column named role).

For each calendar, a user can have only one of the following three roles: Owner, Editor or Viewer.

These roles are currently not stored in dictionary or a constant, and are only assigned to an administration as strings ("Ower", "Editor", "Viewer") through different methods.

Authentication on the User model is handled through Devise, and the current_user method is working.

In order to only allow logged-in users to access in-app resources, I have already add the before_action :authenticate_user! method in the calendars and administrations controllers.

Now, I need to implement a role-based authorization system, so I just installed the CanCanCan gem.

Here is what I want to achieve:

  • All (logged-in) users can create new calendars.
  • If a user is the owner of a calendar, then he can manage the calendar and all the administrations that belong to this calendar, including his own administration.
  • If a user is editor of a calendar, then he can read and update this calendar, and destroy his administration.
  • If a user is viewer of a calendar, then he can read this calendar, and destroy his administration.

To implement the above, I have come up with the following ability.rb file:

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new
    if user.role?(:owner)
      can :manage, Calendar, :user_id => user.id
      can :manage, Administration, :user_id => user.id
      can :manage, Administration, :calendar_id => calendar.id
    elsif user.role?(:editor)
      can [:read, :update], Calendar, :user_id => user.id
      can :destroy, Administration, :user_id => user.id
    elsif user.role?(:viewer)
      can [:read], Calendar, :user_id => user.id
      can :destroy, Administration, :user_id => user.id
    end    
  end
end

Now, when log in and try to visit any calendar page (index, show, edit), I get the following error:

NoMethodError in CalendarsController#show
undefined method `role?' for #<User:0x007fd003dff860>
def initialize(user)
    user ||= User.new
    if user.role?(:owner)
      can :manage, Calendar, :user_id => user.id
      can :manage, Administration, :user_id => user.id
      can :manage, Administration, :calendar_id => calendar.id

I guess the problem comes from the fact that a user does not have a role per se, but only has a role defined for a given calendar.

Which explains why I get a NoMethodError for role? on user.

So, the question would be: how to check a user role for a given calendar?

Any idea how to make things work?


Solution

  • You should have role? method in user model, like below -

    class User < ActiveRecord::Base
      has_many :administrations
      has_many :calendars, through: :administrations
    
      def role?(type)
         administrations.pluck(:role).include?(type.to_s)
      end
    end