Class: Dataloader

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) {|array| ... } ⇒ Dataloader

Creates new dataloader

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :key (Proc)

    A function to produce a cache key for a given load key. Defaults to proc { |key| key }. Useful to provide when objects are keys and two similarly shaped objects should be considered equivalent.

  • :cache (Object)

    An instance of cache used for caching of promies (the only required api is ‘#compute_if_absent`). Defaults to Concurrent::Map.new. Values can be either promises or actual values. You can pass `nil` if you don’t want caching.

  • :max_batch_size (Object)

    Limits the number of items that get passed in to the batchLoadFn. Defaults to Float::INFINITY. You can pass 1 to disable batching.

Yield Parameters:

  • array (Array)

    is batched ids to load

Yield Returns:

  • (Promise)

    a promise of loaded value with batch_load block



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/dataloader.rb', line 117

def initialize(options = {}, &batch_load)
  Thread.current[:pending_batches] ||= []

  unless block_given?
    raise TypeError, "Dataloader must be constructed with a block which accepts " \
      "Array and returns either Array or Hash of the same size (or Promise)"
  end

  @batch_promise = Batch.new(self)
  @batch_load = batch_load

  @key = options.fetch(:key, lambda { |key| key })
  @cache = options.fetch(:cache, Concurrent::Map.new)
  @max_batch_size = options.fetch(:max_batch_size, Float::INFINITY)

  if @cache.nil?
    @cache = NoCache.new
  end
end

Instance Attribute Details

#cacheObject

Returns the internal cache that can be overridden with ‘:cache` option (see constructor) This field is writable, so you can reset the cache with something like:

loader.cache = Concurrent::Map.new

Defaults to Concurrent::Map.new



102
103
104
# File 'lib/dataloader.rb', line 102

def cache
  @cache
end

Class Method Details

.waitObject

Forces all currently pending promises to be executed and resolved

This method is invoked automatically when value of any promise is requested with ‘.sync`

Examples:

Promise.rb implementation that waits for all batched promises (default):

class Promise
  def wait
    Dataloader.wait
  end
end


146
147
148
149
150
151
152
# File 'lib/dataloader.rb', line 146

def self.wait
  until Thread.current[:pending_batches].empty?
    pending = Thread.current[:pending_batches]
    Thread.current[:pending_batches] = []
    pending.each(&:dispatch)
  end
end

Instance Method Details

#load(key) ⇒ Promise<Object>

Loads a key, returning a [Promise](github.com/lgierth/promise.rb) for the value represented by that key.

You can resolve this promise when you actually need the value with ‘promise.sync`.

All calls to ‘#load` are batched until the first `#sync` is encountered. Then is starts batching again, et caetera.

Examples:

Load promises of two users and resolve them:

user_loader = Dataloader.new do |ids|
  User.find(*ids)
end

user1_promise = user_loader.load(1)
user2_promise = user_loader.load(2)

user1 = user1_promise.sync
user2 = user2_promise.sync

Parameters:

  • key (Object)

    key to load using ‘batch_load` proc

Returns:

  • (Promise<Object>)

    A Promise of computed value



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/dataloader.rb', line 172

def load(key)
  if key.nil?
    raise TypeError, "#load must be called with a key, but got: nil"
  end

  result = compute_if_absent(key) do
    batch_promise.queue(key)
  end

  unless result.is_a?(Promise)
    return Promise.new.fulfill(result)
  end

  result
end

#load_many(keys) ⇒ Promise<Object>

Loads multiple keys, promising an array of values:

“‘ruby promise = loader.load_many([’a’, ‘b’]) object_a, object_b = promise.sync “‘

This is equivalent to the more verbose:

“‘ruby promise = Promise.all([loader.load(’a’), loader.load(‘b’)]) object_a, object_b = promise.sync “‘

Examples:

Load promises of two users and resolve them:

user_loader = Dataloader.new do |ids|
  User.find(*ids)
end

users_promise = user_loader.load_many([1, 2])

user1, user2 = users_promise.sync

Parameters:

  • keys (Array<Object>)

    list of keys to load using ‘batch_load` proc

Returns:

  • (Promise<Object>)

    A Promise of computed values



214
215
216
217
218
219
220
# File 'lib/dataloader.rb', line 214

def load_many(keys)
  unless keys.is_a?(Array)
    raise TypeError, "#load_many must be called with an Array, but got: #{keys.class.name}"
  end

  Promise.all(keys.map(&method(:load)))
end