问题
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