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:

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 =)

  \# 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

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!

Perry Smith 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

Jose Ambros-ingerson wrote:

Perry Smith 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

Jose Ambros-ingerson wrote:

Jose Ambros-ingerson wrote:

Perry Smith 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

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.

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

Frederick Cheung 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

Jose Ambros-ingerson wrote:

Frederick Cheung 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 Smith wrote:

Jose Ambros-ingerson wrote:

Frederick Cheung 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

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

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.

Perry Smith 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