Serializing serialized ActiveRecord Attributes (http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html) using AMS is failing. Expected behavior is for said attribute to appear in the serialized object, but instead appears as an empty hash.
For example, take a model with a serialized attribute:
# widget.rb
class Widget < ApplicationRecord
serialize :config, Hash
end
When serializing that model with AMS, the config attribute is an empty hash.
# widget_serializer.rb
class WidgetSerializer < ActiveModel::Serializer
attributes :config
end
# > widget = Widget.create(config: {attr: ["foo", "bar", "baz"]})
# > widget.config
# => {:attr => ["foo", "bar", "baz"]}
# > WidgetSerializer.new(widget).attributes
# => {:config=>{}}
Whereas if I override :config with a method, it behaves as expected:
# widget_serializer.rb
class WidgetSerializer < ActiveModel::Serializer
attributes :config
def config
object.config
end
end
widget = Widget.create(config: {attr: ["foo", "bar", "baz"]})
# > WidgetSerializer.new(widget).attributes
# => {:config=>{:attr => ["foo", "bar", "baz"]}}
ActiveModelSerializers Version: 0.10.2
Output of ruby -e "puts RUBY_DESCRIPTION": ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]
OS Type & Version: Mac OS 10.11.6
Integrated application and version _(e.g., Rails, Grape, etc)_: Rails 5
@danderozier Interesting. I'm guessing the reason for that has to do with a bug in the Rails implementation of read_attribute_for_serialization for serialized fields.
By the way, you probably want to use the JSON encoder for your field, based on what it looks like. It's faster and safer. serialize :preferences, JSON
What happens if you call widget.read_attribute_for_serialization(:config)?
@bf4 Thanks for the reply. widget.read_attribute_for_serialization(:config) gives the expected value, {:attr => ["foo", "bar", "baz"]}.
Whereas if I override :config with a method, it behaves as expected:
FYI, you can also
attributes :config do object.config end
I can't think of anywhere that'd cause that kind of behavior.
What's the backtrace(s) if you do the following?:
class Widget < ApplicationRecord
serialize :config, Hash
+ def config
+ puts caller.join("\n\t")
+ self[:config].freeze
+ end
If it helps, I've thrown together a demo app to exercise this bug: https://github.com/dummied/ams_1908_test_app
Couple of observations:
JSON serialized attribute works fine - this appears to just be for Hash-serialized.config is not called at all from AMS - I don't see the output in tests or server, but _do_ when explicitly calling config on a Thing.Hope this helps!
Looked into this a bit more and it's tied to the attribute name of config.
read_attribute_for_serialization looks to see if the serializer itself responds to the method in question before passing it on to the object being serialized (this allows us to override attributes in the serializer).
def read_attribute_for_serialization(attr)
if respond_to?(attr)
send(attr)
else
object.read_attribute_for_serialization(attr)
end
end
In this case, config comes from here: https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer/concerns/configuration.rb#L10
Possible solutions that come to my mind:
ActiveModel::SerializerI also explored switching to something like self.class.instance_methods(false).include?(attr) in read_attribute_for_serialization's check, but that breaks child serializers.
Also hit this bug with ActiveModelSerializers Version: 0.10.7 - ActiveSupport::Configurable leaks into serializer instances. It's present when serializing POROs as well.
@bf4, here is a failing test case: https://github.com/agalloch/active_model_serializers/commit/a050aedce5274d42ffe01d57ac8a6a008b3a4959
[2] pry(#<ActiveModel::Serializer::SerializationTest::MySerializer>)> method(:config)
=> #<Method: ActiveModel::Serializer::SerializationTest::MySerializer(ActiveSupport::Configurable)#config>
Workaround: delegate :config, to: :object or override it in the serializer class.
Just ran into this issue this morning. @agalloch 's solution didn't work for me - I got Undefined method jsonapi_use_foreign_key_on_belongs_to_relationship for Hash.
I'm using the JSON::API adapter with AMS 0.10.7
Here is my workaround for the issue, it's far from elegant, but it works.
class MySerializer < ActiveModel::Serializer
attributes :config
def config
cfg = object.config.clone
def cfg.jsonapi_use_foreign_key_on_belongs_to_relationship(*_)
nil
end
cfg
end
end
Most helpful comment
Looked into this a bit more and it's tied to the attribute name of
config.read_attribute_for_serializationlooks to see if the serializer itself responds to the method in question before passing it on to the object being serialized (this allows us to override attributes in the serializer).https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer.rb#L197-L203
In this case,
configcomes from here: https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer/concerns/configuration.rb#L10Possible solutions that come to my mind:
ActiveModel::SerializerI also explored switching to something like
self.class.instance_methods(false).include?(attr)inread_attribute_for_serialization's check, but that breaks child serializers.