-
Notifications
You must be signed in to change notification settings - Fork 21.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Updating unrelated attributes resets serialized attribute with a wrong value in 7.1 #49809
Comments
I bisected this behaviour into the 3814168 change (cc @eileencodes). You assign an ActiveRecord record in rails/activemodel/lib/active_model/dirty.rb Line 270 in 392e5ef
which then calls 3814168#diff-5208e586ef2714ffb46327930338aa312264b56587696a1e7fee0a6fe6642788R178-R189 which then calls dup on the ActiveRecord record 3814168#diff-5208e586ef2714ffb46327930338aa312264b56587696a1e7fee0a6fe6642788R185 which resets primary key.
|
The dumbest solution from the rails side is to check if the |
This is the actual change #46282. I reverted it while we were debugging a Rails upgrade and then unreverted it. cc/ @jonathanhefner |
Follow-up to rails#46282. The purpose of the optimization from rails#46282 is to avoid unnecessary `deserialize` / `cast` / `serialize` calls associated with invoking `value_for_database` on an attribute that has not changed. `dup`ing the attribute accomplishes that, but `dup`ing the attribute's value is not appropriate for some value types. For example, a value of the type `ActiveModel::Type::ImmutableString` requires `clone` instead of `dup`, and a value of the type `ActiveRecord::Type::Json` likely requires `deep_dup`. In some cases the only appropriate method may be `deserialize(serialize(value))`, such as when a serializer for the type `ActiveRecord::Type::Serialized` deserializes `ActiveRecord::Base` instances. (In that case, `dup`ing the value would clear its `id`, and `clone`ing the value would only produce a shallow copy.) However, `deserialize(serialize(value))` is expensive and would defeat the purpose of the optimization. Instead of `dup`ing the attribute, this commit changes the optimization to use `with_value_from_database(value_before_type_cast)`, which parallels `with_value_from_database(value_for_database)` in the base implementation. This drops the (cast) value entirely, causing a fresh copy to be deserialized if the attribute is subsequently read. In cases where the attribute is _not_ subsequently read, this will actually be more efficient since no extra work is performed. And in cases where the attribute _is_ subsequently read, it will still be more efficient than `deserialize(serialize(value))`. Fixes rails#49809.
@fatkodima Thank you for investigating! It was extremely helpful! 🙇 So, in my opinion, the root problem is that rails/activemodel/lib/active_model/attribute.rb Lines 155 to 159 in e741796
That can cause surprising behavior in other situations too: user = User.create!(name: 'Tom')
configuration = user.create_configuration!
configuration.options << Option.create(key: 'key', value: 'secret')
user.update!(cached_configuration: configuration)
user.cached_configuration.options.size # => 1
user.dup.cached_configuration.options.size # => 0 Or, a more simplified example: class MyModel
include ActiveModel::Attributes
attribute :foo, :immutable_string, default: "foo"
end
m = MyModel.new
m.dup.foo.frozen? # => true
m.foo.frozen? # => true
m.dup.foo.frozen? # => false (Though if you are using Unfortunately, fixing |
Steps to reproduce
The simplified example below shows that updating an unrelated attribute (name) of a user resets the serialized attribute value with wrong data. Reloading the record fixes the issue.
Expected behavior
Updating a record should not update serialized attributes.
Actual behavior
The attribute is updated with faulty data.
System configuration
Rails version: 7.1.1
Ruby version: 3.2.2
The text was updated successfully, but these errors were encountered: