Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content
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

Serialized ActiveRecord attributes serialize as empty hash #1908

Open
danderozier opened this issue Sep 2, 2016 · 8 comments
Open

Serialized ActiveRecord attributes serialize as empty hash #1908

danderozier opened this issue Sep 2, 2016 · 8 comments

Comments

@danderozier
Copy link

danderozier commented Sep 2, 2016

Expected behavior vs actual behavior

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.

Steps to reproduce

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"]}}

Environment

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

@bf4
Copy link
Member

bf4 commented Sep 2, 2016

@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)?

@danderozier
Copy link
Author

@bf4 Thanks for the reply. widget.read_attribute_for_serialization(:config) gives the expected value, {:attr => ["foo", "bar", "baz"]}.

@bf4
Copy link
Member

bf4 commented Sep 2, 2016

Whereas if I override :config with a method, it behaves as expected:

FYI, you can also

  attributes :config do object.config end

@bf4
Copy link
Member

bf4 commented Sep 2, 2016

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

@dummied
Copy link

dummied commented Nov 13, 2016

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:

  • a JSON serialized attribute works fine - this appears to just be for Hash-serialized.
  • I also added @bf4's suggested debugging code and interestingly, it appears as though 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!

@dummied
Copy link

dummied commented Nov 13, 2016

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).

https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer.rb#L197-L203

    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:

  • Documentation and/or a warning when you try to set an attribute that conflicts with a method AMS is already using/setting.
  • Avoid setting any instance methods - or only do very namespaced instance methods - in ActiveModel::Serializer

I 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.

@agalloch
Copy link

agalloch commented Aug 19, 2018

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: agalloch@a050aed

[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.

@monorkin
Copy link

monorkin commented Aug 22, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants