Using update_attributes(params[:something]) w/ manual assignment

So far this appears to be working, but it looks kind of jacked up.
I’m concerned that I could be opening myself up to a hole in the
future (security or portability) by doing what I’ve outlined below.
Are there any better ways to go about doing this WITHOUT calling save
more than once?

Here’s an example:

POST data: {car => {“make” => “Pontiac”, “model” => “GTO”, “year” =>
“2004”}}

car_controller.rb

def edit
@car = Car.find(:first, :conditions => {:id => params[:id]})
@car.condition = “Good”
@car.value = “$24,924” # Totally hypothetical

if @car.update_attributes(params[:car])
flash[:notice] = “Car updated successfully.”
redirect_to :action => view, :id => @car.id
else
flash[:warning] = “There was a problem updating your entry.”
end
end


As you can see in this example, I’m manually assigning values to an
object inherited from ActiveRecord BEFORE calling update_attributes.
What concerns me is that I’m passing in the hash from POST to
update_attributes, and while it seems to work just fine and I’m
receiving no deprecation warnings, I’m curious: is there a better way
to do this without calling save multiple times?

I have a callback after save that sends out e-mail, so that’s why I
don’t want multiple saves (no sense in 2-3 emails for each edit).

Thanks for your help =)

I don’t see the “multiple” calls to save.

update_attributes will call save but I don’t see the other call to save.

By the way, update_attributes calls save (as of 2.3.2? or was it before
that) only if at least one attribute has changed.

On Jun 9, 10:05 pm, Perry S. [email protected]
wrote:

I don’t see the “multiple” calls to save.

update_attributes will call save but I don’t see the other call to save.

By the way, update_attributes calls save (as of 2.3.2? or was it before
that) only if at least one attribute has changed.

Actually it’s save which is a no-op if no attributes has saved (this
was part of 2.1).

you can do @car.attributes = params[:car] which will do all the
attribute setting that update_attributes does but doesn’t call save.
If you are (and rightly so) worried about what users submitting
attributes you don’t want them to, take a look at attr_protected/
attr_accessible

Fred

Hey Perry,

Thanks for the reply. As for multiple calls to save, I don’t have
that in my example (because I’m not doing it). What I meant was that,
say instead of calling update_attributes, I called save right after
the manual assignment, then called update_attributes again to update
attributes from the params hash.

And that’s just what concerns me - I guess I’m just not sure how
update_attributes works. From the Rails framework documentation:

  # File vendor/rails/activerecord/lib/active_record/base.rb, line

2619
2619: def update_attributes(attributes)
2620: self.attributes = attributes
2621: save
2622: end

What I don’t “get” is how self.attributes = attributes works.
Obviously it’s assigning based on the passed-in parameters hash, but
given in my example that hash did NOT contain “condition” or “value”,
it seems to skip those and go with whatever the current values are.
And as far as I know, this has been standard with update_attributes
from day one.

So - and somebody please verify or correct my understanding - as long
as the hash I pass in to update_attributes doesn’t overwrite my prior
manual assignment methods, this functionality should continue -
through future updates to the framework - to work as expected -
correct?

And if the above is correct, does anybody see anything else wrong that
I may not have considered with doing things this way?

Thanks again =)

On Jun 9, 3:05 pm, Perry S. [email protected]

Phoenix R. wrote:

So - and somebody please verify or correct my understanding - as long
as the hash I pass in to update_attributes doesn’t overwrite my prior
manual assignment methods, this functionality should continue -
through future updates to the framework - to work as expected -
correct?

I believe that is a safe assumption. I think Fred (two posts up) has a
valid point but may not be what you are worried about right now.

Usually, what I do is merge the added options in and then call
update_attributes. Forgive the syntax but something like

new_options = params[:car].merge({
:condition => “Good”,
:value => “$24,924”
})

@car.update_attributes(new_options)

