graphql-batch 0.4.2 → 0.5.1

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
2
  SHA256:
3
- metadata.gz: 257c2068b92162f1e38b17192c08979fdfcd04fe4f4fa93d2a290dc3746e12ec
4
- data.tar.gz: 4b050c894e800925946423723a6431c5a28f3486c4f5de6f2e80c73ed578db18
3
+ metadata.gz: 47a1830934c614913132268636ef825fd7df42865c3b1d4dde4a605b29591627
4
+ data.tar.gz: cd8ae5dcee54e53bb6b1b496b76387dede14bd0079bac9615b297a2a9d7aa269
5
5
  SHA512:
6
- metadata.gz: 5f2d63cc78370652458bc351d5d05f0502f7429c66e4bd0676b1fd62eea0fc9a6eb359d7df9213a3be413bbcd81b29a425e300c8c9f21769b13a0e91864e59b7
7
- data.tar.gz: 0d35c4b83c8f8d732dc24889b16a4ad672e308582b4aab9c2a7f84da6d8388a5df8e0ae5adcca4e5ccb538b5c2f2568e772c635bff31663bb1620358ee98e3f6
6
+ metadata.gz: 0146b5ab9acb4d952215a4ab443b4ccd4d4fbc33a5b7502a53f5ca9598be66d25f10480cf127a9871ef957a3a2ba51d9b65db096161bdd6a8b6d6fbf4d4e7cca
7
+ data.tar.gz: 62d35a163c8e244943324d704f3a273d26b95df88624fbab98159f46ef725318ca3a21c7b58f909c97fc565d3638e2befca02e6cc766cd80439deaf7c93a6df1
@@ -0,0 +1,23 @@
1
+ name: Tests
2
+
3
+ on:
4
+ - push
5
+ - pull_request
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ ruby: [2.4, 2.7, '3.1']
14
+ graphql_version: ['~> 1.10.0', '~> 1.13', '~> 2.0']
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - uses: ruby/setup-ruby@v1
18
+ with:
19
+ bundler-cache: true
20
+ ruby-version: ${{ matrix.ruby }}
21
+ env:
22
+ GRAPHQL_VERSION: ${{ matrix.graphql_version }}
23
+ - run: bundle exec rake
data/.rubocop.yml ADDED
@@ -0,0 +1,11 @@
1
+ inherit_gem:
2
+ rubocop-shopify: rubocop.yml
3
+
4
+ inherit_from:
5
+ - .rubocop_todo.yml
6
+
7
+ AllCops:
8
+ SuggestExtensions: false
9
+ TargetRubyVersion: 2.7
10
+ Exclude:
11
+ - vendor/**/*
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,199 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2020-02-06 13:18:09 -0500 using RuboCop version 0.78.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # Cop supports --auto-correct.
11
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
12
+ # SupportedStyles: outdent, indent
13
+ Layout/AccessModifierIndentation:
14
+ Exclude:
15
+ - 'examples/http_loader.rb'
16
+
17
+ # Offense count: 5
18
+ # Cop supports --auto-correct.
19
+ # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
20
+ # URISchemes: http, https
21
+ Layout/LineLength:
22
+ Max: 182
23
+
24
+ # Offense count: 4
25
+ # Cop supports --auto-correct.
26
+ # Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator.
27
+ # SupportedStylesForExponentOperator: space, no_space
28
+ Layout/SpaceAroundOperators:
29
+ Exclude:
30
+ - 'test/graphql_test.rb'
31
+
32
+ # Offense count: 6
33
+ # Cop supports --auto-correct.
34
+ # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces.
35
+ # SupportedStyles: space, no_space
36
+ # SupportedStylesForEmptyBraces: space, no_space
37
+ Layout/SpaceBeforeBlockBraces:
38
+ Exclude:
39
+ - 'test/support/db.rb'
40
+
41
+ # Offense count: 5
42
+ # Cop supports --auto-correct.
43
+ # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces.
44
+ # SupportedStyles: space, no_space, compact
45
+ # SupportedStylesForEmptyBraces: space, no_space
46
+ Layout/SpaceInsideHashLiteralBraces:
47
+ Exclude:
48
+ - 'test/graphql_test.rb'
49
+ - 'test/multiplex_test.rb'
50
+
51
+ # Offense count: 1
52
+ # Cop supports --auto-correct.
53
+ # Configuration parameters: EnforcedStyle.
54
+ # SupportedStyles: space, no_space
55
+ Layout/SpaceInsideParens:
56
+ Exclude:
57
+ - 'test/loader_test.rb'
58
+
59
+ # Offense count: 3
60
+ Lint/MissingSuper:
61
+ Exclude:
62
+ - 'test/executor_test.rb'
63
+ - 'test/loader_test.rb'
64
+ - 'test/support/loaders.rb'
65
+
66
+ # Offense count: 5
67
+ # Cop supports --auto-correct.
68
+ # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
69
+ Lint/UnusedBlockArgument:
70
+ Exclude:
71
+ - 'lib/graphql/batch.rb'
72
+ - 'test/support/db.rb'
73
+ - 'test/support/loaders.rb'
74
+ - 'test/support/schema.rb'
75
+
76
+ # Offense count: 6
77
+ # Cop supports --auto-correct.
78
+ # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods.
79
+ Lint/UnusedMethodArgument:
80
+ Exclude:
81
+ - 'lib/graphql/batch/loader.rb'
82
+ - 'lib/graphql/batch/setup.rb'
83
+ - 'lib/graphql/batch/setup_multiplex.rb'
84
+ - 'test/executor_test.rb'
85
+
86
+ # Offense count: 2
87
+ Lint/UselessAssignment:
88
+ Exclude:
89
+ - 'test/loader_test.rb'
90
+
91
+ # Offense count: 1
92
+ # Configuration parameters: CountBlocks.
93
+ Metrics/BlockNesting:
94
+ Max: 4
95
+
96
+ # Offense count: 1
97
+ # Cop supports --auto-correct.
98
+ # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods, AllowBracesOnProceduralOneLiners.
99
+ # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
100
+ # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
101
+ # FunctionalMethods: let, let!, subject, watch
102
+ # IgnoredMethods: lambda, proc, it
103
+ Style/BlockDelimiters:
104
+ Exclude:
105
+ - 'test/support/db.rb'
106
+
107
+ # Offense count: 11
108
+ # Cop supports --auto-correct.
109
+ # Configuration parameters: AutoCorrect, EnforcedStyle.
110
+ # SupportedStyles: nested, compact
111
+ Style/ClassAndModuleChildren:
112
+ Exclude:
113
+ - 'lib/graphql/batch/executor.rb'
114
+ - 'lib/graphql/batch/loader.rb'
115
+ - 'lib/graphql/batch/mutation_field_extension.rb'
116
+ - 'lib/graphql/batch/setup.rb'
117
+ - 'lib/graphql/batch/setup_multiplex.rb'
118
+ - 'test/batch_test.rb'
119
+ - 'test/custom_executor_test.rb'
120
+ - 'test/executor_test.rb'
121
+ - 'test/graphql_test.rb'
122
+ - 'test/loader_test.rb'
123
+ - 'test/multiplex_test.rb'
124
+
125
+ # Offense count: 1
126
+ # Cop supports --auto-correct.
127
+ # Configuration parameters: EnforcedStyle, AllowInnerBackticks.
128
+ # SupportedStyles: backticks, percent_x, mixed
129
+ Style/CommandLiteral:
130
+ Exclude:
131
+ - 'graphql-batch.gemspec'
132
+
133
+ # Offense count: 2
134
+ # Cop supports --auto-correct.
135
+ Style/EmptyLiteral:
136
+ Exclude:
137
+ - 'test/support/schema.rb'
138
+
139
+ # Offense count: 24
140
+ # Cop supports --auto-correct.
141
+ # Configuration parameters: EnforcedStyle.
142
+ # SupportedStyles: always, never
143
+ Style/FrozenStringLiteralComment:
144
+ Enabled: false
145
+
146
+ # Offense count: 72
147
+ # Cop supports --auto-correct.
148
+ # Configuration parameters: IgnoreMacros, IgnoredMethods, IgnoredPatterns, IncludedMacros, AllowParenthesesInMultilineCall, AllowParenthesesInChaining, AllowParenthesesInCamelCaseMethod, EnforcedStyle.
149
+ # SupportedStyles: require_parentheses, omit_parentheses
150
+ Style/MethodCallWithArgsParentheses:
151
+ Exclude:
152
+ - 'Gemfile'
153
+ - 'graphql-batch.gemspec'
154
+ - 'test/batch_test.rb'
155
+ - 'test/custom_executor_test.rb'
156
+ - 'test/executor_test.rb'
157
+ - 'test/graphql_test.rb'
158
+ - 'test/loader_test.rb'
159
+ - 'test/multiplex_test.rb'
160
+ - 'test/support/schema.rb'
161
+ - 'test/test_helper.rb'
162
+
163
+ # Offense count: 1
164
+ # Cop supports --auto-correct.
165
+ Style/RedundantBegin:
166
+ Exclude:
167
+ - 'lib/graphql/batch.rb'
168
+
169
+ # Offense count: 1
170
+ # Cop supports --auto-correct.
171
+ # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods.
172
+ # AllowedMethods: present?, blank?, presence, try, try!
173
+ Style/SafeNavigation:
174
+ Exclude:
175
+ - 'test/support/db.rb'
176
+
177
+ # Offense count: 1
178
+ # Cop supports --auto-correct.
179
+ # Configuration parameters: AllowAsExpressionSeparator.
180
+ Style/Semicolon:
181
+ Exclude:
182
+ - 'test/support/schema.rb'
183
+
184
+ # Offense count: 3
185
+ # Cop supports --auto-correct.
186
+ # Configuration parameters: EnforcedStyleForMultiline.
187
+ # SupportedStylesForMultiline: comma, consistent_comma, no_comma
188
+ Style/TrailingCommaInArrayLiteral:
189
+ Exclude:
190
+ - 'test/graphql_test.rb'
191
+
192
+ # Offense count: 23
193
+ # Cop supports --auto-correct.
194
+ # Configuration parameters: EnforcedStyleForMultiline.
195
+ # SupportedStylesForMultiline: comma, consistent_comma, no_comma
196
+ Style/TrailingCommaInHashLiteral:
197
+ Exclude:
198
+ - 'test/graphql_test.rb'
199
+ - 'test/multiplex_test.rb'
data/Gemfile CHANGED
@@ -2,6 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- if ENV["TESTING_INTERPRETER"] == "true"
6
- gem "graphql", "1.10.0.pre2"
5
+ gem 'graphql', ENV['GRAPHQL_VERSION'] if ENV['GRAPHQL_VERSION']
6
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.4.0')
7
+ gem 'rubocop', '~> 1.12.0', require: false
8
+ gem "rubocop-shopify", '~> 1.0.7', require: false
7
9
  end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GraphQL::Batch
