Reputation: 3953
I am using devise and cancan in my app. My Product model has a belong_to :user and the User table has a column seller:boolean. So in my ability class I have this line product.try(:user).seller as shown in the Ability Class pasted in the question, that line throws NoMethodError in ProductsController#create, undefined method `seller' for nil:NilClass any time I try to create a new product. But in the controllers create action, the user object is not nil because when I inspect the log after the error, I see SELECT "users".* FROM "users" WHERE "users"."id" = 11.
Also, in the rails console if I do:
a = Product.find(4)
a.user.seller will return => true
My ability class
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
can :manage, Product do | product |
product.try(:user_id) == user.id
product.try(:user).seller == true
end
end
end
The Productscontroller:
class ProductsController < ApplicationController
before_filter :authenticate_user!, except: [:index, :show]
load_and_authorize_resource only: [:create, :edit, :destroy]
respond_to :html, :json
def index
@products = Product.all
respond_with @products
end
def new
@product = Product.new
end
def create
@user = User.find_by(id: current_user.id)
@product = @user.products.build(product_params)
@product.save
end
end
To make CanCan work with rails 4, my application controller has
class ApplicationController < ActionController::Base
#temprary work-around for cancan gem to work with rails-4
#source https://github.com/ryanb/cancan/issues/835#issuecomment-18663815
before_filter do
resource = controller_path.singularize.gsub('/', '_').to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
end
To give clarity to the screenshots below, here is a shortened version of products/index.html.erb
<% @products.each do |product| %>
<% if user_signed_in? %>
<% if can? :update, product %>
<span class="bottomcentre"><%= link_to 'edit', edit_product_path(product), class: "btn btn-primary" %></span>
<% end %>
<% if can? :destroy, product %>
<span class="bottomright"><%= link_to "Delete", product, data: {confirm: 'Are u sure?'}, method: :delete, class: "btn btn-danger" %></span>
<% end %>
<% end %><!-- closes user_signed_in -->
<% end %>
<br/>
<% if user_signed_in? %>
<% if can? :create, Product %>
<%= link_to 'Create a new Product', new_product_path %>
<% end %>
<% end %>
An additional effect is that the edit and destroy links are shown to a seller for products that are not theirs unless I comment out the line raising the error, that is this product.try(:user).seller == true in the CanCan's Ability Class shown earlier on. So when commented out, I get this screenshot 1 with edit links hidden fro products not belongs to the seller and when uncommented you get screenshot 2, with all product edit link displayed to a seller, even if the products are not theirs.
screenshot 1 with product.try(:user).seller == true commented out in the CanCan Ability Class, and it shows the edit link for only the first two product which belongs to the signed_in seller
screenshot 2 with product.try(:user).seller == true left intact in the CanCan Ability Class. see edit links being displayed for the lower products that is shirt and cufflinks and they don't belong to the signed_in seller.
Upvotes: 1
Views: 2492
Reputation: 34784
Your can :manage
block definition is slightly wrong. It is the return value of the block that determines whether the user has the ability or not. In your block you have two statements of which only the second will have any bearing on whether the user has the ability. You need to join your statements with &&
.
Also, your error seems to be when a product has no user as opposed to when the current user is nil. You need to use try
on the return of product.try(:user)
too as it will be nil if there is no product.
So, all told I think your block needs to be:
can :manage, Product do | product |
product.try(:user_id) == user.id &&
product.try(:user).try(:seller) == true
end
Upvotes: 2