Use active_model_serializer with a non-ActiveRecord object

我们两清 提交于 2020-01-22 08:19:25

问题


I have a model object that is not descended from ActiveRecord::Base and is not stored in the database. I created a serializer for it (with the same name + "Serializer"), and in my controller I'm calling render json: object_instance.

The result is an exception from deep inside of render.

I implemented an as_json method that instantiates the serializer and calls it, but the result is then a missing method in the object, read_attribute_for_serialization.

I want my object to act like an ActiveModel-compatible object at least as far as Active Model Serializer goes, but I don't see any reference to this in their documentation.


回答1:


The model object needs to include it thusly:

# active_model_serializers 0.10+
class ModelName
  include ActiveModel::Serialization
end

# active_model_serializers < 0.10
class ModelName
  include ActiveModel::SerializerSupport
end

This implements the methods needed within the object, and also auto-discovers the serializer matching the object name, so it can be used transparently just like an ActiveRecord object.

This works with active_model_serializers version 0.8.1 through at least 0.9.3.




回答2:


Following on from mutexkid answer, with active_model_serializers 0.10.0.rc4, the following is required to make a plain old ruby object play nice with a serializer:

PORO:

class SearchResult
  include ActiveModel::Serialization

  attr_reader :stories, :users, :friends

  def initialize(search:)
    @stories = search.stories
    @users = search.users
    @friends = search.friends
  end
end

Serializer:

class SearchResultSerializer < ActiveModel::Serializer
  attributes :stories, :users, :friends
end



回答3:


I ran into this yesterday while upgrading to Rails 4.2. ActiveModel::SerializerSupport has been removed for 0.10-stable. I ended up just adding an alias to my POROs which seemed to do the trick:

  alias :read_attribute_for_serialization :send

(active_model_serializers 0.10.0.rc2)

You will also need to add include ActiveModel::Model in your class.




回答4:


My account is too new to comment on Karl Wilbur's answer, but heres the code I added so I could pass in a hash object to serialize:

class SerializableHash < Hash
  alias :read_attribute_for_serialization :[]
end

Usage:

  o = SerializableHash.new(user: current_user, account: current_account)
  respond_with(o, serializer: SettingsSerializer)



回答5:


as of active_model_serializers 0.10.x and Rails 4.2, here's what's required to use a plain old ruby object with active model serializers:

Plain old ruby object:

class SearchResult
  extend ActiveModel::Naming
  include ActiveModel::Serialization
  attr_accessor :stories, :users, :friends

  def id
    hash 
  end

  def initialize(search:)
    @stories = search.stories
    @users = search.users
    @friends = search.friends
  end

  def attributes
    { stories: stories,
      users: users,
      friends: friends
    }
  end
end

Serializer:

class SearchResultSerializer < ActiveModel::Serializer
  attributes :stories, :users, :friends
end



回答6:


Expanding upon @ratbeard's answer the following works with any active_model_serializer version 0.9.x and above. I included the minimum amount of active_support and active_model classes needed to get the PORO serializable hash object to work with activemodel serializers outside of rails. For my purposes as an optimization I pass in a pre-computed serialized array of objects that is computed elsewhere in the app. Instead of re-computing you can overload the initializer in the ActiveModel::Serializer subclass to pass it on if it's available.

lib/serializable_hash.rb

class SerializableHash < Hash
  alias :read_attribute_for_serialization :[]
end

app/serializers/email/foo_serializer.rb

require 'active_model/serializer'
require 'active_support/core_ext/object'
require 'active_support/inflector'
require 'active_support/notifications'
class Email::FooSerializer < ActiveModel::Serializer
  attributes :id, :bars, :created_at, :updated_at

  def initialize(foo, precomputed_serialized_array_of_bars =nil)
    unless serialized_bars
      serialized_bars = {}
      foo.bar.each do | bar |
        serialized_searches[bar.baz] ||= []
        serialized_searches[bar.baz] << Email::BarSerializer.new(bar).attributes
      end
    end

    super(SerializableHash[
      id: foo.id,
      bars: serialized_bars,
      created_at: foo.created_at
      updated_at: foo.updated_at
    ])
  end
end

For completeness I also include this alternate version that uses an instance variable instead of serializable hash:

app/serializers/email/foo_serializer.rb

require 'active_model/serializer'
require 'active_support/core_ext/object'
require 'active_support/inflector'
require 'active_support/notifications'
class Email::FooSerializer < ActiveModel::Serializer
  attributes :id, :bars, :created_at, :updated_at

  def initialize(foo, precomputed_serialized_array_of_bars=nil)
    @bars = precomputed_serialized_array_of_bars
    super(foo)
  end

  def bars
    if @bars
      return @bars
    else
      serialized_bars = {}
      object.bars.each do | bar |
        serialized_bars[bar.baz] ||= []
        serialized_bars[bar.baz] << Email::BarSerializer.new(bar).attributes
      end
      return serialized_bars
    end
  end
end

Then to use serialize the top level object you would simply do

require 'app/serializers/email/foo_serializer'
require 'app/serializers/email/bar_serializer'
Email::FooSerializer.new(Foo.first).as_json

OR if you wanted to use the pre-computed serialized array of bars:

# pre-compute array_of_serialized_bars somewhere up here

require 'app/serializers/email/foo_serializer'
require 'app/serializers/email/bar_serializer'
Email::FooSerializer.new(Foo.first, array_of_pre_serialized_bars).as_json


来源:https://stackoverflow.com/questions/21710886/use-active-model-serializer-with-a-non-activerecord-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!