Change in the behavior of serialized attributes

While upgrading an app to 2.2.1 I noticed a change in the behavior of serialized attributes in ActiveRecord.

I have an AR model with a serialized attribute which is used to store either a fixnum of an array of fixnums. Previously, a single fixnum stored in this attribute would be returned as a fixnum, however since a recent commit [1] it is now returned as a string.

I’m not sure whether is a typical use case, but it doesn’t seem like expected behavior. I started looking at creating a patch but it became non-trivial as I strayed off into ActiveRecord::ConnectionAdapters::Quoting. So instead, I thought it better I get some feedback here.

Cheers,

Paul

[1] http://github.com/rails/rails/commit/c94ba8150a726da4a894cd8325ee682a3286ec9f

While upgrading an app to 2.2.1 I noticed a change in the behavior of
serialized attributes in ActiveRecord.

I have an AR model with a serialized attribute which is used to store either
a fixnum of an array of fixnums. Previously, a single fixnum stored in this
attribute would be returned as a fixnum, however since a recent commit [1]
it is now returned as a string.

I'm not sure whether is a typical use case, but it doesn't seem like
expected behavior. I started looking at creating a patch but it became
non-trivial as I strayed off into ActiveRecord::ConnectionAdapters::Quoting.
So instead, I thought it better I get some feedback here.

Seems like this should function correctly:

YAML.dump(1)

=> "--- 1\n"

I assume the database column is ending up with a literal "1" in it,
without the yaml prefix? If so, does overwriting your setter method
fix that for you?

def whatever=(v)
  write_attribute(:whatever, YAML.dump(v))
end

I assume the database column is ending up with a literal “1” in it,

without the yaml prefix?

Yeah, exactly.

If so, does overwriting your setter method

fix that for you?

My immediate reaction was that it would, I was thinking along the same lines earlier. I’ve just tried it though, and I still end up with a literal “1” in the database. If I take the call to serialize out of the model, the overwritten setter does as you’d expect and I do end up with YAML in the db.

I’ve done a bit of digging and here’s what I think is happening. Before the save happens, each attribute is quoted by attributes_with_quotes, and attributes_with_quotes uses read_attribute to fetch the value of each attribute. But, within read_attribute there is a call to unserialize_attribute (for serialized columns only) and hence we end up back where we started.

Cheers,
Paul

My immediate reaction was that it would, I was thinking along the same lines
earlier. I've just tried it though, and I still end up with a literal "1" in
the database. If I take the call to serialize out of the model, the
overwritten setter does as you'd expect and I do end up with YAML in the db.

I've done a bit of digging and here's what I _think_ is happening. Before
the save happens, each attribute is quoted by attributes_with_quotes, and
attributes_with_quotes uses read_attribute to fetch the value of each
attribute. But, within read_attribute there is a call to
unserialize_attribute (for serialized columns only) and hence we end up back
where we started.

Sorry this took so long. Conferences and projects lead to less
mailing list time.

We already have special code in there for serialized Date and Time
classes, so why not try something like:

http://pastie.caboo.se/327995

That causes two tests to fail simply because they're testing on
uniqueness of a serialized field while not storing serialized fields
in the database.

Sorry this took so long.

No worries. I ended up over writing both the getting and the setting in the app since I was relying on another subtle aspect of the behavior which I think changed.

so why not try something like:

Makes sense. I’m hoping to come back and have another look at this when I get chance, I’ll let you know when I do.

Cheers,
Paul