class JsonapiMapper::DocumentMapper

Attributes

data[RW]
document[RW]
included[RW]
renames[RW]
resources[RW]
types[RW]
unscoped[RW]

Public Class Methods

new(document, unscoped, rules, renames) click to toggle source
# File lib/jsonapi_mapper.rb, line 23
def initialize(document, unscoped, rules, renames)
  self.document = document.deep_symbolize_keys
  self.renames = renames.deep_symbolize_keys
  self.unscoped = unscoped.map(&:to_sym)
  self.resources = {}
  setup_types(rules)
  
  main = if data = self.document[:data]
    if data.is_a?(Array)
      data.map{|r| build_resource(r) }.compact.collect(&:object)
    else
      build_resource(data).try(:object)
    end
  end

  rest = if included = self.document[:included]
    included.map{|r| build_resource(r) }.compact
  end

  resources.each{|_,r| assign_relationships(r) }

  self.data = main
  self.included = rest.try(:map, &:object) || []
end

Public Instance Methods

all() click to toggle source
# File lib/jsonapi_mapper.rb, line 170
def all
  (data_mappable + included)
end
all_errors() click to toggle source
# File lib/jsonapi_mapper.rb, line 203
def all_errors
  errors = []

  if collection?
    data.each_with_index do |resource, i|
      errors << serialize_errors_for("/data/#{i}", resource)
    end
  elsif data
    errors << serialize_errors_for("/data", data)
  end
  
  included.each_with_index do |resource, i|
    errors << serialize_errors_for("/included/#{i}", resource)
  end

  { errors: errors.flatten.compact }
end
all_valid?() click to toggle source
# File lib/jsonapi_mapper.rb, line 179
def all_valid?
  all.map(&:valid?).all? # This does not short-circuit, to get all errors.
end
assign_relationships(resource) click to toggle source
# File lib/jsonapi_mapper.rb, line 128
def assign_relationships(resource)
  resource.relationships.each do |name, ids|
    if ids.is_a?(Array)
      ids.each do |id|
        next unless other = find_resource_object(id)
        resource.object.send(name).push(other)
      end
    else
      next unless other = find_resource_object(ids)
      resource.object.send("#{name}=", other)
    end
  end
end
build_id(json) click to toggle source
# File lib/jsonapi_mapper.rb, line 124
def build_id(json)
  Id.new(json[:type].to_sym, json[:id])
end
build_resource(json) click to toggle source
# File lib/jsonapi_mapper.rb, line 86
def build_resource(json)
  return unless json.is_a? Hash
  return unless json.fetch(:relationships, {}).is_a?(Hash)
  return unless json.fetch(:attributes, {}).is_a?(Hash)
  return unless type = types[json[:type].try(:to_sym)]

  object = if json[:id].nil? || json[:id].to_s.starts_with?("@")
    type.class.new.tap do |o|
      type.rule.scope.each do |k,v|
        o.send("#{k}=", v)
      end
    end
  else
    type.class.where(type.rule.scope).find(json[:id])
  end

  relationships = {}
  json.fetch(:relationships, {}).each do |name, value|
    next unless type.rule.attributes.include?(name)
    next if value[:data].blank?
    relationships[renamed_attr(type.name, name)] = if value[:data].is_a?(Array)
      value[:data].map{|v| build_id(v) } 
    else
      build_id(value[:data])
    end
  end

  if new_values = json[:attributes]
    type.rule.attributes.each do |name|
      next unless new_values.has_key?(name)
      object.send("#{renamed_attr(type.name, name)}=", new_values[name]) 
    end
  end

  resource = Resource.new(object, relationships, build_id(json))
  resources[resource.id] = resource
end
collection?() click to toggle source
# File lib/jsonapi_mapper.rb, line 183
def collection?
  data.is_a?(Array)
end
data_mappable() click to toggle source
# File lib/jsonapi_mapper.rb, line 199
def data_mappable
  collection? ? data : [data].compact
