Hi,
I can't find a good way to do a custom serialization of a plain old Hash.
Here is an example :
class MyObject
include ActiveModel::SerializerSupport
attr_reader :name, :age, :address, :settings
def initialize(name, age, address, settings = {})
@name = name
@age = age
@address = address
@settings = settings.merge(:timezone => 'GMT', :other => 'xyz')
end
end
class MyObjectSerializer < ActiveModel::Serializer
attributes :name, :age, :settings
end
MyObjectSerializer.new(MyObject.new('toto', 12, 'mars', {:timezone => 'Paris', :foo => 'bar'})).as_json
# => {:my_object=>{:name=>"toto", :age=>12, :settings=>{:timezone=>"GMT", :foo=>"bar", :other=>"xyz"}}}
I'd like not to serialize all the key/values of the internal settings Hash, so I make a custom Serializer
class MyObjectSerializer < ActiveModel::Serializer
attributes :name, :age, :settings
def settings
SettingsSerializer.new(object.settings).as_json
end
end
class SettingsSerializer < ActiveModel::Serializer
attributes :timezone
end
But I get an error : NoMethodError: undefined methodread_attribute_for_serialization' for {:timezone=>"GMT", :foo=>"bar", :other=>"xyz"}:Hash`
So I have a few bad choices (as far as I can tell) :
SettingsSerializer#attributes method with only the keys I want, but the natural DSL of AMS (using attributes in the class definition) is uselessHash#only(*keys) but I don't take advantage of the power of AMS.Is there a proper/clean way to do this or not yet?
Thanks
A Hash doesn't implement ActiveModel's interface, right? So no, I don't think there's a super straightforward way of doing this yet; we're not serializing Just Any Old Object.
Since this isn't a bug, I'm going to give it a close, but if we want to talk about supporting non-ActiveModel objects, we can talk about it over on the mailing list: https://groups.google.com/forum/?fromgroups#!forum/rails-api-core
Thanks @steveklabnik for that fast answer.
Any time. :)
What about something like this? It serializes Hash with keys provided object key method or it uses hash key when no key option is given.
module ActiveModel
# Example usage:
#
# relations = Site.first.site_user_relations
# options = {
# each_serializer: SiteUserRelationShortSerializer,
# key: :site_id
# }
# serializer = ActiveModel::HashSerializer.new(relations, options)
# puts serializer.as_json
#
class HashSerializer < Serializer
def attributes
attributes = {}
object.each_pair do |hash_key, item|
if options[:key]
if item.respond_to?(options[:key])
key = item.send(options[:key])
elsif item.respond_to?(:key?) and item.key?(options[:key])
key = item[options[:key]]
else
fail("Wrong key #{options[:key]} for hash serialization.")
end
else
key = hash_key
end
attributes[key] = serialize_item(item)
end
attributes
end
private
def serialize_item(item)
if @options.has_key? :each_serializer
serializer = @options[:each_serializer]
elsif item.respond_to?(:active_model_serializer)
serializer = item.active_model_serializer
end
serializable = serializer ? serializer.new(item, @options) : DefaultSerializer.new(item, @options)
if serializable.respond_to?(:serializable_hash)
serializable.serializable_hash
else
serializable.as_json
end
end
end
end
In case anyone finds this via google I have written a SO answer over here that explains how to use PORO hashes with AMS: http://stackoverflow.com/a/34056668/511168
@therealjessesanford This is fixed in 0.10. I'll reply in SO.
I have found an easy way to make serializer for hash:
class LocationSerializer < ActiveModel::Serializer
attributes :lat, :lon, :city
attribute :address do
# Example some custom key
end
def read_attribute_for_serialization(attr)
object[attr.to_s]
end
end
My hash example {'lat' => 53.3, 'lon' => 0.19, 'city' => 'London', ...}
I can even use my LocationSerializer through has_one on regular serializer, works good.
[for v10.6]
Most helpful comment
I have found an easy way to make serializer for hash:
My hash example
{'lat' => 53.3, 'lon' => 0.19, 'city' => 'London', ...}I can even use my LocationSerializer through
has_oneon regular serializer, works good.[for v10.6]