class Graphiti::Sideload

Constants

HOOK_ACTIONS
TYPES

Attributes

group_name[R]
name[R]
parent[R]
parent_resource_class[R]
polymorphic_as[R]

Public Class Methods

after_save(only: [], except: [], &blk) click to toggle source
# File lib/graphiti/sideload.rb, line 310
def self.after_save(only: [], except: [], &blk)
  actions = HOOK_ACTIONS - except
  actions = only & actions
  actions = [:save] if only.empty? && except.empty?
  actions.each do |a|
    hooks[:"after_#{a}"] << blk
  end
end
assign(&blk) click to toggle source
# File lib/graphiti/sideload.rb, line 60
def self.assign(&blk)
  self.assign_proc = blk
end
assign_each(&blk) click to toggle source
# File lib/graphiti/sideload.rb, line 64
def self.assign_each(&blk)
  self.assign_each_proc = blk
end
hooks() click to toggle source
# File lib/graphiti/sideload.rb, line 319
def self.hooks
  @hooks ||= {}.tap do |h|
    HOOK_ACTIONS.each do |a|
      h[:"after_#{a}"] = []
      h[:"before_#{a}"] = []
    end
  end
end
new(name, opts) click to toggle source
# File lib/graphiti/sideload.rb, line 20
def initialize(name, opts)
  @name = name
  validate_options!(opts)
  @parent_resource_class = opts[:parent_resource]
  @resource_class = opts[:resource]
  @primary_key = opts[:primary_key]
  @foreign_key = opts[:foreign_key]
  @type = opts[:type]
  @base_scope = opts[:base_scope]
  @readable = opts[:readable]
  @writable = opts[:writable]
  @as = opts[:as]
  @link = opts[:link]
  @single = opts[:single]
  @remote = opts[:remote]
  apply_belongs_to_many_filter if type == :many_to_many

  @description = opts[:description]

  # polymorphic has_many
  @polymorphic_as = opts[:polymorphic_as]
  # polymorphic_belongs_to-specific
  @group_name = opts[:group_name]
  @polymorphic_child = opts[:polymorphic_child]
  @parent = opts[:parent]
  @always_include_resource_ids = opts[:always_include_resource_ids]

  if polymorphic_child?
    parent.resource.polymorphic << resource_class
  end

  if remote?
    @resource_class = create_remote_resource
  end
end
params(&blk) click to toggle source
# File lib/graphiti/sideload.rb, line 68
def self.params(&blk)
  self.params_proc = blk
end
pre_load(&blk) click to toggle source
# File lib/graphiti/sideload.rb, line 72
def self.pre_load(&blk)
  self.pre_load_proc = blk
end
scope(&blk) click to toggle source
# File lib/graphiti/sideload.rb, line 56
def self.scope(&blk)
  self.scope_proc = blk
end

Public Instance Methods

always_include_resource_ids?() click to toggle source
# File lib/graphiti/sideload.rb, line 121
def always_include_resource_ids?
  !!@always_include_resource_ids
end
assign(parents, children) click to toggle source
# File lib/graphiti/sideload.rb, line 263
def assign(parents, children)
  track_associated = type == :has_one
  associated = [] if track_associated
  if performant_assign?
    map = child_map(children)
  end

  parents.each do |parent|
    relevant_children = if performant_assign?
      children_for(parent, map) || []
    else
      fire_assign_each(parent, children)
    end

    if relevant_children.is_a?(Array)
      associated |= relevant_children if track_associated
      associate_all(parent, relevant_children)
    else
      associated << relevant_children if track_associated && relevant_children
      associate(parent, relevant_children)
    end
  end
  children.replace(associated) if track_associated
end
assign_each(parent, children) click to toggle source
# File lib/graphiti/sideload.rb, line 187
def assign_each(parent, children)
  raise "Override #assign_each in subclass"
end
associate(parent, child) click to toggle source
# File lib/graphiti/sideload.rb, line 341
def associate(parent, child)
  parent_resource.associate(parent, child, association_name, type)
end
associate_all(parent, children) click to toggle source
# File lib/graphiti/sideload.rb, line 337
def associate_all(parent, children)
  parent_resource.associate_all(parent, children, association_name, type)
end
association_name() click to toggle source
# File lib/graphiti/sideload.rb, line 175
def association_name
  @as || name
end
base_scope() click to toggle source
# File lib/graphiti/sideload.rb, line 204
def base_scope
  if @base_scope
    @base_scope.respond_to?(:call) ? @base_scope.call : @base_scope
  else
    resource.base_scope
  end
