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