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
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.

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