Class: Graphiti::Sideload

Inherits:
Object show all
Defined in:
lib/graphiti/sideload.rb

Direct Known Subclasses

BelongsTo, HasMany

Defined Under Namespace

Classes: BelongsTo, HasMany, HasOne, ManyToMany, PolymorphicBelongsTo

Constant Summary collapse

HOOK_ACTIONS =
[:save, :create, :update, :destroy, :disassociate]
TYPES =
[:has_many, :belongs_to, :has_one, :many_to_many]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, opts) ⇒ Sideload

Returns a new instance of Sideload.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 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]
  if polymorphic_child?
    parent.resource.polymorphic << resource_class
  end

  if remote?
    @resource_class = create_remote_resource
  end
end

Instance Attribute Details

#group_nameObject (readonly)

Returns the value of attribute group_name.



6
7
8
# File 'lib/graphiti/sideload.rb', line 6

def group_name
  @group_name
end

Returns the value of attribute link.



6
7
8
# File 'lib/graphiti/sideload.rb', line 6

def link
  @link
end

#nameObject (readonly)

Returns the value of attribute name.



6
7
8
# File 'lib/graphiti/sideload.rb', line 6

def name
  @name
end

#parentObject (readonly)

Returns the value of attribute parent.



6
7
8
# File 'lib/graphiti/sideload.rb', line 6

def parent
  @parent
end

#parent_resource_classObject (readonly)

Returns the value of attribute parent_resource_class.



6
7
8
# File 'lib/graphiti/sideload.rb', line 6

def parent_resource_class
  @parent_resource_class
end

#polymorphic_asObject (readonly)

Returns the value of attribute polymorphic_as.



6
7
8
# File 'lib/graphiti/sideload.rb', line 6

def polymorphic_as
  @polymorphic_as
end

Class Method Details

.after_save(only: [], except: [], &blk) ⇒ Object



268
269
270
271
272
273
274
275
# File 'lib/graphiti/sideload.rb', line 268

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) ⇒ Object



58
59
60
# File 'lib/graphiti/sideload.rb', line 58

def self.assign(&blk)
  self.assign_proc = blk
end

.assign_each(&blk) ⇒ Object



62
63
64
# File 'lib/graphiti/sideload.rb', line 62

def self.assign_each(&blk)
  self.assign_each_proc = blk
end

.hooksObject



277
278
279
280
281
282
283
284
# File 'lib/graphiti/sideload.rb', line 277

def self.hooks
  @hooks ||= {}.tap do |h|
    HOOK_ACTIONS.each do |a|
      h[:"after_#{a}"] = []
      h[:"before_#{a}"] = []
    end
  end
end


74
75
76
# File 'lib/graphiti/sideload.rb', line 74

def self.link(&blk)
  self.link_proc = blk
end

.params(&blk) ⇒ Object



66
67
68
# File 'lib/graphiti/sideload.rb', line 66

def self.params(&blk)
  self.params_proc = blk
end

.pre_load(&blk) ⇒ Object



70
71
72
# File 'lib/graphiti/sideload.rb', line 70

def self.pre_load(&blk)
  self.pre_load_proc = blk
end

.scope(&blk) ⇒ Object



54
55
56
# File 'lib/graphiti/sideload.rb', line 54

def self.scope(&blk)
  self.scope_proc = blk
end

Instance Method Details

#assign(parents, children) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/graphiti/sideload.rb', line 221

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) ⇒ Object



160
161
162
# File 'lib/graphiti/sideload.rb', line 160

def assign_each(parent, children)
  raise "Override #assign_each in subclass"
end

#associate(parent, child) ⇒ Object



299
300
301
# File 'lib/graphiti/sideload.rb', line 299

def associate(parent, child)
  parent_resource.associate(parent, child, association_name, type)
end

#associate_all(parent, children) ⇒ Object



295
296
297
# File 'lib/graphiti/sideload.rb', line 295

def associate_all(parent, children)
  parent_resource.associate_all(parent, children, association_name, type)
end

#association_nameObject



148
149
150
# File 'lib/graphiti/sideload.rb', line 148

def association_name
  @as || name
end

#base_scopeObject



177
178
179
180
181
182
183
# File 'lib/graphiti/sideload.rb', line 177

def base_scope
  if @base_scope
    @base_scope.respond_to?(:call) ? @base_scope.call : @base_scope
  else
    resource.base_scope
  end
end

#create_remote_resourceObject



78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/graphiti/sideload.rb', line 78

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

#descriptionObject



172
173
174
175
# File 'lib/graphiti/sideload.rb', line 172

def description
  return @description if @description.present?
  parent_resource_class.resolve_i18n_field_description(name, field_type: :relationships)
end

#disassociate(parent, child) ⇒ Object



303
304
305
# File 'lib/graphiti/sideload.rb', line 303

def disassociate(parent, child)
  parent_resource.disassociate(parent, child, association_name, type)
end

#errorsObject



91
92
93
# File 'lib/graphiti/sideload.rb', line 91

def errors
  @errors ||= []
end

#fire_hooks!(parent, objects, method) ⇒ Object



286
287
288
289
290
291
292
293
# File 'lib/graphiti/sideload.rb', line 286

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_keyObject



144
145
146
# File 'lib/graphiti/sideload.rb', line 144

def foreign_key
  @foreign_key ||= infer_foreign_key
end

#ids_for_parents(parents) ⇒ Object



307
308
309
310
311
312
# File 'lib/graphiti/sideload.rb', line 307

def ids_for_parents(parents)
  parent_ids = parents.map(&primary_key)
  parent_ids.compact!
  parent_ids.uniq!
  parent_ids
end

#infer_foreign_keyObject

Override in subclass



206
207
208
209
210
211
# File 'lib/graphiti/sideload.rb', line 206

def infer_foreign_key
  model = parent_resource_class.model
  namespace = namespace_for(model)
  model_name = model.name.gsub("#{namespace}::", "")
  :"#{model_name.underscore}_id"
end

#link?Boolean

Returns:

  • (Boolean)


115
116
117
118
119
120
121
122
123
# File 'lib/graphiti/sideload.rb', line 115

def link?
  return true if link_proc

  if @link.nil?
    !!@parent_resource_class.autolink
  else
    !!@link
  end
end

#load(parents, query, graph_parent) ⇒ Object



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/graphiti/sideload.rb', line 185

def load(parents, query, graph_parent)
  params, opts, proxy = nil, nil, nil

  with_error_handling Errors::SideloadParamsError do
    params = load_params(parents, query)
    params_proc&.call(params, parents)
    return [] if blank_query?(params)
    opts = load_options(parents, query)
    opts[:sideload] = self
    opts[:parent] = graph_parent
  end

  with_error_handling(Errors::SideloadQueryBuildingError) do
    proxy = resource.class._all(params, opts, base_scope)
    pre_load_proc&.call(proxy, parents)
  end

  proxy.to_a
end

#load_params(parents, query) ⇒ Object



168
169
170
# File 'lib/graphiti/sideload.rb', line 168

def load_params(parents, query)
  raise "Override #load_params in subclass"
end

#parent_resourceObject



217
218
219
# File 'lib/graphiti/sideload.rb', line 217

def parent_resource
  @parent_resource ||= parent_resource_class.new
end

#performant_assign?Boolean

Returns:

  • (Boolean)


314
315
316
# File 'lib/graphiti/sideload.rb', line 314

def performant_assign?
  !self.class.assign_each_proc
end

#polymorphic_child?Boolean

Returns:

  • (Boolean)


136
137
138
# File 'lib/graphiti/sideload.rb', line 136

def polymorphic_child?
  !!@polymorphic_child
end

#polymorphic_has_many?Boolean

Returns:

  • (Boolean)


111
112
113
# File 'lib/graphiti/sideload.rb', line 111

def polymorphic_has_many?
  !!@polymorphic_as
end

#polymorphic_parent?Boolean

Returns:

  • (Boolean)


132
133
134
# File 'lib/graphiti/sideload.rb', line 132

def polymorphic_parent?
  resource.polymorphic?
end

#primary_keyObject



140
141
142
# File 'lib/graphiti/sideload.rb', line 140

def primary_key
  @primary_key ||= :id
end

#readable?Boolean

Returns:

  • (Boolean)


99
100
101
# File 'lib/graphiti/sideload.rb', line 99

def readable?
  !!@readable
end

#remote?Boolean

Returns:

  • (Boolean)


95
96
97
# File 'lib/graphiti/sideload.rb', line 95

def remote?
  !!@remote
end

#resolve(parents, query, graph_parent) ⇒ Object



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/graphiti/sideload.rb', line 246

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

#resourceObject



213
214
215
# File 'lib/graphiti/sideload.rb', line 213

def resource
  @resource ||= resource_class.new
end

#resource_classObject



152
153
154
# File 'lib/graphiti/sideload.rb', line 152

def resource_class
  @resource_class ||= infer_resource_class
end

#resource_class_loaded?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


319
320
321
322
323
324
# File 'lib/graphiti/sideload.rb', line 319

def resource_class_loaded?
  resource_class
  true
rescue Graphiti::Errors::ResourceNotFound
  false
end

#scope(parents) ⇒ Object



156
157
158
# File 'lib/graphiti/sideload.rb', line 156

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?Boolean

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

Returns:

  • (Boolean)


127
128
129
130
# File 'lib/graphiti/sideload.rb', line 127

def shared_remote?
  resource.remote? &&
    resource.remote_base_url = parent_resource_class.remote_base_url
end

#single?Boolean

Returns:

  • (Boolean)


107
108
109
# File 'lib/graphiti/sideload.rb', line 107

def single?
  !!@single
end

#typeObject



164
165
166
# File 'lib/graphiti/sideload.rb', line 164

def type
  @type || raise("Override #type in subclass. Should be one of #{TYPES.inspect}")
end

#writable?Boolean

Returns:

  • (Boolean)


103
104
105
# File 'lib/graphiti/sideload.rb', line 103

def writable?
  !!@writable
end