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 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
  - - ">="