The general gist is (and I think you already know this):

  1. setting @car.xyz = 5 does not do a save
  2. update_attributes could blow away previous sets if it contains the
    same attribute
  3. attributes not in the hash passed to update_attributes are not
    altered (how could they?)

As far as “idioms”, your style you will not see in Ruby code as often as
some form of merging the options and then calling update_attributes. It
is a bit slower that way but Ruby programmers seem to want clarity over
a tiny bit if performance.

On Jun 9, 10:51 pm, Phoenix R. [email protected] wrote:

  # File vendor/rails/activerecord/lib/active_record/base.rb, line

2619
2619: def update_attributes(attributes)
2620: self.attributes = attributes
2621: save
2622: end

What I don’t “get” is how self.attributes = attributes works.

The attributes= method is badly named, since it’s not actually an
assignment. A better name would be merge_attributes. The
implementation is basically

new_attributes.each do |key, value|
send(key + ‘=’, value)
end

Fred

Perry S. wrote:

By the way, update_attributes calls save (as of 2.3.2? or was it before
that) only if at least one attribute has changed.

It appears to me that this is not true. I tried it below using 2.3.5.
As you can see the ‘locale’ attribute was “es” and it got updated to
“es” anyway. Did I get this wrong?

Language.first
Language Load (0.8ms) SELECT * FROM languages LIMIT 1
±—±--------±-------+
| id | name | locale |
±—±--------±-------+
| 1 | Español | es |
±—±--------±-------+
1 row in set

Language.first.update_attributes({:locale=>“es”})
Language Load (0.4ms) SELECT * FROM languages LIMIT 1
SQL (0.1ms) BEGIN
Language Update (0.3ms) UPDATE languages SET name = ‘spanish’,
locale = ‘es’ WHERE id = 1
SQL (1.2ms) COMMIT
=> true

Jose Ambros-ingerson wrote:

Perry S. wrote:

By the way, update_attributes calls save (as of 2.3.2? or was it before
that) only if at least one attribute has changed.

Though failure to behave this way (see above) is hurting me;
I have an observer that get’s triggered on the save, but if the save
didn’t change anything (the record attributes are the same as before the
save) I would of preferred that the observer had not been triggered.

Is writing a custom update_attributes (that does a save only if it would
result in different attribute values) the way to go?

THanks in advance for your help, Jose

On May 27, 10:55 pm, Jose Ambros-ingerson [email protected]
wrote:

Perry S. wrote:

By the way, update_attributes calls save (as of 2.3.2? or was it before
that) only if at least one attribute has changed.

It appears to me that this is not true. I tried it below using 2.3.5.
As you can see the ‘locale’ attribute was “es” and it got updated to
“es” anyway. Did I get this wrong?

Actually that bit in the logs show the name getting set to spanish as
well. It’s also possible that you’ve turned off this behaviour -
ActiveRecord::Base.partial_updates (or something along those lines)
controls whether this is enabled if my memory is correct

Fred

Wow, thanks for such great feedback Perry and Fred! You both hit the
point from multiple angles, all of which are important. Thank you
both very much for your help!

On Jun 9, 6:02 pm, Perry S. [email protected]

Jose Ambros-ingerson wrote:

Jose Ambros-ingerson wrote:

Perry S. wrote:

By the way, update_attributes calls save (as of 2.3.2? or was it before
that) only if at least one attribute has changed.

Though failure to behave this way (see above) is hurting me;
I have an observer that get’s triggered on the save, but if the save
didn’t change anything (the record attributes are the same as before the
save) I would of preferred that the observer had not been triggered.

Is writing a custom update_attributes (that does a save only if it would
result in different attribute values) the way to go?

THanks in advance for your help, Jose

I suggest looking at
http://railscasts.com/episodes/109-tracking-attribute-changes

It gives you an overview of how it is suppose to work. It seems like
you should be able to test the model to see if it thinks a particular
value has been changed or not and hopefully figure out what is going on.

Jose Ambros-ingerson wrote:

