cancan icon indicating copy to clipboard operation
cancan copied to clipboard

cc2.0: Problems with multiple rules for same controller

Open gamov opened this issue 11 years ago • 9 comments

Some of my findings using cc2.0: 1 . Craps out with multiple rules going through the same nested association:

Sale.joins(:order_request => :business_site).where({:order_request => {:business_site => {:business_id => 12}}} | {:order_request => {:business_site => {:name => '3'}}} )

works fine in the console (Sale belong_to OrderRequest belong_to BusinessSite belong_to Business) When I write the follow rule: can :access, :sales, {:order_request => {:business_site => {:business_id => 3}}} Sales index display fine but if I duplicate the rule (or add any other rules referencing :business_site, I get the following malformed SQL:

SELECT "sales".* FROM "sales" INNER JOIN "order_requests" ON "order_requests"."id" = "sales"."order_request_id" INNER JOIN "business_sites" ON "business_sites"."id" = "order_requests"."business_site_id" WHERE (("order_requests"."business_sites" = '---
:business_id: 3
') OR ("order_requests"."business_sites" = '---
:business_id: 3
'))

The debugger tells me that it's the '@model_class.send(:sanitize_sql, conditions)' line in ActiveRecordAdapter that returns the malformed SQL. Indeed:

Sale.send(:sanitize_sql, order_request: {business_site: {business_id: 4}})
# => "\"order_request\".\"business_site\" = '---\n:business_id: 4\n'"

Update: This is actually a misuse of sanitize_sql, it should be at most 1 level deep. In that case, order_request should be removed.

2 . cc shouldn't duplicate same rules As we see, cc OR'ed the two rules conditions (as stated in the doc). However, when the rule is the same, it shouldn't, should it?

3 . Multiple overlapping roles I found out of those issues when I was adding different roles to a user. Those roles abilities overlap for some controllers. This means I can't be sure that all role configurations will work together... I'm wondering what should I do to have a predictable behavior.

Ruby: 1.9.3 / Rails 3.0.20

gamov avatar Aug 22 '12 15:08 gamov

CanCan can't handle complex SQL very well, due to the nature of constructing queries. Specifically, joining through numerous tables. I'm not sure if @ryanb has plans to dig deep into this issue but this might help in the meantime:

The longer your can declaration, the deeper the query. Though it's unideal, try denormalizing your dataset a bit. This might be too long:

can :access, :sales, {:order_request => {:business_site => {:business_id => 3}}}

If you denormalized your data so that business_id lived on your sale model, your authorization declaration could be simplified:

can :access, :sales, {:business_id => 3}

mikepack avatar Sep 29 '12 04:09 mikepack

You can use through associations instead of denormalizing

class Sales < ActiveModel::Base
  belongs_to :order_request
  has_one :business_site, :through => :order_request
  has_one :business, :through => :business_site
end

so the ability is similar

can :access, :sales, :business_site => { :business_id => 3 }

Alternatively, you could also do

can :access, :sales, :business => { :id => 3 }

graywh avatar Sep 29 '12 15:09 graywh

Thanks for your comments @mikepack and @graywh. At this point of time, I don't want to denormalize. I've just tried using :through associations but cc fails the same. In the meantime, I'll add unless/if clauses in my ability files where needed... :o(

gamov avatar Oct 03 '12 07:10 gamov

Sorry, I tried entering that without looking at how I'd done it before. I've updated my comment.

graywh avatar Oct 03 '12 13:10 graywh

@graywh belongs_to does not have a :through option. I believe you're looking for has_one. This is a good thought, but it doesn't ultimately eliminate the join which is the cause of the issue. Denormalizing would be the only way to eliminate the join.

mikepack avatar Oct 03 '12 16:10 mikepack

It does use joins, but it works for me with 1.6.8. Did something change in 2.0 to break that?

And thanks for the correction.

graywh avatar Oct 03 '12 17:10 graywh

To clarify, with Cancan 1.6.8 (or branch 2.0) and ActiveRecord 3.2.8, all the through associations, and these abilities

can :manage, Sale, :business => { :id => 3 }
can :manage, Sale, :business => { :name => '3' }

it generates the following SQL with Sale.accessible_by

SELECT "sales".* FROM "sales"
INNER JOIN "order_requests" ON "order_requests"."id" = "sales"."order_request_id"
INNER JOIN "business_sites" ON "business_sites"."id" = "order_requests"."business_site_id"
INNER JOIN "businesses" ON "businesses"."id" = "business_sites"."business_id"
WHERE (("businesses"."id" = 3) OR ("businesses"."id" = 3))

graywh avatar Oct 03 '12 18:10 graywh

@graywh have you tried setting both ability declarations to :business => { :id => 3}

gamov avatar Jun 11 '13 09:06 gamov

Thanks for your submission! The ryanb/cancan repository has been inactive since Sep 06, 2013. Since only Ryan himself has commit permissions, the CanCan project is on a standstill.

CanCan has many open issues, including missing support for Rails 4. To keep CanCan alive, an active fork exists at cancancommunity/cancancan. The new gem is cancancan. More info is available at #994.

If your pull request or issue is still applicable, it would be really appreciated if you resubmit it to CanCanCan.

We hope to see you on the other side!

xhoy avatar Jul 01 '14 07:07 xhoy