diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 714ff65d4..3a0abe9cb 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -9,6 +9,7 @@ require 'active_model/serializer/fieldset' require 'active_model/serializer/lint' require 'active_model/serializer/links' +require 'active_model/serializer/meta' require 'active_model/serializer/type' # ActiveModel::Serializer is an abstract class that is @@ -20,6 +21,7 @@ class Serializer include Attributes include Caching include Links + include Meta include Type require 'active_model/serializer/adapter' diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index 744d62e47..d5ceaa916 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -6,6 +6,7 @@ class JsonApi < Base autoload :PaginationLinks autoload :FragmentCache autoload :Link + autoload :Meta autoload :Deserialization # TODO: if we like this abstraction and other API objects to it, @@ -157,6 +158,9 @@ def resource_object_for(serializer) links = links_for(serializer) resource_object[:links] = links if links.any? + meta = meta_for(serializer) + resource_object[:meta] = meta unless meta.nil? + resource_object end @@ -217,6 +221,10 @@ def links_for(serializer) def pagination_links_for(serializer, options) JsonApi::PaginationLinks.new(serializer.object, options[:serialization_context]).serializable_hash(options) end + + def meta_for(serializer) + Meta.new(serializer).as_json + end end end end diff --git a/lib/active_model/serializer/adapter/json_api/meta.rb b/lib/active_model/serializer/adapter/json_api/meta.rb new file mode 100644 index 000000000..8fba89861 --- /dev/null +++ b/lib/active_model/serializer/adapter/json_api/meta.rb @@ -0,0 +1,29 @@ +module ActiveModel + class Serializer + module Adapter + class JsonApi + class Meta + def initialize(serializer) + @object = serializer.object + @scope = serializer.scope + + # Use the return value of the block unless it is nil. + if serializer._meta.respond_to?(:call) + @value = instance_eval(&serializer._meta) + else + @value = serializer._meta + end + end + + def as_json + @value + end + + protected + + attr_reader :object, :scope + end + end + end + end +end diff --git a/lib/active_model/serializer/meta.rb b/lib/active_model/serializer/meta.rb new file mode 100644 index 000000000..5160585e0 --- /dev/null +++ b/lib/active_model/serializer/meta.rb @@ -0,0 +1,29 @@ +module ActiveModel + class Serializer + module Meta + extend ActiveSupport::Concern + + included do + with_options instance_writer: false, instance_reader: true do |serializer| + serializer.class_attribute :_meta # @api private + end + + extend ActiveSupport::Autoload + end + + module ClassMethods + # Set the JSON API meta attribute of a serializer. + # @example + # class AdminAuthorSerializer < ActiveModel::Serializer + # meta { stuff: 'value' } + # @example + # meta do + # { comment_count: object.comments.count } + # end + def meta(value = nil, &block) + self._meta = block || value + end + end + end + end +end diff --git a/test/adapter/json_api/resource_meta_test.rb b/test/adapter/json_api/resource_meta_test.rb new file mode 100644 index 000000000..4298f03c8 --- /dev/null +++ b/test/adapter/json_api/resource_meta_test.rb @@ -0,0 +1,63 @@ +require 'test_helper' + +module ActiveModel + class Serializer + module Adapter + class JsonApi + class ResourceMetaTest < Minitest::Test + class MetaHashPostSerializer < ActiveModel::Serializer + attributes :id + meta stuff: 'value' + end + + class MetaBlockPostSerializer < ActiveModel::Serializer + attributes :id + meta do + { comments_count: object.comments.count } + end + end + + def setup + @post = Post.new(id: 1337, comments: [], author: nil) + end + + def test_meta_hash_object_resource + hash = ActiveModel::SerializableResource.new( + @post, + serializer: MetaHashPostSerializer, + adapter: :json_api + ).serializable_hash + expected = { + stuff: 'value' + } + assert_equal(expected, hash[:data][:meta]) + end + + def test_meta_block_object_resource + hash = ActiveModel::SerializableResource.new( + @post, + serializer: MetaBlockPostSerializer, + adapter: :json_api + ).serializable_hash + expected = { + comments_count: @post.comments.count + } + assert_equal(expected, hash[:data][:meta]) + end + + def test_meta_object_resource_in_array + hash = ActiveModel::SerializableResource.new( + [@post, @post], + each_serializer: MetaBlockPostSerializer, + adapter: :json_api + ).serializable_hash + expected = { + comments_count: @post.comments.count + } + assert_equal([expected, expected], hash[:data].map { |obj| obj[:meta] }) + end + end + end + end + end +end diff --git a/test/serializers/meta_test.rb b/test/serializers/meta_test.rb index a555adb7e..e75954767 100644 --- a/test/serializers/meta_test.rb +++ b/test/serializers/meta_test.rb @@ -3,6 +3,8 @@ module ActiveModel class Serializer class MetaTest < ActiveSupport::TestCase + MetaBlogSerializer = Class.new(ActiveModel::Serializer) + def setup @blog = Blog.new(id: 1, name: 'AMS Hints', @@ -125,6 +127,20 @@ def test_meta_is_present_on_arrays_with_root } assert_equal(expected, actual) end + + def test_meta_is_set_with_direct_attributes + MetaBlogSerializer.meta stuff: 'value' + blog_meta_serializer = MetaBlogSerializer.new(@blog) + assert_equal(blog_meta_serializer.meta, stuff: 'value') + end + + def test_meta_is_set_with_block + MetaBlogSerializer.meta do + { articles_count: object.articles.count } + end + blog_meta_serializer = MetaBlogSerializer.new(@blog) + assert_equal(blog_meta_serializer.meta, articles_count: @blog.articles.count) + end end end end