graphql_preload_queries 0.2.0 → 0.4.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
  SHA256:
3
- metadata.gz: bc56d2e745351d747e3bf8d1950761271b0bafea51732bbff4bc88af59f7cc51
4
- data.tar.gz: 290fea98e4ef1b1f1d1d5ae94a07be35409b314a5c1ce7ca98b38580baffcbd0
3
+ metadata.gz: fcc53358f8564b5b00d02cf7a341a7a82af9a9f02db2458fa4902f16d10847fb
4
+ data.tar.gz: 959b2a80b05637e60127762fae8fc765fff6c28c5438099cefbbe40c2c37a8c6
5
5
  SHA512:
6
- metadata.gz: f857c894b252512962d6488bc131b19e3b91b3ea89845d644604bd93aa4503f192673b1e45ef4f2619726d02e740d0e9086eed88c3142d7f2f4ab2cd1e0137b3
7
- data.tar.gz: 9a10333cf145142d5e57890da59a90f2fc4ea755a2c0e412e8269e531850b62f132e63e5fe0eb5ff064f85c70fc6d26ebea7164badd73ce4177c688b172877e8
6
+ metadata.gz: 12797d60fbcdb9d528a56563d29b22c14555de16f4d0e34d063efc48444ec9e0936e854f5cf4912b7575720b40f7f79bfee25ab0b13039f9276efad5b99591e1
7
+ data.tar.gz: 991bb3d2cf4c6a8ccc2df2ce26f48cf564d6a37c6bc5f4a68a0d8de8a6d8ae8c48b62a4595b800dd62f72dcca180a31f23e3c28d0025625e76dea6c1f758961d
@@ -45,4 +45,5 @@ jobs:
45
45
  run: |
46
46
  bundle exec rspec
47
47
  - name: Code style (Rubocop)
48
- run: bundle exec rubocop
48
+ run: bundle exec rubocop
49
+ if: matrix.ruby == '2.5'
data/.rubocop.yml CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require: rubocop-rspec
4
4
  AllCops:
5
- TargetRubyVersion: 2.4
5
+ TargetRubyVersion: 2.5
6
6
 
7
7
  RSpec/FilePath:
8
8
  Enabled: false
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.2.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.5)
90
- mini_mime (1.0.2)
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.4)
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 helps you to define all nested preloads to be added when required for graphql data results and avoid the common problem "N+1 Queries".
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 :friends, [Types::UserType], null: false
14
- field :parents, [Types::UserType], null: false
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
- * ```add_preload :friends, { allParents: { preload: :parents, friends: :friends } }```
31
- (Nested 2 levels preloading) ```friends: { parents: :friends }``` association will be preloaded if query includes ```friends``` inside ```parents```, like: ```user(id: 10) { friends { allParents { { friends { ... } } } } }```
32
-
33
- * ```add_preload 'friends|allFriends', :friends```
34
- (Multiple gql queries) ```:friends``` association will be preloaded if query includes ```friends``` or ```allFriends```, like: ```user(id: 10) { friends { ... } }``` OR ```user(id: 10) { allFriends { ... } }```
35
-
36
- * ```add_preload 'ignoredFriends', 'ignored_friends.user'```
37
- (Deep preloading) ```{ ignored_friends: :user }``` association will be preloaded if query includes ```inogredFriends```, like: ```user(id: 10) { ignoredFriends { ... } }```
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
- ```ruby
41
- # queries/users.rb
42
- def user(id:)
43
- user = include_gql_preloads(:user, User.where(id: id))
44
- end
45
- ```
46
- - include_gql_preloads: Will preload all preloads configured in UserType based on the gql query.
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, affected_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 preload all preloads configured in UserType based on the gql query.
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 gql_result_key (String | Sym)
8
+ # @param query_key (String | Sym)
9
9
  # @param collection (ActiveCollection)
10
10
  # @param type_klass (GQL TypeClass)
11
- def include_gql_preloads(gql_result_key, collection, type_klass = nil)
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
- def include_gql_preloads(gql_result_key, collection, type_klass = nil)
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
- Rails.application.config.to_prepare do
4
- GraphQL::Execution::Interpreter::Runtime.class_eval do
5
- alias_method :continue_value_old, :continue_value
6
- # gql args: path, value, parent_type, field, is_non_null, ast_node
7
- def continue_value(*args)
8
- value = args[1]
9
- ast_node = args[5]
10
- field = args[3]
11
- type_klass = field.owner
12
- if !value.is_a?(ActiveRecord::Relation) || value.loaded? || !type_klass.respond_to?(:preloads)
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
- klass = GraphqlPreloadQueries::Extensions::Preload
17
- klass.preload_associations(value, ast_node, type_klass)
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.4.0')
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
 
@@ -5,4 +5,8 @@ require 'graphql'
5
5
  require 'graphql_preload_queries/extensions/preload'
6
6
 
7
7
  module GraphqlPreloadQueries
8
+ DEBUG = false
9
+ def self.log(msg)
10
+ puts "***GraphqlPreloadQueries: #{msg}" if DEBUG
11
+ end
8
12
  end
@@ -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
- apply_preloads(value, filter_preloads(node, type_klass.preloads || {}))
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, []) unless multiple_preload
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GraphqlPreloadQueries
4
- VERSION = '0.2.0'
4
+ VERSION = '0.4.0'
5
5
  end
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.2.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: 2020-12-02 00:00:00.000000000 Z
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.4.0
157
+ version: '2.0'
157
158
  required_rubygems_version: !ruby/object:Gem::Requirement
158
159
  requirements:
159
160
  - - ">="