batch-loader 1.3.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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