graphql-batch 0.2.5 → 0.3.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
2
  SHA1:
3
- metadata.gz: 2ee4c1ca2f39afd8e9ecc36c867cc389357169f9
4
- data.tar.gz: c0282358b5bf23cef97f6055af9b6010743a00c5
3
+ metadata.gz: dc3c6396e81315bc1c3a008f3b19e8e249eb2c46
4
+ data.tar.gz: 02272cc3f0ba19341379ea4d2534fda470698c1c
5
5
  SHA512:
6
- metadata.gz: a0374a2cfdca4c4b61738914c21966582edb27920ea1ae986bc780dae9c1eb2f159f56228a4098c2d7205103f2125dba161e5c71e6d3f1ace468a7bb1e37f494
7
- data.tar.gz: fac574fdb4ea3466c46c6a9ac6eda5ee229ecb3882ff2f9f11f65a47c9a987baac869643e319830ef14f4b6b4ef552243bc4f6584ea09e6c27e04f0962197a63
6
+ metadata.gz: f9f63fc31a2231ce25bcf11c8f8f9b96d38131712b6977d1b8bba6f288b168720450fbdbf07a926044f34a6bfeb6e3f16b561a0065d0adb80bb64df7a4a19a69
7
+ data.tar.gz: 04e5caec058af7eb05c441d519c76a63a33b7c6108f1cfb96b8f42e19f0b54f936d548b23f14c2b09f694ca9b87315a7526c120781fd2b8abadf4ad1b10ed549
data/.gitignore CHANGED
@@ -1,6 +1,6 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
- /Gemfile.lock
3
+ /Gemfile*.lock
4
4
  /_yardoc/
5
5
  /coverage/
6
6
  /doc/
data/.travis.yml CHANGED
@@ -2,4 +2,7 @@ language: ruby
2
2
  rvm:
3
3
  - 2.1
4
4
  - 2.2
5
- before_install: gem install bundler -v 1.11.2
5
+ gemfile:
6
+ - Gemfile
7
+ - Gemfile.graphql13
8
+ before_install: gem install bundler -v 1.13.3
data/Gemfile.graphql13 ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'graphql', github: 'rmosolgo/graphql-ruby', branch: 'master'
data/README.md CHANGED
@@ -49,7 +49,9 @@ end
49
49
  Use the batch execution strategy with your schema
50
50
 
51
51
  ```ruby
52
- MySchema = GraphQL::Schema.new(query: MyQueryType)
52
+ MySchema = GraphQL::Schema.define do
53
+ query MyQueryType
54
+ end
53
55
  MySchema.query_execution_strategy = GraphQL::Batch::ExecutionStrategy
54
56
  MySchema.mutation_execution_strategy = GraphQL::Batch::MutationExecutionStrategy
55
57
  ```
@@ -109,26 +111,19 @@ end
109
111
 
110
112
  ## Unit Testing
111
113
 
112
- GraphQL::Batch::Promise#sync can be used to wait for a promise to be resolved and return its result. This can be useful for debugging and unit testing loaders.
114
+ Your loaders can be tested outside of a GraphQL query by doing the
115
+ batch loads in a block passed to GraphQL::Batch.batch. That method
116
+ will set up thread-local state to store the loaders, batch load any
117
+ promise returned from the block then clear the thread-local state
118
+ to avoid leaking state between tests.
113
119
 
114
120
  ```ruby
115
121
  def test_single_query
116
122
  product = products(:snowboard)
117
- query = RecordLoader.for(Product).load(args["id"]).then(&:title)
118
- assert_equal product.title, query.sync
119
- end
120
- ```
121
-
122
- Use GraphQL::Batch::Promise.all instead of Promise.all to be able to call sync on the returned promise.
123
-
124
- ```ruby
125
- def test_batch_query
126
- products = [products(:snowboard), products(:jacket)]
127
- query1 = RecordLoader.for(Product).load(products(:snowboard).id).then(&:title)
128
- query2 = RecordLoader.for(Product).load(products(:jacket).id).then(&:title)
129
- results = GraphQL::Batch::Promise.all([query1, query2]).sync
130
- assert_equal products(:snowboard).title, results[0]
131
- assert_equal products(:jacket).title, results[1]
123
+ title = GraphQL::Batch.batch do
124
+ RecordLoader.for(Product).load(product.id).then(&:title)
125
+ end
126
+ assert_equal product.title, title
132
127
  end
133
128
  ```
134
129
 
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_runtime_dependency "graphql", ">= 0.8", "< 2"
22
- spec.add_runtime_dependency "promise.rb", "~> 0.7.0.rc2"
22
+ spec.add_runtime_dependency "promise.rb", "~> 0.7.2"
23
23
 
24
24
  spec.add_development_dependency "byebug" if RUBY_ENGINE == 'ruby'
