diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index fa0e3dc65..973911d3f 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -23,9 +23,9 @@ class << self end def self.inherited(base) - base._attributes = self._attributes.try(:dup) || [] + base._attributes = self._attributes.try(:dup) || [] base._attributes_keys = self._attributes_keys.try(:dup) || {} - base._associations = self._associations.try(:dup) || {} + base._associations = self._associations.try(:dup) || [] base._urls = [] serializer_file = File.open(caller.first[/^[^:]+/]) base._cache_digest = Digest::MD5.hexdigest(serializer_file.read) @@ -45,7 +45,7 @@ def self.attributes(*attrs) def self.attribute(attr, options = {}) key = options.fetch(:key, attr) - @_attributes_keys[attr] = {key: key} if key != attr + @_attributes_keys[attr] = { key: key } if key != attr @_attributes << key unless @_attributes.include?(key) define_method key do object.read_attribute_for_serialization(attr) @@ -58,13 +58,17 @@ def self.fragmented(serializer) # Enables a serializer to be automatically cached def self.cache(options = {}) - @_cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching - @_cache_key = options.delete(:key) - @_cache_only = options.delete(:only) - @_cache_except = options.delete(:except) + @_cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching + @_cache_key = options.delete(:key) + @_cache_only = options.delete(:only) + @_cache_except = options.delete(:except) @_cache_options = (options.empty?) ? nil : options end + Association = Struct.new(:name, :options) + SingularAssociation = Class.new(Association) + CollectionAssociation = Class.new(Association) + # Defines an association in the object should be rendered. # # The serializer object should implement the association name @@ -72,7 +76,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| + CollectionAssociation.new(name, options) + end end # Defines an association in the object that should be rendered. @@ -82,7 +88,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| + SingularAssociation.new(name, options) + end end # Defines an association in the object should be rendered. @@ -92,21 +100,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| + SingularAssociation.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 - 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._associations << block.call(name, options) end end @@ -198,31 +209,50 @@ def attributes(options = {}) end end + def associations + Enumerator.new do |y| + self.class._associations.dup.each do |association| + y.yield association + end + end + end + + # @return [Enumerable] + # + def associations + + end + require 'pp' def each_association(&block) return unless object return unless block_given? - self.class._associations.dup.each do |name, association_options| - association_value = send(name) + # self._associations[attr] = {type: type, association_options: options} + # @param [Hash] before + # + self.class._associations.dup.each do |association| + association_options = association.options + + association_value = send(association.name) - serializer_class = ActiveModel::Serializer.serializer_for(association_value, association_options) + serializer_class = ActiveModel::Serializer.serializer_for(association_value, association.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(association.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 + association.options[:virtual_value] = virtual_value end elsif !association_value.nil? && !association_value.instance_of?(Object) - association_options[:association_options][:virtual_value] = association_value + association.options[:virtual_value] = association_value end - block.call(name, serializer, association_options[:association_options]) + block.call(association.name, serializer, association.options) end end diff --git a/test/adapter/json_api/has_many_explicit_serializer_test.rb b/test/adapter/json_api/has_many_explicit_serializer_test.rb index 2adb0eb53..e7ac6e6ff 100644 --- a/test/adapter/json_api/has_many_explicit_serializer_test.rb +++ b/test/adapter/json_api/has_many_explicit_serializer_test.rb @@ -8,7 +8,7 @@ class JsonApi class HasManyExplicitSerializerTest < Minitest::Test def setup @post = Post.new(title: 'New Post', body: 'Body') - @author = Author.new(name: 'Jane Blogger') + @author = Author.new(name: 'Jane Blogger', roles: [], bio: nil) @author.posts = [@post] @post.author = @author @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @@ -58,9 +58,9 @@ def test_includes_linked_data }, { id: @author.id.to_s, - type: "authors", + type: 'authors', relationships: { - posts: { data: [ {type: "posts", id: @post.id.to_s } ] } + posts: { data: [ {type: 'posts', id: @post.id.to_s } ] } } } ] diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb index 04681d2c4..dfc64dcee 100644 --- a/test/serializers/associations_test.rb +++ b/test/serializers/associations_test.rb @@ -39,26 +39,22 @@ def setup @post.author = @author @author.posts = [@post] + @post_serializer = PostSerializer.new(@post, {custom_options: true}) @post_serializer = PostSerializer.new(@post, {custom_options: true}) @author_serializer = AuthorSerializer.new(@author) @comment_serializer = CommentSerializer.new(@comment) 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.each_association do |name, serializer, options| - if name == :posts + case name + when :posts assert_equal({embed: :ids}, options) assert_kind_of(ActiveModel::Serializer.config.array_serializer, serializer) - elsif name == :bio + when :bio assert_equal({}, options) assert_nil serializer - elsif name == :roles + when :roles assert_equal({embed: :ids}, options) assert_kind_of(ActiveModel::Serializer.config.array_serializer, serializer) else @@ -84,16 +80,12 @@ def test_serializer_options_are_passed_into_associations_serializers 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.each_association do |name, serializer, options| - if name == :post + case name + when :post assert_equal({}, options) assert_kind_of(PostSerializer, serializer) - elsif name == :author + when :author assert_equal({}, options) assert_nil serializer else @@ -122,15 +114,16 @@ 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 - } - } + + inherited_associations = inherited_klass._associations + parent_associations = PostSerializer._associations + + assert( + parent_associations.all? do |association| + inherited_associations.include?(association) + end ) - assert_equal(inherited_klass._associations, expected_associations) + assert(inherited_associations.map(&:name).include?(:top_comments)) end end end