ruby-on-railsauthorizationruby-on-rails-6cancancanruby-on-rails-6.1

Authorization issue with 3 types of roles using cancancan


I have the user table (created by devise), I added a role column, which can be: admin, manager and agent. And in user.rb I put an ENUM like this:

class User < ApplicationRecord
  enum role: { admin: 0, manager: 1, agent: 2 }
end

I'm using cancancan and would like to do 2 things:

1 - How to show a menu or a different button according to the registered user's role? In the view I put it like this:

<% if current.present? && current_user.admin? %>
            <%= link_to 'Purchases (Admin access)', admin_purchases_path, class: 'nav-link mr-5 ' %>
          <% end %>

2 - How to show a specific page according to the user's role? In my User.rb I put this:

def admin?
    self.role == 'admin'
  end

I'm trying it on ability.rb:

  user ||= User.new # guest user (not logged in)
  if admin?
    can :manage, :all
  else
    can :read, :all
  end

And on the controllers, I put:

 load_and_authorize_resource

But I'm getting an error:

NoMethodError in Home#index
undefined method `admin?' for nil:NilClass
<% if current_user.admin? %> <<< On this line here

Solution

  • You can use the safe navigation operator to avoid a nil error:

    <% if current_user&.admin? %>
    

    However its still somewhat of an anti-pattern. The whole point of using an authorization library like CanCanCan is to consolidate the authorization logic instead of spreading it all over your controllers and views.

    Your view should instead be asking CanCanCan if the user is authorized to do whatever it is that the link does:

    <% if can? :administrate, :purchases %>
      <%= link_to 'Purchases (Admin access)', admin_purchases_path, class: 'nav-link mr-5 ' %>
    <% end %>
    

    It doesnt have to know if the user is an admin or that admins are allowed to administrate purchases. Thats CanCanCans job.

    user ||= User.new # guest user (not logged in)
    if user.admin?
      can :manage, :all
      can :administrate, :all
    else
      can :read, :all
    end
    

    In my User.rb I put this ..

    That code is completely reduntant. enum role: { admin: 0, manager: 1, agent: 2 } already creates the interogration methods admin?, manager? and agent?.

    The problem with your ability.rb is that you're calling if admin? without actually specifying the recipient. You should be calling if user.admin?.