can? methods are not always being called #101

Closed
wprater opened this Issue Jul 22, 2010 · 25 comments

5 participants

@wprater

Hello,

There seems to be an issue with either my setup, or cancan. Im running:
Rails 3.0 beta4
cancan 1.2 "c5737f6d2"

It seems all my methods and abilities are setup correctly, as the normally function just fine. However, there are times on certain page loads the can? method does not get called, but does return false.

The method is being called in an erb template and then a partial like so:
current_consultant.can?(:update, entry)

And the ability is like so, which works most of the time, but not on constant refresh:

  can :update, TimecardEntry do |entry|
    puts 'CALLING :update, TimecardEntry'
    entry_approval = TimecardEntryApproval.for_entry(entry)
    entry_is_not_approved = entry_approval.nil? \
                            || entry_approval.status != TimecardEntryApproval::STATUS_APPROVED \
                            && entry_approval.status != TimecardEntryApproval::STATUS_SUBMITTED

    consultant.can?(:read, entry.timecard) && entry_is_not_approved
  end

Not sure how else to describe it, but Im having a lot of trouble with this one, since it's so intermittent. Help or ideas are appreciated.

@ryanb
Owner

What is current_consultant that you're calling can? on in the ERB template? Try calling can? directly.

CanCan doesn't share anything across requests, so I'm not certain why there are different results each time. What are you using for authentication? Perhaps the current_user is sometimes not getting set properly.

Also, are you testing this on only one running Rails app? If you're using Passenger in development for example it may be loading multiple instances which can possibly cause different behaviors.

@wprater

Thanks for your response.

current_consultant is an instance of the Consultant model. Im using devise 1.1rc2 for authentication.

I've tried removing setting the CanCan call directly as well like so:
Ability.new(@consultant).can?(:update, entry)

Same abnormal results, sometimes the can? method does not get called at all yet something is returning false.

Im only running this one Rails app and am not using Passenger.

@ryanb
Owner

Is consultant the name of the user model? Are you certain it is getting authorized properly each time? I'm not sure what would be causing the inconsistency unless something that is being passed into CanCan is inconsistent.

@wprater

Yes, consultant is the name of the user model. I have double checked that the consultant is being authorized and that it is when can? is being called against.

It appears what is being passed into CanCan is consistent. Do you have any other ideas I could try?

@ryanb
Owner

I'm not sure what it could be. Inconsistent problems are always difficult to debug. Do you have any caching going on? Sometimes that can lead to inconsistent behavior. If you set up some automated tests that might help to see if those are inconsistent too.

http://wiki.github.com/ryanb/cancan/testing-abilities

Let me know if you find out anything more about the inconsistency. CanCan should be creating new instances of everything per request, so it should be consistent.

@wprater

No caching, unless there is a default of sorts in Rails 3 beta4. I've been trying to step through this with a debugger, but its hard since it's intermittent. :-|

Thanks again for your help, I'll still sift around through the code.

@wprater

Something I just realized, that I am not providing a current_user but it appears this is not relevant if one always calls the can? from the model and uses the delegation example you provided, correct? ie

delegate :can?, :cannot?, :to => :ability

def ability
  @ability ||= Ability.new(self)
end
@ryanb
Owner

Right, the can? method in the controller and view basically delegates to the ability instance, so it should be the same thing as what you're doing there.

However, one key difference is that with the default implementation there is only one Ability instance, but here you have a separate one for every Consultant instance. That shouldn't make it inconsistent though.

@ryanb
Owner

Closing this since no one else seems to be having this problem. I'll have to assume it's something specific to your environment. If you can duplicate the problem in a simpler app and determine the it is something that can be fixed in CanCan, please comment here and I'll reopen.

@emoreno

Hi,

we seem to be having the exact same issue. We we're using cancan 1.3 and we upgraded to 1.4 and it is still happening.

When reloading a page, sometimes it works and sometimes it doesn't. We raised an exception inside ability and it does show up just sometimes.

In our view we have:
<% if can? :update, @freight %>

and in ability:

if user.industry?(:shipper)

  can [:update, :destroy], Freight do |freight|
    freight.user == user && freight.published?
  end

We're not sure what can be causing this, since it's intermitent. Whenever the method is not called, it just returns false. Any help will be appreciated.

