I recently wanted to write a simple migration script. I wrote:
@entries = Entries.all(:text => /test/)
@entries.each do |entry|
entry.update(:text => entry.text.gsub!(/test/, "no-test"))
end
It didn’t save the records, even though the update statement returned true. What did I miss?
In the 1.x series of datamapper the dirty tracking is done via calling
#==on the new and old attribute values to detect dirtyness. If an object is mutated inplace (for example with the String bang methods), the change cannot be detected as the “orignal” state gets mutated also.Basically the following happens internally:
In your example you assign the original mutated attribute back to the object, no identity change => no update detected.
In case you create a new object via
String#gsubistead of mutating the original attribute value viaString#gsub!you end up with a detectable change.With assigning a new object with different value the following happens:
And for having all cases covered, assigning a new object with same value:
Hopefully this explains the semantic differences good enough to explain all similar cases.
BTW In datamapper 2.0 we have a differend mechanism that will also catch in place mutations. Disclaimer, I’m the author of this component called dm-session.