Friday, April 22, 2011

Inside rails.

You must be very careful when create "before_*" callbacks in Rails.

I found one interesting feature (of course it is not bug :D ) ActiveRecord raise with error RecordNotSaved if you have "before_*" callback that return "false". For example:

class User < ActiveRecord::Base
  before_save :set_short_link

  private
  def set_short_link
    short_link = name.to_url if name.changed?
  end
end

You can`t save instance of this class if you don`t change "name" because method "set_short_link" return false (see /activerecord/lib/active_record/base.rb, line 2568)

Right version will be:

class User < ActiveRecord::Base
  before_save :set_short_link

  private
  def set_short_link
    short_link = name.to_url if name.changed?
    true
  end
end

3 comments:

  1. You are right, but in this case you save time only on url generation. I'm considering the url will be always the same for unique name and you don't use slug table to track all slugs (like slugged do). I think that following code will be more readable and it also protect from empty urls as a bonus

    def set_short_link
      short_link = name.to_url
    end

    Moreover you should use self to specify your intentions to change attribute value instead of to arrange local variable, so the final solution I'd like to use is

    def set_short_link
      self.short_link = name.to_url
    end

    ReplyDelete
  2. Thanks for reply.
    But... this is a fiction example. I never wrote this code. I just try to tell about interesting features that i found in before_* callbacks.

    ReplyDelete
  3. Yes, I see, but using explicit return just true/false from callback looks odd for me. Definitely it should be documented (with something like "# cancel callback and abort saving"

    But maybe I think so because I rarely use this feature (in spite of it's the only documented way to canceling callbacks)

    ReplyDelete