From 03829e128b6f9411db28493fd7477d8275be470a Mon Sep 17 00:00:00 2001 From: Gary Gordon Date: Fri, 24 Oct 2014 08:23:07 -0400 Subject: [PATCH] Explicitly set serializer for associations Document specifying serializer for assocaition --- README.md | 6 ++ lib/active_model/serializer.rb | 20 ++++-- .../serializer/adapter/json_api.rb | 2 +- .../serializer/array_serializer.rb | 6 +- .../has_many_explicit_serializer_test.rb | 62 +++++++++++++++++++ test/fixtures/poro.rb | 23 +++++++ 6 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 test/adapter/json_api/has_many_explicit_serializer_test.rb diff --git a/README.md b/README.md index 4121dfe2c..502486ab5 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,12 @@ The `has_many` and `belongs_to` declarations describe relationships between resources. By default, when you serialize a `Post`, you will get its `Comment`s as well. +You may also use the `:serializer` option to specify a custom serializer class, for example: + +```ruby + has_many :comments, serializer: CommentPreviewSerializer +``` + The `url` declaration describes which named routes to use while generating URLs for your JSON. Not every adapter will require URLs. diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index dbc2060d9..63cbe716b 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -21,7 +21,6 @@ def self.inherited(base) def self.attributes(*attrs) @_attributes.concat attrs - attrs.each do |attr| define_method attr do object.read_attribute_for_serialization(attr) @@ -72,11 +71,12 @@ def self.urls(*attrs) @_urls.concat attrs end - def self.serializer_for(resource) + def self.serializer_for(resource, options = {}) if resource.respond_to?(:to_ary) config.array_serializer else - get_serializer_for(resource.class) + options.fetch(:options, {}) + .fetch(:serializer, get_serializer_for(resource.class)) end end @@ -132,8 +132,11 @@ def attributes(options = {}) def each_association(&block) self.class._associations.dup.each do |name, options| association = object.send(name) - serializer_class = ActiveModel::Serializer.serializer_for(association) - serializer = serializer_class.new(association) if serializer_class + serializer_class = ActiveModel::Serializer.serializer_for(association, options) + serializer = serializer_class.new( + association, + serializer_from_options(options) + ) if serializer_class if block_given? block.call(name, serializer, options[:options]) @@ -141,6 +144,13 @@ def each_association(&block) end end + def serializer_from_options(options) + opts = {} + serializer = options.fetch(:options, {}).fetch(:serializer, nil) + opts[:serializer] = serializer if serializer + opts + end + private def self.get_serializer_for(klass) diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index 693ec316b..a3f9b39f0 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -55,7 +55,7 @@ def add_links(name, serializers, options) end def add_link(name, serializer, options) - if serializer + if serializer && serializer.object type = serializer.object.class.to_s.underscore if name.to_s == type || !type @hash[@root][:links][name] = serializer.id.to_s diff --git a/lib/active_model/serializer/array_serializer.rb b/lib/active_model/serializer/array_serializer.rb index b52a4f51d..55651a196 100644 --- a/lib/active_model/serializer/array_serializer.rb +++ b/lib/active_model/serializer/array_serializer.rb @@ -6,7 +6,11 @@ class ArraySerializer def initialize(objects, options = {}) @objects = objects.map do |object| - serializer_class = ActiveModel::Serializer.serializer_for(object) + serializer_class = + options.fetch( + :serializer, + ActiveModel::Serializer.serializer_for(object) + ) serializer_class.new(object) 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 new file mode 100644 index 000000000..406df833f --- /dev/null +++ b/test/adapter/json_api/has_many_explicit_serializer_test.rb @@ -0,0 +1,62 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class Adapter + class JsonApi + # Test 'has_many :assocs, serializer: AssocXSerializer' + class HasManyExplicitSerializerTest < Minitest::Test + def setup + @post = Post.new(title: 'New Post', body: 'Body') + @author = Author.new(name: 'Jane Blogger') + @author.posts = [@post] + @post.author = @author + @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] + @first_comment.post = @post + @second_comment.post = @post + + @serializer = PostPreviewSerializer.new(@post) + @adapter = ActiveModel::Serializer::Adapter::JsonApi.new( + @serializer, + include: 'comments,author,author' + ) + end + + def test_includes_comment_ids + assert_equal(['1', '2'], + @adapter.serializable_hash[:posts][:links][:comments]) + end + + def test_includes_linked_comments + assert_equal([{ id: '1' }, { id: '2' }], + @adapter.serializable_hash[:linked][:comments]) + end + + def test_includes_author_id + assert_equal(@author.id.to_s, + @adapter.serializable_hash[:posts][:links][:author]) + end + + def test_includes_linked_authors + assert_equal([{ id: @author.id.to_s }], + @adapter.serializable_hash[:linked][:authors]) + end + + def test_explicit_serializer_with_null_resource + @post.author = nil + assert_equal(nil, + @adapter.serializable_hash[:posts][:links][:author]) + end + + def test_explicit_serializer_with_null_collection + @post.comments = [] + assert_equal([], + @adapter.serializable_hash[:posts][:links][:comments]) + end + end + end + end + end +end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 7d8d57b42..729024682 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -67,3 +67,26 @@ class ProfileSerializer < ActiveModel::Serializer belongs_to :writer has_many :articles end + +CommentPreviewSerializer = Class.new(ActiveModel::Serializer) do + attributes :id + + belongs_to :post +end + +AuthorPreviewSerializer = Class.new(ActiveModel::Serializer) do + attributes :id + + has_many :posts +end + +PostPreviewSerializer = Class.new(ActiveModel::Serializer) do + def self.root_name + 'posts' + end + + attributes :title, :body, :id + + has_many :comments, serializer: CommentPreviewSerializer + belongs_to :author, serializer: AuthorPreviewSerializer +end