insights-graphql-preload 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 92bc2cc866e8255995fa5ac242ae473834c0109cea9f862ae33d5723e3abc84c
4
+ data.tar.gz: f654c8c14ef3a8016a976a6f8cbdc25801f75440fc4e95ffe9b58c839d9afae2
5
+ SHA512:
6
+ metadata.gz: 7fc0795faca6a2583f9b7c186ddc3450e3401c29dab086a7831ac79f90797e34b4508215ad311803a77e8221cccff5fc7541070f143a48453f77263c5431b12b
7
+ data.tar.gz: b7e2dcb4a99730d080e08835ad51127f3121cc768ee9c4c7d986770b0539c984db1d31d69ce17c8915a38b8eac73812282dfee00b33d5da761f8f5bb76740fef
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .rspec_status
10
+ /gemfiles/*.lock
data/.travis.yml ADDED
@@ -0,0 +1,41 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ before_install: gem install bundler -v '~> 2.0'
5
+
6
+ jobs:
7
+ include:
8
+ - rvm: 2.7.0
9
+ gemfile: gemfiles/graphql_1.10.gemfile
10
+ env:
11
+ - GRAPHQL_RUBY_INTERPRETER=yes
12
+ - RAILS_VERSION=6.0
13
+ - rvm: 2.7.0
14
+ gemfile: gemfiles/graphql_1.10.gemfile
15
+ env:
16
+ - GRAPHQL_RUBY_INTERPRETER=no
17
+ - RAILS_VERSION=6.0
18
+ - rvm: 2.6.5
19
+ gemfile: gemfiles/graphql_1.9.gemfile
20
+ env:
21
+ - GRAPHQL_RUBY_INTERPRETER=yes
22
+ - RAILS_VERSION=6.0
23
+ - rvm: 2.6.5
24
+ gemfile: gemfiles/graphql_1.9.gemfile
25
+ env:
26
+ - GRAPHQL_RUBY_INTERPRETER=no
27
+ - RAILS_VERSION=6.0
28
+ - rvm: 2.5.7
29
+ gemfile: gemfiles/graphql_1.9.gemfile
30
+ env:
31
+ - GRAPHQL_RUBY_INTERPRETER=yes
32
+ - RAILS_VERSION=5.2
33
+ - rvm: 2.5.7
34
+ gemfile: gemfiles/graphql_1.9.gemfile
35
+ env:
36
+ - GRAPHQL_RUBY_INTERPRETER=no
37
+ - RAILS_VERSION=5.2
38
+ - rvm: 2.4.9
39
+ gemfile: gemfiles/graphql_1.8.gemfile
40
+ env:
41
+ - RAILS_VERSION=5.1
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ lib/**/*.rb - README.md
2
+ --embed-mixin DSL
data/Appraisals ADDED
@@ -0,0 +1,11 @@
1
+ appraise "graphql-1.8" do
2
+ gem "graphql", "~> 1.8.0"
3
+ end
4
+
5
+ appraise "graphql-1.9" do
6
+ gem "graphql", "~> 1.9.0"
7
+ end
8
+
9
+ appraise "graphql-1.10" do
10
+ gem "graphql", "~> 1.10.0"
11
+ end
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in graphql-preload.gemspec
4
+ gemspec
5
+
6
+ gem 'activerecord', ENV['RAILS_VERSION'] && "~> #{ENV['RAILS_VERSION']}.0"
7
+
8
+ # See https://github.com/nepalez/rspec-sqlimit/issues/13
9
+ gem 'rspec-sqlimit', git: 'https://github.com/nepalez/rspec-sqlimit', branch: 'master', ref: '90c85f4143ae584872a6ce1d240d77fa4f3ceb49' # v0.0.3
data/Gemfile.lock ADDED
@@ -0,0 +1,192 @@
1
+ GIT
2
+ remote: https://github.com/nepalez/rspec-sqlimit
3
+ revision: 90c85f4143ae584872a6ce1d240d77fa4f3ceb49
4
+ ref: 90c85f4143ae584872a6ce1d240d77fa4f3ceb49
5
+ branch: master
6
+ specs:
7
+ rspec-sqlimit (0.0.3)
8
+ rails (> 4.2, < 7.0)
9
+ rspec (~> 3.0)
10
+
11
+ PATH
12
+ remote: .
13
+ specs:
14
+ graphql-preload (2.1.0)
15
+ activerecord (>= 4.1, < 7)
16
+ graphql (>= 1.8, < 2)
17
+ graphql-batch (~> 0.3)
18
+ promise.rb (~> 0.7)
19
+
20
+ GEM
21
+ remote: https://rubygems.org/
22
+ specs:
23
+ actioncable (6.0.2.2)
24
+ actionpack (= 6.0.2.2)
25
+ nio4r (~> 2.0)
26
+ websocket-driver (>= 0.6.1)
27
+ actionmailbox (6.0.2.2)
28
+ actionpack (= 6.0.2.2)
29
+ activejob (= 6.0.2.2)
30
+ activerecord (= 6.0.2.2)
31
+ activestorage (= 6.0.2.2)
32
+ activesupport (= 6.0.2.2)
33
+ mail (>= 2.7.1)
34
+ actionmailer (6.0.2.2)
35
+ actionpack (= 6.0.2.2)
36
+ actionview (= 6.0.2.2)
37
+ activejob (= 6.0.2.2)
38
+ mail (~> 2.5, >= 2.5.4)
39
+ rails-dom-testing (~> 2.0)
40
+ actionpack (6.0.2.2)
41
+ actionview (= 6.0.2.2)
42
+ activesupport (= 6.0.2.2)
43
+ rack (~> 2.0, >= 2.0.8)
44
+ rack-test (>= 0.6.3)
45
+ rails-dom-testing (~> 2.0)
46
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
47
+ actiontext (6.0.2.2)
48
+ actionpack (= 6.0.2.2)
49
+ activerecord (= 6.0.2.2)
50
+ activestorage (= 6.0.2.2)
51
+ activesupport (= 6.0.2.2)
52
+ nokogiri (>= 1.8.5)
53
+ actionview (6.0.2.2)
54
+ activesupport (= 6.0.2.2)
55
+ builder (~> 3.1)
56
+ erubi (~> 1.4)
57
+ rails-dom-testing (~> 2.0)
58
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
59
+ activejob (6.0.2.2)
60
+ activesupport (= 6.0.2.2)
61
+ globalid (>= 0.3.6)
62
+ activemodel (6.0.2.2)
63
+ activesupport (= 6.0.2.2)
64
+ activerecord (6.0.2.2)
65
+ activemodel (= 6.0.2.2)
66
+ activesupport (= 6.0.2.2)
67
+ activestorage (6.0.2.2)
68
+ actionpack (= 6.0.2.2)
69
+ activejob (= 6.0.2.2)
70
+ activerecord (= 6.0.2.2)
71
+ marcel (~> 0.3.1)
72
+ activesupport (6.0.2.2)
73
+ concurrent-ruby (~> 1.0, >= 1.0.2)
74
+ i18n (>= 0.7, < 2)
75
+ minitest (~> 5.1)
76
+ tzinfo (~> 1.1)
77
+ zeitwerk (~> 2.2)
78
+ appraisal (2.2.0)
79
+ bundler
80
+ rake
81
+ thor (>= 0.14.0)
82
+ builder (3.2.4)
83
+ coderay (1.1.2)
84
+ concurrent-ruby (1.1.6)
85
+ crass (1.0.6)
86
+ diff-lcs (1.3)
87
+ erubi (1.9.0)
88
+ globalid (0.4.2)
89
+ activesupport (>= 4.2.0)
90
+ graphql (1.10.5)
91
+ graphql-batch (0.4.2)
92
+ graphql (>= 1.3, < 2)
93
+ promise.rb (~> 0.7.2)
94
+ i18n (1.8.2)
95
+ concurrent-ruby (~> 1.0)
96
+ loofah (2.4.0)
97
+ crass (~> 1.0.2)
98
+ nokogiri (>= 1.5.9)
99
+ mail (2.7.1)
100
+ mini_mime (>= 0.1.1)
101
+ marcel (0.3.3)
102
+ mimemagic (~> 0.3.2)
103
+ method_source (1.0.0)
104
+ mimemagic (0.3.4)
105
+ mini_mime (1.0.2)
106
+ mini_portile2 (2.4.0)
107
+ minitest (5.14.0)
108
+ nio4r (2.5.2)
109
+ nokogiri (1.10.9)
110
+ mini_portile2 (~> 2.4.0)
111
+ promise.rb (0.7.4)
112
+ pry (0.13.0)
113
+ coderay (~> 1.1)
114
+ method_source (~> 1.0)
115
+ rack (2.2.2)
116
+ rack-test (1.1.0)
117
+ rack (>= 1.0, < 3)
118
+ rails (6.0.2.2)
119
+ actioncable (= 6.0.2.2)
120
+ actionmailbox (= 6.0.2.2)
121
+ actionmailer (= 6.0.2.2)
122
+ actionpack (= 6.0.2.2)
123
+ actiontext (= 6.0.2.2)
124
+ actionview (= 6.0.2.2)
125
+ activejob (= 6.0.2.2)
126
+ activemodel (= 6.0.2.2)
127
+ activerecord (= 6.0.2.2)
128
+ activestorage (= 6.0.2.2)
129
+ activesupport (= 6.0.2.2)
130
+ bundler (>= 1.3.0)
131
+ railties (= 6.0.2.2)
132
+ sprockets-rails (>= 2.0.0)
133
+ rails-dom-testing (2.0.3)
134
+ activesupport (>= 4.2.0)
135
+ nokogiri (>= 1.6)
136
+ rails-html-sanitizer (1.3.0)
137
+ loofah (~> 2.3)
138
+ railties (6.0.2.2)
139
+ actionpack (= 6.0.2.2)
140
+ activesupport (= 6.0.2.2)
141
+ method_source
142
+ rake (>= 0.8.7)
143
+ thor (>= 0.20.3, < 2.0)
144
+ rake (13.0.1)
145
+ rspec (3.9.0)
146
+ rspec-core (~> 3.9.0)
147
+ rspec-expectations (~> 3.9.0)
148
+ rspec-mocks (~> 3.9.0)
149
+ rspec-core (3.9.1)
150
+ rspec-support (~> 3.9.1)
151
+ rspec-expectations (3.9.1)
152
+ diff-lcs (>= 1.2.0, < 2.0)
153
+ rspec-support (~> 3.9.0)
154
+ rspec-mocks (3.9.1)
155
+ diff-lcs (>= 1.2.0, < 2.0)
156
+ rspec-support (~> 3.9.0)
157
+ rspec-support (3.9.2)
158
+ sprockets (4.0.0)
159
+ concurrent-ruby (~> 1.0)
160
+ rack (> 1, < 3)
161
+ sprockets-rails (3.2.1)
162
+ actionpack (>= 4.0)
163
+ activesupport (>= 4.0)
164
+ sprockets (>= 3.0.0)
165
+ sqlite3 (1.4.2)
166
+ thor (1.0.1)
167
+ thread_safe (0.3.6)
168
+ tzinfo (1.2.6)
169
+ thread_safe (~> 0.1)
170
+ websocket-driver (0.7.1)
171
+ websocket-extensions (>= 0.1.0)
172
+ websocket-extensions (0.1.4)
173
+ yard (0.9.24)
174
+ zeitwerk (2.3.0)
175
+
176
+ PLATFORMS
177
+ ruby
178
+
179
+ DEPENDENCIES
180
+ activerecord
181
+ appraisal
182
+ bundler (~> 2.0)
183
+ graphql-preload!
184
+ pry (~> 0.10)
185
+ rake (~> 13.0)
186
+ rspec (~> 3.8)
187
+ rspec-sqlimit!
188
+ sqlite3
189
+ yard (~> 0.9)
190
+
191
+ BUNDLED WITH
192
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Grand Rounds
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # GraphQL::Preload
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/graphql-preload.svg)](https://rubygems.org/gems/graphql-preload)
4
+
5
+ Provides a DSL for the [`graphql` gem](https://github.com/rmosolgo/graphql-ruby) that allows ActiveRecord associations to be preloaded in field definitions. Based on a [gist](https://gist.github.com/theorygeek/a1a59a2bf9c59e4b3706ac68d12c8434) by @theorygeek.
6
+
7
+ This fork works with Ruby on Rails 6.0 and GraphQL-Ruby 1.10 (but not in interpreter mode yet).
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'graphql-preload'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install graphql-preload
24
+
25
+ ## Usage
26
+
27
+ First, enable preloading in your `GraphQL::Schema`:
28
+
29
+ ```ruby
30
+ class PreloadSchema < GraphQL::Schema
31
+ use GraphQL::Batch
32
+
33
+ enable_preloading
34
+
35
+ query QueryType
36
+ end
37
+ ```
38
+
39
+ Call `preload` when defining your field:
40
+
41
+ ```ruby
42
+ class PostType < GraphQL::Schema::Object
43
+ # This runs Post.includes(:comments) but only if comments are requested
44
+ field :comments, [CommentType], null: false, preload: :comments
45
+
46
+ # Block syntax is supported too
47
+ field :comments, [CommentType], null: false
48
+ preload :comments
49
+
50
+ # Post.includes(:comments, :authors)
51
+ preload [:comments, :authors]
52
+
53
+ # Post.includes(:comments, authors: [:followers, :posts])
54
+ preload [:comments, { authors: [:followers, :posts] }]
55
+ end
56
+ end
57
+ ```
58
+
59
+ ### `preload_scope`
60
+ 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).
61
+
62
+ This functionality is surfaced through the `preload_scope` option:
63
+
64
+ ```ruby
65
+ class UserType < GraphQL::Schema::Object
66
+ field :posts, [PostType], null: false,
67
+ preload: :posts,
68
+ preload_scope: ->(*) { Post.order(rating: :desc) }
69
+ end
70
+ ```
71
+
72
+ ## Development
73
+
74
+ 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.
75
+
76
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
77
+
78
+ ## Contributing
79
+
80
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ConsultingMD/graphql-preload.
81
+
82
+ ## License
83
+
84
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'graphql/preload'
5
+ require 'pry'
6
+
7
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_RETRY: "1"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rspec-sqlimit", git: "https://github.com/nepalez/rspec-sqlimit", branch: "master", ref: "90c85f4143ae584872a6ce1d240d77fa4f3ceb49"
6
+ gem "graphql", "~> 1.10.0"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rspec-sqlimit", git: "https://github.com/nepalez/rspec-sqlimit", branch: "master", ref: "90c85f4143ae584872a6ce1d240d77fa4f3ceb49"
6
+ gem "graphql", "~> 1.8.0"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rspec-sqlimit", git: "https://github.com/nepalez/rspec-sqlimit", branch: "master", ref: "90c85f4143ae584872a6ce1d240d77fa4f3ceb49"
6
+ gem "graphql", "~> 1.9.0"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'graphql/preload/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'insights-graphql-preload'
9
+ spec.version = GraphQL::Preload::VERSION
10
+ spec.authors = ['Ryan Foster, Etienne Tripier']
11
+ spec.email = ['etienne.tripier@grandrounds.com']
12
+
13
+ spec.summary = 'Preload ActiveRecord associations with graphql-batch'
14
+ spec.homepage = 'https://github.com/ConsultingMD/graphql-preload'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_runtime_dependency 'activerecord', '>= 4.1', '< 7'
25
+ spec.add_runtime_dependency 'graphql', '>= 1.8', '< 2'
26
+ spec.add_runtime_dependency 'graphql-batch', '~> 0.3'
27
+ spec.add_runtime_dependency 'promise.rb', '~> 0.7'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 2.0'
30
+ spec.add_development_dependency 'pry', '~> 0.10'
31
+ spec.add_development_dependency 'rake', '~> 13.0'
32
+ spec.add_development_dependency 'rspec', '~> 3.8'
33
+ spec.add_development_dependency 'rspec-sqlimit'
34
+ spec.add_development_dependency 'sqlite3'
35
+ spec.add_development_dependency 'yard', '~> 0.9'
36
+ spec.add_development_dependency "appraisal"
37
+ end
@@ -0,0 +1,59 @@
1
+ require 'graphql'
2
+ require 'graphql/batch'
3
+ require 'promise.rb'
4
+
5
+ GraphQL::Field.accepts_definitions(
6
+ preload: ->(type, *args) do
7
+ type.metadata[:preload] ||= []
8
+ type.metadata[:preload].concat(args)
9
+ end,
10
+ preload_scope: ->(type, arg) { type.metadata[:preload_scope] = arg }
11
+ )
12
+
13
+ GraphQL::Schema.accepts_definitions(
14
+ enable_preloading: ->(schema) do
15
+ schema.instrument(:field, GraphQL::Preload::Instrument.new)
16
+ end
17
+ )
18
+
19
+ module GraphQL
20
+ # Provides a GraphQL::Field definition to preload ActiveRecord::Associations
21
+ module Preload
22
+ autoload :Instrument, 'graphql/preload/instrument'
23
+ autoload :Loader, 'graphql/preload/loader'
24
+ autoload :VERSION, 'graphql/preload/version'
25
+
26
+ module SchemaMethods
27
+ def enable_preloading
28
+ instrument(:field, GraphQL::Preload::Instrument.new)
29
+ end
30
+ end
31
+
32
+ module FieldMetadata
33
+ def initialize(*args, preload: nil, preload_scope: nil, **kwargs, &block)
34
+ super(*args, **kwargs, &block)
35
+ self.preload(preload) if preload
36
+ self.preload_scope(preload_scope) if preload_scope
37
+ end
38
+
39
+ def preload(associations)
40
+ @preload ||= []
41
+ @preload.concat Array.wrap associations
42
+ end
43
+
44
+ def preload_scope(scope_proc)
45
+ @preload_scope = scope_proc
46
+ end
47
+
48
+ def to_graphql
49
+ field_defn = super
50
+ field_defn.metadata[:preload] = @preload
51
+ field_defn.metadata[:preload_scope] = @preload_scope
52
+ field_defn
53
+ end
54
+ end
55
+ end
56
+
57
+ Schema.extend Preload::SchemaMethods
58
+ Schema::Field.prepend Preload::FieldMetadata
59
+ end
@@ -0,0 +1,98 @@
1
+ module GraphQL
2
+ module Preload
3
+ # Provides an instrument for the GraphQL::Field :preload definition
4
+ class Instrument
5
+ def instrument(_type, field)
6
+ metadata = merged_metadata(field)
7
+ return field if metadata.fetch(:preload, nil).nil?
8
+
9
+ old_resolver = field.resolve_proc
10
+ new_resolver = ->(obj, args, ctx) do
11
+ return old_resolver.call(obj, args, ctx) unless obj
12
+
13
+ if metadata[:preload_scope]
14
+ scope = metadata[:preload_scope].call(args, ctx)
15
+ end
16
+
17
+ is_graphql_object = obj.is_a?(GraphQL::Schema::Object)
18
+ respond_to_object = obj.respond_to?(:object)
19
+ record = is_graphql_object && respond_to_object ? obj.object : obj
20
+
21
+ preload(record, metadata[:preload], scope).then do
22
+ old_resolver.call(obj, args, ctx)
23
+ end
24
+ end
25
+
26
+ field.redefine do
27
+ resolve(new_resolver)
28
+ end
29
+ end
30
+
31
+ private def preload(record, associations, scope)
32
+ if associations.is_a?(String)
33
+ raise TypeError, "Expected #{associations} to be a Symbol, not a String"
34
+ elsif associations.is_a?(Symbol)
35
+ return preload_single_association(record, associations, scope)
36
+ end
37
+
38
+ promises = []
39
+
40
+ Array.wrap(associations).each do |association|
41
+ case association
42
+ when Symbol
43
+ promises << preload_single_association(record, association, scope)
44
+ when Array
45
+ association.each do |sub_association|
46
+ promises << preload(record, sub_association, scope)
47
+ end
48
+ when Hash
49
+ association.each do |sub_association, nested_association|
50
+ promises << preload_single_association(record, sub_association, scope).then do
51
+ associated_records = record.public_send(sub_association)
52
+
53
+ case associated_records
54
+ when ActiveRecord::Base
55
+ preload(associated_records, nested_association, scope)
56
+ else
57
+ Promise.all(
58
+ Array.wrap(associated_records).map do |associated_record|
59
+ preload(associated_record, nested_association, scope)
60
+ end
61
+ )
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ Promise.all(promises)
69
+ end
70
+
71
+ private def preload_single_association(record, association, scope)
72
+ # We would like to pass the `scope` (which is an `ActiveRecord::Relation`),
73
+ # directly into `Loader.for`. However, because the scope is
74
+ # created for each parent record, they are different objects and
75
+ # return different loaders, breaking batching.
76
+ # Therefore, we pass in `scope.to_sql`, which is the same for all the
77
+ # scopes and set the `scope` using an accessor. The actual scope
78
+ # object used will be the last one, which shouldn't make any difference,
79
+ # because even though they are different objects, they are all
80
+ # functionally equivalent.
81
+ loader = GraphQL::Preload::Loader.for(record.class, association, scope.try(:to_sql))
82
+ loader.scope = scope
83
+ loader.load(record)
84
+ end
85
+
86
+ private def merged_metadata(field)
87
+ type_class = field.metadata.fetch(:type_class, nil)
88
+
89
+ if type_class.nil? || !type_class.respond_to?(:to_graphql)
90
+ field.metadata
91
+ else
92
+ field.metadata.merge(type_class.to_graphql.metadata)
93
+ end
94
+ end
95
+
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,76 @@
1
+ module GraphQL
2
+ module Preload
3
+ # Preloads ActiveRecord::Associations when called from the Preload::Instrument
4
+ class Loader < GraphQL::Batch::Loader
5
+ attr_accessor :scope
6
+ attr_reader :association, :model
7
+
8
+ def cache_key(record)
9
+ record.object_id
10
+ end
11
+
12
+ def initialize(model, association, _scope_sql)
13
+ @association = association
14
+ @model = model
15
+
16
+ validate_association
17
+ end
18
+
19
+ def load(record)
20
+ unless record.is_a?(model)
21
+ raise TypeError, "Loader for #{model} can't load associations for #{record.class} objects"
22
+ end
23
+
24
+ return Promise.resolve(record) if association_loaded?(record)
25
+ super
26
+ end
27
+
28
+ def perform(records)
29
+ preload_association(records)
30
+ records.each { |record| fulfill(record, record) }
31
+ end
32
+
33
+ private def association_loaded?(record)
34
+ record.association(association).loaded?
35
+ end
36
+
37
+ private def preload_association(records)
38
+ preloader = ActiveRecord::Associations::Preloader.new.preload(records, association, preload_scope).first
39
+ return unless preload_scope
40
+ return if Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new("6.0.0")
41
+
42
+ if preloader.is_a?(::ActiveRecord::Associations::Preloader::AlreadyLoaded)
43
+ raise ArgumentError,
44
+ "Preloading association twice with scopes is not possible. " \
45
+ "To resolve this problem add a scoped association (e.g., `has_many :records, -> { scope_name }, ...`) to the model"
46
+ end
47
+
48
+ # this commit changes the way preloader works with scopes
49
+ # https://github.com/rails/rails/commit/2847653869ffc1ff5139c46e520c72e26618c199#diff-3bba5f66eb1ed62bd5700872fcd6c632
50
+ preloader.send(:owners).each do |owner|
51
+ preloader.send(:associate_records_to_owner, owner, preloader.records_by_owner[owner] || [])
52
+ end
53
+ end
54
+
55
+ private def preload_scope
56
+ return nil unless scope
57
+ reflection = model.reflect_on_association(association)
58
+ raise ArgumentError, 'Cannot specify preload_scope for polymorphic associations' if reflection.polymorphic?
59
+ scope if scope.try(:klass) == reflection.klass
60
+ end
61
+
62
+ private def validate_association
63
+ unless association.is_a?(Symbol)
64
+ raise ArgumentError, 'Association must be a Symbol object'
65
+ end
66
+
67
+ unless model < ActiveRecord::Base
68
+ raise ArgumentError, 'Model must be an ActiveRecord::Base descendant'
69
+ end
70
+
71
+ return if model.reflect_on_association(association)
72
+ raise TypeError, "Association :#{association} does not exist on #{model}"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,5 @@
1
+ module GraphQL
2
+ module Preload
3
+ VERSION = '2.1.0'.freeze
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,243 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: insights-graphql-preload
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Foster, Etienne Tripier
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-06-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.1'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.1'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '7'
33
+ - !ruby/object:Gem::Dependency
34
+ name: graphql
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '1.8'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '2'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '1.8'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '2'
53
+ - !ruby/object:Gem::Dependency
54
+ name: graphql-batch
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '0.3'
60
+ type: :runtime
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '0.3'
67
+ - !ruby/object:Gem::Dependency
68
+ name: promise.rb
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '0.7'
74
+ type: :runtime
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '0.7'
81
+ - !ruby/object:Gem::Dependency
82
+ name: bundler
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '2.0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '2.0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: pry
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '0.10'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '0.10'
109
+ - !ruby/object:Gem::Dependency
110
+ name: rake
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '13.0'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '13.0'
123
+ - !ruby/object:Gem::Dependency
124
+ name: rspec
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '3.8'
130
+ type: :development
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: '3.8'
137
+ - !ruby/object:Gem::Dependency
138
+ name: rspec-sqlimit
139
+ requirement: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ type: :development
145
+ prerelease: false
146
+ version_requirements: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ - !ruby/object:Gem::Dependency
152
+ name: sqlite3
153
+ requirement: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ type: :development
159
+ prerelease: false
160
+ version_requirements: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ - !ruby/object:Gem::Dependency
166
+ name: yard
167
+ requirement: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - "~>"
170
+ - !ruby/object:Gem::Version
171
+ version: '0.9'
172
+ type: :development
173
+ prerelease: false
174
+ version_requirements: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - "~>"
177
+ - !ruby/object:Gem::Version
178
+ version: '0.9'
179
+ - !ruby/object:Gem::Dependency
180
+ name: appraisal
181
+ requirement: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ type: :development
187
+ prerelease: false
188
+ version_requirements: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ description:
194
+ email:
195
+ - etienne.tripier@grandrounds.com
196
+ executables: []
197
+ extensions: []
198
+ extra_rdoc_files: []
199
+ files:
200
+ - ".gitignore"
201
+ - ".travis.yml"
202
+ - ".yardopts"
203
+ - Appraisals
204
+ - Gemfile
205
+ - Gemfile.lock
206
+ - LICENSE.txt
207
+ - README.md
208
+ - Rakefile
209
+ - bin/console
210
+ - bin/setup
211
+ - gemfiles/.bundle/config
212
+ - gemfiles/graphql_1.10.gemfile
213
+ - gemfiles/graphql_1.8.gemfile
214
+ - gemfiles/graphql_1.9.gemfile
215
+ - graphql-preload.gemspec
216
+ - lib/graphql/preload.rb
217
+ - lib/graphql/preload/instrument.rb
218
+ - lib/graphql/preload/loader.rb
219
+ - lib/graphql/preload/version.rb
220
+ homepage: https://github.com/ConsultingMD/graphql-preload
221
+ licenses:
222
+ - MIT
223
+ metadata: {}
224
+ post_install_message:
225
+ rdoc_options: []
226
+ require_paths:
227
+ - lib
228
+ required_ruby_version: !ruby/object:Gem::Requirement
229
+ requirements:
230
+ - - ">="
231
+ - !ruby/object:Gem::Version
232
+ version: '0'
233
+ required_rubygems_version: !ruby/object:Gem::Requirement
234
+ requirements:
235
+ - - ">="
236
+ - !ruby/object:Gem::Version
237
+ version: '0'
238
+ requirements: []
239
+ rubygems_version: 3.0.3
240
+ signing_key:
241
+ specification_version: 4
242
+ summary: Preload ActiveRecord associations with graphql-batch
243
+ test_files: []