end
build_resource_proxy(parents, query, graph_parent) click to toggle source
# File lib/graphiti/sideload.rb, line 212
def build_resource_proxy(parents, query, graph_parent)
  params = nil
  opts = nil
  proxy = nil

  with_error_handling Errors::SideloadParamsError do
    params = load_params(parents, query)
    params_proc&.call(params, parents, context)
    return [] if blank_query?(params)

    opts = load_options(parents, query)
    opts[:sideload] = self
    opts[:parent] = graph_parent
  end

  with_error_handling(Errors::SideloadQueryBuildingError) do
    scope = base_scope
    scope[:foreign_key] = foreign_key if remote?
    proxy = resource.class._all(params, opts, scope)
    pre_load_proc&.call(proxy, parents)
  end

  proxy
end
clear_resources() click to toggle source

See github.com/graphiti-api/graphiti/issues/186

# File lib/graphiti/sideload.rb, line 258
def clear_resources
  @resource = nil
  @parent_resource = nil
end
create_remote_resource() click to toggle source
# File lib/graphiti/sideload.rb, line 80
def create_remote_resource
  remote_url = @remote
  klass = Class.new(Graphiti::Resource) {
    self.adapter = Graphiti::Adapters::GraphitiAPI
    self.model = OpenStruct
    self.remote = remote_url
    self.validate_endpoints = false
  }
  name = "#{parent_resource_class.name}.#{@name}.remote"
  klass.class_eval("def self.name;'#{name}';end", __FILE__, __LINE__)
  klass
end
description() click to toggle source
# File lib/graphiti/sideload.rb, line 199
def description
  return @description if @description.present?
  parent_resource_class.resolve_i18n_field_description(name, field_type: :relationships)
end
disassociate(parent, child) click to toggle source
# File lib/graphiti/sideload.rb, line 345
def disassociate(parent, child)
  parent_resource.disassociate(parent, child, association_name, type)
end
errors() click to toggle source
# File lib/graphiti/sideload.rb, line 93
def errors
  @errors ||= []
end
fire_hooks!(parent, objects, method) click to toggle source
# File lib/graphiti/sideload.rb, line 328
def fire_hooks!(parent, objects, method)
  return unless self.class.hooks

  all = self.class.hooks[:"after_#{method}"] + self.class.hooks[:after_save]
  all.compact.each do |hook|
    resource.instance_exec(parent, objects, &hook)
  end
end
foreign_key() click to toggle source
# File lib/graphiti/sideload.rb, line 171
def foreign_key
  @foreign_key ||= infer_foreign_key
end
ids_for_parents(parents) click to toggle source
# File lib/graphiti/sideload.rb, line 349
def ids_for_parents(parents)
  parent_ids = parents.map(&primary_key)
  parent_ids.compact!
  parent_ids.uniq!
  parent_ids
end
infer_foreign_key() click to toggle source

Override in subclass

# File lib/graphiti/sideload.rb, line 242
def infer_foreign_key
  model = parent_resource_class.model
  namespace = namespace_for(model)
  model_name = model.name.gsub("#{namespace}::", "")
  :"#{model_name.underscore}_id"
end
load(parents, query, graph_parent) click to toggle source
# File lib/graphiti/sideload.rb, line 237
def load(parents, query, graph_parent)
  build_resource_proxy(parents, query, graph_parent).to_a
end
load_params(parents, query) click to toggle source
# File lib/graphiti/sideload.rb, line 195
def load_params(parents, query)
  raise "Override #load_params in subclass"
end
parent_resource() click to toggle source
# File lib/graphiti/sideload.rb, line 253
def parent_resource
  @parent_resource ||= parent_resource_class.new
end
performant_assign?() click to toggle source
# File lib/graphiti/sideload.rb, line 356
def performant_assign?
  !self.class.assign_each_proc
end
polymorphic_child?() click to toggle source
# File lib/graphiti/sideload.rb, line 163
def polymorphic_child?
  !!@polymorphic_child
end
polymorphic_has_many?() click to toggle source
# File lib/graphiti/sideload.rb, line 117
def polymorphic_has_many?
  !!@polymorphic_as
end
polymorphic_has_one?() click to toggle source
# File lib/graphiti/sideload.rb, line 113
def polymorphic_has_one?
  !!@polymorphic_as
end
polymorphic_parent?() click to toggle source
# File lib/graphiti/sideload.rb, line 159
def polymorphic_parent?
  resource.polymorphic?
end
primary_key() click to toggle source
# File lib/graphiti/sideload.rb, line 167
def primary_key
  @primary_key ||= :id
end
readable?() click to toggle source
# File lib/graphiti/sideload.rb, line 101
def readable?
  !!@readable
end
remote?() click to toggle source
# File lib/graphiti/sideload.rb, line 97
def remote?
  !!@remote
end
resolve(parents, query, graph_parent) click to toggle source
# File lib/graphiti/sideload.rb, line 288
def resolve(parents, query, graph_parent)
  if single? && parents.length > 1
    raise Errors::SingularSideload.new(self, parents.length)
  end

  if self.class.scope_proc
    sideload_scope = fire_scope(parents)
    sideload_scope = Scope.new sideload_scope,
      resource,
      query,
      parent: graph_parent,
      sideload: self,
      sideload_parent_length: parents.length,
      default_paginate: false
    sideload_scope.resolve do |sideload_results|
      fire_assign(parents, sideload_results)
    end
  else
    load(parents, query, graph_parent)
  end
