rails/rails — pull request #13867 by mauricio (2014-01-29 04:17:19+00:00)
Title: Fixing issue with activerecord serialization not being able to dump a record after loading it from YAML - fixes #13861

This is a backwards incompatible change for people using `store/serialization`.

This PR fixes the issue raised at https://github.com/rails/rails/issues/13861

The main issue is that [encode_with](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/core.rb#L277) says that the attributes value that is set to the `coder` object is the same that gets set at [init_with](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/core.rb#L205) and while this is true, `init_with` calls `self.class.initialize_attributes(coder['attributes'])` without setting the `:serialized` option and creates the `Serialization::Attibute` object with the `serialized` field set to true as can be seen [here](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_methods/serialization.rb#L105), but the YAML produced by a `YAML.dump` call is like this:

```yaml
--- !ruby/object:Admin::User
attributes:
id: 721854607
name: John Doe
settings: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
color: black
preferences: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
remember_login: true
json_data: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
height: tall
json_data_empty: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
is_a_good_guy: true
params: !ruby/hash:ActiveSupport::HashWithIndifferentAccess {}
account_id:
```
The `json_data` field that should have been a JSON string, since it's store `:coder` turns it into JSON before saving it, was saved just like any YAML field. Now, when you `YAML.load` the content above you will get the following error:

TypeError: no implicit conversion of ActiveSupport::HashWithIndifferentAccess into String

And it happens because the `init_with` method expected to receive a JSON string at the `json_data` field and not the `ActiveSupport::HashWithIndifferentAccess`, since `:serialized` will be set to true. That's why at the original issue description @hundredwatt says that calling `record.reload` works, because when you call `reload` at the object `init_with` is called with the data as it comes from the database column, that will be a JSON string and not a real hash object.

The solution here was creating a separate `attributes_for_coder` method that the serialization code can override when needed to generate the encoded value for the field so it can be safely dumped and then loaded back again through the correct process.