Reputation: 179
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
Upvotes: 0
Views: 1683
Reputation: 35575
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
Upvotes: 0
Reputation: 10673
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.
Upvotes: 1