graphql-preload 1.0.4 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +21 -0
- data/graphql-preload.gemspec +2 -2
- data/lib/graphql/preload.rb +2 -1
- data/lib/graphql/preload/instrument.rb +29 -11
- data/lib/graphql/preload/loader.rb +7 -7
- data/lib/graphql/preload/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9cba006dfca0e52d2cbd40c5364594f358a3ac6
|
4
|
+
data.tar.gz: 3086dde1fc427ba144b721b9ae0d10cdca6f0f2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d1490ee109e5d08f0bd5c8a5d84ba29981487a0a7950942fbab89936272dbe73180e135406b748c297991381ea867fea8b5a5a2ac2d684e3ab29d8945562bc94
|
7
|
+
data.tar.gz: 7de182aa0dba1117bbfb15482e7a218407f4022bdc62d8336f1133cac9c2149d424be4fa43f35e23dcc4ccab28692a154ce4e0f6ff8c5e73ccdf3cffbc862122
|
data/README.md
CHANGED
@@ -49,6 +49,27 @@ Call `preload` when defining your field:
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
+
### `preload_scope`
|
53
|
+
Starting with Rails 4.1, you can scope your preloaded records by passing a valid scope to [`ActiveRecord::Associations::Preloader`](https://apidock.com/rails/v4.1.8/ActiveRecord/Associations/Preloader/preload). Scoping can improve performance by reducing the number of models to be instantiated and can help with certain business goals (e.g., only returning records that have not been soft deleted).
|
54
|
+
|
55
|
+
This functionality is surfaced through the `preload_scope` option:
|
56
|
+
|
57
|
+
PostType = GraphQL::ObjectType.define do
|
58
|
+
name 'Post'
|
59
|
+
|
60
|
+
field :comments, !types[!CommentType] do
|
61
|
+
preload :comments
|
62
|
+
preload_scope ->(args, ctx) { Comment.where(deleted_at: nil) }
|
63
|
+
|
64
|
+
# Resolves with records returned from the following query:
|
65
|
+
# SELECT "comments".*
|
66
|
+
# FROM "comments"
|
67
|
+
# WHERE "comments"."deleted_at" IS NULL
|
68
|
+
# AND "comments"."post_id" IN (1, 2, 3)
|
69
|
+
resolve ->(obj, args, ctx) { obj.comments }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
52
73
|
## Development
|
53
74
|
|
54
75
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/graphql-preload.gemspec
CHANGED
@@ -21,12 +21,12 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
22
|
spec.require_paths = ['lib']
|
23
23
|
|
24
|
-
spec.add_runtime_dependency 'activerecord', '>=
|
24
|
+
spec.add_runtime_dependency 'activerecord', '>= 4.1', '< 6'
|
25
25
|
spec.add_runtime_dependency 'graphql', '>= 1.5', '< 2'
|
26
26
|
spec.add_runtime_dependency 'graphql-batch', '~> 0.3'
|
27
27
|
spec.add_runtime_dependency 'promise.rb', '~> 0.7'
|
28
28
|
|
29
|
-
spec.add_development_dependency 'bundler', '~> 1.
|
29
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
30
30
|
spec.add_development_dependency 'minitest', '~> 5.0'
|
31
31
|
spec.add_development_dependency 'pry', '~> 0.10'
|
32
32
|
spec.add_development_dependency 'rake', '~> 10.0'
|
data/lib/graphql/preload.rb
CHANGED
@@ -6,7 +6,8 @@ GraphQL::Field.accepts_definitions(
|
|
6
6
|
preload: ->(type, *args) do
|
7
7
|
type.metadata[:preload] ||= []
|
8
8
|
type.metadata[:preload].concat(args)
|
9
|
-
end
|
9
|
+
end,
|
10
|
+
preload_scope: ->(type, arg) { type.metadata[:preload_scope] = arg }
|
10
11
|
)
|
11
12
|
|
12
13
|
GraphQL::Schema.accepts_definitions(
|
@@ -9,7 +9,11 @@ module GraphQL
|
|
9
9
|
new_resolver = ->(obj, args, ctx) do
|
10
10
|
return old_resolver.call(obj, args, ctx) unless obj
|
11
11
|
|
12
|
-
|
12
|
+
if field.metadata[:preload_scope]
|
13
|
+
scope = field.metadata[:preload_scope].call(args, ctx)
|
14
|
+
end
|
15
|
+
|
16
|
+
preload(obj, field.metadata[:preload], scope).then do
|
13
17
|
old_resolver.call(obj, args, ctx)
|
14
18
|
end
|
15
19
|
end
|
@@ -19,32 +23,35 @@ module GraphQL
|
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
22
|
-
private def preload(record, associations)
|
23
|
-
|
24
|
-
|
26
|
+
private def preload(record, associations, scope)
|
27
|
+
if associations.is_a?(String)
|
28
|
+
raise TypeError, "Expected #{associations} to be a Symbol, not a String"
|
29
|
+
elsif associations.is_a?(Symbol)
|
30
|
+
return preload_single_association(record, associations, scope)
|
31
|
+
end
|
25
32
|
|
26
33
|
promises = []
|
27
34
|
|
28
35
|
Array.wrap(associations).each do |association|
|
29
36
|
case association
|
30
37
|
when Symbol
|
31
|
-
promises << preload_single_association(record, association)
|
38
|
+
promises << preload_single_association(record, association, scope)
|
32
39
|
when Array
|
33
40
|
association.each do |sub_association|
|
34
|
-
promises << preload(record, sub_association)
|
41
|
+
promises << preload(record, sub_association, scope)
|
35
42
|
end
|
36
43
|
when Hash
|
37
44
|
association.each do |sub_association, nested_association|
|
38
|
-
promises << preload_single_association(record, sub_association).then do
|
45
|
+
promises << preload_single_association(record, sub_association, scope).then do
|
39
46
|
associated_records = record.public_send(sub_association)
|
40
47
|
|
41
48
|
case associated_records
|
42
49
|
when ActiveRecord::Base
|
43
|
-
preload(associated_records, nested_association)
|
50
|
+
preload(associated_records, nested_association, scope)
|
44
51
|
else
|
45
52
|
Promise.all(
|
46
53
|
Array.wrap(associated_records).map do |associated_record|
|
47
|
-
preload(associated_record, nested_association)
|
54
|
+
preload(associated_record, nested_association, scope)
|
48
55
|
end
|
49
56
|
)
|
50
57
|
end
|
@@ -56,8 +63,19 @@ module GraphQL
|
|
56
63
|
Promise.all(promises)
|
57
64
|
end
|
58
65
|
|
59
|
-
private def preload_single_association(record, association)
|
60
|
-
|
66
|
+
private def preload_single_association(record, association, scope)
|
67
|
+
# We would like to pass the `scope` (which is an `ActiveRecord::Relation`),
|
68
|
+
# directly into `Loader.for`. However, because the scope is
|
69
|
+
# created for each parent record, they are different objects and
|
70
|
+
# return different loaders, breaking batching.
|
71
|
+
# Therefore, we pass in `scope.to_sql`, which is the same for all the
|
72
|
+
# scopes and set the `scope` using an accessor. The actual scope
|
73
|
+
# object used will be the last one, which shouldn't make any difference,
|
74
|
+
# because even though they are different objects, they are all
|
75
|
+
# functionally equivalent.
|
76
|
+
loader = GraphQL::Preload::Loader.for(record.class, association, scope.try(:to_sql))
|
77
|
+
loader.scope = scope
|
78
|
+
loader.load(record)
|
61
79
|
end
|
62
80
|
end
|
63
81
|
end
|
@@ -2,13 +2,14 @@ module GraphQL
|
|
2
2
|
module Preload
|
3
3
|
# Preloads ActiveRecord::Associations when called from the Preload::Instrument
|
4
4
|
class Loader < GraphQL::Batch::Loader
|
5
|
+
attr_accessor :scope
|
5
6
|
attr_reader :association, :model
|
6
7
|
|
7
8
|
def cache_key(record)
|
8
9
|
record.object_id
|
9
10
|
end
|
10
11
|
|
11
|
-
def initialize(model, association)
|
12
|
+
def initialize(model, association, _scope_sql)
|
12
13
|
@association = association
|
13
14
|
@model = model
|
14
15
|
|
@@ -34,12 +35,11 @@ module GraphQL
|
|
34
35
|
end
|
35
36
|
|
36
37
|
private def preload_association(records)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
38
|
+
ActiveRecord::Associations::Preloader.new.preload(records, association, preload_scope)
|
39
|
+
end
|
40
|
+
|
41
|
+
private def preload_scope
|
42
|
+
scope if scope.try(:klass) == model.reflect_on_association(association).klass
|
43
43
|
end
|
44
44
|
|
45
45
|
private def validate_association
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-preload
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Foster, Etienne Tripier
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,7 +16,7 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '4.1'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: '6'
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
29
|
+
version: '4.1'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '6'
|
@@ -84,14 +84,14 @@ dependencies:
|
|
84
84
|
requirements:
|
85
85
|
- - "~>"
|
86
86
|
- !ruby/object:Gem::Version
|
87
|
-
version: '1.
|
87
|
+
version: '1.16'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
90
|
version_requirements: !ruby/object:Gem::Requirement
|
91
91
|
requirements:
|
92
92
|
- - "~>"
|
93
93
|
- !ruby/object:Gem::Version
|
94
|
-
version: '1.
|
94
|
+
version: '1.16'
|
95
95
|
- !ruby/object:Gem::Dependency
|
96
96
|
name: minitest
|
97
97
|
requirement: !ruby/object:Gem::Requirement
|