We're going to try to debug the source code, to see if we can find something.

@ryanb
Owner

I'm reopening this issue and will try to do some investigating. If anyone has ideas on what the problem can be, please comment here.

@emoreno, which version of Rails are you using? Also what other plugins and gems are you using? Do you have automated tests, and if so are you seeing the issue there?

@emoreno

The only gem that I can think that could be interfering with this issue would be devise. We're using Rails 3. Currently we're only using automated tests in models and controllers, but we'll have a look at it, right now we're debugging cancan to see if we can find something.

@ryanb
Owner

@emoreno, you may want to try attempting to re-create the problem in a simpler app, maybe with just devise and cancan installed. If you are able to duplicate it there please send me that app and I can take a closer look.

@emoreno

Hi,
we finally realized what the problem was, apparently using a default_scope inside the class you're checking permissions for in Cancan adds the class as its own ancestor, so when comparing subject.kind_of?(@subjects) it throws false. This line of code is inside matches_subject_class?(subject) in can_definition. Thank you for the help

@ryanb
Owner

@emoreno, How interesting, thanks for discovering the problem. I'll look for a solution.

@ryanb
Owner

@emoreno, just checking, are you passing in an array of records into the can? call?

@projects = Project.all
...
can? :read, @projects

CanCan is not intended to be used this way. Only one model instance should be passed to can?. You can use the accessible_by scope if you need to filter multiple records.

@emoreno

We passed only one record not an array, for now we removed the default scopes from the two models that we we're using and will keep looking for a way to remove this problem.

Thanks for your help.

@ryanb
Owner

Hmm, I'll do some experimenting and try to duplicate this problem.

@heronog

Hi, I think I have found a workaround. At least for my application.

I have 2 levels of nesting like this:
User -> Photos -> Comments

In the Photo#show action I am getting the inconsistent behavior when trying to check if a user can destroy a comment (comment author and owner of photo should be able to) so I can print a link to this action

I want to make clear that CanCan works fine on Comment#create and Comment#destroy (that is, when executing those actions) but only shows this behavior on Photo#show. I haven't found any other case where this is happening.

What I did was this:

in app/models/comment.rb
def can_destroy(user)
user.id == self.user.id or user.id == self.commentable.user.id
# change to
# user == self.user or user == self.commentable.user
# to bring back the bug!
end

in app/models/ability.rb
can :destroy, Comment do |comment|

comment.can_destroy(user) # puede eliminar su comentario.
end

in app/views/comments/_comment.html.erb
(this partial is called from photos/show.html.erb)
<% if comment.can_destroy(current_user) %>


<%= link_to "Eliminar",
[comment.commentable.user, comment.commentable, comment],
:method => "delete",
:confirm => "Seguro que desea elilminar este comentario?" %>

<% end %>

@codezomb

This issue is still persisting. No update in a while, so I thought I'd let you know. Same issue as above, removed default scope, and it worked just fine.

Edit: The default scope affected was only on a nested resource. Trying to access authorizing @topic worked fine, but authorizing a topic post was giving the inconsistent authorization. Hope this helps :)

~ Thanks, and hope to see a fix.

@ryanb
Owner

@gitt, are you using CanCan version 1.4.1? Also which version of Rails are you using?

@codezomb

Ryan, nope appears I'm using 1.4.0 and rails 3.0.1 I'll update to 1.4.1 and get back to you

Edit: Updated to 1.4.1 and the issue still exists, however, it appears that upgrading to rails 3.0.3 fixed the issue!

@ryanb
Owner

@gitt, if you are able, try upgrading to Rails 3.0.3. I believe this issue is the same as this lighthouse ticket.

https://rails.lighthouseapp.com/projects/8994/tickets/5497-default-scope-and-class-reloading

I'm not sure how I can resolve this in CanCan, but if anyone has ideas please let me know.

Update: oh, I see that upgrading to Rails 3.0.3 did fix this. That's great!

@codezomb

@ryan, upgraded and fixed :)

@ryanb
Owner

Closing this because it is a bug in Rails 3 and I don't think there's a way for me to workaround it in CanCan. The solution is to upgrade to Rails 3.0.3. AFAIK it's not an issue in Rails 2.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment