Class: GraphQL::Dataloader::Source

Inherits:
Object
  • Object
show all
Defined in:
lib/graphql/dataloader/source.rb

Constant Summary collapse

MAX_ITERATIONS =
1000

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#dataloaderObject (readonly)

Returns the value of attribute dataloader.



18
19
20
# File 'lib/graphql/dataloader/source.rb', line 18

def dataloader
  @dataloader
end

#pendingObject (readonly)

Returns the value of attribute pending.



172
173
174
# File 'lib/graphql/dataloader/source.rb', line 172

def pending
  @pending
end

#resultsObject (readonly)

Returns the value of attribute results.



172
173
174
# File 'lib/graphql/dataloader/source.rb', line 172

def results
  @results
end

Class Method Details

.batch_key_for(*batch_args, **batch_kwargs) ⇒ Object

These arguments are given to dataloader.with(source_class, ...). The object returned from this method is used to de-duplicate batch loads under the hood by using it as a Hash key.

By default, the arguments are all put in an Array. To customize how this source's batches are merged, override this method to return something else.

For example, if you pass ActiveRecord::Relations to .with(...), you could override this method to call .to_sql on them, thus merging .load(...) calls when they apply to equivalent relations.



161
162
163
# File 'lib/graphql/dataloader/source.rb', line 161

def self.batch_key_for(*batch_args, **batch_kwargs)
  [*batch_args, **batch_kwargs]
end

Instance Method Details

#clear_cachevoid

This method returns an undefined value.

Clear any already-loaded objects for this source



167
168
169
170
# File 'lib/graphql/dataloader/source.rb', line 167

def clear_cache
  @results.clear
  nil
end

#fetch(keys) ⇒ Array<Object>

Subclasses must implement this method to return a value for each of keys



86
87
88
89
# File 'lib/graphql/dataloader/source.rb', line 86

def fetch(keys)
  # somehow retrieve these from the backend
  raise "Implement `#{self.class}#fetch(#{keys.inspect}) to return a record for each of the keys"
end

#load(value) ⇒ Object



51
52
53
54
55
56
57
58
59
60
# File 'lib/graphql/dataloader/source.rb', line 51

def load(value)
  result_key = result_key_for(value)
  if @results.key?(result_key)
    result_for(result_key)
  else
    @pending[result_key] ||= value
    sync([result_key])
    result_for(result_key)
  end
end

#load_all(values) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/graphql/dataloader/source.rb', line 64

def load_all(values)
  result_keys = []
  pending_keys = []
  values.each { |v|
    k = result_key_for(v)
    result_keys << k
    if !@results.key?(k)
      @pending[k] ||= v
      pending_keys << k
    end
  }

  if pending_keys.any?
    sync(pending_keys)
  end

  result_keys.map { |k| result_for(k) }
end

#merge(new_results) ⇒ void

This method returns an undefined value.

Add these key-value pairs to this source's cache (future loads will use these merged values).



117
118
119
120
121
122
123
# File 'lib/graphql/dataloader/source.rb', line 117

def merge(new_results)
  new_results.each do |new_k, new_v|
    key = result_key_for(new_k)
    @results[key] = new_v
  end
  nil
end

#pending?Boolean



109
110
111
# File 'lib/graphql/dataloader/source.rb', line 109

def pending?
  !@pending.empty?
end

#request(value) ⇒ Dataloader::Request



21
22
23
24
25
26
27
# File 'lib/graphql/dataloader/source.rb', line 21

def request(value)
  res_key = result_key_for(value)
  if !@results.key?(res_key)
    @pending[res_key] ||= value
  end
  Dataloader::Request.new(self, value)
end

#request_all(values) ⇒ Dataloader::Request



39
40
41
42
43
44
45
46
47
# File 'lib/graphql/dataloader/source.rb', line 39

def request_all(values)
  values.each do |v|
    res_key = result_key_for(v)
    if !@results.key?(res_key)
      @pending[res_key] ||= v
    end
  end
  Dataloader::RequestAll.new(self, values)
end

#result_key_for(value) ⇒ Object

Implement this method to return a stable identifier if different key objects should load the same data value.



34
35
36
# File 'lib/graphql/dataloader/source.rb', line 34

def result_key_for(value)
  value
end

#run_pending_keysvoid

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.

This method returns an undefined value.

Called by GraphQL::Dataloader to resolve and pending requests to this source.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/graphql/dataloader/source.rb', line 128

def run_pending_keys
  if !@fetching.empty?
    @fetching.each_key { |k| @pending.delete(k) }
  end
  return if @pending.empty?
  fetch_h = @pending
  @pending = {}
  @fetching.merge!(fetch_h)
  results = fetch(fetch_h.values)
  fetch_h.each_with_index do |(key, _value), idx|
    @results[key] = results[idx]
  end
  nil
rescue StandardError => error
  fetch_h.each_key { |key| @results[key] = error }
ensure
  fetch_h && fetch_h.each_key { |k| @fetching.delete(k) }
end

#setup(dataloader) ⇒ Object

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.

Called by GraphQL::Dataloader to prepare the GraphQL::Dataloader::Source's internal state



8
9
10
11
12
13
14
15
16
# File 'lib/graphql/dataloader/source.rb', line 8

def setup(dataloader)
  # These keys have been requested but haven't been fetched yet
  @pending = {}
  # These keys have been passed to `fetch` but haven't been finished yet
  @fetching = {}
  # { key => result }
  @results = {}
  @dataloader = dataloader
end

#sync(pending_result_keys) ⇒ void

This method returns an undefined value.

Wait for a batch, if there's anything to batch. Then run the batch and update the cache.



95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/graphql/dataloader/source.rb', line 95

def sync(pending_result_keys)
  @dataloader.yield
  iterations = 0
  while pending_result_keys.any? { |key| !@results.key?(key) }
    iterations += 1
    if iterations > MAX_ITERATIONS
      raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency."
    end
    @dataloader.yield
  end
  nil
end