The shocking truth about rails callbacks! (mind answering my poll?)

Ok, the title’s a bit overly dramatic.  But i recently stumbled upon a feature of rails callbacks (methods called by before_save, after_create, etc) which meant my code was breaking, in a silent way.  After speaking to a few people about it, it seems a lot of people were unaware of this as well.  If you don’t want to read me waffling about my voyage of discovery then would you mind just skipping down to my poll about this?   I’m trying to get a sense of how many other people didn’t know about this either.

Halfway down the api page documenting callbacks ( http://www.railsbrain.com/api/rails-2.2.2/doc/index.html?a=C00000640&name=Callbacks ) , we read this:

Canceling callbacks

If a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_*false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks defined as methods on the model, which are called last.

Now, i noticed this only because i encountered what seemed to be a bug in my code.  A record wasn’t being saved, and i couldn’t work out why.  It wasn’t failing validations.  If i tried to do it manually in the console, with the same data, i got this odd situation:

@user.valid?
=> true

@user.save
=> false

@user.errors.full_messages
=> []

WTF? i thought…  after a lot of logging, i managed to work out that it was because i’d added a before_create filter, which set one of the fields to the same as whatever was in a particular field in an associated object, like this:

before_create :set_student_access_to_same_as_music_service</pre>
def set_student_access_to_same_as_music_service
  self.student_access = self.music_service.show_student_access if self.music_service
end

So, if the user’s music service’s show_student_access field is false, we set the new user’s student_access to false, as a sensible default value.  As a side effect, the method returns false, as that’s the last thing to be evaluated.  And because the callback method returned false, the callback chain is halted and the record isn’t saved.  But, it’s not failing validations so i don’t get to see why.

Now, i’m not saying this is wrong – it’s in the api after all.  I’m just curious as to how many people were also unaware of this – out of the people i’ve spoken to it seems about only half of them knew, which is pretty bad considering it’s a major feature of callbacks, that would be causing unexpected effects all over the place if you weren’t careful about not returning false.  So, would you mind answering my poll, below?

thanks! max

Advertisements

7 responses to “The shocking truth about rails callbacks! (mind answering my poll?)

  1. I voted yes, but it’s something I’ll occasionally forget about, and then go through the pain of relearning when trying to find out when validations are apparently failing.

  2. this is why I loved datamapper’s callbacks http://datamapper.org/doku.php?id=docs:hooks

  3. It’s funny – I stumbled accross this article by searching for a way to cancel the corresponding action of a callback, which I think is pretty kick-ass that you can do that. Although, I will have to agree that it seems like it should only cancel either on an error being raised or an explicit command (event.cancel or the likes).

  4. That same thing happened to me. Been spending 3 days trying to debug it… Got any ideas where to start?

  5. Avishai – what’s your problem exactly?

  6. None of my validations work. I have a Mongoid model with
    field :price, :type => Integer
    validates :price, :numericality => { :greater_than => 0, :allow_blank => false }

    and doing something like a.price = “!$@$HKR#HU” will return a.valid? => true, regardless of what invalid data you feed it.

    Could a halted callback chain be the cause of this? How would I even begin debugging it?

  7. Avashi – i’ve never used mongoid. I’d recommend taking this question to http://stackoverflow.com

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s