How to Avoid and Infinite Loop with before_save and after_save

I have an entity that looks something like this:

journal
author_first_name
author_last_name
journal_name
journal_email_address

where
journal_name = author_first_name + ' ' + author_last_name
journal_email_address = journal_name + '-' id

I had journal_name and journal_email_address implemented as attributes on the Journal model, but the time came along where, for various reasons, it has become valuable to also store that information in the database. So, what I thought I’d do, in app/models/journal.rb:
def make_journal_name
self.journal_name = self.author_first_name + ' ' + self.author_last_name
end

def make_inbound_email
self.inbound_email = self.journal_name.gsub(/[^a-zA-Z0-9]/, '') + '-' + self.id.to_s
end
def before_save
make_journal_name
make_inbound_email
end

which works fine in the case of an update of an existing record. However, it does not work in the case of a creation of a new record because before saving self.id does not exist. The result was my e-mail addresses are looking like “SallyJones-” as opposed to “SallyJones-64”.

Okay, I thought, “since I need an ID to work with I’ll move this to after_save,” like so:
def after_save
make_journal_name
make_inbound_email
self.save
end

but that creates an infinite loop.

The solution to this problem is to use an undocumented method: update_without_callbacks.
def after_save
make_journal_name
make_inbound_email
self.update_without_callbacks
end

update_without_callbacks is an instance method on ActiveRecord::Base, but for some reason it’s undocumented. In fact, it explicitly has :nodoc set in the source, although I can’t figure out why. To me, it seems that having more documented is better than having less.

3 comments:

Sean Schofield said...

Wow can't believe someone else was having this same problem. Thanks for the tip it helped me to resolve a very frustrating situation.

Unknown said...

where is this method gone?

Unknown said...

I managed to get it using

myvar = Model.find(:somethingspecial)
myvar.column = false
myvar.send(:update_without_callbacks)

Thank you for the post, I was able to get to this using your suggestion