How Do I: Avoid an Infinite Loop using Before Save and After Save?

8 views
Skip to first unread message

Scott

unread,
Oct 25, 2007, 6:41:37 PM10/25/07
to Ruby on Rails: Talk
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.

At this point, I'm sort of stumped for what to do...thoughts on how I
could make this work?

Lionel Bouton

unread,
Oct 25, 2007, 7:10:40 PM10/25/07
to rubyonra...@googlegroups.com

I don't really understand why you need these columns as they could
easily be computed on the fly by the model with simple methods.

If you really, really want to do this, you can update the record with
raw SQL instead of using self.save.
From memory:
execute("UPDATE #{table_name} SET inbound_email =
'#{make_inbound_email}', journal_name = '#{make_journal_name}' WHERE id
= '#{id}'")
It would work with your current make_* methods as they return the value
they set in the model which have the added benefit that your model is in
sync with the DB.

But I'd really like to know why you have to break database basic
normalization rules (don't put twice the same data in your database to
avoid inconsistencies, should be part of the "first normal form" if my
old DB courses serve me right).

Lionel

Scott

unread,
Oct 25, 2007, 7:21:38 PM10/25/07
to Ruby on Rails: Talk

Thank you for your response.

Actually, that doesn't quite solve my problem because my original
intention of using before_save worked for updates, and the SQL above
is an UPDATE. The real problem is when I'm creating a new object,
i.e. doing an INSERT. I could change the SQL to an INSERT, but I'd
really have to make sure that didn't screw anything up.

As far as the need: I do lookups based on those attributes, the email
address especially, and it will speed things up (and eliminate errors)
if I can do an lookup on an indexed column as opposed to parsing the
string to figure out what to look up.

Another option is to call make_* methods from the #create and #update
methods in my controllers, although that seems like an odd place for
them.

Scott

Lionel Bouton

unread,
Oct 25, 2007, 7:39:28 PM10/25/07
to rubyonra...@googlegroups.com
Scott wrote:
> Thank you for your response.
>
> Actually, that doesn't quite solve my problem because my original
> intention of using before_save worked for updates, and the SQL above
> is an UPDATE.

Right, the insert is already done at this point. So the only task left
is to fill the missing columns which only an update can do...

> The real problem is when I'm creating a new object,
> i.e. doing an INSERT. I could change the SQL to an INSERT, but I'd
> really have to make sure that didn't screw anything up.
>

It would because the INSERT is already done: you'll get an SQL exception.

> As far as the need: I do lookups based on those attributes, the email
> address especially, and it will speed things up (and eliminate errors)
> if I can do an lookup on an indexed column as opposed to parsing the
> string to figure out what to look up.
>

Maybe, depends on the details, if you can pre-process the query to infer
a query on the components, it should be the fastest way (query on
smaller columns are always faster). If you can't, then... you can't :-)
Depending on your database on you can even index expressions, so you
could make find_sql with conditions that look like
"(col1 || ' ' || col2) LIKE 'escaped_querystring'"
and have the database index concat(col1, ' ', col2) for you (PostgreSQL
supports this since ages ago). This should be the more robust and
fastest way.

Lionel

Mark Reginald James

unread,
Oct 25, 2007, 7:40:28 PM10/25/07
to rubyonra...@googlegroups.com
Scott wrote:

>
> def after_save
> make_journal_name
> make_inbound_email
> self.save
> end
>
> but that creates an infinite loop.

Instead of "save" use "update_without_callbacks"

--
We develop, watch us RoR, in numbers too big to ignore.

Scott

unread,
Oct 25, 2007, 8:58:28 PM10/25/07
to Ruby on Rails: Talk
Mark...update_without_callbacks does exactly what I needed. Thanks!

However, I can't find it mentioned anywhere in the documentation. I
grepped the code and found that it's set for #:nodoc.

a) how could i have known this exists without knowing every line of
code?
b) how did you know this exists?
c) why is this method, and others, not included in the docs? someone
had to choose to add #:nodoc to the code...why did they choose to do
that?

Mark Reginald James

unread,
Oct 28, 2007, 7:51:26 AM10/28/07
to rubyonra...@googlegroups.com
Scott wrote:
> Mark...update_without_callbacks does exactly what I needed. Thanks!
>
> However, I can't find it mentioned anywhere in the documentation. I
> grepped the code and found that it's set for #:nodoc.
>
> a) how could i have known this exists without knowing every line of
> code?
> b) how did you know this exists?
> c) why is this method, and others, not included in the docs? someone
> had to choose to add #:nodoc to the code...why did they choose to do
> that?

update_without_callbacks is created using alias_method_chain,
so it's not easy to add to the rdocs, but it should be.

Reply all
Reply to author
Forward
0 new messages