25
25
  spec.add_development_dependency "bundler", "~> 1.10"
data/lib/graphql/batch.rb CHANGED
@@ -3,7 +3,18 @@ require "promise.rb"
3
3
 
4
4
  module GraphQL
5
5
  module Batch
6
- BrokenPromiseError = Class.new(StandardError)
6
+ BrokenPromiseError = ::Promise::BrokenError
7
+ class NestedError < StandardError; end
8
+
9
+ def self.batch
10
+ raise NestedError if GraphQL::Batch::Executor.current
11
+ begin
12
+ GraphQL::Batch::Executor.current = GraphQL::Batch::Executor.new
13
+ Promise.sync(yield)
14
+ ensure
15
+ GraphQL::Batch::Executor.current = nil
16
+ end
17
+ end
7
18
  end
8
19
  end
9
20
 
@@ -11,5 +22,10 @@ require_relative "batch/version"
11
22
  require_relative "batch/loader"
12
23
  require_relative "batch/executor"
13
24
  require_relative "batch/promise"
14
- require_relative "batch/execution_strategy"
15
- require_relative "batch/mutation_execution_strategy"
25
+ require_relative "batch/setup"
26
+
27
+ # Allow custom execution strategies to be removed upstream
28
+ if defined?(GraphQL::Query::SerialExecution)
29
+ require_relative "batch/execution_strategy"
30
+ require_relative "batch/mutation_execution_strategy"
31
+ end
@@ -1,22 +1,21 @@
1
1
  module GraphQL::Batch
2
2
  class ExecutionStrategy < GraphQL::Query::SerialExecution
3
- attr_accessor :disable_batching
4
-
5
3
  def execute(_, _, query)
6
- as_promise(super).sync
4
+ GraphQL::Batch.batch do
5
+ as_promise_unless_resolved(super)
6
+ end
7
7
  rescue GraphQL::InvalidNullError => err
8
8
  err.parent_error? || query.context.errors.push(err)
9
9
  nil
10
- ensure
11
- GraphQL::Batch::Executor.current.clear
12
10
  end
13
11
 
14
- private
15
-
16
- def as_promise(result)
17
- GraphQL::Batch::Promise.resolve(as_promise_unless_resolved(result))
12
+ # Needed for MutationExecutionStrategy
13
+ def deep_sync(result) #:nodoc:
14
+ Promise.sync(as_promise_unless_resolved(result))
18
15
  end
19
16
 
17
+ private
18
+
20
19
  def as_promise_unless_resolved(result)
21
20
  all_promises = []
22
21
  each_promise(result) do |obj, key, promise|
@@ -27,7 +26,7 @@ module GraphQL::Batch
27
26
  end
28
27
  end
29
28
  return result if all_promises.empty?
30
- Promise.all(all_promises).then { result }
29
+ ::Promise.all(all_promises).then { result }
31
30
  end
32
31
 
33
32
  def each_promise(obj, &block)
@@ -4,7 +4,11 @@ module GraphQL::Batch
4
4
  private_constant :THREAD_KEY
5
5
 
6
6
  def self.current
7
- Thread.current[THREAD_KEY] ||= new
7
+ Thread.current[THREAD_KEY]
8
+ end
9
+
10
+ def self.current=(executor)
11
+ Thread.current[THREAD_KEY] = executor
8
12
  end
9
13
 
10
14
  attr_reader :loaders
@@ -19,19 +23,16 @@ module GraphQL::Batch
19
23
  @loading = false
20
24
  end
21
25
 
26
+ def resolve(loader)
27
+ with_loading(true) { loader.resolve }
28
+ end
29
+
22
30
  def shift
23
31
  @loaders.shift.last
24
32
  end
25
33
 
26
34
  def tick
27
- with_loading(true) { shift.resolve }
28
- end
29
-
30
- def wait(promise)
31
- tick while promise.pending? && !loaders.empty?
32
- if promise.pending?
33
- promise.reject(BrokenPromiseError.new("Promise wasn't fulfilled after all queries were loaded"))
34
- end
35
+ resolve(shift)
35
36
  end
36
37
 
37
38
  def wait_all
@@ -2,8 +2,10 @@ module GraphQL::Batch
2
2
  class Loader
3
3
  def self.for(*group_args)
4
4
  loader_key = [self].concat(group_args)
5
- Executor.current.loaders[loader_key] ||= new(*group_args).tap do |loader|
5
+ executor = Executor.current
6
+ executor.loaders[loader_key] ||= new(*group_args).tap do |loader|
6
7
  loader.loader_key = loader_key
8
+ loader.executor = executor
7
9
  end
8
10
  end
9
11
 
@@ -15,21 +17,17 @@ module GraphQL::Batch
15
17
  self.for.load_many(keys)