end
find_resource_object(id) click to toggle source
# File lib/jsonapi_mapper.rb, line 142
def find_resource_object(id)
  return unless type = types[id.type]

  resources[id].try(:object) ||
    type.class.where(type.rule.scope).find(id.raw) or
    raise ActiveRecord::RecordNotFound
      .new("Couldn't find #{id.type} with id=#{id.raw}")
end
map_all(cls, &blk) click to toggle source
# File lib/jsonapi_mapper.rb, line 195
def map_all(cls, &blk)
  all.select{|o| o.is_a?(cls)}.map(&blk)
end
map_data(cls, &blk) click to toggle source
# File lib/jsonapi_mapper.rb, line 191
def map_data(cls, &blk)
  data_mappable.select{|o| o.is_a?(cls)}.map(&blk)
end
renamed_attr(type, attr) click to toggle source
# File lib/jsonapi_mapper.rb, line 161
def renamed_attr(type, attr)
  renames.fetch(:attributes, {}).fetch(type, {}).fetch(attr, attr)
end
renamed_type(type_name) click to toggle source
# File lib/jsonapi_mapper.rb, line 151
def renamed_type(type_name)
  renames.fetch(:types, {})[type_name] ||
    type_name.to_s.singularize.camelize.constantize
end
save_all() click to toggle source
# File lib/jsonapi_mapper.rb, line 174
def save_all
  return false unless all.all?(&:valid?)
  all.collect(&:save).all?
end
setup_types(rules) click to toggle source
# File lib/jsonapi_mapper.rb, line 48
def setup_types(rules)
  self.types = {}
  rules.each do |type_name, ruleset|
    type_name = type_name.to_sym

    attrs, scope = if ruleset.last.is_a?(Hash)
      [ruleset[0..-2], ruleset.last]
    else
      unless unscoped.map(&:to_sym).include?(type_name)
        raise RulesError.new("Missing Scope for #{type_name}")
      end
      [ruleset, {}]
    end

    unless attrs.all?{|v| v.is_a?(Symbol) || v.is_a?(String) } 
      raise RulesError.new('Attributes must be Strings or Symbols')
    end

    attrs = attrs.map(&:to_sym)
    scope.symbolize_keys!

    danger = scope.keys.to_set & attrs.map{|a| renamed_attr(type_name, a) }.to_set
    if danger.count > 0
      raise RulesError.new("Don't let user set the scope: #{danger.to_a}")
    end

    cls = renamed_type(type_name)

    attrs.map{|a| renamed_attr(type_name, a) }.each do |attr|
      unless cls.new.respond_to?(attr)
        raise NoMethodError.new("undefined method #{attr} for #{cls}")
      end
    end

    types[type_name] = Type.new(type_name, cls, Rule.new(attrs, scope))
  end
end
single?() click to toggle source
# File lib/jsonapi_mapper.rb, line 187
def single?
  !collection?
end
unrenamed_attr(type_name, attr) click to toggle source
# File lib/jsonapi_mapper.rb, line 165
def unrenamed_attr(type_name, attr)
  renames.fetch(:attributes, {}).fetch(type_name, {})
    .find{|k,v| v == attr }.try(:first) || attr
end
unrenamed_type(type) click to toggle source
# File lib/jsonapi_mapper.rb, line 156
def unrenamed_type(type)
  type_name = type.to_s.underscore.pluralize
  renames.fetch(:types, {}).find{|k,v| v == type }.try(:first) || type_name
end

Private Instance Methods

serialize_errors_for(prefix, model) click to toggle source
# File lib/jsonapi_mapper.rb, line 223
def serialize_errors_for(prefix, model)
  return if model.errors.empty?
  model.errors.collect do |attr, value|
    type_name = unrenamed_type(model.class)
    meta = { type: type_name.to_s }
    meta[:id] = model.id if model.id
    {
      status: 422,
      title: value,
      detail: value,
      code: value.parameterize.underscore,
      meta: meta,
      source: {
        pointer: "#{prefix}/attributes/#{unrenamed_attr(type_name, attr)}"
      }
    }
  end
end