Using blocks to define abilities in CanCan raises an exception
I am using Ryan Bate's CanCan gem to define abilities and some basic functionality is failing. I have a Product model and Products controller where my index action looks like this:
def index
@req_host_w_port = request.host_with_port
@products = Product.accessible_by(current_ability, :index)
end
I get an error when I try to retrieve the products with the accessible_by
method, I get this error:
Cannot determine SQL conditions or joins from block for :index Product(id: integer, secret: string, context_date: datetime, expiration_date: datetime, blurb: text, created_at: datetime, updated_at: datetime, owner_id: integer, product_type_id: integer, approved_at: datetime)
My ability class looks like this:
can :index, Product do |product|
product && !!product.approved_at
end
This seems like a very simple example so I am surprised it is failing and wondering if I am overlooking something simple (i.e. staring at my code for too long).
I did probe a bit more and ran a simple test. If you look at the code below, one example works fine and one fails where they should actually do the same exact thing.
# This works
can :index, Product, :approved_at => nil
# This fails
can :index, Product do |product|
product && product.approved_at.nil?
end
So the problem seems to be in the way CanCan is processing these blocks. I dove deeper into the library and found where the error was raised - in CanCan's ability class definition:
def relevant_can_definitions_for_query(action, subject)
relevant_can_definitions(action, subject).each do |can_definition|
if can_definition.only_block?
raise Error, "Cannot determine SQL conditions or joins from block for #{action.inspect} #{subject.inspect}"
end
end
end
So I checked out what this only_block?
method is. The method returns true if a can definition has a block but no conditions which makes no sense to me because the whole point of the block is to define the conditions within the block when they are too complicated for the foll开发者_如何学Pythonowing syntax:
can :manage, [Account], :account_manager_id => user.id
Any insight on this issue would be great! I also filed an issue on the CanCan github page but I'm getting to the point where I may need to ditch the library. However, I understand that many people are using CanCan successfully and this is such basic functionality I think I must be doing something wrong. Especially since the git repo was updated 3 days ago and Ryan Bates mentions Rails 3 support in the README.
Thanks!
Ok so I went through the wiki and saw that accessible_by
will not work when defining blocks. Seems a bit odd to have this restriction and not mention it in the README, but at least I know it's a limitation of the library and not a bug in mine or Ryan Bates' code. For those interested, the proper way to define my ability above is as follows:
# will fail
can :index, Product do |product|
product && product.approved_at.nil?
end
# will pass
can :index, Product
cannot :index, Product, :approved_at => nil
I've run into this problem as well, when sometimes you can only express an ability/permission via a block (Ruby code) and can't express it as an SQL condition or named scope.
As a workaround, I just do my finding as I would normally do it without CanCan, and then I filter that set down using select!
to only contain the subset of records that the user actually has permission to view:
# Don't use accessible_by
@blog_posts = BlogPost.where(...)
@blog_posts.select! {|blog_post| can?(:read, blog_post)}
Or more generically:
@objects = Object.all
@objects.select! {|_| can?(:read, _)}
I filed a ticket to get this added to the documentation:
https://github.com/ryanb/cancan/issues/issue/276
精彩评论