graphql-preload 1.0.4 → 2.0.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 +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
|