0

I am using Rails 4 with Devise and CanCanCan, and for some reason when I try to give users CRUD permissions only for products that they have made, none of the CanCanCan permission settings go through and users can only do read only. However, the moderator and admin permissions work as intended.

ability.rb

class Ability
  include CanCan::Ability

     def initialize(user)

       user ||= User.new

    if user.is? :admin
        can :manage, :all
      elsif user.is? :moderator
        can :read, :all
        can :manage, @products
      elsif user.is? :user
        can :read, :all
        can :create, :all
        can :manage, @products do |product|
          product.try(:user) == user
        end
      else
        can :read, :all
      end
    end
  end

user.rb

  has_many :products
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  before_save :assign_role

  Roles = [ :admin , :moderator , :user ]

  def is?( requested_role )
    self.role == requested_role.to_s
  end

  def assign_role
    self.role = Role.find_by role: "user" if self.role.nil?
  end

.html file

  <% if can? :update, @products %>
  <%= link_to "Edit", edit_product_path(@product) %>
  <% end %>
  <% if can? :destroy, @products %>
  <%= link_to "Delete", product_path(@product), method: :delete, data: { confirm: "Are you sure?"} %>
  <% end %>

controller

class ProductsController < ApplicationController
    respond_to :html, :json
    load_and_authorize_resource
    def create
        @product = current_user.products.build(product_params)
        @product.user_id = current_user.id
        if @product.save
            redirect_to products_path
        else
            render 'new'
        end
    end
        def product_params
            params.require(:product).permit(:product_name, :product_description, :user_id)
        end

EDIT:

users SQL table

+------------------------+--------------+------+-----+---------+----------------+
| Field                  | Type         | Null | Key | Default | Extra          |
+------------------------+--------------+------+-----+---------+----------------+
| id                     | int(11)      | NO   | PRI | NULL    | auto_increment |
| created_at             | datetime     | YES  |     | NULL    |                |
| updated_at             | datetime     | YES  |     | NULL    |                |
| email                  | varchar(255) | NO   | UNI |         |                |
| encrypted_password     | varchar(255) | NO   |     |         |                |
| reset_password_token   | varchar(255) | YES  | UNI | NULL    |                |
| reset_password_sent_at | datetime     | YES  |     | NULL    |                |
| remember_created_at    | datetime     | YES  |     | NULL    |                |
| sign_in_count          | int(11)      | NO   |     | 0       |                |
| current_sign_in_at     | datetime     | YES  |     | NULL    |                |
| last_sign_in_at        | datetime     | YES  |     | NULL    |                |
| current_sign_in_ip     | varchar(255) | YES  |     | NULL    |                |
| last_sign_in_ip        | varchar(255) | YES  |     | NULL    |                |
| role                   | varchar(255) | YES  |     | NULL    |                |
+------------------------+--------------+------+-----+---------+----------------+

products SQL table

+-----------------------------------+--------------+------+-----+---------+----------------+
| Field                             | Type         | Null | Key | Default | Extra          |
+-----------------------------------+--------------+------+-----+---------+----------------+
| id                                | int(11)      | NO   | PRI | NULL    | auto_increment |
| project_goal                      | int(11)      | YES  |     | NULL    |                |
| product_name                      | varchar(255) | YES  |     | NULL    |                |
| product_description               | varchar(255) | YES  |     | NULL    |                |
| project_category                  | varchar(255) | YES  |     | NULL    |                |
...
| expiration_date                   | datetime     | YES  |     | NULL    |                |
| created_at                        | datetime     | YES  |     | NULL    |                |
| updated_at                        | datetime     | YES  |     | NULL    |                |
| user_id                           | int(11)      | YES  |     | NULL    |                |
+-----------------------------------+--------------+------+-----+---------+----------------+

And here is my file structure

FileStructure

aishaq11
  • 179
  • 1
  • 1
  • 16

2 Answers2

1

Rather than setting up a block like this:

can :manage, @products do |product|
  product.try(:user) == user
end

you can try to establish permissions based on user_id:

can :manage, Product do |product|
  product.user_id == user.id
end

You can express this permission even more succinctly like so:

can :manage, Product, user_id: user.id

While the above syntax should work, if you'e having problems try this slightly more verbose version:

can :manage, Product, :user_id => user.id

This approach presumes, of course, that there's a relationship between User and Product (where products belong to users).

Also, keep in mind that :manage means "any operation," so it's redundant to define conditions for any of the CRUD operations and/or your custom methods on after specifying :manage.

As a final note, you might want to try using a different symbol for your standard access level, since the symbol :user can easily be mistaken for a user object when you actually mean user role.

MarsAtomic
  • 10,436
  • 5
  • 35
  • 56
  • So I tried doing `can :manage, @products do |product| product.user_id == user.id end`, but for some reason it's throwing me "undefined method `user_id' for nil:NilClass`", even though I have defined it in my product_params. The `can :manage, Product, user_id: user.id` didn't throw anything, but just didn't work. – aishaq11 Sep 16 '15 at 13:35
  • When I wrote my block style permission, I copy-pasted one of your errors and didn't catch it. Remember that the @product notation is used to indicate whether permission is applied to a specific instance of the class, whereas using the class name (capitalized, singular) indicates whether the permission applies to the entire class. – MarsAtomic Sep 16 '15 at 17:59
  • Yeah I figured, I tried it both ways and I still get the same error either way. :/ – aishaq11 Sep 16 '15 at 19:34
  • What does "both ways" mean? It's still not at all clear what you've attempted beyond your original efforts. How did you set up roles? What are you doing to generate that error message? What, precisely, does "just didn't work" mean? Please keep in mind that I'm not literally standing behind you right now, looking over your shoulder. I cannot see what you see. You need to provide a serious amount of information to get to the bottom of the problem because what I've supplied you should be working. – MarsAtomic Sep 16 '15 at 20:01
  • Both ways means I tried @products and Product as my parameter for what the user `can :manage`. "Roles" is simply a column in my users table, which is linked to Products with the **id** key. I am not getting an error message with my original block, and "just didn't work" means that CanCanCan didn't hide the options that I have wrapped in the `if` tags if the user is not the owner of the product. I have updated my original post with more information that I thought was relevant, but if I am missing any information, then please tell me because I want to solve this problem. – aishaq11 Sep 16 '15 at 21:37
0

You seem to be using an instance variable in your abilities class. Try this and see if it works:

class Ability
  include CanCan::Ability

 def initialize(user)

   user ||= User.new

if user.is? :admin
    can :manage, :all
  elsif user.is? :moderator
    can :read, :all
    can :manage, Product # use class here not instance variable
  elsif user.is? :user
    can :read, :all
    can :create, :all
    can :manage, Product do |product| # <–-use the class here not instance variable.
      product.try(:user) == user # cancancan code examples general call ids you might wanna consider revisions?
    end
  else
    can :read, :all
  end
end
  end
BenKoshy
  • 33,477
  • 14
  • 111
  • 80