module ActiveModelSerializers::Adapter::JsonApi::Deserialization

NOTE(Experimental): This is an experimental feature. Both the interface and internals could be subject to changes.

Constants

InvalidDocument

Public Instance Methods

field_key(field, options) click to toggle source

@api private

# File lib/active_model_serializers/adapter/json_api/deserialization.rb, line 154
def field_key(field, options)
  (options[:keys] || {}).fetch(field.to_sym, field).to_sym
end
filter_fields(fields, options) click to toggle source

@api private

# File lib/active_model_serializers/adapter/json_api/deserialization.rb, line 145
def filter_fields(fields, options)
  if (only = options[:only])
    fields.slice!(*Array(only).map(&:to_s))
  elsif (except = options[:except])
    fields.except!(*Array(except).map(&:to_s))
  end
end
parse(document, options = {}) { |invalid_document, reason| ... } click to toggle source

Same as parse!, but returns an empty hash instead of raising InvalidDocument on invalid payloads.

# File lib/active_model_serializers/adapter/json_api/deserialization.rb, line 85
def parse(document, options = {})
  document = document.dup.permit!.to_h if document.is_a?(ActionController::Parameters)

  validate_payload(document) do |invalid_document, reason|
    yield invalid_document, reason if block_given?
    return {}
  end

  primary_data = document['data']
  attributes = primary_data['attributes'] || {}
  attributes['id'] = primary_data['id'] if primary_data['id']
  relationships = primary_data['relationships'] || {}

  filter_fields(attributes, options)
  filter_fields(relationships, options)

  hash = {}
  hash.merge!(parse_attributes(attributes, options))
  hash.merge!(parse_relationships(relationships, options))

  hash
end
parse!(document, options = {}) click to toggle source

Transform a JSON API document, containing a single data object, into a hash that is ready for ActiveRecord::Base.new() and such. Raises InvalidDocument if the payload is not properly formatted.

@param [Hash|ActionController::Parameters] document @param [Hash] options

only: Array of symbols of whitelisted fields.
except: Array of symbols of blacklisted fields.
keys: Hash of translated keys (e.g. :author => :user).
polymorphic: Array of symbols of polymorphic fields.

@return [Hash]

@example

document = {
  data: {
    id: 1,
    type: 'post',
    attributes: {
      title: 'Title 1',
      date: '2015-12-20'
    },
    associations: {
      author: {
        data: {
          type: 'user',
          id: 2
        }
      },
      second_author: {
        data: nil
      },
      comments: {
        data: [{
          type: 'comment',
          id: 3
        },{
          type: 'comment',
          id: 4
        }]
      }
    }
  }
}

parse(document) #=>
  # {
  #   title: 'Title 1',
  #   date: '2015-12-20',
  #   author_id: 2,
  #   second_author_id: nil
  #   comment_ids: [3, 4]
  # }

parse(document, only: [:title, :date, :author],
                keys: { date: :published_at },
                polymorphic: [:author]) #=>
  # {
  #   title: 'Title 1',
  #   published_at: '2015-12-20',
  #   author_id: '2',
  #   author_type: 'people'
  # }
# File lib/active_model_serializers/adapter/json_api/deserialization.rb, line 77
def parse!(document, options = {})
  parse(document, options) do |invalid_payload, reason|
    fail InvalidDocument, "Invalid payload (#{reason}): #{invalid_payload}"
  end
end
parse_attributes(attributes, options) click to toggle source

@api private

# File lib/active_model_serializers/adapter/json_api/deserialization.rb, line 159
def parse_attributes(attributes, options)
  transform_keys(attributes, options)
    .map { |(k, v)| { field_key(k, options) => v } }
    .reduce({}, :merge)
end
parse_relationship(assoc_name, assoc_data, options) click to toggle source

Given an association name, and a relationship data attribute, build a hash mapping the corresponding ActiveRecord attribute to the corresponding value.

@example

parse_relationship(:comments, [{ 'id' => '1', 'type' => 'comments' },
                               { 'id' => '2', 'type' => 'comments' }],
                              {})
 # => { :comment_ids => ['1', '2'] }
parse_relationship(:author, { 'id' => '1', 'type' => 'users' }, {})
 # => { :author_id => '1' }
parse_relationship(:author, nil, {})
 # => { :author_id => nil }

@param [Symbol] assoc_name @param [Hash] assoc_data @param [Hash] options @return [Hash{Symbol, Object}]

@api private

# File lib/active_model_serializers/adapter/json_api/deserialization.rb, line 183
def parse_relationship(assoc_name, assoc_data, options)
  prefix_key = field_key(assoc_name, options).to_s.singularize
  hash =
    if assoc_data.is_a?(Array)
      { "#{prefix_key}_ids".to_sym => assoc_data.map { |ri| ri['id'] } }
    else
      { "#{prefix_key}_id".to_sym => assoc_data ? assoc_data['id'] : nil }
    end

  polymorphic = (options[:polymorphic] || []).include?(assoc_name.to_sym)
  if polymorphic
    hash["#{prefix_key}_type".to_sym] = assoc_data.present? ? assoc_data['type'].classify : nil
  end

  hash
end
parse_relationships(relationships, options) click to toggle source

@api private

# File lib/active_model_serializers/adapter/json_api/deserialization.rb, line 201
def parse_relationships(relationships, options)
  transform_keys(relationships, options)
    .map { |(k, v)| parse_relationship(k, v['data'], options) }
    .reduce({}, :merge)
end
transform_keys(hash, options) click to toggle source

@api private

# File lib/active_model_serializers/adapter/json_api/deserialization.rb, line 208
def transform_keys(hash, options)
  transform = options[:key_transform] || :underscore
  CaseTransform.send(transform, hash)
end
validate_payload(payload) { |payload, 'Expected hash'| ... } click to toggle source

Checks whether a payload is compliant with the JSON API spec.

@api private rubocop:disable Metrics/CyclomaticComplexity

# File lib/active_model_serializers/adapter/json_api/deserialization.rb, line 112
def validate_payload(payload)
  unless payload.is_a?(Hash)
    yield payload, 'Expected hash'
    return
  end

  primary_data = payload['data']
  unless primary_data.is_a?(Hash)
    yield payload, { data: 'Expected hash' }
    return
  end

  attributes = primary_data['attributes'] || {}
  unless attributes.is_a?(Hash)
    yield payload, { data: { attributes: 'Expected hash or nil' } }
    return
  end

  relationships = primary_data['relationships'] || {}
  unless relationships.is_a?(Hash)
    yield payload, { data: { relationships: 'Expected hash or nil' } }
    return
  end

  relationships.each do |(key, value)|
    unless value.is_a?(Hash) && value.key?('data')
      yield payload, { data: { relationships: { key => 'Expected hash with :data key' } } }
    end
  end
end