Frederick C. wrote:

On May 27, 10:55�pm, Jose Ambros-ingerson [email protected]
wrote:

Actually that bit in the logs show the name getting set to spanish as
well. It’s also possible that you’ve turned off this behaviour -
ActiveRecord::Base.partial_updates (or something along those lines)
controls whether this is enabled if my memory is correct

Fred

THanks Fred and Perry for your replies.
I followed up on your suggestions yet I have not been able to make it
work as advertised.
As shown in the example below I’ve made sure that
ActiveRecord::Base.partial_updates
is true (as indicated by Fred and in the Railscasts episode) and tried
it in my dev machine and in the production server with the same result
(both running 2.3.5).
Any ideas on how to make it work?
does it work for you?

Thanks in advance, Jose


$ ./script/console
Loading production environment (Rails 2.3.5)

?> ActiveRecord::Base.partial_updates
=> true

Language.first
Language Load (0.2ms) SELECT * FROM languages LIMIT 1
=> #<Language id: 1, name: “spanish”, locale: “es”>

Language.first.update_attributes({:locale=>“es”})
Language Load (0.2ms) SELECT * FROM languages LIMIT 1
SQL (0.1ms) BEGIN
Language Update (0.4ms) UPDATE languages SET name = ‘spanish’,
locale = ‘es’ WHERE id = 1
SQL (0.2ms) COMMIT
=> true

I’ve not looked / tested on 2.3.5 but it was working in 2.3.4 for me.

I was hoping you would do something like:

a = Language.first
puts a.locale
a.update_attributes({:local => ‘es’})

I’m not sure with all the “lazy” execution stuff what Language.first is
doing. The select you are seeing may be after the check to see if the
record is dirty or not.

Also, I can’t remember the names of the methods but there is a way to
ask if a.locale has been modified. I would also experiment with a.save
and see if it saves it or not. Last, you might have something like a
plugin that has hooked in to update_attributes.

Also, perhaps your “es” is not equal to the “es” in the database? You
might experiment with:

a = Language.first
b = a.locale
a.locale = b
a.save

That should not save anything. Also, you can ask if a.locale has
changed after setting it to b. Last, you can set a.locale to “es” and
then ask if it has changed. Maybe before a.locale is utf8 and after it
is ascii?

Not sure if this helps or not.

Perry S. wrote:

Jose Ambros-ingerson wrote:

Frederick C. wrote:

THanks Fred and Perry for your replies.
I followed up on your suggestions yet I have not been able to make it
work as advertised.
As shown in the example below I’ve made sure that
ActiveRecord::Base.partial_updates
is true (as indicated by Fred and in the Railscasts episode) and tried
it in my dev machine and in the production server with the same result
(both running 2.3.5).
Any ideas on how to make it work?
does it work for you?

Thanks in advance, Jose


$ ./script/console
Loading production environment (Rails 2.3.5)

?> ActiveRecord::Base.partial_updates
=> true

Language.first
Language Load (0.2ms) SELECT * FROM languages LIMIT 1
=> #<Language id: 1, name: “spanish”, locale: “es”>

Language.first.update_attributes({:locale=>“es”})
Language Load (0.2ms) SELECT * FROM languages LIMIT 1
SQL (0.1ms) BEGIN
Language Update (0.4ms) UPDATE languages SET name = ‘spanish’,
locale = ‘es’ WHERE id = 1
SQL (0.2ms) COMMIT
=> true

I’ve not looked / tested on 2.3.5 but it was working in 2.3.4 for me.

I was hoping you would do something like:

a = Language.first
puts a.locale
a.update_attributes({:local => ‘es’})

I’m not sure with all the “lazy” execution stuff what Language.first is
doing. The select you are seeing may be after the check to see if the
record is dirty or not.

Also, I can’t remember the names of the methods but there is a way to
ask if a.locale has been modified. I would also experiment with a.save
and see if it saves it or not. Last, you might have something like a
plugin that has hooked in to update_attributes.

