class Graphiti::Util::Persistence

Save the given Resource#model, and all of its nested relationships. @api private

Public Class Methods

new(resource, meta, attributes, relationships, caller_model, foreign_key = nil) click to toggle source

@param [Resource] resource the resource instance @param [Hash] meta see (Deserializer#meta) @param [Hash] attributes see (Deserializer#attributes) @param [Hash] relationships see (Deserializer#relationships) @param [Model] caller_model The persisted parent object in the request graph @param [Symbol] foreign_key Attribute assigned by parent object in graph

# File lib/graphiti/util/persistence.rb, line 10
def initialize(resource, meta, attributes, relationships, caller_model, foreign_key = nil)
  @resource = resource
  @meta = meta
  @attributes = attributes
  @relationships = relationships
  @caller_model = caller_model
  @foreign_key = foreign_key
  @adapter = @resource.adapter

  # Find the correct child resource for a given jsonapi type
  if (meta_type = @meta[:type].try(:to_sym))
    if @resource.type != meta_type && @resource.polymorphic?
      @resource = @resource.class.resource_for_type(meta_type).new
    end
  end
end

Public Instance Methods

iterate(only: [], except: []) { |x| ... } click to toggle source
# File lib/graphiti/util/persistence.rb, line 71
def iterate(only: [], except: [])
  opts = {
    resource: @resource,
    relationships: @relationships
  }.merge(only: only, except: except)

  Graphiti::Util::RelationshipPayload.iterate(**opts) do |x|
    yield x
  end
end
run() click to toggle source

Perform the actual save logic.

belongs_to must be processed before/separately from has_many - we need to know the primary key value of the parent before persisting the child.

Flow:

  • process parents

  • update attributes to reflect parent primary keys

  • persist current object

  • associate temp id with current object

  • associate parent objects with current object

  • process children

  • associate children

  • record hooks for later playback

  • run post-process sideload hooks

  • return current object

@return a model instance

# File lib/graphiti/util/persistence.rb, line 46
def run
  attributes = @adapter.persistence_attributes(self, @attributes)

  parents = @adapter.process_belongs_to(self, attributes)
  persisted = persist_object(@meta[:method], attributes)
  @resource.decorate_record(persisted)
  assign_temp_id(persisted, @meta[:temp_id])

  associate_parents(persisted, parents)

  children = @adapter.process_has_many(self, persisted)

  associate_children(persisted, children) unless @meta[:method] == :destroy

  post_process(persisted, parents)
  post_process(persisted, children)
  after_graph_persist = -> { @resource.after_graph_persist(persisted, metadata) }
  add_hook(after_graph_persist, :after_graph_persist)
  before_commit = -> { @resource.before_commit(persisted, metadata) }
  add_hook(before_commit, :before_commit)
  after_commit = -> { @resource.after_commit(persisted, metadata) }
  add_hook(after_commit, :after_commit)
  persisted
end

Private Instance Methods

add_hook(prc, lifecycle_event) click to toggle source
# File lib/graphiti/util/persistence.rb, line 84
def add_hook(prc, lifecycle_event)
  ::Graphiti::Util::TransactionHooksRecorder.add(prc, lifecycle_event)
end
assign_temp_id(object, temp_id) click to toggle source
# File lib/graphiti/util/persistence.rb, line 152
def assign_temp_id(object, temp_id)
  object.instance_variable_set(:@_jsonapi_temp_id, temp_id)
end
associate_children(object, children) click to toggle source
# File lib/graphiti/util/persistence.rb, line 111
def associate_children(object, children)
  children.each do |x|
    if x[:object] && object
      if x[:meta][:method] == :disassociate
        x[:sideload].disassociate(object, x[:object])
      elsif x[:meta][:method] == :destroy
        if x[:sideload].type == :many_to_many
          x[:sideload].disassociate(object, x[:object])
        end
        # otherwise, no need to disassociate destroyed objects
      elsif [:has_many, :many_to_many].include?(x[:sideload].type)
        x[:sideload].associate_all(object, Array(x[:object]))
      else
        x[:sideload].associate(object, x[:object])
      end
    end
  end
end
associate_parents(object, parents) click to toggle source
# File lib/graphiti/util/persistence.rb, line 88
def associate_parents(object, parents)
  # No need to associate to destroyed objects
  parents = parents.select { |x| x[:meta][:method] != :destroy }

  parents.each do |x|
    if x[:object] && object
      if x[:meta][:method] == :disassociate
        if x[:sideload].type == :belongs_to
          x[:sideload].disassociate(object, x[:object])
        else
          x[:sideload].disassociate(x[:object], object)
        end
      elsif x[:sideload].type == :belongs_to
        x[:sideload].associate(object, x[:object])
      elsif [:has_many, :many_to_many].include?(x[:sideload].type)
        x[:sideload].associate_all(object, Array(x[:object]))
      else
        x[:sideload].associate(x[:object], object)
      end
    end
  end
end
call_resource_method(method_name, attributes, caller_model) click to toggle source
# File lib/graphiti/util/persistence.rb, line 166
def call_resource_method(method_name, attributes, caller_model)
  method = @resource.method(method_name)

  if method.arity == 1
    method.call(attributes)
  else
    method.call(attributes, metadata)
  end
end
metadata() click to toggle source
# File lib/graphiti/util/persistence.rb, line 156
def metadata
  {
    method: @meta[:method],
    temp_id: @meta[:temp_id],
    caller_model: @caller_model,
    attributes: @attributes,
    relationships: @relationships
  }
end
persist_object(method, attributes) click to toggle source
# File lib/graphiti/util/persistence.rb, line 130
def persist_object(method, attributes)
  case method
    when :destroy
      call_resource_method(:destroy, attributes[:id], @caller_model)
    when :update, nil, :disassociate
      call_resource_method(:update, attributes, @caller_model)
    else
      call_resource_method(:create, attributes, @caller_model)
  end
end
post_process(caller_model, processed) click to toggle source
# File lib/graphiti/util/persistence.rb, line 141
def post_process(caller_model, processed)
  groups = processed.group_by { |x| x[:meta][:method] }
  groups.each_pair do |method, group|
    group.group_by { |g| g[:sideload] }.each_pair do |sideload, members|
      objects = members.map { |x| x[:object] }
      hook = -> { sideload.fire_hooks!(caller_model, objects, method) }
      add_hook(hook, :before_commit)
    end
  end
end