diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 0397a847e..efe298f0e 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -222,10 +222,22 @@ def each_association(&block) serializer_class = ActiveModel::Serializer.serializer_for(association_value, association_options) if serializer_class - serializer = serializer_class.new( - association_value, - options.except(:serializer).merge(serializer_from_options(association_options)) - ) + begin + serializer = serializer_class.new( + association_value, + options.except(:serializer).merge(serializer_from_options(association_options)) + ) + rescue NoMethodError + # 1. Failure to serialize an element in a collection, e.g. [ {hi: "Steve" } ] will fail + # with NoMethodError when the ArraySerializer finds no serializer for the hash { hi: "Steve" }, + # and tries to call new on that nil. + # 2. Convert association_value to hash using implicit as_json + # 3. Set as virtual value (serializer is nil) + # 4. Consider warning when this happens + 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 + end elsif !association_value.nil? && !association_value.instance_of?(Object) association_options[:association_options][:virtual_value] = association_value end diff --git a/test/adapter/json/has_many_test.rb b/test/adapter/json/has_many_test.rb index 19fe16cd3..4ca26b713 100644 --- a/test/adapter/json/has_many_test.rb +++ b/test/adapter/json/has_many_test.rb @@ -8,7 +8,7 @@ class HasManyTestTest < Minitest::Test def setup ActionController::Base.cache_store.clear @author = Author.new(id: 1, name: 'Steve K.') - @post = Post.new(title: 'New Post', body: 'Body') + @post = Post.new(id: 42, title: 'New Post', body: 'Body') @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') @post.comments = [@first_comment, @second_comment] @@ -17,16 +17,28 @@ def setup @second_comment.post = @post @blog = Blog.new(id: 1, name: "My Blog!!") @post.blog = @blog - - @serializer = PostSerializer.new(@post) - @adapter = ActiveModel::Serializer::Adapter::Json.new(@serializer) + @tag = Tag.new(id: 1, name: "#hash_tag") + @post.tags = [@tag] end def test_has_many + serializer = PostSerializer.new(@post) + adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) assert_equal([ {id: 1, body: 'ZOMG A COMMENT'}, {id: 2, body: 'ZOMG ANOTHER COMMENT'} - ], @adapter.serializable_hash[:post][:comments]) + ], adapter.serializable_hash[:post][:comments]) + end + + def test_has_many_with_no_serializer + serializer = PostWithTagsSerializer.new(@post) + adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) + assert_equal({ + id: 42, + tags: [ + {"attributes"=>{"id"=>1, "name"=>"#hash_tag"}} + ] + }, adapter.serializable_hash[:post_with_tags]) end end end diff --git a/test/adapter/json_api/has_many_test.rb b/test/adapter/json_api/has_many_test.rb index a544fc800..8393f6649 100644 --- a/test/adapter/json_api/has_many_test.rb +++ b/test/adapter/json_api/has_many_test.rb @@ -27,7 +27,8 @@ def setup @blog.articles = [@post] @post.blog = @blog @post_without_comments.blog = nil - + @tag = Tag.new(id: 1, name: "#hash_tag") + @post.tags = [@tag] @serializer = PostSerializer.new(@post) @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) end @@ -103,6 +104,20 @@ def test_include_type_for_association_when_different_than_name } assert_equal expected, actual end + + def test_has_many_with_no_serializer + serializer = PostWithTagsSerializer.new(@post) + adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) + assert_equal({ + data: { + id: "1", + type: "posts", + relationships: { + tags: {:data=>nil} + } + } + }, adapter.serializable_hash) + end end end end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 29e193ee0..ea293eb3e 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -77,6 +77,7 @@ class ProfilePreviewSerializer < ActiveModel::Serializer User = Class.new(Model) Location = Class.new(Model) Place = Class.new(Model) +Tag = Class.new(Model) module Spam; end Spam::UnrelatedLink = Class.new(Model) @@ -219,6 +220,12 @@ def self.root_name belongs_to :author, serializer: AuthorPreviewSerializer end +PostWithTagsSerializer = Class.new(ActiveModel::Serializer) do + attributes :id + + has_many :tags +end + Spam::UnrelatedLinkSerializer = Class.new(ActiveModel::Serializer) do attributes :id end diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb index ab481de7d..bf468931b 100644 --- a/test/serializers/associations_test.rb +++ b/test/serializers/associations_test.rb @@ -29,8 +29,10 @@ def setup @author.roles = [] @blog = Blog.new({ name: 'AMS Blog' }) @post = Post.new({ title: 'New Post', body: 'Body' }) + @tag = Tag.new({name: '#hashtagged'}) @comment = Comment.new({ id: 1, body: 'ZOMG A COMMENT' }) @post.comments = [@comment] + @post.tags = [@tag] @post.blog = @blog @comment.post = @post @comment.author = nil @@ -65,6 +67,14 @@ def test_has_many_and_has_one end end + def test_has_many_with_no_serializer + PostWithTagsSerializer.new(@post).each_association do |name, serializer, options| + assert_equal name, :tags + assert_equal serializer, nil + assert_equal options, {:virtual_value=>[{"attributes"=>{"name"=>"#hashtagged"}}]} + end + end + def test_serializer_options_are_passed_into_associations_serializers @post_serializer.each_association do |name, association| if name == :comments