2
2
 
3
- [![Build Status](https://travis-ci.org/Shopify/graphql-batch.svg?branch=master)](https://travis-ci.org/Shopify/graphql-batch)
3
+ [![Build Status](https://github.com/Shopify/graphql-batch/actions/workflows/ci.yml/badge.svg)](https://github.com/Shopify/graphql-batch/actions)
4
4
  [![Gem Version](https://badge.fury.io/rb/graphql-batch.svg)](https://rubygems.org/gems/graphql-batch)
5
5
 
6
6
  Provides an executor for the [`graphql` gem](https://github.com/rmosolgo/graphql-ruby) which allows queries to be batched.
@@ -50,7 +50,7 @@ end
50
50
 
51
51
  Use `GraphQL::Batch` as a plugin in your schema _after_ specifying the mutation
52
52
  so that `GraphQL::Batch` can extend the mutation fields to clear the cache after
53
- they are resolved (for graphql >= `1.5.0`).
53
+ they are resolved.
54
54
 
55
55
  ```ruby
56
56
  class MySchema < GraphQL::Schema
@@ -61,16 +61,6 @@ class MySchema < GraphQL::Schema
61
61
  end
62
62
  ```
63
63
 
64
- For pre `1.5.0` versions:
65
-
66
- ```ruby
67
- MySchema = GraphQL::Schema.define do
68
- query MyQueryType
69
-
70
- GraphQL::Batch.use(self)
71
- end
72
- ```
73
-
74
64
  #### Field Usage
75
65
 
76
66
  The loader class can be used from the resolver for a graphql field by calling `.for` with the grouping arguments to get a loader instance, then call `.load` on that instance with the key to load.
@@ -155,19 +145,19 @@ end
155
145
  ## Unit Testing
156
146
 
157
147
  Your loaders can be tested outside of a GraphQL query by doing the
158
- batch loads in a block passed to GraphQL::Batch.batch. That method
148
+ batch loads in a block passed to `GraphQL::Batch.batch`. That method
159
149
  will set up thread-local state to store the loaders, batch load any
160
150
  promise returned from the block then clear the thread-local state
161
151
  to avoid leaking state between tests.
162
152
 
163
153
  ```ruby
164
- def test_single_query
165
- product = products(:snowboard)
166
- title = GraphQL::Batch.batch do
167
- RecordLoader.for(Product).load(product.id).then(&:title)
168
- end
169
- assert_equal product.title, title
154
+ def test_single_query
155
+ product = products(:snowboard)
156
+ title = GraphQL::Batch.batch do
157
+ RecordLoader.for(Product).load(product.id).then(&:title)
170
158
  end
159
+ assert_equal product.title, title
160
+ end
171
161
  ```
172
162
 
173
163
  ## Development
data/Rakefile CHANGED
@@ -7,4 +7,13 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.test_files = FileList['test/**/*_test.rb']
8
8
  end
9
9
 
10
- task :default => :test
10
+ task(default: :test)
11
+
12
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.4.0')
13
+ task :rubocop do
14
+ require 'rubocop/rake_task'
15
+ RuboCop::RakeTask.new
16
+ end
17
+
18
+ task(default: :rubocop)
19
+ end
@@ -0,0 +1,75 @@
1
+ ####
2
+ # This is a loader for has_one_attached and has_many_attached Active Storage attachments
3
+ # To load a variant for an attachment, 2 queries are required
4
+ # Using preloading via the includes method.
5
+ ####
6
+
7
+ ####
8
+ # The model with an attached image and many attached pictures
9
+ ####
10
+
11
+ # class Event < ApplicationRecord
12
+ # has_one_attached :image
13
+ # has_many_attached :pictures
14
+ # end
15
+
16
+ ####
17
+ # An example data type using the AttachmentLoader
18
+ ####
19
+
20
+ # class Types::EventType < Types::BaseObject
21
+ # graphql_name 'Event'
22
+ #
23
+ # field :id, ID, null: false
24
+ # field :image, String, null: true
25
+ # field :pictures, String, null: true
26
+ #
27
+ # def image
28
+ # AttachmentLoader.for(:Event, :image).load(object.id).then do |image|
29
+ # Rails.application.routes.url_helpers.url_for(
30
+ # image.variant({ quality: 75 })
31
+ # )
32
+ # end
33
+ # end
34
+ #
35
+ # def pictures
36
+ # AttachmentLoader.for(:Event, :pictures, association_type: :has_many_attached).load(object.id).then do |pictures|
37
+ # pictures.map do |picture|
38
+ # Rails.application.routes.url_helpers.url_for(
39
+ # picture.variant({ quality: 75 })
40
+ # )
41
+ # end
42
+ # end
43
+ # end
44
+ # end
45
+ module Loaders
46
+ class ActiveStorageLoader < GraphQL::Batch::Loader
47
+ attr_reader :record_type, :attachment_name, :association_type # should be has_one_attached or has_many_attached
48
+
49
+ def initialize(record_type, attachment_name, association_type: :has_one_attached)
50
+ super()
51
+ @record_type = record_type
52
+ @attachment_name = attachment_name
53
+ @association_type = association_type
54
+ end
55
+
56
+ def perform(record_ids)
57
+ # find records and fulfill promises
58
+ attachments = ActiveStorage::Attachment.includes(:blob).where(
59
+ record_type: record_type, record_id: record_ids, name: attachment_name
60
+ )
61
+
62
+ if @association_type == :has_one_attached
63
+ attachments.each do |attachment|
64
+ fulfill(attachment.record_id, attachment)
65
+ end
66
+
67
+ record_ids.each { |id| fulfill(id, nil) unless fulfilled?(id) }
68
+ else
69
+ record_ids.each do |record_id|
70
+ fulfill(record_id, attachments.select { |attachment| attachment.record_id == record_id })
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -5,6 +5,7 @@ class AssociationLoader < GraphQL::Batch::Loader
5
5
  end
6
6
 
7
7
  def initialize(model, association_name)
8
+ super()
8
9
  @model = model
9
10
  @association_name = association_name
10
11
  validate
@@ -35,7 +36,7 @@ class AssociationLoader < GraphQL::Batch::Loader
35
36
  end
36
37
 
37
38
  def preload_association(records)
38
- ::ActiveRecord::Associations::Preloader.new.preload(records, @association_name)
39
+ ::ActiveRecord::Associations::Preloader.new(records: records, associations: @association_name).call
39
40
  end
40
41
 
41
42
  def read_association(record)
@@ -38,6 +38,7 @@
38
38
  module Loaders
39
39
  class HTTPLoader < GraphQL::Batch::Loader
40
40
  def initialize(host:, size: 4, timeout: 4)
41
+ super()
41
42
  @host = host
42
43
  @size = size
43
44
  @timeout = timeout
@@ -1,5 +1,6 @@
1
1
  class RecordLoader < GraphQL::Batch::Loader
2
2
  def initialize(model, column: model.primary_key, where: nil)
3
+ super()
3
4
  @model = model
4
5
  @column = column.to_s
5
6
  @column_type = model.type_for_attribute(@column)
@@ -0,0 +1,81 @@
1
+ ####
2
+ # This is a has_many loader which takes advantage of Postgres'
3
+ # windowing functionality to load the first N records for
4
+ # a given relationship.
5
+ ####
6
+
7
+ ####
8
+ # An example data type using the WindowKeyLoader
9
+ ####
10
+
11
+ # class Types::CategoryType < Types::BaseObject
12
+ # graphql_name 'Category'
13
+
14
+ # field :id, ID, null: false
15
+ # field :events, [Types::EventType], null: false do
16
+ # argument :first, Int, required: false, default_value: 5
17
+ # end
18
+
19
+ # def events(first:)
20
+ # WindowKeyLoader.for(
21
+ # Event,
22
+ # :category_id,
23
+ # limit: first, order_col: :start_time, order_dir: :desc
24
+ # ).load(object.id)
25
+ # end
26
+ # end
27
+
28
+ ####
29
+ # The SQL that is produced
30
+ ####
31
+
32
+ # SELECT
33
+ # "events".*
34
+ # FROM (
35
+ # SELECT
36
+ # "events".*,
37
+ # row_number() OVER (PARTITION BY category_id ORDER BY start_time DESC) AS rank
38
+ # FROM
39
+ # "events"
40
+ # WHERE
41
+ # "events"."category_id" IN(1, 2, 3, 4, 5)) AS events
42
+ # WHERE (rank <= 5)
43
+
44
+ class WindowKeyLoader < GraphQL::Batch::Loader
45
+ attr_reader :model, :foreign_key, :limit, :order_col, :order_dir
46
+
47
+ def initialize(model, foreign_key, limit:, order_col:, order_dir: :asc)
48
+ super()
49
+ @model = model
50
+ @foreign_key = foreign_key
51
+ @limit = limit
52
+ @order_col = order_col
53
+ @order_dir = order_dir
54
+ end
55
+
56
+ def perform(foreign_ids)
57
+ # build the sub-query, limiting results by foreign key at this point
58
+ # we don't want to execute this query but get its SQL to be used later
59
+ ranked_from =
60
+ model.select(
61
+ "*",
62
+ "row_number() OVER (
63
+ PARTITION BY #{foreign_key} ORDER BY #{order_col} #{order_dir}
64
+ ) as rank"
65
+ ).where(foreign_key => foreign_ids).to_sql
66
+
67
+ # use the sub-query from above to query records which have a rank
68
+ # value less than or equal to our limit
69
+ records =
70
+ model.from("(#{ranked_from}) as #{model.table_name}").where(
71
+ "rank <= #{limit}"
72
+ ).to_a
73
+
74
+ # match records and fulfill promises
75
+ foreign_ids.each do |foreign_id|
76
+ matching_records =
77
+ records.select { |r| foreign_id == r.send(foreign_key) }
78
+ fulfill(foreign_id, matching_records)
79
+ end
80
+ end
81
+ end
@@ -1,7 +1,4 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'graphql/batch/version'
1
+ require_relative 'lib/graphql/batch/version'
5
2
 
6
3
  Gem::Specification.new do |spec|
7
4
  spec.name = "graphql-batch"
@@ -18,10 +15,12 @@ Gem::Specification.new do |spec|
18
15
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
16
  spec.require_paths = ["lib"]
20
17
 
21
- spec.add_runtime_dependency "graphql", ">= 1.3", "< 2"
18
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
19
+
20
+ spec.add_runtime_dependency "graphql", ">= 1.10", "< 3"
22
21
  spec.add_runtime_dependency "promise.rb", "~> 0.7.2"
23
22
 
24
23
  spec.add_development_dependency "byebug" if RUBY_ENGINE == 'ruby'
25
- spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rake", ">= 12.3.3"
26
25
  spec.add_development_dependency "minitest"
27
26
  end
@@ -1,21 +1,20 @@
1
1
  module GraphQL::Batch
2
2
  class Loader
3
- def self.for(*group_args)
4
- loader_key = loader_key_for(*group_args)
5
- executor = Executor.current
6
-
7
- unless executor
8
- raise GraphQL::Batch::NoExecutorError, 'Cannot create loader without'\
9
- ' an Executor. Wrap the call to `for` with `GraphQL::Batch.batch`'\
10
- ' or use `GraphQL::Batch::Setup` as a query instrumenter if'\
11
- ' using with `graphql-ruby`'
3
+ # Use new argument forwarding syntax if available as an optimization
4
+ if RUBY_ENGINE && Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7")
5
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
6
+ def self.for(...)
7
+ current_executor.loader(loader_key_for(...)) { new(...) }
8
+ end
9
+ RUBY
10
+ else
11
+ def self.for(*group_args)
12
+ current_executor.loader(loader_key_for(*group_args)) { new(*group_args) }
12
13
  end
13
-
14
- executor.loader(loader_key) { new(*group_args) }
15
14
  end
16
15
 
17
- def self.loader_key_for(*group_args)
18
- [self].concat(group_args)
16
+ def self.loader_key_for(*group_args, **group_kwargs)
17
+ [self, group_kwargs, group_args]
19
18
  end
20
19
 
21
20
  def self.load(key)
@@ -26,6 +25,23 @@ module GraphQL::Batch
26
25
  self.for.load_many(keys)
27
26
  end
28
27
 
28
+ class << self
29
+ private
30
+
31
+ def current_executor
32
+ executor = Executor.current
33
+
34
+ unless executor
35
+ raise GraphQL::Batch::NoExecutorError, 'Cannot create loader without'\
36
+ ' an Executor. Wrap the call to `for` with `GraphQL::Batch.batch`'\
37
+ ' or use `GraphQL::Batch::SetupMultiplex` as a query instrumenter if'\
38
+ ' using with `graphql-ruby`'
39
+ end
40
+
41
+ executor
42
+ end
43
+ end
44
+
29
45
  attr_accessor :loader_key, :executor
30
46
 
31
47
  def load(key)
@@ -79,7 +95,15 @@ module GraphQL::Batch
79
95
 
80
96
  # Returns true when the key has already been fulfilled, otherwise returns false
81
97
  def fulfilled?(key)
82
- promise_for(key).fulfilled?
98
+ promise = promise_for(key)
99
+ # When a promise is fulfilled through this class, it will either:
100
+ # become fulfilled, if fulfilled with a literal value
101
+ # become pending with a new source if fulfilled with a promise
102
+ # Either of these is acceptable, promise.rb will automatically re-wait
103
+ # on the new source promise as needed.
104
+ return true if promise.fulfilled?
105
+
106
+ promise.pending? && promise.source != self
83
107
  end
84
108
 
85
109
  # Must override to load the keys and call #fulfill for each key
@@ -124,7 +148,13 @@ module GraphQL::Batch
124
148
 
125
149
  def check_for_broken_promises(load_keys)
126
150
  load_keys.each do |key|
127
- next unless promise_for(key).pending?
151
+ promise = promise_for(key)
152
+ # When a promise is fulfilled through this class, it will either:
153
+ # become not pending, if fulfilled with a literal value
154
+ # become pending with a new source if fulfilled with a promise
155
+ # Either of these is acceptable, promise.rb will automatically re-wait
156
+ # on the new source promise as needed.
157
+ next unless promise.pending? && promise.source == self
128
158
 
129
159
  reject(key, ::Promise::BrokenError.new("#{self.class} didn't fulfill promise for key #{key.inspect}"))
130
160
  end
@@ -6,15 +6,11 @@ module GraphQL::Batch
6
6
  end
7
7
 
8
8
  def before_multiplex(multiplex)
9
- Setup.start_batching(@executor_class)
9
+ GraphQL::Batch::Executor.start_batch(@executor_class)
10
10
  end
11
11
 
12
12
  def after_multiplex(multiplex)
13
- Setup.end_batching
14
- end
15
-
16
- def instrument(type, field)
17
- Setup.instrument_field(@schema, type, field)
13
+ GraphQL::Batch::Executor.end_batch
18
14
  end
19
15
  end
20
16
  end
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module Batch
3
- VERSION = "0.4.2"
3
+ VERSION = "0.5.1"
4
4
  end
5
5
  end
data/lib/graphql/batch.rb CHANGED
@@ -16,29 +16,17 @@ module GraphQL
16
16
  end
17
17
 
18
18
  def self.use(schema_defn, executor_class: GraphQL::Batch::Executor)
19
- # Support 1.10+ which passes the class instead of the definition proxy
20
- schema = schema_defn.is_a?(Class) ? schema_defn : schema_defn.target
21
- current_gem_version = Gem::Version.new(GraphQL::VERSION)
22
- if current_gem_version >= Gem::Version.new("1.6.0")
23
- instrumentation = GraphQL::Batch::SetupMultiplex.new(schema, executor_class: executor_class)
24
- schema_defn.instrument(:multiplex, instrumentation)
25
- if schema.mutation
26
- if current_gem_version >= Gem::Version.new('1.9.0.pre3') &&
27
- (schema.mutation.is_a?(Class) || schema.mutation.metadata[:type_class])
28
- require_relative "batch/mutation_field_extension"
29
- schema.mutation.fields.each do |name, f|
30
- field = f.respond_to?(:type_class) ? f.type_class : f.metadata[:type_class]
31
- field.extension(GraphQL::Batch::MutationFieldExtension)
32
- end
33
- else
34
- schema_defn.instrument(:field, instrumentation)
35
- end
19
+ instrumentation = GraphQL::Batch::SetupMultiplex.new(schema_defn, executor_class: executor_class)
20
+ schema_defn.instrument(:multiplex, instrumentation)
21
+
22
+ if schema_defn.mutation
23
+ require_relative "batch/mutation_field_extension"
24
+
25
+ schema_defn.mutation.fields.each do |name, field|
26
+ field.extension(GraphQL::Batch::MutationFieldExtension)
36
27
  end
37
- else
38
- instrumentation = GraphQL::Batch::Setup.new(schema, executor_class: executor_class)
39
- schema_defn.instrument(:query, instrumentation)
40
- schema_defn.instrument(:field, instrumentation)
41
28
  end
29
+
42
30
  schema_defn.lazy_resolve(::Promise, :sync)
43
31
  end
44
32
  end
@@ -47,5 +35,4 @@ end
47
35
  require_relative "batch/version"
48
36
  require_relative "batch/loader"
49
37
  require_relative "batch/executor"
50
- require_relative "batch/setup"
51
38
  require_relative "batch/setup_multiplex"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-batch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dylan Thacker-Smith
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-12-09 00:00:00.000000000 Z
11
+ date: 2022-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.3'
19
+ version: '1.10'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '2'
22
+ version: '3'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '1.3'
29
+ version: '1.10'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '2'
32
+ version: '3'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: promise.rb
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -62,16 +62,16 @@ dependencies:
62
62
  name: rake
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - "~>"
65
+ - - ">="
66
66
  - !ruby/object:Gem::Version
67
- version: '10.0'
67
+ version: 12.3.3
68
68
  type: :development
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - "~>"
72
+ - - ">="
73
73
  - !ruby/object:Gem::Version
74
- version: '10.0'
74
+ version: 12.3.3
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: minitest
77
77
  requirement: !ruby/object:Gem::Requirement
@@ -93,8 +93,10 @@ executables: []
93
93
  extensions: []
94
94
  extra_rdoc_files: []
95
95
  files:
96
+ - ".github/workflows/ci.yml"
96
97
  - ".gitignore"
97
- - ".travis.yml"
98
+ - ".rubocop.yml"
99
+ - ".rubocop_todo.yml"
98
100
  - CONTRIBUTING.md
99
101
  - Gemfile
100
102
  - LICENSE.txt
@@ -102,21 +104,23 @@ files:
102
104
  - Rakefile
103
105
  - bin/console
104
106
  - bin/setup
107
+ - examples/active_storage_loader.rb
105
108
  - examples/association_loader.rb
106
109
  - examples/http_loader.rb
107
110
  - examples/record_loader.rb
111
+ - examples/window_key_loader.rb
108
112
  - graphql-batch.gemspec
109
113
  - lib/graphql/batch.rb
110
114
  - lib/graphql/batch/executor.rb
111
115
  - lib/graphql/batch/loader.rb
112
116
  - lib/graphql/batch/mutation_field_extension.rb
113
- - lib/graphql/batch/setup.rb
114
117
  - lib/graphql/batch/setup_multiplex.rb
115
118
  - lib/graphql/batch/version.rb
116
119
  homepage: https://github.com/Shopify/graphql-batch
117
120
  licenses:
118
121
  - MIT
119
- metadata: {}
122
+ metadata:
123
+ allowed_push_host: https://rubygems.org
120
124
  post_install_message:
121
125
  rdoc_options: []
122
126
  require_paths:
@@ -132,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
136
  - !ruby/object:Gem::Version
133
137
  version: '0'
134
138
  requirements: []
135
- rubygems_version: 3.0.3
139
+ rubygems_version: 3.2.20
136
140
  signing_key:
137
141
  specification_version: 4
138
142
  summary: A query batching executor for the graphql gem
data/.travis.yml DELETED
@@ -1,7 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3
4
- - 2.6
5
- env:
6
- - TESTING_INTERPRETER=true
7
- - TESTING_INTERPRETER=false
@@ -1,45 +0,0 @@
1
- module GraphQL::Batch
2
- class Setup
3
- class << self
4
- def start_batching(executor_class)
5
- GraphQL::Batch::Executor.start_batch(executor_class)
6
- end
7
-
8
- def end_batching
9
- GraphQL::Batch::Executor.end_batch
10
- end
11
-
12
- def instrument_field(schema, type, field)
13
- return field unless type == schema.mutation
14
- old_resolve_proc = field.resolve_proc
15
- field.redefine do
16
- resolve ->(obj, args, ctx) {
17
- GraphQL::Batch::Executor.current.clear
18
- begin
19
- ::Promise.sync(old_resolve_proc.call(obj, args, ctx))
20
- ensure
21
- GraphQL::Batch::Executor.current.clear
22
- end
23
- }
24
- end
25
- end
26
- end
27
-
28
- def initialize(schema, executor_class:)
29
- @schema = schema
30
- @executor_class = executor_class
31
- end
32
-
33
- def before_query(query)
34
- Setup.start_batching(@executor_class)
35
- end
36
-
37
- def after_query(query)
38
- Setup.end_batching
39
- end
40
-
41
- def instrument(type, field)
42
- Setup.instrument_field(@schema, type, field)
43
- end
44
- end
45
- end