batch-loader 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a7165f52041d179cfe8de386a0d761d35d1cbca2
4
- data.tar.gz: 0d4f594bea1a9d78cb3b94f41c90cf14860dedee
2
+ SHA256:
3
+ metadata.gz: 061a141c0204a3e32aa437b0258e0051b1a0d725de948a762f2444d6994ebfb8
4
+ data.tar.gz: 9ca768f5ecbe6fe8b72edd4a9161d7aee635023ce111f26f2406a71633a2e2ad
5
5
  SHA512:
6
- metadata.gz: 157d5e17609ef8dc6df9ee49755441083d5d49f5b482e14087d8fba3d004ecb2e1a721ae28eb77a5b23be399c99383cf8cc29cdc0e0efe7d88d051bd44c80412
7
- data.tar.gz: a2ad4e96c32219022efa84d7d27bfb90244ce8e8c54072370d5e667031bbc2bf37faee159f645d632b72d440b45a6c2e0b5074c216572ff7b3bf9cc334362cc3
6
+ metadata.gz: c09ec97852edb3ff184a5e5f4fb90e615cdf86b65920a7e9da56e5e4fd633e99ca5252d48a08b8802f860a960539fe5adafebf9b26b910ebe03423fa66b58b3f
7
+ data.tar.gz: 14769f25d9e61311700997c3008f6df75eaf7b5fbce5db121a36dacbf483246d145862f63c813d4273d30f626690a1941fb17abd1e31b8b91f90d477c67001a1
data/.travis.yml CHANGED
@@ -1,23 +1,13 @@
1
- sudo: false
2
1
  language: ruby
3
- before_install: gem install bundler -v 1.17.1
4
- matrix:
5
- include:
6
- - gemfile: graphql-1.7.gemfile
7
- env: GRAPHQL_RUBY_VERSION=1_7 CI=true
8
- rvm: 2.3.8
9
- - gemfile: graphql-1.8.gemfile
10
- env: GRAPHQL_RUBY_VERSION=1_8 CI=true
11
- rvm: 2.3.8
12
- - gemfile: graphql-1.7.gemfile
13
- env: GRAPHQL_RUBY_VERSION=1_7 CI=true
14
- rvm: 2.4.5
15
- - gemfile: graphql-1.8.gemfile
16
- env: GRAPHQL_RUBY_VERSION=1_8 CI=true
17
- rvm: 2.4.5
18
- - gemfile: graphql-1.7.gemfile
19
- env: GRAPHQL_RUBY_VERSION=1_7 CI=true
20
- rvm: 2.5.3
21
- - gemfile: graphql-1.8.gemfile
22
- env: GRAPHQL_RUBY_VERSION=1_8 CI=true
23
- rvm: 2.5.3
2
+ before_install: gem install bundler -v 2.0.1
3
+ rvm:
4
+ - 2.3.8
5
+ - 2.4.9
6
+ - 2.5.7
7
+ - 2.6.5
8
+ - 2.7.0
9
+ env:
10
+ - CI=true
11
+ gemfile:
12
+ - graphql-1.7.gemfile
13
+ - graphql-latest.gemfile
data/CHANGELOG.md CHANGED
@@ -8,13 +8,50 @@ one of the following labels: `Added`, `Changed`, `Deprecated`,
8
8
  to manage the versions of this gem so
9
9
  that you can set version constraints properly.
10
10
 
11
- #### [Unreleased](https://github.com/exAspArk/batch-loader/compare/v1.3.0...HEAD)
11
+ #### [Unreleased](https://github.com/exAspArk/batch-loader/compare/v1.4.1...HEAD)
12
12
 
13
13
  * WIP
14
14
 
