Using before_save to update a record only once

I’d like to use before_save to perform an action (I won’t get into the
details), but only the first time the record is changed from 0 to 1.
Here’s the code I have so far in my model:

def before_save
if completed == 1
# perform the action
end
end

What the above does is perform the action when “completed” is set to
one, but the problem is I don’t want this to happen every single time
there is a save. So what I’d like to be able to do is check the previous
value of “completed” and only perform the action if it used to be 0 and
is now 1. For example (psuedo code):

def before_save
if old_completed == 0 and completed == 1
# perform the action
end
end

How would I go about this? Obviously, I could add code outside the
model, but before_save seems like the nicest, cleanest place to for this
code to be. I searched this forum and found a post that kind of helps
answer my question, but it’s not super clear and I’m still not sure what
the best solution is. It sounds like I have to do an external find just
to do this check, and that just doesn’t seem right to me. Is that really
the only way?

What the above does is perform the action when “completed” is set to

How would I go about this? Obviously, I could add code outside the
model, but before_save seems like the nicest, cleanest place to for this
code to be. I searched this forum and found a post that kind of helps
answer my question, but it’s not super clear and I’m still not sure what
the best solution is. It sounds like I have to do an external find just
to do this check, and that just doesn’t seem right to me. Is that really
the only way?

Don’t have a specific answer for you, but the acts_as_changed plugin
might come in handy for you…

http://agilewebdevelopment.com/plugins/acts_as_changed

-philip

Philip H. wrote:

What the above does is perform the action when “completed” is set to

How would I go about this? Obviously, I could add code outside the
model, but before_save seems like the nicest, cleanest place to for this
code to be. I searched this forum and found a post that kind of helps
answer my question, but it’s not super clear and I’m still not sure what
the best solution is. It sounds like I have to do an external find just
to do this check, and that just doesn’t seem right to me. Is that really
the only way?

Don’t have a specific answer for you, but the acts_as_changed plugin
might come in handy for you…

http://agilewebdevelopment.com/plugins/acts_as_changed

-philip

Thanks, Philip. I’m having a difficult time understanding exactly what
it does. The one line explanation isn’t 100% clear to me. It sort of
looks like a speed related plug-in that simply updates fields that been
changed, rather than going through each field in the entire table, but I
could be wrong about that.

changed, rather than going through each field in the entire table, but I
could be wrong about that.

I’ve never used it myself :slight_smile: ANd I think you’re right about that.
There’s a “changed_attributes” method that returns what’s changed though
that you could use perhaps…

Guess I thought it might provide more inspiration than an outright
answer
:slight_smile:

Thinking more about it, you could override the completed method to
record
that it was changed and then check that in your before_save…

-philip

Philip H. wrote:

changed, rather than going through each field in the entire table, but I
could be wrong about that.

I’ve never used it myself :slight_smile: ANd I think you’re right about that.
There’s a “changed_attributes” method that returns what’s changed though
that you could use perhaps…

Guess I thought it might provide more inspiration than an outright
answer
:slight_smile:

Thinking more about it, you could override the completed method to
record
that it was changed and then check that in your before_save…

-philip

Sorry I’m a little late here – been away for a couple days. That sounds
interesting. Can you explain in more detail how I’d do something like
that?

answer
that?
Hrm… something like this assuming your model has ‘completed’
attribute.

class MyModel < …

attr :is_complete

def completed=(val)
if completed == 0 && val == 1 then
is_complete = true
else
is_complete = false
end
completed = val
end

def before_save
if is_complete != true then
return false
end
true
end

end

Of course there is another way, you could write your own plugin
acts_as_update_once to do this across all your controllers. Make sure
that the logic you use short-circuits quickly for the condition that
happens most of the time. This way performance will be good.

In the above example there’s an “end” missing at the very end, but other
than that, it’s basically what I’m using, minus a few unnecessary
details.

Philip H. wrote:

answer
that?
Hrm… something like this assuming your model has ‘completed’
attribute.

class MyModel < …

attr :is_complete

def completed=(val)
if completed == 0 && val == 1 then
is_complete = true
else
is_complete = false
end
completed = val
end

def before_save
if is_complete != true then
return false
end
true
end

end

Thanks, that makes sense. I implemented something similar to your
example, but for some reason the completed field now never gets saved.
My code looks something like this:

class MyModel < ActiveRecord::Base
attr :completed_is_changed

This checks to see if the completed fiel was set to true for the

first time.
def completed=(val)
if completed == 0 && val == 1
completed_is_changed = true
else
completed_is_changed = false
end

completed = val

end

def before_save
if completed_is_changed == true
#perform the action.
end
end

The actions are performed, but the completed field value is now never
saved to the database. I would expect that the line “completed = val”
would take care of that, but it doesn’t seem to. Is something missing?

MenDAKE :

else

end

The actions are performed, but the completed field value is
now never saved to the database. I would expect that the
line “completed = val” would take care of that, but it doesn’t
seem to. Is something missing?

You can write it like that :

def completed=(val)
completed_is_changed = completed == 0 && val == 1
write_attribute(:completed, val)
end

– Jean-François.


Ruby ( http://www.rubyfrance.org ) on Rails ( http://www.railsfrance.org
)

def completed=(val)
if completed == 0 && val == 1 then
is_complete = true
else
is_complete = false
end
completed = val

Ah, I suspect this is it… do something like…

self[‘completed’] = val

or whatever rails does by default for this thing…

That did it. Thanks very much for the help.