graphql_preload_queries 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7e487e7903d2626761868cfce9a2aa368eeb8efbfa7a930eb00988b80c0ed331
4
+ data.tar.gz: cbdff09086c7530c1a0015c3294ed897bb21fd5621fb46a62acb452543d503db
5
+ SHA512:
6
+ metadata.gz: 2ced151a4c528db0b2bb29f5be1d8fe6aa4d316344510503682dccb1937760b92953ac1b2825e01d5397c751f618967a85bb9f518951a7127f0525f882412517
7
+ data.tar.gz: b8c4a168788b2288955e941e3b5a806a125de2fe52c19fdf58956fba84c30c80e3e7e6e43e79fb285e38e7dbba590616b767d4617a7ec1869ca102d8708e41cb
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Settings><!--This file was automatically generated by Ruby plugin.
3
+ You are allowed to:
4
+ 1. Reorder generators
5
+ 2. Remove generators
6
+ 3. Add installed generators
7
+ To add new installed generators automatically delete this file and reload the project.
8
+ --><GeneratorsGroup><Generator name="controller" /><Generator name="integration_test" /><Generator name="mailer" /><Generator name="migration" /><Generator name="model" /><Generator name="observer" /><Generator name="plugin" /><Generator name="resource" /><Generator name="scaffold" /><Generator name="session_migration" /></GeneratorsGroup></Settings>
@@ -0,0 +1,48 @@
1
+ name: Graphql Preload Queries
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+
9
+ jobs:
10
+ build:
11
+ name: Tests and Code Style
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ matrix:
15
+ ruby: [2.4, 2.5, 2.6]
16
+ rails: [6]
17
+ include:
18
+ - ruby: 2.7
19
+ rails: 6
20
+ exclude: # rails 6 requires ruby >= 2.5
21
+ - ruby: 2.4
22
+ rails: 6
23
+
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - name: Set up Ruby
27
+ uses: actions/setup-ruby@v1
28
+ with:
29
+ ruby-version: ${{ matrix.ruby }}
30
+ - name: Install sqlite3
31
+ run: sudo apt-get install libsqlite3-dev
32
+
33
+ - name: Install bundler
34
+ env:
35
+ GEMFILE_PATH: gemfiles/Gemfile_${{ matrix.rails }}
36
+ RAILS_V: ${{ matrix.rails }}
37
+ run: |
38
+ rm -f Gemfile.lock && rm -f Gemfile
39
+ cp $GEMFILE_PATH ./Gemfile
40
+ bundler_v='2.1.4'
41
+ if [ $RAILS_V = "4" ]; then bundler_v="1.16.6"; fi
42
+ gem install bundler -v "~> $bundler_v"
43
+ bundle _${bundler_v}_ install --jobs 4 --retry 3
44
+ - name: Tests (rspec)
45
+ run: |
46
+ bundle exec rspec
47
+ - name: Code style (Rubocop)
48
+ run: bundle exec rubocop
@@ -0,0 +1,9 @@
1
+ .bundle/
2
+ log/*.log
3
+ pkg/
4
+ test/dummy/db/*.sqlite3
5
+ test/dummy/db/*.sqlite3-journal
6
+ test/dummy/db/*.sqlite3-*
7
+ test/dummy/log/*.log
8
+ test/dummy/storage/
9
+ test/dummy/tmp/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -0,0 +1,22 @@
1
+ # This is the configuration used to check the rubocop source code.
2
+
3
+ require: rubocop-rspec
4
+ AllCops:
5
+ TargetRubyVersion: 2.4
6
+
7
+ RSpec/FilePath:
8
+ Enabled: false
9
+
10
+ RSpec/ExampleLength:
11
+ Enabled: false
12
+
13
+ Style/FrozenStringLiteralComment:
14
+ Exclude:
15
+ - 'spec/**/*.rb'
16
+
17
+ Metrics/BlockLength:
18
+ Exclude:
19
+ - 'spec/**/*.rb'
20
+
21
+ Style/Documentation:
22
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5
+
6
+ # Declare your gem's dependencies in graphql_preload_queries.gemspec.
7
+ # Bundler will treat runtime dependencies like base dependencies, and
8
+ # development dependencies will be added by default to the :development group.
9
+ gemspec
10
+
11
+ # Declare any dependencies that are still in development here instead of in
12
+ # your gemspec. These might include edge Rails or gems from your path or
13
+ # Git. Remember to move these dependencies to your gemspec before releasing
14
+ # your gem to rubygems.org.
15
+
16
+ # To use a debugger
17
+ # gem 'byebug', group: [:development, :test]
@@ -0,0 +1,194 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ graphql_preload_queries (0.1.0)
5
+ graphql
6
+ rails
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ actioncable (6.0.3.4)
12
+ actionpack (= 6.0.3.4)
13
+ nio4r (~> 2.0)
14
+ websocket-driver (>= 0.6.1)
15
+ actionmailbox (6.0.3.4)
16
+ actionpack (= 6.0.3.4)
17
+ activejob (= 6.0.3.4)
18
+ activerecord (= 6.0.3.4)
19
+ activestorage (= 6.0.3.4)
20
+ activesupport (= 6.0.3.4)
21
+ mail (>= 2.7.1)
22
+ actionmailer (6.0.3.4)
23
+ actionpack (= 6.0.3.4)
24
+ actionview (= 6.0.3.4)
25
+ activejob (= 6.0.3.4)
26
+ mail (~> 2.5, >= 2.5.4)
27
+ rails-dom-testing (~> 2.0)
28
+ actionpack (6.0.3.4)
29
+ actionview (= 6.0.3.4)
30
+ activesupport (= 6.0.3.4)
31
+ rack (~> 2.0, >= 2.0.8)
32
+ rack-test (>= 0.6.3)
33
+ rails-dom-testing (~> 2.0)
34
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
35
+ actiontext (6.0.3.4)
36
+ actionpack (= 6.0.3.4)
37
+ activerecord (= 6.0.3.4)
38
+ activestorage (= 6.0.3.4)
39
+ activesupport (= 6.0.3.4)
40
+ nokogiri (>= 1.8.5)
41
+ actionview (6.0.3.4)
42
+ activesupport (= 6.0.3.4)
43
+ builder (~> 3.1)
44
+ erubi (~> 1.4)
45
+ rails-dom-testing (~> 2.0)
46
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
47
+ activejob (6.0.3.4)
48
+ activesupport (= 6.0.3.4)
49
+ globalid (>= 0.3.6)
50
+ activemodel (6.0.3.4)
51
+ activesupport (= 6.0.3.4)
52
+ activerecord (6.0.3.4)
53
+ activemodel (= 6.0.3.4)
54
+ activesupport (= 6.0.3.4)
55
+ activestorage (6.0.3.4)
56
+ actionpack (= 6.0.3.4)
57
+ activejob (= 6.0.3.4)
58
+ activerecord (= 6.0.3.4)
59
+ marcel (~> 0.3.1)
60
+ activesupport (6.0.3.4)
61
+ concurrent-ruby (~> 1.0, >= 1.0.2)
62
+ i18n (>= 0.7, < 2)
63
+ minitest (~> 5.1)
64
+ tzinfo (~> 1.1)
65
+ zeitwerk (~> 2.2, >= 2.2.2)
66
+ ast (2.4.1)
67
+ builder (3.2.4)
68
+ concurrent-ruby (1.1.7)
69
+ crass (1.0.6)
70
+ database_cleaner (1.8.5)
71
+ database_cleaner-active_record (1.8.0)
72
+ activerecord
73
+ database_cleaner (~> 1.8.0)
74
+ diff-lcs (1.4.4)
75
+ erubi (1.10.0)
76
+ globalid (0.4.2)
77
+ activesupport (>= 4.2.0)
78
+ graphql (1.11.6)
79
+ i18n (1.8.5)
80
+ concurrent-ruby (~> 1.0)
81
+ loofah (2.7.0)
82
+ crass (~> 1.0.2)
83
+ nokogiri (>= 1.5.9)
84
+ mail (2.7.1)
85
+ mini_mime (>= 0.1.1)
86
+ marcel (0.3.3)
87
+ mimemagic (~> 0.3.2)
88
+ method_source (1.0.0)
89
+ mimemagic (0.3.5)
90
+ mini_mime (1.0.2)
91
+ mini_portile2 (2.4.0)
92
+ minitest (5.14.2)
93
+ nio4r (2.5.4)
94
+ nokogiri (1.10.10)
95
+ mini_portile2 (~> 2.4.0)
96
+ parallel (1.20.1)
97
+ parser (2.7.2.0)
98
+ ast (~> 2.4.1)
99
+ rack (2.2.3)
100
+ rack-test (1.1.0)
101
+ rack (>= 1.0, < 3)
102
+ rails (6.0.3.4)
103
+ actioncable (= 6.0.3.4)
104
+ actionmailbox (= 6.0.3.4)
105
+ actionmailer (= 6.0.3.4)
106
+ actionpack (= 6.0.3.4)
107
+ actiontext (= 6.0.3.4)
108
+ actionview (= 6.0.3.4)
109
+ activejob (= 6.0.3.4)
110
+ activemodel (= 6.0.3.4)
111
+ activerecord (= 6.0.3.4)
112
+ activestorage (= 6.0.3.4)
113
+ activesupport (= 6.0.3.4)
114
+ bundler (>= 1.3.0)
115
+ railties (= 6.0.3.4)
116
+ sprockets-rails (>= 2.0.0)
117
+ rails-dom-testing (2.0.3)
118
+ activesupport (>= 4.2.0)
119
+ nokogiri (>= 1.6)
120
+ rails-html-sanitizer (1.3.0)
121
+ loofah (~> 2.3)
122
+ railties (6.0.3.4)
123
+ actionpack (= 6.0.3.4)
124
+ activesupport (= 6.0.3.4)
125
+ method_source
126
+ rake (>= 0.8.7)
127
+ thor (>= 0.20.3, < 2.0)
128
+ rainbow (3.0.0)
129
+ rake (13.0.1)
130
+ regexp_parser (1.8.2)
131
+ rexml (3.2.4)
132
+ rspec-core (3.10.0)
133
+ rspec-support (~> 3.10.0)
134
+ rspec-expectations (3.10.0)
135
+ diff-lcs (>= 1.2.0, < 2.0)
136
+ rspec-support (~> 3.10.0)
137
+ rspec-mocks (3.10.0)
138
+ diff-lcs (>= 1.2.0, < 2.0)
139
+ rspec-support (~> 3.10.0)
140
+ rspec-rails (4.0.1)
141
+ actionpack (>= 4.2)
142
+ activesupport (>= 4.2)
143
+ railties (>= 4.2)
144
+ rspec-core (~> 3.9)
145
+ rspec-expectations (~> 3.9)
146
+ rspec-mocks (~> 3.9)
147
+ rspec-support (~> 3.9)
148
+ rspec-support (3.10.0)
149
+ rubocop (1.4.0)
150
+ parallel (~> 1.10)
151
+ parser (>= 2.7.1.5)
152
+ rainbow (>= 2.2.2, < 4.0)
153
+ regexp_parser (>= 1.8)
154
+ rexml
155
+ rubocop-ast (>= 1.1.1)
156
+ ruby-progressbar (~> 1.7)
157
+ unicode-display_width (>= 1.4.0, < 2.0)
158
+ rubocop-ast (1.1.1)
159
+ parser (>= 2.7.1.5)
160
+ rubocop-rspec (2.0.0)
161
+ rubocop (~> 1.0)
162
+ rubocop-ast (>= 1.1.0)
163
+ ruby-progressbar (1.10.1)
164
+ sprockets (4.0.2)
165
+ concurrent-ruby (~> 1.0)
166
+ rack (> 1, < 3)
167
+ sprockets-rails (3.2.2)
168
+ actionpack (>= 4.0)
169
+ activesupport (>= 4.0)
170
+ sprockets (>= 3.0.0)
171
+ sqlite3 (1.4.2)
172
+ thor (1.0.1)
173
+ thread_safe (0.3.6)
174
+ tzinfo (1.2.8)
175
+ thread_safe (~> 0.1)
176
+ unicode-display_width (1.7.0)
177
+ websocket-driver (0.7.3)
178
+ websocket-extensions (>= 0.1.0)
179
+ websocket-extensions (0.1.5)
180
+ zeitwerk (2.4.1)
181
+
182
+ PLATFORMS
183
+ ruby
184
+
185
+ DEPENDENCIES
186
+ database_cleaner-active_record
187
+ graphql_preload_queries!
188
+ rspec-rails
189
+ rubocop
190
+ rubocop-rspec
191
+ sqlite3
192
+
193
+ BUNDLED WITH
194
+ 2.1.4
@@ -0,0 +1,20 @@
1
+ Copyright 2020
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,97 @@
1
+ # GraphqlPreloadQueries (In progress)
2
+ This gem helps to define all possible preloads for graphql data results and avoid the common problem "N+1 Queries".
3
+
4
+ ## Usage
5
+ * Preloads in query results
6
+ ```ruby
7
+ # queries/articles.rb
8
+ def articles
9
+ resolve_preloads(Article.all, { allComments: :comments })
10
+ end
11
+ ```
12
+ When articles query is performed and:
13
+ * The query includes "allComments", then ```:comments``` will automatically be preloaded
14
+ * The query does not include "allComments", then ```:comments``` is not preloaded
15
+
16
+ * Preloads in mutation results
17
+ ```ruby
18
+ # mutations/articles/approve.rb
19
+ def resolve
20
+ affected_articles = Article.where(id: [1,2,3])
21
+ res = resolve_preloads(affected_articles, { allComments: :comments })
22
+ { articles => res }
23
+ end
24
+ ```
25
+ When approve mutation is performed and:
26
+ * The result articles query includes "allComments", then ```:comments``` will automatically be preloaded
27
+ * The result articles query does not include "allComments", then ```:comments``` is not preloaded
28
+
29
+ * Preloads in ObjectTypes
30
+ ```ruby
31
+ # types/article_type.rb
32
+ module Types
33
+ class ArticleType < Types::BaseObject
34
+ preload_field :allComments, [Types::CommentType], preload: { owner: :author }, null: false
35
+ end
36
+ end
37
+ ```
38
+ When any query is retrieving an article data and:
39
+ * The query includes ```owner``` inside ```allComments```, then ```:author``` will automatically be preloaded inside "allComments" query
40
+ * The query does not include ```owner```, then ```:author``` is not preloaded
41
+ This field is exactly the same as the graphql field, except that this field expects for "preload" setting which contains all configurations for preloading
42
+
43
+ Complex preload settings
44
+ ```ruby
45
+ # category query
46
+ {
47
+ 'posts' =>
48
+ [:posts, # :posts preload key will be used when: { posts { id ... } }
49
+ {
50
+ 'authors|allAuthors' => [:author, { # :author key will be used when: { posts { allAuthors { id ... } } }
51
+ address: :address # :address key will be used when: { posts { allAuthors { address { id ... } } } }
52
+ }],
53
+ history: :versions # :versions key will be used when: { posts { history { ... } } }
54
+ }
55
+ ],
56
+ 'disabledPosts' => ['category_disabled_posts.post', { # :category_disabled_posts.post key will be used when: { disabledPosts { ... } }
57
+ authors: :authors # :authors key will be used when: { disabledPosts { authors { ... } } }
58
+ }]
59
+ }
60
+ ```
61
+ * ```authors|allAuthors``` means that the preload will be added if "authors" or "allAuthors" is present in the query
62
+ * ```category_disabled_posts.post``` means an inner preload, sample: ```posts.preload({ category_disabled_posts: :post })```
63
+
64
+ ### Important:
65
+ Is needed to omit "extra" params auto provided by Graphql when using custom resolver (only in case not using params), sample:
66
+ ```ruby
67
+ # types/post_type.rb
68
+ preload_field :allComments, [Types::CommentType], preload: { owner: :author }, null: false
69
+ def allComments(_omit_gql_params) # custom method resolver that omits non used params
70
+ object.allComments
71
+ end
72
+ ```
73
+
74
+
75
+ ## Installation
76
+ Add this line to your application's Gemfile:
77
+
78
+ ```ruby
79
+ gem 'graphql_preload_queries'
80
+ ```
81
+
82
+ And then execute:
83
+ ```bash
84
+ $ bundle install
85
+ ```
86
+
87
+ Or install it yourself as:
88
+ ```bash
89
+ $ gem install graphql_preload_queries
90
+ ```
91
+
92
+ ## Contributing
93
+ 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.
94
+
95
+
96
+ ## License
97
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'GraphqlPreloadQueries'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ require 'bundler/gem_tasks'
25
+
26
+ require 'rake/testtask'
27
+
28
+ Rake::TestTask.new(:test) do |t|
29
+ t.libs << 'test'
30
+ t.pattern = 'test/**/*_test.rb'
31
+ t.verbose = false
32
+ end
33
+
34
+ task default: :test
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # This command will automatically be run when you run "rails" with Rails gems
5
+ # installed from the root of your application.
6
+
7
+ ENGINE_ROOT = File.expand_path('..', __dir__)
8
+ ENGINE_PATH = File.expand_path('../lib/graphql_preload_queries/engine', __dir__)
9
+ APP_PATH = File.expand_path('../spec/dummy/config/application', __dir__)
10
+
11
+ # Set up gems listed in the Gemfile.
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
13
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
14
+
15
+ require 'rails/all'
16
+ require 'rails/engine/commands'
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # preload resolver for mutations
4
+ Rails.application.config.to_prepare do
5
+ GraphQL::Schema::Mutation.class_eval do
6
+ # Add corresponding preloads to mutation results
7
+ # @param key (sym) key of the query
8
+ # @param data (ActiveCollection)
9
+ # @param preload_config (Same as Field: field[:preload])
10
+ def resolve_preloads(key, data, preload_config)
11
+ node = find_node(key)
12
+ return data unless node
13
+
14
+ # relay support (TODO: add support to skip when not using relay)
15
+ node = node.selections.first if %w[nodes edges].include?(node.selections.first.name)
16
+ GraphqlPreloadQueries::Extensions::Preload.resolve_preloads(data, node, preload_config)
17
+ end
18
+
19
+ private
20
+
21
+ def find_node(key)
22
+ main_node = context.query.document.definitions.first.selections.first
23
+ main_node.selections.find { |node_i| node_i.name == key.to_s }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql_preload_queries/extensions/preload'
4
+
5
+ Rails.application.config.to_prepare do
6
+ # Custom preload field for Object types
7
+ Types::BaseObject.class_eval do
8
+ # @param key field[:key]
9
+ # @param type field[:type]
10
+ # @param settings field[:settings] ++ { preload: {} }
11
+ # preload: (Hash) { allPosts: [:posts, { author: :author }] }
12
+ # ==> <cat1>.preload(posts: :author) // if author and posts are in query
13
+ # ==> <cat1>.preload(:posts) // if only author is in the query
14
+ # ==> <cat1>.preload() // if both of them are not in the query
15
+ # TODO: ability to merge extensions + extras
16
+ def self.preload_field(key, type, settings = {})
17
+ klass = GraphqlPreloadQueries::Extensions::Preload
18
+ custom_attrs = {
19
+ extras: [:ast_node],
20
+ extensions: [klass => settings.delete(:preload)]
21
+ }
22
+ field key, type, settings.merge(custom_attrs)
23
+
24
+ # Fix: omit non expected "extras" param auto provided by graphql
25
+ define_method(key) { |_omit_non_used_args| object.send(key) } unless method_defined? key
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # preload resolver for queries
4
+ Rails.application.config.to_prepare do
5
+ Types::QueryType.class_eval do
6
+ # Add corresponding preloads to query results
7
+ # Note: key is automatically calculated based on method name
8
+ # @param data (ActiveCollection)
9
+ # @param preload_config (Same as Field: field[:preload])
10
+ def resolve_preloads(data, preload_config)
11
+ node = find_node(caller[0][/`.*'/][1..-2])
12
+ return data unless node
13
+
14
+ # relay support (TODO: add support to skip when not using relay)
15
+ node = node.selections.first if %w[nodes edges].include?(node.selections.first.name)
16
+ GraphqlPreloadQueries::Extensions::Preload.resolve_preloads(data, node, preload_config)
17
+ end
18
+
19
+ private
20
+
21
+ def find_node(key)
22
+ main_node = context.query.document.definitions.first
23
+ main_node.selections.find { |node_i| node_i.name == key.to_s }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'rails', '~> 4'
6
+
7
+ # Specify your gem's dependencies in pub_sub_model_sync.gemspec
8
+ gemspec
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'rails', '~> 5'
6
+
7
+ # Specify your gem's dependencies in pub_sub_model_sync.gemspec
8
+ gemspec
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'rails', '~> 6'
6
+
7
+ # Specify your gem's dependencies in pub_sub_model_sync.gemspec
8
+ gemspec
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+ require_relative 'lib/graphql_preload_queries/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'graphql_preload_queries'
8
+ spec.version = GraphqlPreloadQueries::VERSION
9
+ spec.authors = ['owen2345']
10
+ spec.email = ['owenperedo@gmail.com']
11
+
12
+ spec.summary = 'Permit to avoid N+1 queries problem when using graphql queries'
13
+ spec.description = 'Permit to avoid N+1 queries problem when using graphql queries'
14
+ spec.homepage = 'https://github.com/owen2345/graphql_preload_queries'
15
+ spec.license = 'MIT'
16
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
17
+
18
+ # spec.metadata["allowed_push_host"] = ""
19
+
20
+ spec.metadata['homepage_uri'] = spec.homepage
21
+ spec.metadata['source_code_uri'] = spec.homepage
22
+ spec.metadata['changelog_uri'] = spec.homepage
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
27
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ end
29
+ spec.bindir = 'exe'
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ['lib']
32
+
33
+ spec.add_dependency 'graphql'
34
+ spec.add_dependency 'rails'
35
+ spec.add_development_dependency 'database_cleaner-active_record'
36
+ spec.add_development_dependency 'rspec-rails'
37
+ spec.add_development_dependency 'rubocop'
38
+ spec.add_development_dependency 'rubocop-rspec'
39
+ spec.add_development_dependency 'sqlite3'
40
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql_preload_queries/engine'
4
+ require 'graphql'
5
+ require 'graphql_preload_queries/extensions/preload'
6
+
7
+ module GraphqlPreloadQueries
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlPreloadQueries
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace GraphqlPreloadQueries
6
+ end
7
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: add generic resolver
4
+
5
+ module GraphqlPreloadQueries
6
+ module Extensions
7
+ class Preload < GraphQL::Schema::FieldExtension
8
+ # extension to add eager loading when a field was already processed
9
+ def resolve(object:, arguments:, **_rest)
10
+ klass = GraphqlPreloadQueries::Extensions::Preload
11
+ res = yield(object, arguments)
12
+ return res unless res
13
+
14
+ klass.resolve_preloads(res, arguments[:ast_node], (options || {}))
15
+ end
16
+
17
+ class << self
18
+ # Add all the corresponding preloads to the collection
19
+ # @param data (ActiveCollection)
20
+ # @return @data with preloads configured
21
+ # Sample: resolve_preloads(Category.all, { allPosts: :posts })
22
+ def resolve_preloads(data, query_node, preload_config)
23
+ apply_preloads(data, filter_preloads(query_node, preload_config))
24
+ end
25
+
26
+ def apply_preloads(collection, preloads)
27
+ collection.eager_load(preloads)
28
+ end
29
+
30
+ # find all configured preloads inside a node
31
+ def filter_preloads(node, preload_conf, root = nested_hash)
32
+ preload_conf.map do |key, sub_preload_conf|
33
+ filter_preload(node, key, sub_preload_conf, root)
34
+ end
35
+ root
36
+ end
37
+
38
+ private
39
+
40
+ # find preloads under a specific key
41
+ def filter_preload(node, key, preload_conf, root)
42
+ sub_node = node.selections.find do |node_i|
43
+ key.to_s.split('|').include?(node_i.name.to_s)
44
+ end
45
+
46
+ multiple_preload = preload_conf.is_a?(Array)
47
+ return unless sub_node
48
+ return add_preload_key(root, preload_conf, []) unless multiple_preload
49
+
50
+ child_root = nested_hash
51
+ filter_preloads(sub_node, preload_conf[1], child_root)
52
+ add_preload_key(root, preload_conf[0], child_root.presence || [])
53
+ end
54
+
55
+ # parse nested preload key and add it to the tree
56
+ # Sample: parent_preload: "categories.users"
57
+ # ==> { categories: { users: [res here] } }
58
+ def add_preload_key(root, key, value)
59
+ key_path = key.to_s.split('.').map(&:to_sym)
60
+ root.dig(*key_path)
61
+ *path, last = key_path
62
+ path.inject(root, :fetch)[last] = value
63
+ end
64
+
65
+ def nested_hash
66
+ Hash.new { |h, k| h[k] = {} }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlPreloadQueries
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphql_preload_queries
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - owen2345
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-11-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: graphql
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: database_cleaner-active_record
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sqlite3
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Permit to avoid N+1 queries problem when using graphql queries
112
+ email:
113
+ - owenperedo@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".generators"
119
+ - ".github/workflows/ruby.yml"
120
+ - ".gitignore"
121
+ - ".rspec"
122
+ - ".rubocop.yml"
123
+ - Gemfile
124
+ - Gemfile.lock
125
+ - MIT-LICENSE
126
+ - README.md
127
+ - Rakefile
128
+ - bin/rails
129
+ - config/initializers/add_mutation_resolver.rb
130
+ - config/initializers/add_preload_field.rb
131
+ - config/initializers/add_query_resolver.rb
132
+ - gemfiles/Gemfile_4
133
+ - gemfiles/Gemfile_5
134
+ - gemfiles/Gemfile_6
135
+ - graphql_preload_queries.gemspec
136
+ - lib/graphql_preload_queries.rb
137
+ - lib/graphql_preload_queries/engine.rb
138
+ - lib/graphql_preload_queries/extensions/preload.rb
139
+ - lib/graphql_preload_queries/version.rb
140
+ homepage: https://github.com/owen2345/graphql_preload_queries
141
+ licenses:
142
+ - MIT
143
+ metadata:
144
+ homepage_uri: https://github.com/owen2345/graphql_preload_queries
145
+ source_code_uri: https://github.com/owen2345/graphql_preload_queries
146
+ changelog_uri: https://github.com/owen2345/graphql_preload_queries
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: 2.4.0
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubygems_version: 3.0.8
163
+ signing_key:
164
+ specification_version: 4
165
+ summary: Permit to avoid N+1 queries problem when using graphql queries
166
+ test_files: []