diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 9eb744f8f..a5810c4ba 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -3,19 +3,24 @@ module ActiveModel class Serializer extend ActiveSupport::Autoload + autoload :Configuration autoload :ArraySerializer autoload :Adapter - include Configuration - - class Association < Struct.new(:name, :serializer, :options) + autoload :Association + autoload :Reflection + autoload :SingularReflection + autoload :CollectionReflection + autoload :BelongsToReflection + autoload :HasOneReflection + autoload :HasManyReflection - end + include Configuration class << self attr_accessor :_attributes attr_accessor :_attributes_keys - attr_accessor :_associations + attr_accessor :_reflections attr_accessor :_urls attr_accessor :_cache attr_accessor :_fragmented @@ -29,7 +34,7 @@ class << self def self.inherited(base) base._attributes = self._attributes.try(:dup) || [] base._attributes_keys = self._attributes_keys.try(:dup) || {} - base._associations = self._associations.try(:dup) || {} + base._reflections = self._reflections.try(:dup) || [] base._urls = [] serializer_file = File.open(caller.first[/^[^:]+/]) base._cache_digest = Digest::MD5.hexdigest(serializer_file.read) @@ -76,7 +81,9 @@ def self.cache(options = {}) # with the association name does not exist, the association name is # dispatched to the serialized object. def self.has_many(*attrs) - associate(:has_many, attrs) + associate attrs do |name, options| + HasManyReflection.new(name, options) + end end # Defines an association in the object that should be rendered. @@ -86,7 +93,9 @@ def self.has_many(*attrs) # with the association name does not exist, the association name is # dispatched to the serialized object. def self.belongs_to(*attrs) - associate(:belongs_to, attrs) + associate attrs do |name, options| + BelongsToReflection.new(name, options) + end end # Defines an association in the object should be rendered. @@ -96,21 +105,24 @@ def self.belongs_to(*attrs) # with the association name does not exist, the association name is # dispatched to the serialized object. def self.has_one(*attrs) - associate(:has_one, attrs) + associate attrs do |name, options| + HasOneReflection.new(name, options) + end end - def self.associate(type, attrs) #:nodoc: + # has_one :author, :image, only: :object + def self.associate(attrs, &block) #:nodoc: options = attrs.extract_options! - self._associations = _associations.dup + self._reflections = _reflections.dup - attrs.each do |attr| - unless method_defined?(attr) - define_method attr do - object.send attr + attrs.each do |name| + unless method_defined?(name) + define_method name do + object.send name end end - self._associations[attr] = { type: type, association_options: options } + self._reflections << block.call(name, options) end end @@ -128,9 +140,7 @@ def self.serializer_for(resource, options = {}) elsif resource.respond_to?(:to_ary) config.array_serializer else - options - .fetch(:association_options, {}) - .fetch(:serializer, get_serializer_for(resource.class)) + options.fetch(:serializer, get_serializer_for(resource.class)) end end @@ -207,44 +217,44 @@ def associations return unless object Enumerator.new do |y| - self.class._associations.dup.each do |name, association_options| + self.class._reflections.dup.each do |reflection| + name = reflection.name association_value = send(name) - - serializer_class = ActiveModel::Serializer.serializer_for(association_value, association_options) + serializer_class = ActiveModel::Serializer.serializer_for(association_value, reflection.options) if serializer_class begin serializer = serializer_class.new( association_value, - options.except(:serializer).merge(serializer_from_options(association_options)) + options.except(:serializer).merge(serializer_from_options(reflection.options)) ) rescue ActiveModel::Serializer::ArraySerializer::NoSerializerError virtual_value = association_value virtual_value = virtual_value.as_json if virtual_value.respond_to?(:as_json) - association_options[:association_options][:virtual_value] = virtual_value + reflection.options[:virtual_value] = virtual_value end elsif !association_value.nil? && !association_value.instance_of?(Object) - association_options[:association_options][:virtual_value] = association_value + reflection.options[:virtual_value] = association_value end - y.yield Association.new(name, serializer, association_options[:association_options]) + y.yield Association.new(name, serializer, reflection.options) end end end - def serializer_from_options(options) - opts = {} - serializer = options.fetch(:association_options, {}).fetch(:serializer, nil) - opts[:serializer] = serializer if serializer - opts - end - def self.serializers_cache @serializers_cache ||= ThreadSafe::Cache.new end private + def serializer_from_options(options) + opts = {} + serializer = options.fetch(:serializer, nil) + opts[:serializer] = serializer if serializer + opts + end + attr_reader :options def self.get_serializer_for(klass) diff --git a/lib/active_model/serializer/association.rb b/lib/active_model/serializer/association.rb new file mode 100644 index 000000000..b3d99ff41 --- /dev/null +++ b/lib/active_model/serializer/association.rb @@ -0,0 +1,5 @@ +module ActiveModel + class Serializer + Association = Struct.new(:name, :serializer, :options) + end +end diff --git a/lib/active_model/serializer/belongs_to_reflection.rb b/lib/active_model/serializer/belongs_to_reflection.rb new file mode 100644 index 000000000..909ada030 --- /dev/null +++ b/lib/active_model/serializer/belongs_to_reflection.rb @@ -0,0 +1,9 @@ +module ActiveModel + class Serializer + class BelongsToReflection < SingularReflection + def macro + :belongs_to + end + end + end +end diff --git a/lib/active_model/serializer/collection_reflection.rb b/lib/active_model/serializer/collection_reflection.rb new file mode 100644 index 000000000..c7157aa27 --- /dev/null +++ b/lib/active_model/serializer/collection_reflection.rb @@ -0,0 +1,6 @@ +module ActiveModel + class Serializer + class CollectionReflection < Reflection + end + end +end diff --git a/lib/active_model/serializer/has_many_reflection.rb b/lib/active_model/serializer/has_many_reflection.rb new file mode 100644 index 000000000..713be5fff --- /dev/null +++ b/lib/active_model/serializer/has_many_reflection.rb @@ -0,0 +1,9 @@ +module ActiveModel + class Serializer + class HasManyReflection < CollectionReflection + def macro + :has_many + end + end + end +end diff --git a/lib/active_model/serializer/has_one_reflection.rb b/lib/active_model/serializer/has_one_reflection.rb new file mode 100644 index 000000000..47bd8e28f --- /dev/null +++ b/lib/active_model/serializer/has_one_reflection.rb @@ -0,0 +1,9 @@ +module ActiveModel + class Serializer + class HasOneReflection < SingularReflection + def macro + :has_one + end + end + end +end diff --git a/lib/active_model/serializer/reflection.rb b/lib/active_model/serializer/reflection.rb new file mode 100644 index 000000000..bec5a6910 --- /dev/null +++ b/lib/active_model/serializer/reflection.rb @@ -0,0 +1,7 @@ +module ActiveModel + class Serializer + # Holds all the meta-data about an association as it was specified in the + # ActiveModel::Serializer class. + Reflection = Struct.new(:name, :options) + end +end diff --git a/lib/active_model/serializer/singular_reflection.rb b/lib/active_model/serializer/singular_reflection.rb new file mode 100644 index 000000000..26f43a512 --- /dev/null +++ b/lib/active_model/serializer/singular_reflection.rb @@ -0,0 +1,6 @@ +module ActiveModel + class Serializer + class SingularReflection < Reflection + end + end +end diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb index df9927fe4..97ff559ee 100644 --- a/test/serializers/associations_test.rb +++ b/test/serializers/associations_test.rb @@ -45,12 +45,6 @@ def setup end def test_has_many_and_has_one - assert_equal( - { posts: { type: :has_many, association_options: { embed: :ids } }, - roles: { type: :has_many, association_options: { embed: :ids } }, - bio: { type: :has_one, association_options: {} } }, - @author_serializer.class._associations - ) @author_serializer.associations.each do |association| name = association.name serializer = association.serializer @@ -90,17 +84,14 @@ def test_serializer_options_are_passed_into_associations_serializers serializer = association.serializer if name == :comments + puts serializer.first + assert serializer.first.custom_options[:custom_options] end end end def test_belongs_to - assert_equal( - { post: { type: :belongs_to, association_options: {} }, - author: { type: :belongs_to, association_options: {} } }, - @comment_serializer.class._associations - ) @comment_serializer.associations.each do |association| name = association.name serializer = association.serializer @@ -130,22 +121,25 @@ def test_belongs_to_with_custom_method def test_associations_inheritance inherited_klass = Class.new(PostSerializer) - assert_equal(PostSerializer._associations, inherited_klass._associations) + assert_equal(PostSerializer._reflections, inherited_klass._reflections) end def test_associations_inheritance_with_new_association inherited_klass = Class.new(PostSerializer) do has_many :top_comments, serializer: CommentSerializer end - expected_associations = PostSerializer._associations.merge( - top_comments: { - type: :has_many, - association_options: { - serializer: CommentSerializer - } - } + + assert( + PostSerializer._reflections.all? do |reflection| + inherited_klass._reflections.include?(reflection) + end + ) + + assert( + inherited_klass._reflections.any? do |reflection| + reflection.name == :top_comments + end ) - assert_equal(inherited_klass._associations, expected_associations) end end end