16
18
  end
17
19
 
18
- attr_accessor :loader_key
20
+ attr_accessor :loader_key, :executor
19
21
 
20
22
  def load(key)
21
- loader = Executor.current.loaders[loader_key] ||= self
22
- if loader != self
23
- raise "load called on loader that wasn't registered with executor"
24
- end
25
23
  cache[cache_key(key)] ||= begin
26
24
  queue << key
27
- Promise.new
25
+ Promise.new.tap { |promise| promise.source = self }
28
26
  end
29
27
  end
30
28
 
31
29
  def load_many(keys)
32
- Promise.all(keys.map { |key| load(key) })
30
+ ::Promise.all(keys.map { |key| load(key) })
33
31
  end
34
32
 
35
33
  def resolve #:nodoc:
@@ -44,6 +42,16 @@ module GraphQL::Batch
44
42
  end
45
43
  end
46
44
 
45
+ # For Promise#sync
46
+ def wait #:nodoc:
47
+ if executor
48
+ executor.loaders.delete(loader_key)
49
+ executor.resolve(self)
50
+ else
51
+ resolve
52
+ end
53
+ end
54
+
47
55
  protected
48
56
 
49
57
  # Fulfill the key with provided value, for use in #perform
@@ -91,7 +99,7 @@ module GraphQL::Batch
91
99
 
92
100
  def check_for_broken_promises(load_keys)
93
101
  each_pending_promise(load_keys) do |key, promise|
94
- promise.reject(BrokenPromiseError.new("#{self.class} didn't fulfill promise for key #{key.inspect}"))
102
+ promise.reject(::Promise::BrokenError.new("#{self.class} didn't fulfill promise for key #{key.inspect}"))
95
103
  end
96
104
  end
97
105
  end
@@ -1,18 +1,17 @@
1
1
  module GraphQL::Batch
2
2
  class MutationExecutionStrategy < GraphQL::Batch::ExecutionStrategy
3
+ attr_accessor :enable_batching
4
+
3
5
  class FieldResolution < GraphQL::Batch::ExecutionStrategy::FieldResolution
4
6
  def get_finished_value(raw_value)
5
- return super if execution_context.strategy.disable_batching
6
-
7
- raw_value = GraphQL::Batch::Promise.resolve(raw_value).sync
7
+ strategy = execution_context.strategy
8
+ return super if strategy.enable_batching
8
9
 
9
- execution_context.strategy.disable_batching = true
10
10
  begin
11
- result = super(raw_value)
12
- GraphQL::Batch::Executor.current.wait_all
13
- result
11
+ strategy.enable_batching = true
12
+ strategy.deep_sync(Promise.sync(super))
14
13
  ensure
15
- execution_context.strategy.disable_batching = false
14
+ strategy.enable_batching = false
16
15
  end
17
16
  end
18
17
  end
@@ -1,11 +1,8 @@
1
1
  module GraphQL::Batch
2
2
  class Promise < ::Promise
3
- def wait
4
- Executor.current.wait(self)
5
- end
6
-
7
3
  def defer
8
- Executor.current.defer { super }
4
+ executor = Executor.current
5
+ executor ? executor.defer { super } : super
9
6
  end
10
7
  end
11
8
  end
@@ -0,0 +1,14 @@
1
+ module GraphQL::Batch
2
+ module Setup
3
+ extend self
4
+
5
+ def before_query(query)
6
+ raise NestedError if GraphQL::Batch::Executor.current
7
+ GraphQL::Batch::Executor.current = GraphQL::Batch::Executor.new
8
+ end
9
+
10
+ def after_query(query)
11
+ GraphQL::Batch::Executor.current = nil
12
+ end
13
+ end
14
+ end
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module Batch
3
- VERSION = "0.2.5"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
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.2.5
4
+ version: 0.3.0
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: 2016-11-08 00:00:00.000000000 Z
11
+ date: 2016-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -36,14 +36,14 @@ dependencies:
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.7.0.rc2
39
+ version: 0.7.2
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 0.7.0.rc2
46
+ version: 0.7.2
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: byebug
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -110,6 +110,7 @@ files:
110
110
  - ".gitignore"
111
111
  - ".travis.yml"
112
112
  - Gemfile
113
+ - Gemfile.graphql13
113
114
  - LICENSE.txt
114
115
  - README.md
115
116
  - Rakefile
@@ -122,6 +123,7 @@ files:
122
123
  - lib/graphql/batch/loader.rb
123
124
  - lib/graphql/batch/mutation_execution_strategy.rb
124
125
  - lib/graphql/batch/promise.rb
126
+ - lib/graphql/batch/setup.rb
125
127
  - lib/graphql/batch/version.rb
126
128
  homepage: https://github.com/Shopify/graphql-batch
127
129
  licenses: