class JsonapiCompliable::Deserializer

Responsible for parsing incoming write payloads

Given a PUT payload like:

{
  data: {
    id: '1',
    type: 'posts',
    attributes: { title: 'My Title' },
    relationships: {
      author: {
        data: {
          id: '1',
          type: 'authors'
        }
      }
    }
  },
  included: [
    {
      id: '1'
      type: 'authors',
      attributes: { name: 'Joe Author' }
    }
  ]
}

You can now easily deal with this payload:

deserializer.attributes
# => { id: '1', title: 'My Title' }
deserializer.meta
# => { type: 'posts', method: :update }
deserializer.relationships
# {
#   author: {
#     meta: { ... },
#     attributes: { ... },
#     relationships: { ... }
#   }
# }

When creating objects, we accept a temp-id so that the client can track the object it just created. Expect this in meta:

{ type: 'authors', method: :create, temp_id: 'abc123' }

Public Class Methods

new(payload, env) click to toggle source

@param payload [Hash] The incoming payload with symbolized keys @param env [Hash] the Rack env (e.g. request.env).

# File lib/jsonapi_compliable/deserializer.rb, line 50
def initialize(payload, env)
  @payload = payload || {}
  @payload = @payload[:_jsonapi] if @payload.has_key?(:_jsonapi)
  @env = env
  validate_content_type
end

Public Instance Methods

attributes() click to toggle source

@return [Hash] the raw :attributes hash + id

# File lib/jsonapi_compliable/deserializer.rb, line 76
def attributes
  @attributes ||= raw_attributes.tap do |hash|
    hash[:id] = id if id
  end
end
attributes=(attrs) click to toggle source

Override the attributes # @see attributes

# File lib/jsonapi_compliable/deserializer.rb, line 84
def attributes=(attrs)
  @attributes = attrs
end
data() click to toggle source

@return [Hash] the raw :data value of the payload

# File lib/jsonapi_compliable/deserializer.rb, line 66
def data
  @payload[:data] || {}
end
id() click to toggle source

@return [String] the raw :id value of the payload

# File lib/jsonapi_compliable/deserializer.rb, line 71
def id
  data[:id]
end
include_directive(memo = {}, relationship_node = nil) click to toggle source

Parses the relationships recursively and builds an all-hash include directive like

{ posts: { comments: {} } }

Relationships that have been marked for destruction will NOT be part of the include directive.

@return [Hash] the include directive

# File lib/jsonapi_compliable/deserializer.rb, line 117
def include_directive(memo = {}, relationship_node = nil)
  relationship_node ||= relationships

  relationship_node.each_pair do |name, relationship_payload|
    merge_include_directive(memo, name, relationship_payload)
  end

  memo
end
meta() click to toggle source

'meta' information about this resource. Includes:

type: the jsonapi type method: create/update/destroy/disassociate. Based on the request env or the method within the relationships hash temp_id: the temp-id, if specified

@return [Hash]

# File lib/jsonapi_compliable/deserializer.rb, line 95
def meta
  {
    type: data[:type],
    temp_id: data[:'temp-id'],
    method: method
  }
end
relationships() click to toggle source

@return [Hash] the relationships hash

# File lib/jsonapi_compliable/deserializer.rb, line 104
def relationships
  @relationships ||= process_relationships(raw_relationships)
end
validate_content_type() click to toggle source

checks Content-Type header and prints a warning if it doesn't seem correct

# File lib/jsonapi_compliable/deserializer.rb, line 58
def validate_content_type
  content_type = @env['CONTENT_TYPE'] || ""
  if !(content_type.include?("application/json") || content_type.include?("application/vnd.api+json"))
    print("WARNING - JSONAPI Compliable :: Content-Type header appears to be set to an invalid value: #{content_type}\n")
  end
end

Private Instance Methods

deep_merge!(a, b) click to toggle source
# File lib/jsonapi_compliable/deserializer.rb, line 164
def deep_merge!(a, b)
  JsonapiCompliable::Util::Hash.deep_merge!(a, b)
end
included() click to toggle source
# File lib/jsonapi_compliable/deserializer.rb, line 138
def included
  @payload[:included] || []
end
merge_include_directive(memo, name, relationship_payload) click to toggle source
# File lib/jsonapi_compliable/deserializer.rb, line 129
def merge_include_directive(memo, name, relationship_payload)
  arrayified = [relationship_payload].flatten
  return if arrayified.all? { |rp| removed?(rp) }

  memo[name] ||= {}
  deep_merge!(memo[name], sub_directives(memo[name], arrayified))
  memo
end
method() click to toggle source
# File lib/jsonapi_compliable/deserializer.rb, line 142
def method
  case @env['REQUEST_METHOD']
    when 'POST' then :create
    when 'PUT', 'PATCH' then :update
    when 'DELETE' then :destroy
  end
end
process_relationship(relationship_data) click to toggle source
# File lib/jsonapi_compliable/deserializer.rb, line 180
def process_relationship(relationship_data)
  if relationship_data.is_a?(Array)
    relationship_data.map do |rd|
      process_relationship_datum(rd)
    end
  else
    process_relationship_datum(relationship_data)
  end
end
process_relationship_datum(datum) click to toggle source
# File lib/jsonapi_compliable/deserializer.rb, line 190
def process_relationship_datum(datum)
  temp_id = datum[:'temp-id']
  included_object = included.find do |i|
    next unless i[:type] == datum[:type]

    (i[:id] && i[:id] == datum[:id]) ||
      (i[:'temp-id'] && i[:'temp-id'] == temp_id)
  end
  included_object ||= {}
  included_object[:relationships] ||= {}

  attributes = included_object[:attributes] || {}
  attributes[:id] = datum[:id] if datum[:id]
  relationships = process_relationships(included_object[:relationships] || {})
  method = datum[:method]
  method = method.to_sym if method

  {
    meta: {
      jsonapi_type: datum[:type],
      temp_id: temp_id,
      method: method
    },
    attributes: attributes,
    relationships: relationships
  }
end
process_relationships(relationship_hash) click to toggle source
# File lib/jsonapi_compliable/deserializer.rb, line 168
def process_relationships(relationship_hash)
  {}.tap do |hash|
    relationship_hash.each_pair do |name, relationship_payload|
      name = name.to_sym

      if relationship_payload[:data]
        hash[name] = process_relationship(relationship_payload[:data])
      end
    end
  end
end
raw_attributes() click to toggle source
# File lib/jsonapi_compliable/deserializer.rb, line 218
def raw_attributes
  if data
    data[:attributes] || {}
  else
    {}
  end
end
raw_relationships() click to toggle source
# File lib/jsonapi_compliable/deserializer.rb, line 226
def raw_relationships
  if data
    data[:relationships] || {}
  else
    {}
  end
end
removed?(relationship_payload) click to toggle source
# File lib/jsonapi_compliable/deserializer.rb, line 150
def removed?(relationship_payload)
  method = relationship_payload[:meta][:method]
  [:disassociate, :destroy].include?(method)
end
sub_directives(memo, relationship_payloads) click to toggle source
# File lib/jsonapi_compliable/deserializer.rb, line 155
def sub_directives(memo, relationship_payloads)
  {}.tap do |subs|
    relationship_payloads.each do |rp|
      sub_directive = include_directive(memo, rp[:relationships])
      deep_merge!(subs, sub_directive)
    end
  end
end