end
resource() click to toggle source
# File lib/graphiti/sideload.rb, line 249
def resource
  @resource ||= resource_class.new
end
resource_class() click to toggle source
# File lib/graphiti/sideload.rb, line 179
def resource_class
  @resource_class ||= infer_resource_class
end
resource_class_loaded?() click to toggle source

@api private

# File lib/graphiti/sideload.rb, line 361
def resource_class_loaded?
  resource_class
  true
rescue Graphiti::Errors::ResourceNotFound
  false
end
scope(parents) click to toggle source
# File lib/graphiti/sideload.rb, line 183
def scope(parents)
  raise "No #scope defined for sideload with name '#{name}'. Make sure to define this in your adapter, or pass a block that defines the scope."
end
shared_remote?() click to toggle source

The parent resource is a remote, AND the sideload is a remote to the same endpoint

# File lib/graphiti/sideload.rb, line 154
def shared_remote?
  resource.remote? &&
    resource.remote_base_url = parent_resource_class.remote_base_url
end
single?() click to toggle source
# File lib/graphiti/sideload.rb, line 109
def single?
  !!@single
end
type() click to toggle source
# File lib/graphiti/sideload.rb, line 191
def type
  @type || raise("Override #type in subclass. Should be one of #{TYPES.inspect}")
end
writable?() click to toggle source
# File lib/graphiti/sideload.rb, line 105
def writable?
  !!@writable
end

Private Instance Methods

blank_query?(params) click to toggle source
# File lib/graphiti/sideload.rb, line 370
def blank_query?(params)
  if (filter = params[:filter])
    if filter.values == [""]
      return true
    end
  end
  false
end
context() click to toggle source
# File lib/graphiti/sideload.rb, line 465
def context
  Graphiti.context[:object]
end
evaluate_flag(flag) click to toggle source

TODO: call this at runtime to support procs

# File lib/graphiti/sideload.rb, line 452
def evaluate_flag(flag)
  return false if flag.blank?

  case flag.class.name
  when "Symbol", "String"
    resource.send(flag)
  when "Proc"
    resource.instance_exec(&flag)
  else
    !!flag
  end
end
fire_assign(parents, children) click to toggle source
# File lib/graphiti/sideload.rb, line 410
def fire_assign(parents, children)
  with_error_handling Errors::SideloadAssignError do
    if self.class.assign_proc
      instance_exec(parents, children, &self.class.assign_proc)
    else
      assign(parents, children)
    end
  end
end
fire_assign_each(parent, children) click to toggle source
# File lib/graphiti/sideload.rb, line 402
def fire_assign_each(parent, children)
  if self.class.assign_each_proc
    instance_exec(parent, children, &self.class.assign_each_proc)
  else
    assign_each(parent, children)
  end
end
fire_scope(parents) click to toggle source
# File lib/graphiti/sideload.rb, line 429
def fire_scope(parents)
  parent_ids = ids_for_parents(parents)
  if self.class.scope_proc
    instance_exec(parent_ids, parents, &self.class.scope_proc)
  else
    method = method(:scope)
    if [2, -2].include?(method.arity)
      scope(parent_ids, parents)
    else
      scope(parent_ids)
    end
  end
end
infer_resource_class() click to toggle source
# File lib/graphiti/sideload.rb, line 443
def infer_resource_class
  Util::Class.infer_resource_class(parent_resource.class, name)
end
load_options(parents, query) click to toggle source
# File lib/graphiti/sideload.rb, line 391
def load_options(parents, query)
  {}.tap do |opts|
    opts[:default_paginate] = false
    opts[:sideload_parent_length] = parents.length
    opts[:query] = query
    opts[:after_resolve] = ->(results) {
      fire_assign(parents, results)
    }
  end
end
namespace_for(klass) click to toggle source
# File lib/graphiti/sideload.rb, line 447
def namespace_for(klass)
  Util::Class.namespace_for(klass)
end
validate_options!(opts) click to toggle source
# File lib/graphiti/sideload.rb, line 379
def validate_options!(opts)
  if opts[:remote]
    if opts[:resource]
      raise Errors::SideloadConfig.new(@name, opts[:parent_resource], "cannot pass :remote and :resource options together")
    end

    if opts[:link]
      raise Errors::SideloadConfig.new(@name, opts[:parent_resource], "remote sideloads do not currently support :link")
    end
  end
end
with_error_handling(error_class) { || ... } click to toggle source
# File lib/graphiti/sideload.rb, line 420
def with_error_handling(error_class)
  begin
    result = yield
  rescue
    raise error_class.new(parent_resource_class, name)
  end
  result
end