From 2c4193851b3a2e3120cbb3c61fa45577bd5c8ed7 Mon Sep 17 00:00:00 2001 From: Yohan Robert Date: Wed, 10 Feb 2016 01:02:48 +0100 Subject: [PATCH] Follow up to #1454 PR #1454 was merged with some missing fixes. All these fixes are addressed by this commit: - Rename ActiveModel::Serializer::Adapter::JsonApi::Association to ActiveModel::Serializer::Adapter::JsonApi::Relationship - Move ActiveModel::Serializer::Adapter:: JsonApi::Relationship and ActiveModel::Serializer::Adapter::JsonApi::ResourceIdentifier to ActiveModel::Serializer::Adapter::JsonApi::ApiObjects module - Add unit test for ActiveModel::Serializer::Adapter::JsonApi::Relationship - Add unit test for ActiveModel::Serializer::Adapter::JsonApi::ResourceIdentifier --- CHANGELOG.md | 5 + .../serializer/adapter/json_api.rb | 14 +- .../adapter/json_api/api_objects.rb | 13 ++ .../json_api/api_objects/relationship.rb | 52 ++++++ .../api_objects/resource_identifier.rb | 39 ++++ .../adapter/json_api/association.rb | 48 ----- .../adapter/json_api/resource_identifier.rb | 41 ----- .../json_api/api_objects/relationship_test.rb | 168 ++++++++++++++++++ .../api_objects/resource_identifier_test.rb | 88 +++++++++ 9 files changed, 372 insertions(+), 96 deletions(-) create mode 100644 lib/active_model/serializer/adapter/json_api/api_objects.rb create mode 100644 lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb create mode 100644 lib/active_model/serializer/adapter/json_api/api_objects/resource_identifier.rb delete mode 100644 lib/active_model/serializer/adapter/json_api/association.rb delete mode 100644 lib/active_model/serializer/adapter/json_api/resource_identifier.rb create mode 100644 test/adapter/json_api/api_objects/relationship_test.rb create mode 100644 test/adapter/json_api/api_objects/resource_identifier_test.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index b6a852b0..ab451b60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,19 @@ ## 0.10.x Breaking changes: + Features: +- [#1504](https://github.com/rails-api/active_model_serializers/pull/1504) Adds the changes missing from #1454 + and add more tests for resource identifier and relationship objects. (@groyoh) - [#1018](https://github.com/rails-api/active_model_serializers/pull/1018) Add more tests and docs for top-level links. (@leandrocp) - [#1454](https://github.com/rails-api/active_model_serializers/pull/1454) Add support for relationship-level links and meta attributes. (@beauby) - [#1340](https://github.com/rails-api/active_model_serializers/pull/1340) Add support for resource-level meta. (@beauby) + Fixes: - [#1501](https://github.com/rails-api/active_model_serializers/pull/1501) Adds tests for SerializableResource::use_adapter?,doc typos (@domitian) - [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00) + Misc: ### v0.10.0.rc4 (2016/01/27 11:00 +00:00) diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index a4157193..cfe4e04d 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -6,10 +6,9 @@ module ActiveModel autoload :PaginationLinks autoload :FragmentCache autoload :Link - autoload :Association - autoload :ResourceIdentifier autoload :Meta autoload :Deserialization + require 'active_model/serializer/adapter/json_api/api_objects' # TODO: if we like this abstraction and other API objects to it, # then extract to its own file and require it. @@ -100,7 +99,7 @@ module ActiveModel end def process_resource(serializer, primary) - resource_identifier = JsonApi::ResourceIdentifier.new(serializer).as_json + resource_identifier = ApiObjects::ResourceIdentifier.new(serializer).as_json return false unless @resource_identifiers.add?(resource_identifier) resource_object = resource_object_for(serializer) @@ -136,7 +135,7 @@ module ActiveModel def resource_object_for(serializer) resource_object = cache_check(serializer) do - resource_object = JsonApi::ResourceIdentifier.new(serializer).as_json + resource_object = ApiObjects::ResourceIdentifier.new(serializer).as_json requested_fields = fieldset && fieldset.fields_for(resource_object[:type]) attributes = attributes_for(serializer, requested_fields) @@ -160,12 +159,13 @@ module ActiveModel def relationships_for(serializer, requested_associations) include_tree = IncludeTree.from_include_args(requested_associations) serializer.associations(include_tree).each_with_object({}) do |association, hash| - hash[association.key] = JsonApi::Association.new(serializer, + hash[association.key] = ApiObjects::Relationship.new( + serializer, association.serializer, association.options, association.links, - association.meta) - .as_json + association.meta + ).as_json end end diff --git a/lib/active_model/serializer/adapter/json_api/api_objects.rb b/lib/active_model/serializer/adapter/json_api/api_objects.rb new file mode 100644 index 00000000..bad3173c --- /dev/null +++ b/lib/active_model/serializer/adapter/json_api/api_objects.rb @@ -0,0 +1,13 @@ +module ActiveModel + class Serializer + module Adapter + class JsonApi + module ApiObjects + extend ActiveSupport::Autoload + autoload :Relationship + autoload :ResourceIdentifier + end + end + end + end +end diff --git a/lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb b/lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb new file mode 100644 index 00000000..d1ebc1b9 --- /dev/null +++ b/lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb @@ -0,0 +1,52 @@ +module ActiveModel + class Serializer + module Adapter + class JsonApi + module ApiObjects + class Relationship + def initialize(parent_serializer, serializer, options = {}, links = {}, meta = nil) + @object = parent_serializer.object + @scope = parent_serializer.scope + + @options = options + @data = data_for(serializer, options) + @links = links.each_with_object({}) do |(key, value), hash| + hash[key] = Link.new(parent_serializer, value).as_json + end + @meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta + end + + def as_json + hash = {} + hash[:data] = data if options[:include_data] + links = self.links + hash[:links] = links if links.any? + meta = self.meta + hash[:meta] = meta if meta + + hash + end + + protected + + attr_reader :object, :scope, :data, :options, :links, :meta + + private + + def data_for(serializer, options) + if serializer.respond_to?(:each) + serializer.map { |s| ResourceIdentifier.new(s).as_json } + else + if options[:virtual_value] + options[:virtual_value] + elsif serializer && serializer.object + ResourceIdentifier.new(serializer).as_json + end + end + end + end + end + end + end + end +end diff --git a/lib/active_model/serializer/adapter/json_api/api_objects/resource_identifier.rb b/lib/active_model/serializer/adapter/json_api/api_objects/resource_identifier.rb new file mode 100644 index 00000000..058f0603 --- /dev/null +++ b/lib/active_model/serializer/adapter/json_api/api_objects/resource_identifier.rb @@ -0,0 +1,39 @@ +module ActiveModel + class Serializer + module Adapter + class JsonApi + module ApiObjects + class ResourceIdentifier + def initialize(serializer) + @id = id_for(serializer) + @type = type_for(serializer) + end + + def as_json + { id: id, type: type } + end + + protected + + attr_reader :id, :type + + private + + def type_for(serializer) + return serializer._type if serializer._type + if ActiveModelSerializers.config.jsonapi_resource_type == :singular + serializer.object.class.model_name.singular + else + serializer.object.class.model_name.plural + end + end + + def id_for(serializer) + serializer.read_attribute_for_serialization(:id).to_s + end + end + end + end + end + end +end diff --git a/lib/active_model/serializer/adapter/json_api/association.rb b/lib/active_model/serializer/adapter/json_api/association.rb deleted file mode 100644 index b6cfc70d..00000000 --- a/lib/active_model/serializer/adapter/json_api/association.rb +++ /dev/null @@ -1,48 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class JsonApi - class Association - def initialize(parent_serializer, serializer, options, links, meta) - @object = parent_serializer.object - @scope = parent_serializer.scope - - @options = options - @data = data_for(serializer, options) - @links = links - .map { |key, value| { key => Link.new(parent_serializer, value).as_json } } - .reduce({}, :merge) - @meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta - end - - def as_json - hash = {} - hash[:data] = @data if @options[:include_data] - hash[:links] = @links if @links.any? - hash[:meta] = @meta if @meta - - hash - end - - protected - - attr_reader :object, :scope - - private - - def data_for(serializer, options) - if serializer.respond_to?(:each) - serializer.map { |s| ResourceIdentifier.new(s).as_json } - else - if options[:virtual_value] - options[:virtual_value] - elsif serializer && serializer.object - ResourceIdentifier.new(serializer).as_json - end - end - end - end - end - end - end -end diff --git a/lib/active_model/serializer/adapter/json_api/resource_identifier.rb b/lib/active_model/serializer/adapter/json_api/resource_identifier.rb deleted file mode 100644 index 99bff298..00000000 --- a/lib/active_model/serializer/adapter/json_api/resource_identifier.rb +++ /dev/null @@ -1,41 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class JsonApi - class ResourceIdentifier - def initialize(serializer) - @id = id_for(serializer) - @type = type_for(serializer) - end - - def as_json - { id: @id.to_s, type: @type } - end - - protected - - attr_reader :object, :scope - - private - - def type_for(serializer) - return serializer._type if serializer._type - if ActiveModelSerializers.config.jsonapi_resource_type == :singular - serializer.object.class.model_name.singular - else - serializer.object.class.model_name.plural - end - end - - def id_for(serializer) - if serializer.respond_to?(:id) - serializer.id - else - serializer.object.id - end - end - end - end - end - end -end diff --git a/test/adapter/json_api/api_objects/relationship_test.rb b/test/adapter/json_api/api_objects/relationship_test.rb new file mode 100644 index 00000000..5564400e --- /dev/null +++ b/test/adapter/json_api/api_objects/relationship_test.rb @@ -0,0 +1,168 @@ +require 'test_helper' + +module ActiveModel + class Serializer + module Adapter + class JsonApi + module ApiObjects + class RelationshipTest < ActiveSupport::TestCase + def setup + @blog = Blog.new(id: 1) + @author = Author.new(id: 1, name: 'Steve K.', blog: @blog) + @serializer = BlogSerializer.new(@blog) + ActionController::Base.cache_store.clear + end + + def test_relationship_with_data + expected = { + data: { + id: '1', + type: 'blogs' + } + } + test_relationship(expected, options: { include_data: true }) + end + + def test_relationship_with_nil_model + @serializer = BlogSerializer.new(nil) + expected = { data: nil } + test_relationship(expected, options: { include_data: true }) + end + + def test_relationship_with_nil_serializer + @serializer = nil + expected = { data: nil } + test_relationship(expected, options: { include_data: true }) + end + + def test_relationship_with_data_array + posts = [Post.new(id: 1), Post.new(id: 2)] + @serializer = ActiveModel::Serializer::ArraySerializer.new(posts) + @author.posts = posts + @author.blog = nil + expected = { + data: [ + { + id: '1', + type: 'posts' + }, + { + id: '2', + type: 'posts' + } + ] + } + test_relationship(expected, options: { include_data: true }) + end + + def test_relationship_data_not_included + test_relationship({}, options: { include_data: false }) + end + + def test_relationship_simple_link + links = { self: 'a link' } + test_relationship({ links: { self: 'a link' } }, links: links) + end + + def test_relationship_many_links + links = { + self: 'a link', + related: 'another link' + } + expected = { + links: { + self: 'a link', + related: 'another link' + } + } + test_relationship(expected, links: links) + end + + def test_relationship_block_link + links = { self: proc { "#{object.id}" } } + expected = { links: { self: "#{@blog.id}" } } + test_relationship(expected, links: links) + end + + def test_relationship_block_link_with_meta + links = { + self: proc do + href "#{object.id}" + meta(id: object.id) + end + } + expected = { + links: { + self: { + href: "#{@blog.id}", + meta: { id: @blog.id } + } + } + } + test_relationship(expected, links: links) + end + + def test_relationship_simple_meta + meta = { id: '1' } + expected = { meta: meta } + test_relationship(expected, meta: meta) + end + + def test_relationship_block_meta + meta = proc do + { id: object.id } + end + expected = { + meta: { + id: @blog.id + } + } + test_relationship(expected, meta: meta) + end + + def test_relationship_with_everything + links = { + self: 'a link', + related: proc do + href "#{object.id}" + meta object.id + end + + } + meta = proc do + { id: object.id } + end + expected = { + data: { + id: '1', + type: 'blogs' + }, + links: { + self: 'a link', + related: { + href: '1', meta: 1 + } + }, + meta: { + id: @blog.id + } + } + test_relationship(expected, meta: meta, options: { include_data: true }, links: links) + end + + private + + def test_relationship(expected, params = {}) + options = params.fetch(:options, {}) + links = params.fetch(:links, {}) + meta = params[:meta] + parent_serializer = AuthorSerializer.new(@author) + relationship = Relationship.new(parent_serializer, @serializer, options, links, meta) + assert_equal(expected, relationship.as_json) + end + end + end + end + end + end +end diff --git a/test/adapter/json_api/api_objects/resource_identifier_test.rb b/test/adapter/json_api/api_objects/resource_identifier_test.rb new file mode 100644 index 00000000..a40f0707 --- /dev/null +++ b/test/adapter/json_api/api_objects/resource_identifier_test.rb @@ -0,0 +1,88 @@ +require 'test_helper' + +module ActiveModel + class Serializer + module Adapter + class JsonApi + module ApiObjects + class ResourceIdentifierTest < ActiveSupport::TestCase + class WithDefinedTypeSerializer < Serializer + type 'with_defined_type' + end + + class WithDefinedIdSerializer < Serializer + def id + 'special_id' + end + end + + class FragmentedSerializer < Serializer; end + + def setup + @model = Author.new(id: 1, name: 'Steve K.') + ActionController::Base.cache_store.clear + end + + def test_defined_type + test_type(WithDefinedTypeSerializer, 'with_defined_type') + end + + def test_singular_type + test_type_inflection(AuthorSerializer, 'author', :singular) + end + + def test_plural_type + test_type_inflection(AuthorSerializer, 'authors', :plural) + end + + def test_id_defined_on_object + test_id(AuthorSerializer, @model.id.to_s) + end + + def test_id_defined_on_serializer + test_id(WithDefinedIdSerializer, 'special_id') + end + + def test_id_defined_on_fragmented + FragmentedSerializer.fragmented(WithDefinedIdSerializer.new(@author)) + test_id(FragmentedSerializer, 'special_id') + end + + private + + def test_type_inflection(serializer_class, expected_type, inflection) + original_inflection = ActiveModelSerializers.config.jsonapi_resource_type + ActiveModelSerializers.config.jsonapi_resource_type = inflection + test_type(serializer_class, expected_type) + ActiveModelSerializers.config.jsonapi_resource_type = original_inflection + end + + def test_type(serializer_class, expected_type) + serializer = serializer_class.new(@model) + resource_identifier = ResourceIdentifier.new(serializer) + expected = { + id: @model.id.to_s, + type: expected_type + } + + assert_equal(expected, resource_identifier.as_json) + end + + def test_id(serializer_class, id) + serializer = serializer_class.new(@model) + resource_identifier = ResourceIdentifier.new(serializer) + inflection = ActiveModelSerializers.config.jsonapi_resource_type + type = @model.class.model_name.send(inflection) + expected = { + id: id, + type: type + } + + assert_equal(expected, resource_identifier.as_json) + end + end + end + end + end + end +end