I think I've found a significant bug in ActiveRecord::save relating to
the use of gsub! on String attributes.
Given: A model m with one attribute x, a string.
If I attempt to update the value of x using m.x.gsub!, I can't get the
modified value to save. Here's an example - notice how even though I
update the string value using gsub!, when I save the record, the change
doesn't stick. This definitely used to work before 2.1.
0000isp37920l:Rails 2.1 Reference weyus$ script/console
Loading development environment (Rails 2.1.0)
ActiveRecord::Base.partial_updates
=> true
Can you explain why when partial_updates are enabled that updating a
string attribute in place will not work, or point me to a relevant
explanation? I'm having trouble thinking that through - granted it is
3:15 AM as well ;)...
Can you explain why when partial_updates are enabled that updating a
string attribute in place will not work, or point me to a relevant
explanation? I'm having trouble thinking that through - granted it is
3:15 AM as well ;)...
"That's going to be the way for Active Record attributes. If you want
dirty tracking (and partial updates), you don't get to do inplace
modifications."
6:23pm here
(this is my vague understanding - haven't played with this myself yet)
ActiveRecord tracks changes to fields based on calls to "model.field="
and (presumably) other things that change the value of the field
through a call to a method on the model object.
However, if you say:
m.x.gsub!('abc', '123')
you are getting the value of m.x as a string, and then calling gsub!
which modifies the string in place.
ActiveRecord has no easy way to know you've done this - it would have
to somehow track all object changes to know that m.x is dirty and
needs saving.
If you said:
m.x = m.x.gsub('abc','123')
everything would work fine, as the call to "=" marks the field as changed.
"Naturally, we can't just change this as existing implementations may
break. But I think we should make partial updates the default for apps
created with 2.1. And then finally the default for all by 3.0. We can
do this by making an option like config.active_record.partial_updates
= true that's only set for apps created post-2.1 and when that option
is not set, we complain in the log about the deprecated status. "
FWIW, my app. was created on Rails 1.0, and this totally broke for me
when I upgraded to 2.1, so I suspect that this sentiment didn't get
carried through.
"Naturally, we can't just change this as existing implementations may
break. But I think we should make partial updates the default for apps
created with 2.1. And then finally the default for all by 3.0. We can
do this by making an option like config.active_record.partial_updates
= true that's only set for apps created post-2.1 and when that option
is not set, we complain in the log about the deprecated status. "
FWIW, my app. was created on Rails 1.0, and this totally broke for me
when I upgraded to 2.1, so I suspect that this sentiment didn't get
carried through.
THere was some flip-flopping over what the default should be - can't
remember what the final decision was.
I would prefer to freeze all attributes so you get an exception when
you try to modify them in-place. This is what the dirty plugin does.
Unfortunately there are too many quirks with certain frozen classes
(such as Date, which modifies itself) to make this change in 2.1.
Note also that
instance.attribute = instance.attribute << 'a' << 'b' also doesn't
"qualify" as a changed attribute, since it reassigns it the same object.
Go figure.
-R