Also, perhaps your “es” is not equal to the “es” in the database? You
might experiment with:

a = Language.first
b = a.locale
a.locale = b
a.save

That should not save anything. Also, you can ask if a.locale has
changed after setting it to b. Last, you can set a.locale to “es” and
then ask if it has changed. Maybe before a.locale is utf8 and after it
is ascii?

Not sure if this helps or not.

Hi Perry,
Tried your suggestion;

a = Language.first
Language Load (0.4ms) SELECT * FROM languages LIMIT 1
±—±--------±-------+
| id | name | locale |
±—±--------±-------+
| 1 | Español | es |
±—±--------±-------+
1 row in set

b =a.locale
=> “es”

a.locale = b
=> “es”

a.changed?
=> false

a.save
SQL (0.2ms) BEGIN
Language Update (0.6ms) UPDATE languages SET name = ‘spanish’,
locale = ‘es’ WHERE id = 1
SQL (0.5ms) COMMIT
=> true

So I guess this rules out all but a some plugin interference; I’ll look
into it;
Do you know of a good way to do this?

Thanks again, Jose

In fact, I just tried the most simple case and I see an mySQL update

a = Language.first
Language Load (0.5ms) SELECT * FROM languages LIMIT 1
±—±--------±-------+
| id | name | locale |
±—±--------±-------+
| 1 | Español | es |
±—±--------±-------+
1 row in set

a.save
SQL (0.2ms) BEGIN
Language Update (0.4ms) UPDATE languages SET name = ‘spanish’,
locale = ‘es’ WHERE id = 1
SQL (0.5ms) COMMIT
=> true

Frederick C. wrote:

On May 27, 10:55�pm, Jose Ambros-ingerson [email protected]
wrote:

Actually that bit in the logs show the name getting set to spanish as
well. It’s also possible that you’ve turned off this behaviour -
ActiveRecord::Base.partial_updates (or something along those lines)
controls whether this is enabled if my memory is correct

Fred

THanks Fred and Perry for your replies.
I followed up on your suggestions yet I have not been able to make it
work as advertised.
As shown in the example below I’ve made sure that
ActiveRecord::Base.partial_updates
is true (as indicated by Fred and in the Railscasts episode) and tried
it in my dev machine and in the production server with the same result
(both running 2.3.5).
Any ideas on how to make it work?
does it work for you?

Thanks in advance, Jose


$ ./script/console
Loading production environment (Rails 2.3.5)

?> ActiveRecord::Base.partial_updates
=> true

Language.first
Language Load (0.2ms) SELECT * FROM languages LIMIT 1
=> #<Language id: 1, name: “spanish”, locale: “es”>

Language.first.update_attributes({:locale=>“es”})
Language Load (0.2ms) SELECT * FROM languages LIMIT 1
SQL (0.1ms) BEGIN
Language Update (0.4ms) UPDATE languages SET name = ‘spanish’,
locale = ‘es’ WHERE id = 1
SQL (0.2ms) COMMIT
=> true

Perry S. wrote:

So I guess this rules out all but a some plugin interference; I’ll look
into it;

I finally was able to figure out what was going on.
It was interference from the be9-acl9 gem which is based on acl9 with
some SQL query generation improvements (as of v 0.11)
The partial_updates problem was present in acl9’s v0.11 but has been
fixed in v 0.12 which I will be using now.

Thanks to everyone for your help with this issue.

Jose

So I guess this rules out all but a some plugin interference; I’ll look
into it;
Do you know of a good way to do this?

What database are you using? Maybe this feature is just for some of the
databases but not all? (That wouldn’t make much sense to me but its a
thought.)

It seems like you could add in a validation for locale (for example) and
when it is called, just raise an exception. Then look at the stack (and
look at the code for each of the routines in the stack) to see why you
got down as far as you did.

Maybe someone else will chip in with some suggestions too.