diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index e00a46eac..69a07e54d 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -6,9 +6,8 @@ class JsonApi < Base autoload :PaginationLinks autoload :FragmentCache autoload :Link - autoload :Association - autoload :ResourceIdentifier 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. @@ -99,7 +98,7 @@ def resource_objects_for(serializers) 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) @@ -135,7 +134,7 @@ def attributes_for(serializer, fields) 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) @@ -156,12 +155,13 @@ def resource_object_for(serializer) 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, - association.serializer, - association.options, - association.links, - association.meta) - .as_json + hash[association.key] = ApiObjects::Relationship.new( + serializer, + association.serializer, + association.options, + association.links, + 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 000000000..bad3173c3 --- /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 000000000..d1ebc1b96 --- /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 000000000..058f06031 --- /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 b6cfc70dd..000000000 --- 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 99bff2981..000000000 --- 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 000000000..5564400ea --- /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 000000000..a40f07071 --- /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