-
Notifications
You must be signed in to change notification settings - Fork 783
inherited_resources and collections #274
Comments
Good point. I'll mark this to be added. |
Thanks Ryan! |
I recently found this problem as well. Basically, CanCan::InheritedResource#resource_base is calling #end_of_association_chain from IR. Instad, it should be calling #collection. I monkey patched CanCan with the following:
|
I added a pull request for this fix now (including a spec) based on aq1018s suggestion. Hope that helps. |
Thanks tanordheim, I'll close this and get that pulled in soon. For the record it's issue #297. |
I don't believe that this is correct. I am using collection and it was being called correctly through inherited_resources. This change broke my app. While collection is still getting called I now always get the full collection rather than the collection scoped by cancan ability. |
Hm, can you post some code as a gist or something? I'm running an app here now with cancan (from ryanb's master) and InheritedResources using custom collection methods - and that works just fine. |
I'll open this issue again. Let us know what you figure out @nestegg. I don't use Inherited Resources so I'm leaving it up to other to contribute to this. |
Looks like others have had issues with this too. There is a pull request to revert the change, see issue #309. I'm leaving this issue up for discussion. |
The change is now reverted in the 1.6.1 release. If you submit a pull request regarding Inherited Resources, post here to get others to try it out and provide feedback. |
Please ignore this comment. It was wrong. See my next comment please.Hmmm... This is actually quite a complicated issue. For index action: CanCan::ControllerResource#collection_instance= method actually sets the collection instance variable. Say if you have PostsController, calling #load_resource actually sets @posts as whatever is returned by CanCan::InheritedResource#resource_base, which loads #end_of_association_chain (lazy loaded), but then #accessible_by actually loads the entire association chain without scope or pagination from db into memory. However, in inherited_resources, @posts is actually set in #collection method, not #end_of_association_chain. Also #collection first checks to see if @posts is already set, and load it if it's not. Now, if we override #collection in PostsController, like this:
Since @posts has already been loaded by load_resource, end_of_association_chain will never be called again. If we take out "||", we are essentially hitting the db twice. Another issue is that when the first time @posts is loaded by CanCan, it loads EVERYTHING, which will cause performance issues. The solution: when loading for index, or other collection based actions, #load_resource should use #collection When loading for show, or other member based actions, #load_resource should use #end_of_association_chain. This is because it needs an association for #build_resource, #find_resource, etc... I don't know if this made any sense. But I tried. : ) |
Ok, I was COMPLETELY wrong! Please ignore what I said earlier. After tracing the code more carefully, here is what I found: 1). To properly authorize a collection, CanCan::InheritedResource#resource_base MUST return a scope to be used by #accessible_by. See controller_resource.rb#89 ( load_resource method ). 2). #accessible_by returns a scope If we use #end_of_association_chain in #resource_base, then CanCan assigns @posts for PostsController before #collection gets called, and #collection sees @posts is already there and won't evaluate any further. If we use #collection in #resource_base, but then we cannot guarantee the return value is a scope due to pagination. Either way, there is no ideal solution. If we use #end_of_association_chain, we have to make sure we have to overwrite @posts in collection or else we won't have pagination. If we use #collection, we have to make sure the value returned by #collection must be a scope or association. ( So pagination will have to be moved into the action? Kinda defeats the purpose of inherited_resource. ) Either way, I can't find a good solution... Any comments? AQ |
I'm not really sure about that. Using Having simply the following method in my controller : However, following your explanation, it looks like simplifying the
Which mean that we should probably have our load_resource assign the |
If: 1). let #load_resource instead of #collection to assign @posts There are several drawbacks: 1). we are imposing #collection to not assigning @posts. I have a different idea: override #end_of_association_chain to call #accessible_by at the end. This makes more sense to me because I consider accessible_by apply extra scope for authorization purposes, which should be consider really the 'end' of the association chain, ensuring #find to be scoped correctly. |
Here is a monkey patch (again, sorry, it's 3:30 am) based on my idea. It's super ugly though...
|
Also block ability rules don't support |
Sorry to have missed out on most of the discussion. I had notifications turned off from the "gem no-doc +1" fiasco and didn't realize it. In the default case where you have an InheritedResources controller with load_and_authorize_resource and nothing more, cancan's resource_base must return end_of_association_chain because you need to (potentially) scope it farther. If you're overriding collection, you're taking responsibility for setting the instance variable, so you don't want to use load_resource in a before filter. I'm not sure that it makes sense for cancan to insert itself in this case. If you're going to bypass load_resource you may as well handle the authorize part in your collection definition. |
I almost wanted to do so, because how A (potentially bad) suggestion: In order to keep CanCan simple, we split the gem into 3 pieces:
What do you say? |
I don't see a reason for gem splitting. You're right in that by redefining Using The current implementation is safe, meaning that if the developer doesn't An alternative would be to move cancan's collection loading into cancan's own redefinition of |
@aq1018, as amw mentioned, I don't see any reason for splitting into separate gems. We can dynamically check for the existence of Inherited Resources and load whatever's necessary in the same gem. In the worst case scenario we'd have CanCan::InheritedResource not inherit from CanCan::ControllerResource. Since InheritedResources does most of the loading anyway, that may be the best scenario. @amw, feel free to experiment with your idea in a fork and I'll take a look. I'm fine with moving things around in ControllerResource to make it easy to override the proper things through inheritance. However if it will require a deep restructuring and make things very abstract I would prefer to first start with making InheritedResource duplicate logic from ControllerResource instead of subclassing, and then we can look into abstracting out the similarities. It may be worth checking out how I integrated CanCan into rails_admin. Look for This is a good example of integrating authorization into an engine. Even though InheritedResources isn't an engine it is a similar problem because it manages the loading of resources. There I didn't use ControllerResource at all and the code is fairly simple. |
I could try this out some day, but it'll have to wait. Real busy with work stuff at the moment. I think the current state of cancan's support is quite ok. It works perfectly with single resource actions. It also does it's job for collection actions when not developer doesn't overwrite And just to make this clear - even if we do add our authorization to the default |
I really like the idea of separating I'll try to play around this idea in my own fork as well. I think this issue was mainly caused by people didn't know how to properly override the |
If I may add my 2 cents... This is a classic problem revolving around the concept of "separation of concerns". I have been trying to wrestle down this particular problem space for a while and have determined that fundamentally, we are looking at two separate problems lumped into one. The first being that of resource loading. In essence, this particular aspect deserves to stand on its own (in a way I am supporting splitting the gem up). The second is that of resource authorization. This is especially evident in the fact that there are two distinct methods to perform each action. The major complication, as I see it has to do with the fact that both CanCan and InheritedResources are supplying overlapping functionality, and that alone forces a user into making a choice of one or the other (an unnecessary consequence imho). I feel that in order to make sense of the responsibility of which lib loads an object, that it must be up to the configuration to select the appropriate route, with a reasonable default selected (which CanCan already provides). In addition to that, I feel that either approach MUST provide the necessary hooks into authorization processes (much like the current cancan method 'load_resource' isn't forcing an authorization to take place). That said, InheritedResources, while providing such hooks, leaves a lot to be desired when it comes to integrating it with an authorization system. Though I am not offering any functional solution, I am stating that, ultimately, the loading of a resource, belongs into its own package, in practically every way I am able to slice it. I don't think it should be up to CanCan to figure this out. CanCan's responsibility for loading an object should end at the exact moment that the user specifies that s/he'll take care of it on their own. |
Is this still an issue? I think I may be bumping into this as well. I see there's quite a bit of history here. I am observing that when I override the |
@gvt, no it's been working for me. |
Based on the confirmation by @nestegg and no recent updates, I am going to close this. However, it will be re-opened if someone reports this with their findings. |
What about overwriting 'apply_scopes' with cancan so that you can apply |
it still not working properly. I'm using cancan (1.6.8) and inherited_resources (1.3.1). Recipe from here https://github.com/ryanb/cancan/wiki/Inherited-Resources help. But like for me there should be at least some warning message. |
Make the following fixes
Hi,
I think I found an issue with IR and cancan. consider the following code:
load_resource does not seem to call the collection method to load the resource. I think it should.
The text was updated successfully, but these errors were encountered: