Search code examples
ruby-on-railsrubydevisecancancancancan

Why doesn't the data is not displayed when going through the ability check?(rails,cancancan)


I want 3 user levels as Admin ,Manager,Customer in my rails application. So i've created a devise model as Users and added a migration to add the user role to it.So when a user is signed up it stores the users role(whether he is an admin,a manager or a customer). And in my application there are models and controllers for product,delivery,services. And I want to set access levels to each models.

So Admin have access to all models, controllers

Manager have access to Product, Delivery

Customer have access to Services

And i've written the Ability model as follows

class Ability
  include CanCan::Ability

  def initialize(user)

    user ||= User.new # guest user (not logged in)

    if user.roles == "admin"
      can :manage , :all
    elsif user.roles == "manager"
      can :read, Products, Delivery
    elsif user.roles == "customer"
      can :read, Services
    end
end
end

And in my product's show view i've the following code

<% if can? :manage ,@products%>

<h1>Products</h1>

<% @products.each do |product| %>
<p>     <%= product.name%>
<p>         <%= product.price %><br>
<p>    <%= product.qty %><br>

  <%end%>
<%end%>

But even i sign in as an admin i can't see the content .But when i remove the ability checking lines it can be viewed without having any issue.So i predict that the issue should be in the way that have used authorization. And My user model is as follows.

    class User < ApplicationRecord
  belongs_to :role
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

         ROLES = %i[admin manager customer]
def user_params
  params.require(:user).permit(:name, :email, :password, :password_confirmation, :role)
end

end

And in views/devise/registrations/new.html.erb I've included the following code block.

<div>
    <%= f.collection_select(:role, User::ROLES, :to_s, lambda{|i| i.to_s.humanize}) %>
  </div>

This is what i have got to check the ability in my products/show view

<% if can? :manage ,@products%>

<h1>Products</h1>

<% @products.each do |product| %>
<p>     <%= product.name%>
<p>         <%= product.price %><br>
<p>    <%= product.qty %><br>

  <%end%>
<%end%>

Solution

  • user.roles return an array and you are comparing array with string user.roles == "admin" which will always be false.

    Instead use methods from User model likeuser.role?(:admin) to check the roles

    So you ability should look like

    class Ability
      include CanCan::Ability
    
      def initialize(user)
        user ||= User.new # guest user (not logged in)
        if user.role? :admin
          can :manage, :all
        elsif user.role? :manager
          can :read, [Product, Delivery]
        elsif user.role? :customer
          can :read, Service
        end
      end
    end
    

    Now in your controller you should have written something lie

    class ProductsController < ActionController
      load_and_authorize_resource
    
      def index
      ...
      end
    
      # and other resouceful actions
    end
    

    load_and_authorize_resource does lot of magic and one of them is load all the products on which current_user(devise user) has access to.

    Now in view, you don't have to explicitly check <% if can? :manage ,@products%>. @products will have only products which the current user has access to.

    <h1>Products</h1>
    <%@products.present?%>
       <% @products.each do |product| %>
        <p><%= product.name %></p>
        <p><%= product.price %></p>
        <p><%= product.qty %></p>
      <%end%>
    <%else%>
      <p>There are no products to display.</p>
    <%end%>