15
+ #### [v1.5.0](https://github.com/exAspArk/batch-loader/compare/v1.4.1...v1.5.0)
16
+
17
+ * `Added`: Support for GraphQL Interpreter. [#62](https://github.com/exAspArk/batch-loader/pull/62)
18
+ * `Deprecated`: `BatchLoader.for` in GraphQL. [#62](https://github.com/exAspArk/batch-loader/pull/62)
19
+
20
+ Please use `BatchLoader::GraphQL.for` instead:
21
+
22
+ ```rb
23
+ field :user, UserType, null: false
24
+
25
+ def user # resolver
26
+ BatchLoader::GraphQL.for...
27
+ end
28
+ ```
29
+
30
+ Or wrap a BatchLoader instance with `BatchLoader::GraphQL.wrap`:
31
+
32
+ ```rb
33
+ field :user, UserType, null: false
34
+
35
+ def user # resolver
36
+ BatchLoader::GraphQL.wrap(lazy_user)
37
+ end
38
+
39
+ def lazy_user
40
+ BatchLoader.for...
41
+ end
42
+ ```
43
+
44
+ #### [v1.4.1](https://github.com/exAspArk/batch-loader/compare/v1.4.0...v1.4.1)
45
+
46
+ * `Fixes`: Does not allow mutating and corrupting a list of items in a `batch` block. [#46](https://github.com/exAspArk/batch-loader/pull/46)
47
+
48
+ #### [v1.4.0](https://github.com/exAspArk/batch-loader/compare/v1.3.0...v1.4.0)
49
+
50
+ * `Added`: new `replace_methods` argument to `BatchLoader#batch` to allow control over `define_method` calls. [#45](https://github.com/exAspArk/batch-loader/pull/45)
51
+
15
52
  #### [v1.3.0](https://github.com/exAspArk/batch-loader/compare/v1.2.2...v1.3.0)
16
53
 
17
- * `Added`: `BatchLoader::GraphQL` to make it work with `graphql` gem version `>= 1.8.7`. [#30](https://github.com/exAspArk/batch-loader/issues/30)
54
+ * `Added`: `BatchLoader::GraphQL.for` to make it compatible with `graphql` gem versions `>= 1.8.7`. [#30](https://github.com/exAspArk/batch-loader/issues/30)
18
55
 
19
56
  #### [v1.2.2](https://github.com/exAspArk/batch-loader/compare/v1.2.1...v1.2.2)
20
57
 
data/README.md CHANGED
@@ -8,6 +8,20 @@
8
8
 
9
9
  This gem provides a generic lazy batching mechanism to avoid N+1 DB queries, HTTP queries, etc.
10
10
 
11
+ Developers from these companies use `BatchLoader`:
12
+
13
+ <a href="https://about.gitlab.com/"><img src="images/gitlab.png" height="35" width="114" alt="GitLab" style="max-width:100%;"></a>
14
+ <img src="images/space.png" height="35" width="10" alt="" style="max-width:100%;">
15
+ <a href="https://www.netflix.com/"><img src="images/netflix.png" height="35" width="110" alt="Netflix" style="max-width:100%;"></a>
16
+ <img src="images/space.png" height="35" width="10" alt="" style="max-width:100%;">
17
+ <a href="https://www.alibaba.com/"><img src="images/alibaba.png" height="35" width="86" alt="Alibaba" style="max-width:100%;"></a>
18
+ <img src="images/space.png" height="35" width="10" alt="" style="max-width:100%;">
19
+ <a href="https://www.universe.com/"><img src="images/universe.png" height="35" width="137" alt="Universe" style="max-width:100%;"></a>
20
+ <img src="images/space.png" height="35" width="10" alt="" style="max-width:100%;">
21
+ <a href="https://www.wealthsimple.com/"><img src="images/wealthsimple.png" height="35" width="150" alt="Wealthsimple" style="max-width:100%;"></a>
22
+ <img src="images/space.png" height="35" width="10" alt="" style="max-width:100%;">
23
+ <a href="https://decidim.org/"><img src="images/decidim.png" height="35" width="94" alt="Decidim" style="max-width:100%;"></a>
24
+
11
25
  ## Contents
12
26
 
13
27
  * [Highlights](#highlights)
@@ -20,8 +34,10 @@ This gem provides a generic lazy batching mechanism to avoid N+1 DB queries, HTT
20
34
  * [Loading multiple items](#loading-multiple-items)
21
35
  * [Batch key](#batch-key)
22
36
  * [Caching](#caching)
37
+ * [Replacing methods](#replacing-methods)
23
38
  * [Installation](#installation)
24
39
  * [API](#api)
40
+ * [Related tools](#related-tools)
25
41
  * [Implementation details](#implementation-details)
26
42
  * [Development](#development)
27
43
  * [Contributing](#contributing)
@@ -29,10 +45,6 @@ This gem provides a generic lazy batching mechanism to avoid N+1 DB queries, HTT
29
45
  * [License](#license)
30
46
  * [Code of Conduct](#code-of-conduct)
31
47
 
32
- <a href="https://www.universe.com/" target="_blank" rel="noopener noreferrer">
33
- <img src="images/universe.png" height="41" width="153" alt="Sponsored by Universe" style="max-width:100%;">
34
- </a>
35
-
36
48
  ## Highlights
37
49
 
38
50
  * Generic utility to avoid N+1 DB queries, HTTP requests, etc.
@@ -223,23 +235,38 @@ Batching is particularly useful with GraphQL. Using such techniques as preloadin
223
235
  Let's take a look at the simple [graphql-ruby](https://github.com/rmosolgo/graphql-ruby) schema example:
224
236
 
225
237
  ```ruby
226
- Schema = GraphQL::Schema.define do
227
- query QueryType
238
+ class MyProjectSchema < GraphQL::Schema
239
+ query Types::QueryType
228
240
  end
229
241
 
230
- QueryType = GraphQL::ObjectType.define do
231
- name "Query"
232
- field :posts, !types[PostType], resolve: ->(obj, args, ctx) { Post.all }
242
+ module Types
243
+ class QueryType < Types::BaseObject
244
+ field :posts, [PostType], null: false
245
+
246
+ def posts
247
+ Post.all
248
+ end
249
+ end
233
250
  end
234
251
 
235
- PostType = GraphQL::ObjectType.define do
236
- name "Post"
237
- field :user, !UserType, resolve: ->(post, args, ctx) { post.user } # N+1 queries
252
+ module Types
253
+ class PostType < Types::BaseObject
254
+ name "Post"
255
+
256
+ field :user, UserType, null: false
257
+
258
+ def user
259
+ post.user # N+1 queries
260
+ end
261
+ end
238
262
  end
239
263
 
240
- UserType = GraphQL::ObjectType.define do
241
- name "User"
242
- field :name, !types.String
264
+ module Types
265
+ class UserType < Types::BaseObject
266
+ name "User"
267
+
268
+ field :name, String, null: false
269
+ end
243
270
  end
244
271
  ```
245
272
 
@@ -255,17 +282,22 @@ query = "
255
282
  }
256
283
  }
257
284
  "
258
- Schema.execute(query)
285
+ MyProjectSchema.execute(query)
259
286
  ```
260
287
 
261
288
  To avoid this problem, all we have to do is to change the resolver to return `BatchLoader::GraphQL` ([#32](https://github.com/exAspArk/batch-loader/pull/32) explains why not just `BatchLoader`):
262
289
 
263
290
  ```ruby
264
- PostType = GraphQL::ObjectType.define do
265
- name "Post"
266
- field :user, !UserType, resolve: ->(post, args, ctx) do
267
- BatchLoader::GraphQL.for(post.user_id).batch do |user_ids, loader|
268
- User.where(id: user_ids).each { |user| loader.call(user.id, user) }
291
+ module Types
292
+ class PostType < Types::BaseObject
293
+ name "Post"
294
+
295
+ field :user, UserType, null: false
296
+
297
+ def user
298
+ BatchLoader::GraphQL.for(post.user_id).batch do |user_ids, loader|
299
+ User.where(id: user_ids).each { |user| loader.call(user.id, user) }
300
+ end
269
301
  end
270
302
  end
271
303
  end
@@ -274,8 +306,8 @@ end
274
306
  And setup GraphQL to use the built-in `lazy_resolve` method:
275
307
 
276
308
  ```ruby
277
- Schema = GraphQL::Schema.define do
278
- query QueryType
309
+ class MyProjectSchema < GraphQL::Schema
310
+ query Types::QueryType
279
311
  use BatchLoader::GraphQL
280
312
  end
281
313
  ```
@@ -292,7 +324,7 @@ BatchLoader.for(post.user_id).batch(default_value: NullUser.new) do |user_ids, l
292
324
  end
293
325
  ```
294
326
 
295
- For batches where the value is some kind of collection, such as an Array or Hash, `loader` also supports being called with a block, which yields the _current_ value, and returns the _next_ value. This is extremely useful for 1:Many relationships:
327
+ For batches where the value is some kind of collection, such as an Array or Hash, `loader` also supports being called with a block, which yields the _current_ value, and returns the _next_ value. This is extremely useful for 1:Many (`has_many`) relationships:
296
328
 
297
329
  ```ruby
298
330
  BatchLoader.for(user.id).batch(default_value: []) do |user_ids, loader|
@@ -374,6 +406,21 @@ puts user_lazy(1) # SELECT * FROM users WHERE id IN (1)
374
406
  puts user_lazy(1) # SELECT * FROM users WHERE id IN (1)
375
407
  ```
376
408
 
409
+ If you set `cache: false`, it's likely you also want `replace_methods: false` (see below section).
410
+
411
+ ### Replacing methods
412
+
413
+ By default, `BatchLoader` replaces methods on its instance by calling `#define_method` after batching to copy methods from the loaded value.
414
+ This consumes some time but allows to speed up any future method calls on the instance.
415
+ In some cases, when there are a lot of instances with a huge number of defined methods, this initial process of replacing the methods can be slow.
416
+ You may consider avoiding the "up front payment" and "pay as you go" with `#method_missing` by disabling the method replacement:
417
+
418
+ ```ruby
419
+ BatchLoader.for(id).batch(replace_methods: false) do |ids, loader|
420
+ # ...
421
+ end
422
+ ```
423
+
377
424
  ## Installation
378
425
 
379
426
  Add this line to your application's Gemfile:
@@ -393,20 +440,38 @@ Or install it yourself as:
393
440
  ## API
394
441
 
395
442
  ```ruby
396
- BatchLoader.for(item).batch(default_value: default_value, cache: cache, key: key) do |items, loader, args|
443
+ BatchLoader.for(item).batch(
444
+ default_value: default_value,
445
+ cache: cache,
446
+ replace_methods: replace_methods,
447
+ key: key
448
+ ) do |items, loader, args|
397
449
  # ...
398
450
  end
399
451
  ```
400
452
 
401
- | Argument Key | Default | Description |
402
- | --------------- | --------------------------------------------- | ------------------------------------------------------------- |
403
- | `item` | - | Item which will be collected and used for batching. |
404
- | `default_value` | `nil` | Value returned by default after batching. |
405
- | `cache` | `true` | Set `false` to disable caching between the same executions. |
406
- | `key` | `nil` | Pass custom key to uniquely identify the batch block. |
407
- | `items` | - | List of collected items for batching. |
408
- | `loader` | - | Lambda which should be called to load values loaded in batch. |
409
- | `args` | `{default_value: nil, cache: true, key: nil}` | Arguments passed to the `batch` method. |
453
+ | Argument Key | Default | Description |
454
+ | --------------- | --------------------------------------------- | ------------------------------------------------------------- |
455
+ | `item` | - | Item which will be collected and used for batching. |
456
+ | `default_value` | `nil` | Value returned by default after batching. |
457
+ | `cache` | `true` | Set `false` to disable caching between the same executions. |
458
+ | `replace_methods` | `true` | Set `false` to use `#method_missing` instead of replacing the methods after batching. |
459
+ | `key` | `nil` | Pass custom key to uniquely identify the batch block. |
460
+ | `items` | - | List of collected items for batching. |
461
+ | `loader` | - | Lambda which should be called to load values loaded in batch. |
462
+ | `args` | `{default_value: nil, cache: true, replace_methods: true, key: nil}` | Arguments passed to the `batch` method. |
463
+
464
+ ## Related tools
465
+
466
+ These gems are built by using `BatchLoader`:
467
+
468
+ * [decidim-core](https://github.com/decidim/decidim/) – participatory democracy framework made with Ruby on Rails.
469
+ * [ams_lazy_relationships](https://github.com/Bajena/ams_lazy_relationships/) – ActiveModel Serializers add-on for eliminating N+1 queries.
470
+ * [batch-loader-active-record](https://github.com/mathieul/batch-loader-active-record/) – ActiveRecord lazy association generator to avoid N+1 DB queries.
471
+
472
+ `BatchLoader` in other programming languages:
473
+
474
+ * [batch_loader](https://github.com/exaspark/batch_loader) - Elixir implementation.
410
475
 
411
476
  ## Implementation details
412
477
 
data/batch-loader.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.required_ruby_version = '>= 2.1.0' # keyword args
23
23
 
24
- spec.add_development_dependency "bundler", "~> 1.15"
24
+ spec.add_development_dependency "bundler", "~> 2.0"
25
25
  spec.add_development_dependency "rake", "~> 10.0"
26
26
  spec.add_development_dependency "rspec", "~> 3.0"
27
27
  spec.add_development_dependency "graphql", "~> 1.6"
data/graphql-1.7.gemfile CHANGED
@@ -2,6 +2,6 @@ source "https://rubygems.org"
2
2
 
3
3
  gem 'coveralls'
4
4
 
5
- gem "graphql", "~> 1.7"
5
+ gem "graphql", "~> 1.7.0"
6
6
 
7
7
  gemspec
@@ -2,6 +2,6 @@ source "https://rubygems.org"
2
2
 
3
3
  gem 'coveralls'
4
4
 
5
- gem "graphql", "~> 1.8"
5
+ gem "graphql", ">= 1.8"
6
6
 
7
7
  gemspec
data/lib/batch_loader.rb CHANGED
@@ -23,11 +23,13 @@ class BatchLoader
23
23
  @__executor_proxy = executor_proxy
24
24
  end
25
25
 
26
- def batch(default_value: nil, cache: true, key: nil, &batch_block)
26
+ def batch(default_value: nil, cache: true, replace_methods: nil, key: nil, &batch_block)
27
27
  @default_value = default_value
28
28
  @cache = cache
29
+ @replace_methods = replace_methods.nil? ? cache : replace_methods
29
30
  @key = key
30
31
  @batch_block = batch_block
32
+
31
33
  __executor_proxy.add(item: @item)
32
34
 
33
35
  __singleton_class.class_eval { undef_method(:batch) }
@@ -74,7 +76,7 @@ class BatchLoader
74
76
  def __sync!
75
77
  loaded_value = __sync
76
78
 
77
- if @cache
79
+ if @replace_methods
78
80
  __replace_with!(loaded_value)
79
81
  else
80
82
  loaded_value
@@ -86,7 +88,7 @@ class BatchLoader
86
88
 
87
89
  items = __executor_proxy.list_items
88
90
  loader = __loader
89
- args = {default_value: @default_value, cache: @cache, key: @key}
91
+ args = {default_value: @default_value, cache: @cache, replace_methods: @replace_methods, key: @key}
90
92
  @batch_block.call(items, loader, args)
91
93
  items.each do |item|
92
94
  next if __executor_proxy.value_loaded?(item: item)
@@ -18,7 +18,7 @@ class BatchLoader
18
18
  end
19
19
 
20
20
  def list_items
21
- items_to_load.to_a
21
+ items_to_load.to_a.freeze
22
22
  end
23
23
 
24
24
  def delete(items:)
@@ -4,20 +4,40 @@ class BatchLoader
4
4
  class GraphQL
5
5
  def self.use(schema_definition)
6
6
  schema_definition.lazy_resolve(BatchLoader::GraphQL, :sync)
7
- # for graphql gem versions <= 1.8.6 which work with BatchLoader instead of BatchLoader::GraphQL
8
- schema_definition.instrument(:field, self)
7
+
8
+ # in cases when BatchLoader is being used instead of BatchLoader::GraphQL
9
+ if schema_definition.respond_to?(:interpreter?) && schema_definition.interpreter?
10
+ schema_definition.tracer(self)
11
+ else
12
+ schema_definition.instrument(:field, self)
13
+ end
14
+ end
15
+
16
+ def self.trace(event, _data)
17
+ if event == 'execute_field'
18
+ result = yield
19
+ result.respond_to?(:__sync) ? wrap_with_warning(result) : result
20
+ else
21
+ yield
22
+ end
9
23
  end
10
24
 
11
25
  def self.instrument(type, field)
12
26
  old_resolve_proc = field.resolve_proc
13
27
  new_resolve_proc = ->(object, arguments, context) do
14
28
  result = old_resolve_proc.call(object, arguments, context)
15
- result.respond_to?(:__sync) ? BatchLoader::GraphQL.wrap(result) : result
29
+ result.respond_to?(:__sync) ? wrap_with_warning(result) : result
16
30
  end
17
31
 
18
32
  field.redefine { resolve(new_resolve_proc) }
19
33
  end
20
34
 
35
+ def self.wrap_with_warning(batch_loader)
36
+ warn "DEPRECATION WARNING: using BatchLoader.for in GraphQL is deprecated. Use BatchLoader::GraphQL.for instead or return BatchLoader::GraphQL.wrap from your resolver."
37
+ wrap(batch_loader)
38
+ end
39
+ private_class_method :wrap_with_warning
40
+
21
41
  def self.wrap(batch_loader)
22
42
  BatchLoader::GraphQL.new.tap do |graphql|
23
43
  graphql.batch_loader = batch_loader
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class BatchLoader
4
- VERSION = "1.3.0"
4
+ VERSION = "1.5.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: batch-loader
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - exAspArk
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-02-01 00:00:00.000000000 Z
11
+ date: 2020-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.15'
19
+ version: '2.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.15'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -128,7 +128,7 @@ files:
128
128
  - bin/console
129
129
  - bin/setup
130
130
  - graphql-1.7.gemfile
131
- - graphql-1.8.gemfile
131
+ - graphql-latest.gemfile
132
132
  - lib/batch-loader.rb
133
133
  - lib/batch_loader.rb
134
134
  - lib/batch_loader/executor.rb
@@ -155,8 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
155
  - !ruby/object:Gem::Version
156
156
  version: '0'
157
157
  requirements: []
158
- rubyforge_project:
159
- rubygems_version: 2.6.11
158
+ rubygems_version: 3.0.3
160
159
  signing_key:
161
160
  specification_version: 4
162
161
  summary: Powerful tool to avoid N+1 DB or HTTP queries