class GraphQL::Dataloader::Source

Attributes

dataloader[R]
pending_keys[R]

Public Class Methods

batch_key_for(*batch_args, **batch_kwargs) click to toggle source

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::Relation`s to `.with(…)`, you could override this method to call `.to_sql` on them, thus merging `.load(…)` calls when they apply to equivalent relations.

@param batch_args [Array<Object>] @param batch_kwargs [Hash] @return [Object]

# File lib/graphql/dataloader/source.rb, line 127
def self.batch_key_for(*batch_args, **batch_kwargs)
  [*batch_args, **batch_kwargs]
end

Public Instance Methods

fetch(keys) click to toggle source

Subclasses must implement this method to return a value for each of ‘keys` @param keys [Array<Object>] keys passed to {#load}, {#load_all}, {#request}, or {#request_all} @return [Array<Object>] A loaded value for each of `keys`. The array must match one-for-one to the list of `keys`.

# File lib/graphql/dataloader/source.rb, line 62
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(key) click to toggle source

@param key [Object] A loading key which will be passed to {#fetch} if it isn’t already in the internal cache. @return [Object] The result from {#fetch} for ‘key`. If `key` hasn’t been loaded yet, the Fiber will yield until it’s loaded.

# File lib/graphql/dataloader/source.rb, line 37
def load(key)
  if @results.key?(key)
    result_for(key)
  else
    @pending_keys << key
    sync
    result_for(key)
  end
end
load_all(keys) click to toggle source

@param keys [Array<Object>] Loading keys which will be passed to ‘#fetch` (or read from the internal cache). @return [Object] The result from {#fetch} for `keys`. If `keys` haven’t been loaded yet, the Fiber will yield until they’re loaded.

# File lib/graphql/dataloader/source.rb, line 49
def load_all(keys)
  if keys.any? { |k| !@results.key?(k) }
    pending_keys = keys.select { |k| !@results.key?(k) }
    @pending_keys.concat(pending_keys)
    sync
  end

  keys.map { |k| result_for(k) }
end
pending?() click to toggle source

@return [Boolean] True if this source has any pending requests for data.

# File lib/graphql/dataloader/source.rb, line 85
def pending?
  !@pending_keys.empty?
end
request(key) click to toggle source

@return [Dataloader::Request] a pending request for a value from ‘key`. Call `.load` on that object to wait for the result.

# File lib/graphql/dataloader/source.rb, line 21
def request(key)
  if !@results.key?(key)
    @pending_keys << key
  end
  Dataloader::Request.new(self, key)
end
request_all(keys) click to toggle source

@return [Dataloader::Request] a pending request for a values from ‘keys`. Call `.load` on that object to wait for the results.

# File lib/graphql/dataloader/source.rb, line 29
def request_all(keys)
  pending_keys = keys.select { |k| !@results.key?(k) }
  @pending_keys.concat(pending_keys)
  Dataloader::RequestAll.new(self, keys)
end
run_pending_keys() click to toggle source

Called by {GraphQL::Dataloader} to resolve and pending requests to this source. @api private @return [void]

# File lib/graphql/dataloader/source.rb, line 92
def run_pending_keys
  if !@fetching_keys.empty?
    @pending_keys -= @fetching_keys
  end
  return if @pending_keys.empty?
  fetch_keys = @pending_keys.uniq
  @fetching_keys.concat(fetch_keys)
  @pending_keys = []
  results = fetch(fetch_keys)
  fetch_keys.each_with_index do |key, idx|
    @results[key] = results[idx]
  end
  nil
rescue StandardError => error
  fetch_keys.each { |key| @results[key] = error }
ensure
  if fetch_keys
    @fetching_keys -= fetch_keys
  end
end
setup(dataloader) click to toggle source

Called by {Dataloader} to prepare the {Source}‘s internal state @api private

# File lib/graphql/dataloader/source.rb, line 8
def setup(dataloader)
  # These keys have been requested but haven't been fetched yet
  @pending_keys = []
  # These keys have been passed to `fetch` but haven't been finished yet
  @fetching_keys = []
  # { key => result }
  @results = {}
  @dataloader = dataloader
end
sync() click to toggle source

Wait for a batch, if there’s anything to batch. Then run the batch and update the cache. @return [void]

# File lib/graphql/dataloader/source.rb, line 70
def sync
  pending_keys = @pending_keys.dup
  @dataloader.yield
  iterations = 0
  while pending_keys.any? { |k| !@results.key?(k) }
    iterations += 1
    if iterations > 1000
      raise "#{self.class}#sync tried 1000 times to load pending keys (#{pending_keys}), but they still weren't loaded. There is likely a circular dependency."
    end
    @dataloader.yield
  end
  nil
end

Private Instance Methods

result_for(key) click to toggle source

Reads and returns the result for the key from the internal cache, or raises an error if the result was an error @param key [Object] key passed to {#load} or {#load_all} @return [Object] The result from {#fetch} for ‘key`. @api private

# File lib/graphql/dataloader/source.rb, line 139
      def result_for(key)
        if !@results.key?(key)
          raise GraphQL::InvariantError, <<-ERR
Fetching result for a key on #{self.class} that hasn't been loaded yet (#{key.inspect}, loaded: #{@results.keys})

This key should have been loaded already. This is a bug in GraphQL::Dataloader, please report it on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new.
ERR
        end
        result = @results[key]

        raise result if result.class <= StandardError

        result
      end