diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index e2a6228b6..000b36eec 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -387,7 +387,9 @@ def include!(name, options={}) if association.embed_in_root? && hash.nil? raise IncludeError.new(self.class, association.name) elsif association.embed_in_root? && association.embeddable? - merge_association hash, association.root, association.serializables, unique_values + association.roots.each do |root| + merge_association hash, root, association.serializables_for_root(root), unique_values + end end elsif association.embed_objects? node[association.key] = association.serialize diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index 1f2b0b53f..e393526a8 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -32,6 +32,7 @@ def initialize(name, options={}, serializer_options={}) @embed_objects = embed == :object || embed == :objects @embed_key = options[:embed_key] || :id @embed_in_root = options[:include] + @polymorphic = options[:polymorphic] || false serializer = options[:serializer] @serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer @@ -44,7 +45,6 @@ def initialize(name, options={}, serializer_options={}) alias embeddable? object alias embed_objects? embed_objects alias embed_ids? embed_ids - alias use_id_key? embed_ids? alias embed_in_root? embed_in_root def key @@ -59,7 +59,8 @@ def key private - attr_reader :embed_key, :serializer_class, :options, :serializer_options + attr_reader :embed_key, :serializer_class, :options, :serializer_options, :polymorphic + alias polymorphic? polymorphic def find_serializable(object) if serializer_class @@ -71,52 +72,116 @@ def find_serializable(object) end end + def type_name(object) + object.class.to_s.demodulize.underscore.to_sym + end + + def serialize_item(object) + serializable_hash = find_serializable(object).serializable_hash + if polymorphic? + type_name = type_name object + { + :type => type_name, + type_name => serializable_hash + } + else + serializable_hash + end + end + + def serialization_id(object) + serializer = find_serializable(object) + if serializer.respond_to?(embed_key) + serializer.send(embed_key) + else + object.read_attribute_for_serialization(embed_key) + end + end + + def serialize_id(object) + id = serialization_id object + + if polymorphic? + { + type: type_name(object), + id: id + } + else + id + end + end + + def use_id_key? + embed_ids? && !polymorphic? + end + class HasMany < Association #:nodoc: - def root - options[:root] || name + def roots + if options[:root] + [options[:root]] + elsif polymorphic? + object.map do |item| + polymorphic_root_for_item(item) + end.uniq + else + [name.to_s.pluralize.to_sym] + end end def id_key "#{name.to_s.singularize}_ids".to_sym end - def serializables - object.map do |item| - find_serializable(item) + def serializables_for_root(root) + if options[:root] || !polymorphic? + object.map do |item| + find_serializable(item) + end + else + object.select do |item| + polymorphic_root_for_item(item) == root + end.map do |item| + find_serializable(item) + end end end def serialize object.map do |item| - find_serializable(item).serializable_hash + serialize_item item + if polymorphic? + type_name = type_name item + { + :type => type_name, + type_name => find_serializable(item).serializable_hash + } + else + find_serializable(item).serializable_hash + end end end def serialize_ids object.map do |item| - serializer = find_serializable(item) - if serializer.respond_to?(embed_key) - serializer.send(embed_key) - else - item.read_attribute_for_serialization(embed_key) - end + serialize_id item end end - end - class HasOne < Association #:nodoc: - def initialize(name, options={}, serializer_options={}) - super - @polymorphic = options[:polymorphic] + private + + def polymorphic_root_for_item(item) + item.class.to_s.demodulize.pluralize.underscore.to_sym end + end - def root - if root = options[:root] - root + class HasOne < Association #:nodoc: + def roots + if options[:root] + [options[:root]] elsif polymorphic? - object.class.to_s.pluralize.demodulize.underscore.to_sym + [object.class.to_s.pluralize.demodulize.underscore.to_sym] else - name.to_s.pluralize.to_sym + [name.to_s.pluralize.to_sym] end end @@ -128,57 +193,22 @@ def embeddable? super || !polymorphic? end - def serializables + def serializables_for_root(root) value = object && find_serializable(object) value ? [value] : [] end def serialize if object - if polymorphic? - { - :type => polymorphic_key, - polymorphic_key => find_serializable(object).serializable_hash - } - else - find_serializable(object).serializable_hash - end + serialize_item object end end def serialize_ids if object - serializer = find_serializable(object) - id = - if serializer.respond_to?(embed_key) - serializer.send(embed_key) - else - object.read_attribute_for_serialization(embed_key) - end - - if polymorphic? - { - type: polymorphic_key, - id: id - } - else - id - end + serialize_id object end end - - private - - attr_reader :polymorphic - alias polymorphic? polymorphic - - def use_id_key? - embed_ids? && !polymorphic? - end - - def polymorphic_key - object.class.to_s.demodulize.underscore.to_sym - end end end end diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 5da28d8aa..1bae478c7 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -1089,8 +1089,23 @@ def read_attribute_for_serialization(name) end def tests_can_handle_polymorphism + recipient_serializer = Class.new(ActiveModel::Serializer) do + attributes :address, :id + end + + recipient_class = Class.new(Model) do + def self.to_s + "Recipient" + end + + define_method :active_model_serializer do + recipient_serializer + end + end + email_serializer = Class.new(ActiveModel::Serializer) do attributes :subject, :body + has_many :receivables, polymorphic: true end email_class = Class.new(Model) do @@ -1098,6 +1113,10 @@ def self.to_s "Email" end + define_method :receivables do + @attributes[:receivables] + end + define_method :active_model_serializer do email_serializer end @@ -1108,7 +1127,10 @@ def self.to_s has_one :attachable, polymorphic: true end - email = email_class.new subject: 'foo', body: 'bar' + email = email_class.new subject: 'foo', body: 'bar', receivables: [ + recipient_class.new(id: 1, address: 'person@example.com'), + recipient_class.new(id: 2, address: 'me@0.0.0.0') + ] attachment = Attachment.new name: 'logo.png', url: 'http://example.com/logo.png', attachable: email @@ -1119,13 +1141,33 @@ def self.to_s url: 'http://example.com/logo.png', attachable: { type: :email, - email: { subject: 'foo', body: 'bar' } + email: { + subject: 'foo', + body: 'bar', + receivables: [ + { + type: :recipient, + recipient: { + id: 1, + address: 'person@example.com' + } + }, + { + type: :recipient, + recipient: { + id: 2, + address: 'me@0.0.0.0' + } + } + ] + } } }, actual) end - def test_can_handle_polymoprhic_ids + def test_has_one_can_handle_polymoprhic_ids email_serializer = Class.new(ActiveModel::Serializer) do + embed :ids attributes :subject, :body end @@ -1156,14 +1198,88 @@ def self.to_s url: 'http://example.com/logo.png', attachable: { type: :email, - id: 1 + id: 1, } }, actual) end + def test_has_many_can_handle_polymoprhic_ids + recipient_serializer = Class.new(ActiveModel::Serializer) do + attribute :address + end + + recipient_class = Class.new(Model) do + def self.to_s + "Recipient" + end + + define_method :active_model_serializer do + recipient_serializer + end + end + + email_serializer = Class.new(ActiveModel::Serializer) do + embed :ids + attributes :subject, :body + has_many :receivables, polymorphic: true + end + + email_class = Class.new(Model) do + def self.to_s + "Email" + end + + define_method :receivables do + @attributes[:receivables] + end + + define_method :active_model_serializer do + email_serializer + end + end + + email = email_class.new id: 1, receivables: [ + recipient_class.new(id: 1, address: 'person@example.com'), + recipient_class.new(id: 2, address: 'me@0.0.0.0') + ] + + actual = email_serializer.new(email, {}).as_json + + assert_equal({ + subject: nil, + body: nil, + receivables: [ + { + type: :recipient, + id: 1 + }, + { + type: :recipient, + id: 2 + } + ] + }, actual) + end + def test_polymorphic_associations_are_included_at_root + recipient_serializer = Class.new(ActiveModel::Serializer) do + attributes :address, :id + end + + recipient_class = Class.new(Model) do + def self.to_s + "Recipient" + end + + define_method :active_model_serializer do + recipient_serializer + end + end + email_serializer = Class.new(ActiveModel::Serializer) do + embed :ids, include: true attributes :subject, :body, :id + has_many :receivables, polymorphic: true end email_class = Class.new(Model) do @@ -1171,6 +1287,10 @@ def self.to_s "Email" end + define_method :receivables do + @attributes[:receivables] + end + define_method :active_model_serializer do email_serializer end @@ -1183,25 +1303,49 @@ def self.to_s has_one :attachable, polymorphic: true end - email = email_class.new id: 1, subject: "Hello", body: "World" + email = email_class.new id: 1, subject: "Hello", body: "World", receivables: [ + recipient_class.new(id: 1, address: 'person@example.com'), + recipient_class.new(id: 2, address: 'me@0.0.0.0') + ] attachment = Attachment.new name: 'logo.png', url: 'http://example.com/logo.png', attachable: email actual = attachment_serializer.new(attachment, {}).as_json assert_equal({ + emails: [{ + subject: "Hello", + body: "World", + id: 1, + receivables: [ + { + type: :recipient, + id: 1 + }, + { + type: :recipient, + id: 2 + } + ] + }], + recipients: [ + { + address: 'person@example.com', + id: 1 + }, + { + address: 'me@0.0.0.0', + id: 2 + } + ], attachment: { name: 'logo.png', url: 'http://example.com/logo.png', attachable: { type: :email, id: 1 - }}, - emails: [{ - id: 1, - subject: "Hello", - body: "World" - }] + } + } }, actual) end