graphql_preload_queries 0.2.0 → 0.4.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/.github/workflows/ruby.yml +2 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +24 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +11 -4
- data/README.md +46 -36
- data/config/initializers/add_mutation_helper.rb +3 -2
- data/config/initializers/add_preload_field.rb +9 -1
- data/config/initializers/add_query_helper.rb +4 -3
- data/config/initializers/patch_continue_value.rb +13 -15
- data/graphql_preload_queries.gemspec +1 -1
- data/lib/graphql_preload_queries.rb +4 -0
- data/lib/graphql_preload_queries/extensions/preload.rb +6 -3
- data/lib/graphql_preload_queries/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fcc53358f8564b5b00d02cf7a341a7a82af9a9f02db2458fa4902f16d10847fb
|
|
4
|
+
data.tar.gz: 959b2a80b05637e60127762fae8fc765fff6c28c5438099cefbbe40c2c37a8c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 12797d60fbcdb9d528a56563d29b22c14555de16f4d0e34d063efc48444ec9e0936e854f5cf4912b7575720b40f7f79bfee25ab0b13039f9276efad5b99591e1
|
|
7
|
+
data.tar.gz: 991bb3d2cf4c6a8ccc2df2ce26f48cf564d6a37c6bc5f4a68a0d8de8a6d8ae8c48b62a4595b800dd62f72dcca180a31f23e3c28d0025625e76dea6c1f758961d
|
data/.github/workflows/ruby.yml
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
## 0.4.0 (30-05-2021)
|
|
2
|
+
- feat: refactor `include_gql_preloads` to auto calculate `query_key` and make it optional
|
|
3
|
+
- feat: support `:preload` option when defining a field
|
|
4
|
+
|
|
5
|
+
## 0.3.1 (22-01-2021)
|
|
6
|
+
- feat: auto camelize key for queries and mutations
|
|
7
|
+
|
|
8
|
+
## 0.3 (22-01-2021)
|
|
9
|
+
- feat: add debug mode
|
|
10
|
+
```GraphqlPreloadQueries::DEBUG = true```
|
|
11
|
+
- fix: detect the correct Query Result Type
|
|
12
|
+
|
|
13
|
+
## 0.2.2 (21-01-2021)
|
|
14
|
+
- Fix: Fix deep recursive stack error
|
|
15
|
+
|
|
16
|
+
## 0.2.1 (21-01-2021)
|
|
17
|
+
- fix: add default preload to key
|
|
18
|
+
- fix: fix invalid key when deep preloading
|
|
19
|
+
|
|
20
|
+
## 0.2.0 (02-12-2020)
|
|
21
|
+
- Refactor: Preload associations when iterating activeRecord::Relation
|
|
22
|
+
|
|
23
|
+
## 0.1.0 (10-11-2020
|
|
24
|
+
- Add rails query preload support for queries, mutations and gql object types.
|
data/Gemfile
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
ruby '2.6.5'
|
|
3
4
|
source 'https://rubygems.org'
|
|
4
5
|
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
|
5
6
|
|
|
@@ -7,6 +8,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
|
|
7
8
|
# Bundler will treat runtime dependencies like base dependencies, and
|
|
8
9
|
# development dependencies will be added by default to the :development group.
|
|
9
10
|
gemspec
|
|
11
|
+
gem 'byebug'
|
|
10
12
|
|
|
11
13
|
# Declare any dependencies that are still in development here instead of in
|
|
12
14
|
# your gemspec. These might include edge Rails or gems from your path or
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
graphql_preload_queries (0.
|
|
4
|
+
graphql_preload_queries (0.4.0)
|
|
5
5
|
graphql
|
|
6
6
|
rails
|
|
7
7
|
|
|
@@ -65,6 +65,7 @@ GEM
|
|
|
65
65
|
zeitwerk (~> 2.2, >= 2.2.2)
|
|
66
66
|
ast (2.4.1)
|
|
67
67
|
builder (3.2.4)
|
|
68
|
+
byebug (11.1.3)
|
|
68
69
|
concurrent-ruby (1.1.7)
|
|
69
70
|
crass (1.0.6)
|
|
70
71
|
database_cleaner (1.8.5)
|
|
@@ -86,11 +87,13 @@ GEM
|
|
|
86
87
|
marcel (0.3.3)
|
|
87
88
|
mimemagic (~> 0.3.2)
|
|
88
89
|
method_source (1.0.0)
|
|
89
|
-
mimemagic (0.3.
|
|
90
|
-
|
|
90
|
+
mimemagic (0.3.10)
|
|
91
|
+
nokogiri (~> 1)
|
|
92
|
+
rake
|
|
93
|
+
mini_mime (1.1.0)
|
|
91
94
|
mini_portile2 (2.4.0)
|
|
92
95
|
minitest (5.14.2)
|
|
93
|
-
nio4r (2.5.
|
|
96
|
+
nio4r (2.5.7)
|
|
94
97
|
nokogiri (1.10.10)
|
|
95
98
|
mini_portile2 (~> 2.4.0)
|
|
96
99
|
parallel (1.20.1)
|
|
@@ -183,6 +186,7 @@ PLATFORMS
|
|
|
183
186
|
ruby
|
|
184
187
|
|
|
185
188
|
DEPENDENCIES
|
|
189
|
+
byebug
|
|
186
190
|
database_cleaner-active_record
|
|
187
191
|
graphql_preload_queries!
|
|
188
192
|
rspec-rails
|
|
@@ -190,5 +194,8 @@ DEPENDENCIES
|
|
|
190
194
|
rubocop-rspec
|
|
191
195
|
sqlite3
|
|
192
196
|
|
|
197
|
+
RUBY VERSION
|
|
198
|
+
ruby 2.6.5p114
|
|
199
|
+
|
|
193
200
|
BUNDLED WITH
|
|
194
201
|
2.1.4
|
data/README.md
CHANGED
|
@@ -1,49 +1,51 @@
|
|
|
1
1
|
# GraphqlPreloadQueries
|
|
2
|
-
This gem
|
|
2
|
+
This gem permits your graphql application to define association preloads to improve app performance by removing N+1 query issues.
|
|
3
3
|
|
|
4
4
|
## Usage
|
|
5
5
|
* Object Type
|
|
6
6
|
```ruby
|
|
7
7
|
class UserType < Types::BaseObject
|
|
8
|
-
add_preload 'parents|allParents', { preload: :parents, friends: :friends, parents: :parents }
|
|
9
|
-
add_preload :friends, { parents: { preload: :parents, parents: :parents, friends: :friends } }
|
|
10
|
-
|
|
11
8
|
field :id, Int, null: true
|
|
12
9
|
field :name, String, null: true
|
|
13
|
-
field :
|
|
14
|
-
field :
|
|
10
|
+
field :parents, [Types::UserType], null: false, preload: true
|
|
11
|
+
field :friends, [Types::UserType], null: false, preload: :user_friends
|
|
15
12
|
end
|
|
16
13
|
```
|
|
17
|
-
Examples:
|
|
18
|
-
* ```add_preload :friends```
|
|
19
|
-
```:friends``` association will be preloaded if query includes ```friends```, like: ```user(id: 10) { friends { ... } }```
|
|
20
|
-
|
|
21
|
-
* ```add_preload :allFriends, :friends```
|
|
22
|
-
```:friends``` association will be preloaded if query includes ```allFriends```, like: ```user(id: 10) { allFriends { ... } }```
|
|
23
|
-
|
|
24
|
-
* ```add_preload :allFriends, { preload: :friends, parents: :parents }```
|
|
25
|
-
```:preload``` key can be used to indicate the association name when defining nested preloads, like: ```user(id: 10) { allFriends { id parents { ... } } }```
|
|
26
|
-
|
|
27
|
-
* ```add_preload :friends, { allParents: :parents }```
|
|
28
|
-
(Nested 1 lvl preloading) ```friends: :parents``` association will be preloaded if query includes ```allParents```, like: ```user(id: 10) { friends { allParents { ... } } }```
|
|
29
14
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
15
|
+
`preload:` accepts:
|
|
16
|
+
- `true`: Will use field key as the association name
|
|
17
|
+
`field :parents, ..., preload: true` will preload `parents` association
|
|
18
|
+
- `Symbol`: Custom association name
|
|
19
|
+
`field :friends, ..., preload: :user_friends` will preload `user_friends` association
|
|
20
|
+
- `String`: Tied associations
|
|
21
|
+
`field :excluded_friends, ..., preload: 'excluded_friends.user'` will preload `excluded_friends -> user` association
|
|
22
|
+
- `Hash`: Deep preload definitions
|
|
23
|
+
`field :best_friends, ..., preload: { preload: :user_friends, parents: :parents }'`
|
|
24
|
+
* Will preload `user_friends` and `user_friends.parents` only if query includes inner definition, like `user(id: 10) { bestFriends { id parents { ... } } }`
|
|
25
|
+
* Will not preload `user_friends.parents` if query does not include inner definition, like `user(id: 10) { bestFriends { id } }`
|
|
26
|
+
|
|
38
27
|
|
|
39
28
|
* Preloads in query results
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
29
|
+
- BEFORE
|
|
30
|
+
```ruby
|
|
31
|
+
# queries/users.rb
|
|
32
|
+
def users(ids:)
|
|
33
|
+
users = User.where(id: ids)
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
Does not apply preloads to the root query.
|
|
37
|
+
- AFTER
|
|
38
|
+
```ruby
|
|
39
|
+
def users(ids:)
|
|
40
|
+
user = include_gql_preloads(User.where(id: id))
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
Root query applies all defined preloads
|
|
44
|
+
|
|
45
|
+
- `include_gql_preloads(collection, query_key: nil, type_klass: nil)`: Will include all preloads configured in `type_klass` (UserType) based on the gql query.
|
|
46
|
+
- `collection` (ActiveRecordCollection) Query results
|
|
47
|
+
- `query_key` (String | Sym, default: method name) Field result key
|
|
48
|
+
- `type_klass:` (GQL TypeClass, default: calculates using query_key)
|
|
47
49
|
|
|
48
50
|
* Preloads in mutation results
|
|
49
51
|
```ruby
|
|
@@ -52,12 +54,14 @@ This gem helps you to define all nested preloads to be added when required for g
|
|
|
52
54
|
field :users, [Types::UserType], null: true
|
|
53
55
|
def resolve(ids:)
|
|
54
56
|
affected_users = User.where(id: ids)
|
|
55
|
-
affected_users = include_gql_preloads(:users
|
|
56
|
-
puts affected_users.first&.friends
|
|
57
|
+
affected_users = include_gql_preloads(affected_users, query_key: :users)
|
|
57
58
|
{ users: affected_users }
|
|
58
59
|
end
|
|
59
60
|
```
|
|
60
|
-
- include_gql_preloads: Will
|
|
61
|
+
- `include_gql_preloads(collection, query_key: , type_klass: nil)`: Will include all preloads configured in `type_klass` (UserType) based on the gql query.
|
|
62
|
+
- `collection` (ActiveRecordCollection) Query results
|
|
63
|
+
- `query_key` (String | Sym) Field result key
|
|
64
|
+
- `type_klass:` (GQL TypeClass, default: calculates using query_key)
|
|
61
65
|
|
|
62
66
|
## Installation
|
|
63
67
|
Add this line to your application's Gemfile:
|
|
@@ -76,6 +80,12 @@ Or install it yourself as:
|
|
|
76
80
|
$ gem install graphql_preload_queries
|
|
77
81
|
```
|
|
78
82
|
|
|
83
|
+
For debugging mode:
|
|
84
|
+
```
|
|
85
|
+
# config/initializers/gql_preload.rb
|
|
86
|
+
GraphqlPreloadQueries::DEBUG = true
|
|
87
|
+
```
|
|
88
|
+
|
|
79
89
|
## Contributing
|
|
80
90
|
Bug reports and pull requests are welcome on GitHub at https://github.com/owen2345/graphql_preload_queries. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
81
91
|
|
|
@@ -5,10 +5,11 @@ Rails.application.config.to_prepare do
|
|
|
5
5
|
GraphQL::Schema::Mutation.class_eval do
|
|
6
6
|
# TODO: auto recover type_klass using result key
|
|
7
7
|
# Add corresponding preloads to mutation results
|
|
8
|
-
# @param
|
|
8
|
+
# @param query_key (String | Sym)
|
|
9
9
|
# @param collection (ActiveCollection)
|
|
10
10
|
# @param type_klass (GQL TypeClass)
|
|
11
|
-
def include_gql_preloads(
|
|
11
|
+
def include_gql_preloads(collection, query_key:, type_klass: nil)
|
|
12
|
+
gql_result_key = GraphQL::Schema::Member::BuildType.camelize(query_key.to_s)
|
|
12
13
|
type_klass ||= preload_type_klass(gql_result_key.to_s)
|
|
13
14
|
klass = GraphqlPreloadQueries::Extensions::Preload
|
|
14
15
|
ast_node = preload_find_node(gql_result_key)
|
|
@@ -22,7 +22,7 @@ Rails.application.config.to_prepare do
|
|
|
22
22
|
# add_preload(:allUsers, { preload: :users, 'allComments|comments' => :comments } })
|
|
23
23
|
## preload key can be omitted to use the same name as the key
|
|
24
24
|
# add_preload(:users, { 'allComments|comments' => :comments } })
|
|
25
|
-
def add_preload(key, preload)
|
|
25
|
+
def add_preload(key, preload = key)
|
|
26
26
|
preload ||= key
|
|
27
27
|
raise('Invalid preload query key') if [String, Symbol].exclude?(key.class)
|
|
28
28
|
raise('Invalid preload preload key') if [String, Symbol, Hash].exclude?(preload.class)
|
|
@@ -31,6 +31,14 @@ Rails.application.config.to_prepare do
|
|
|
31
31
|
key = GraphQL::Schema::Member::BuildType.camelize(key.to_s)
|
|
32
32
|
preloads[key] = preload
|
|
33
33
|
end
|
|
34
|
+
|
|
35
|
+
alias_method :field_old, :field
|
|
36
|
+
def field(*args, **kwargs, &block)
|
|
37
|
+
preload = kwargs.delete(:preload)
|
|
38
|
+
key = args[0]
|
|
39
|
+
add_preload(key, preload == true ? key : preload) if preload
|
|
40
|
+
field_old(*args, **kwargs, &block)
|
|
41
|
+
end
|
|
34
42
|
end
|
|
35
43
|
end
|
|
36
44
|
end
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
# preload resolver for queries
|
|
4
4
|
Rails.application.config.to_prepare do
|
|
5
5
|
Types::QueryType.class_eval do
|
|
6
|
-
# TODO: auto recover type_klass using result key
|
|
7
6
|
# Add corresponding preloads to query results
|
|
8
7
|
# Note: key is automatically calculated based on method name
|
|
9
|
-
# @param gql_result_key (String | Sym)
|
|
10
8
|
# @param collection (ActiveCollection)
|
|
11
9
|
# @param type_klass (GQL TypeClass, default: calculates using return type)
|
|
12
|
-
|
|
10
|
+
# @param query_key (String | Sym) Default method name
|
|
11
|
+
def include_gql_preloads(collection, query_key: nil, type_klass: nil)
|
|
12
|
+
query_key ||= caller_locations(1, 1)[0].label
|
|
13
|
+
gql_result_key = GraphQL::Schema::Member::BuildType.camelize(query_key.to_s)
|
|
13
14
|
type_klass ||= preload_type_klass(gql_result_key.to_s)
|
|
14
15
|
klass = GraphqlPreloadQueries::Extensions::Preload
|
|
15
16
|
ast_node = preload_find_node(gql_result_key)
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return continue_value_old(*args)
|
|
14
|
-
end
|
|
3
|
+
require 'graphql/execution/interpreter/runtime'
|
|
4
|
+
module GraphqlPreloadQueries::PatchContinueValue # rubocop:disable Style/ClassAndModuleChildren:
|
|
5
|
+
# gql args: path, value, parent_type, field, is_non_null, ast_node
|
|
6
|
+
def continue_value(*args)
|
|
7
|
+
value = args[1]
|
|
8
|
+
ast_node = args[5]
|
|
9
|
+
field = args[3]
|
|
10
|
+
type_klass = Array(field.instance_variable_get(:@return_type_expr))[0]
|
|
11
|
+
is_active_record = value.is_a?(ActiveRecord::Relation)
|
|
12
|
+
return super if !is_active_record || value.loaded? || !type_klass.respond_to?(:preloads)
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
end
|
|
14
|
+
klass = GraphqlPreloadQueries::Extensions::Preload
|
|
15
|
+
klass.preload_associations(value, ast_node, type_klass)
|
|
19
16
|
end
|
|
20
17
|
end
|
|
18
|
+
GraphQL::Execution::Interpreter::Runtime.prepend GraphqlPreloadQueries::PatchContinueValue
|
|
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
|
|
|
13
13
|
spec.description = 'Permit to avoid N+1 queries problem when using graphql queries'
|
|
14
14
|
spec.homepage = 'https://github.com/owen2345/graphql_preload_queries'
|
|
15
15
|
spec.license = 'MIT'
|
|
16
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 2.
|
|
16
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.0') # rubocop:disable Gemspec/RequiredRubyVersion
|
|
17
17
|
|
|
18
18
|
# spec.metadata["allowed_push_host"] = ""
|
|
19
19
|
|
|
@@ -12,7 +12,10 @@ module GraphqlPreloadQueries
|
|
|
12
12
|
# @param @type_klass (GqlTypeKlass)
|
|
13
13
|
# @return @data with necessary preloads
|
|
14
14
|
def preload_associations(value, node, type_klass)
|
|
15
|
-
|
|
15
|
+
preloads = filter_preloads(node, type_klass.preloads || {})
|
|
16
|
+
log_info = { type_klass: type_klass, preloads: preloads, configured: type_klass.preloads }
|
|
17
|
+
GraphqlPreloadQueries.log("Preloading: #{log_info}")
|
|
18
|
+
apply_preloads(value, preloads)
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
private
|
|
@@ -36,12 +39,12 @@ module GraphqlPreloadQueries
|
|
|
36
39
|
sub_node = sub_node(node, key)
|
|
37
40
|
multiple_preload = preload_conf.is_a?(Hash)
|
|
38
41
|
return unless sub_node
|
|
39
|
-
return add_preload_key(root, preload_conf,
|
|
42
|
+
return add_preload_key(root, preload_conf, {}) unless multiple_preload
|
|
40
43
|
|
|
41
44
|
child_root = nested_hash
|
|
42
45
|
association_name = preload_conf[:preload] || key.to_s.underscore
|
|
43
46
|
filter_preloads(sub_node, preload_conf, child_root)
|
|
44
|
-
add_preload_key(root, association_name, child_root.presence ||
|
|
47
|
+
add_preload_key(root, association_name, child_root.presence || {})
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
def sub_node(node, key)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graphql_preload_queries
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- owen2345
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-05-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: graphql
|
|
@@ -120,6 +120,7 @@ files:
|
|
|
120
120
|
- ".gitignore"
|
|
121
121
|
- ".rspec"
|
|
122
122
|
- ".rubocop.yml"
|
|
123
|
+
- CHANGELOG.md
|
|
123
124
|
- Gemfile
|
|
124
125
|
- Gemfile.lock
|
|
125
126
|
- MIT-LICENSE
|
|
@@ -153,7 +154,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
153
154
|
requirements:
|
|
154
155
|
- - ">="
|
|
155
156
|
- !ruby/object:Gem::Version
|
|
156
|
-
version: 2.
|
|
157
|
+
version: '2.0'
|
|
157
158
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
159
|
requirements:
|
|
159
160
|
- - ">="
|