diff --git a/lib/active_model_serializers/adapter/json_api/deserialization.rb b/lib/active_model_serializers/adapter/json_api/deserialization.rb index b79125ac4..2e771ca55 100644 --- a/lib/active_model_serializers/adapter/json_api/deserialization.rb +++ b/lib/active_model_serializers/adapter/json_api/deserialization.rb @@ -7,6 +7,9 @@ class JsonApi module Deserialization InvalidDocument = Class.new(ArgumentError) + # http://jsonapi.org/format/#document-resource-object-relationships + VALID_RELATIONSHIP_KEYS = %w(links data meta).freeze + module_function # Transform a JSON API document, containing a single data object, @@ -132,8 +135,8 @@ def validate_payload(payload) end relationships.each do |(key, value)| - unless value.is_a?(Hash) && value.key?('data') - yield payload, { data: { relationships: { key => 'Expected hash with :data key' } } } + unless value.is_a?(Hash) && value.keys.any? { |k| VALID_RELATIONSHIP_KEYS.include?(k) } + yield payload, { data: { relationships: { key => "Expected hash with at least a #{VALID_RELATIONSHIP_KEYS.to_sentence(last_word_connector: 'or')} key" } } } end end end @@ -163,6 +166,8 @@ def parse_attributes(attributes, options) # Given an association name, and a relationship data attribute, build a hash # mapping the corresponding ActiveRecord attribute to the corresponding value. # + # Ignores relationships specifying 'link' or 'meta' keys. + # # @example # parse_relationship(:comments, [{ 'id' => '1', 'type' => 'comments' }, # { 'id' => '2', 'type' => 'comments' }], @@ -173,12 +178,15 @@ def parse_attributes(attributes, options) # parse_relationship(:author, nil, {}) # # => { :author_id => nil } # @param [Symbol] assoc_name - # @param [Hash] assoc_data + # @param [Hash] association # @param [Hash] options # @return [Hash{Symbol, Object}] # # @api private - def parse_relationship(assoc_name, assoc_data, options) + def parse_relationship(assoc_name, association, options) + return {} unless association.key?('data') + assoc_data = association['data'] + prefix_key = field_key(assoc_name, options).to_s.singularize hash = if assoc_data.is_a?(Array) @@ -198,7 +206,7 @@ def parse_relationship(assoc_name, assoc_data, options) # @api private def parse_relationships(relationships, options) transform_keys(relationships, options) - .map { |(k, v)| parse_relationship(k, v['data'], options) } + .map { |(k, v)| parse_relationship(k, v, options) } .reduce({}, :merge) end diff --git a/test/action_controller/json_api/deserialization_test.rb b/test/action_controller/json_api/deserialization_test.rb index 025f857b7..56c82431b 100644 --- a/test/action_controller/json_api/deserialization_test.rb +++ b/test/action_controller/json_api/deserialization_test.rb @@ -106,6 +106,43 @@ def test_deserialization assert_equal(expected, response) end + + def test_deserialization_with_links_relationship + hash = { + 'data' => { + 'type' => 'photos', + 'id' => 'zorglub', + 'attributes' => { + 'title' => 'Ember Hamster', + 'src' => 'http://example.com/images/productivity.png', + 'image-width' => '200', + 'imageHeight' => '200', + 'ImageSize' => '1024' + }, + 'relationships' => { + 'images' => { + 'links' => { + 'related' => 'https://example.com/images/tomster/related' + } + } + } + } + } + + post :render_parsed_payload, params: hash + + response = JSON.parse(@response.body) + expected = { + 'id' => 'zorglub', + 'title' => 'Ember Hamster', + 'src' => 'http://example.com/images/productivity.png', + 'image_width' => '200', + 'image_height' => '200', + 'image_size' => '1024' + } + + assert_equal(expected, response) + end end end end