graphql-eager_load 0.2.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a51028f140032e8b436a06fa19ad0d5fe1ad5e16ac2f0881acabaa3738e287bc
4
+ data.tar.gz: cb1cf7507e675e881699e679d6a9f248ce1a7bcd0697e6f1d90020325b1482cd
5
+ SHA512:
6
+ metadata.gz: 29058b322738f586de82e832722625fa5c1c09ce7716cc2377cfe30b1a7e0dbf21655221d9977135f683938241a1b2141632d6ded7affe4dd86e82c1db1c6ee0
7
+ data.tar.gz: 1ca0c090115b58c18bd01826c20f93c6019e53bba375ff970b3b8406223b16c2b2b4a3b57b1314620fbc099d692784924bc154dfd75a3f114c265cdc294b758a
@@ -0,0 +1,34 @@
1
+ on: [push]
2
+
3
+ name: Test
4
+
5
+ jobs:
6
+ run:
7
+ runs-on: ubuntu-latest
8
+
9
+ steps:
10
+ - name: Checkout
11
+ uses: actions/checkout@master
12
+
13
+ - name: Set up Ruby
14
+ uses: actions/setup-ruby@v1
15
+ with:
16
+ ruby-version: '2.7.x'
17
+ bundler-cache: true
18
+
19
+ - name: Cache bundled gems
20
+ uses: actions/cache@v2
21
+ with:
22
+ path: vendor/bundle
23
+ key: ${{ runner.os }}-gems-${{ hashFiles('Gemfile.lock') }}
24
+ restore-keys: |
25
+ ${{ runner.os }}-gems-
26
+
27
+ - name: Install Ruby gems
28
+ run: |
29
+ bundle config path vendor/bundle
30
+ bundle install
31
+
32
+ - name: Run Rspec
33
+ run: |
34
+ bundle exec rspec
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ spec/internal/db/database.sqlite3
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,11 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rspec
4
+
5
+ AllCops:
6
+ Exclude:
7
+ - bin/*
8
+
9
+ Metrics/BlockLength:
10
+ Exclude:
11
+ - spec/**/*
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.1
6
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in graphql-eager-load.gemspec
6
+ gemspec
7
+
8
+ gem 'graphql'
9
+
10
+ group :test, :development do
11
+ gem 'combustion'
12
+ gem 'rails'
13
+ gem 'rake'
14
+ gem 'rubocop'
15
+ gem 'rubocop-performance'
16
+ gem 'rubocop-rspec'
17
+ end
18
+
19
+ group :test do
20
+ gem 'rspec'
21
+ gem 'rspec-rails'
22
+ gem 'sqlite3'
23
+ end
@@ -0,0 +1,202 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ graphql-eager_load (0.2.2)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ actioncable (6.0.3.2)
10
+ actionpack (= 6.0.3.2)
11
+ nio4r (~> 2.0)
12
+ websocket-driver (>= 0.6.1)
13
+ actionmailbox (6.0.3.2)
14
+ actionpack (= 6.0.3.2)
15
+ activejob (= 6.0.3.2)
16
+ activerecord (= 6.0.3.2)
17
+ activestorage (= 6.0.3.2)
18
+ activesupport (= 6.0.3.2)
19
+ mail (>= 2.7.1)
20
+ actionmailer (6.0.3.2)
21
+ actionpack (= 6.0.3.2)
22
+ actionview (= 6.0.3.2)
23
+ activejob (= 6.0.3.2)
24
+ mail (~> 2.5, >= 2.5.4)
25
+ rails-dom-testing (~> 2.0)
26
+ actionpack (6.0.3.2)
27
+ actionview (= 6.0.3.2)
28
+ activesupport (= 6.0.3.2)
29
+ rack (~> 2.0, >= 2.0.8)
30
+ rack-test (>= 0.6.3)
31
+ rails-dom-testing (~> 2.0)
32
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
33
+ actiontext (6.0.3.2)
34
+ actionpack (= 6.0.3.2)
35
+ activerecord (= 6.0.3.2)
36
+ activestorage (= 6.0.3.2)
37
+ activesupport (= 6.0.3.2)
38
+ nokogiri (>= 1.8.5)
39
+ actionview (6.0.3.2)
40
+ activesupport (= 6.0.3.2)
41
+ builder (~> 3.1)
42
+ erubi (~> 1.4)
43
+ rails-dom-testing (~> 2.0)
44
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
45
+ activejob (6.0.3.2)
46
+ activesupport (= 6.0.3.2)
47
+ globalid (>= 0.3.6)
48
+ activemodel (6.0.3.2)
49
+ activesupport (= 6.0.3.2)
50
+ activerecord (6.0.3.2)
51
+ activemodel (= 6.0.3.2)
52
+ activesupport (= 6.0.3.2)
53
+ activestorage (6.0.3.2)
54
+ actionpack (= 6.0.3.2)
55
+ activejob (= 6.0.3.2)
56
+ activerecord (= 6.0.3.2)
57
+ marcel (~> 0.3.1)
58
+ activesupport (6.0.3.2)
59
+ concurrent-ruby (~> 1.0, >= 1.0.2)
60
+ i18n (>= 0.7, < 2)
61
+ minitest (~> 5.1)
62
+ tzinfo (~> 1.1)
63
+ zeitwerk (~> 2.2, >= 2.2.2)
64
+ ast (2.4.1)
65
+ builder (3.2.4)
66
+ combustion (1.3.0)
67
+ activesupport (>= 3.0.0)
68
+ railties (>= 3.0.0)
69
+ thor (>= 0.14.6)
70
+ concurrent-ruby (1.1.6)
71
+ crass (1.0.6)
72
+ diff-lcs (1.4.4)
73
+ erubi (1.9.0)
74
+ globalid (0.4.2)
75
+ activesupport (>= 4.2.0)
76
+ graphql (1.11.1)
77
+ i18n (1.8.5)
78
+ concurrent-ruby (~> 1.0)
79
+ loofah (2.6.0)
80
+ crass (~> 1.0.2)
81
+ nokogiri (>= 1.5.9)
82
+ mail (2.7.1)
83
+ mini_mime (>= 0.1.1)
84
+ marcel (0.3.3)
85
+ mimemagic (~> 0.3.2)
86
+ method_source (1.0.0)
87
+ mimemagic (0.3.5)
88
+ mini_mime (1.0.2)
89
+ mini_portile2 (2.4.0)
90
+ minitest (5.14.1)
91
+ nio4r (2.5.2)
92
+ nokogiri (1.10.10)
93
+ mini_portile2 (~> 2.4.0)
94
+ parallel (1.19.2)
95
+ parser (2.7.1.4)
96
+ ast (~> 2.4.1)
97
+ rack (2.2.3)
98
+ rack-test (1.1.0)
99
+ rack (>= 1.0, < 3)
100
+ rails (6.0.3.2)
101
+ actioncable (= 6.0.3.2)
102
+ actionmailbox (= 6.0.3.2)
103
+ actionmailer (= 6.0.3.2)
104
+ actionpack (= 6.0.3.2)
105
+ actiontext (= 6.0.3.2)
106
+ actionview (= 6.0.3.2)
107
+ activejob (= 6.0.3.2)
108
+ activemodel (= 6.0.3.2)
109
+ activerecord (= 6.0.3.2)
110
+ activestorage (= 6.0.3.2)
111
+ activesupport (= 6.0.3.2)
112
+ bundler (>= 1.3.0)
113
+ railties (= 6.0.3.2)
114
+ sprockets-rails (>= 2.0.0)
115
+ rails-dom-testing (2.0.3)
116
+ activesupport (>= 4.2.0)
117
+ nokogiri (>= 1.6)
118
+ rails-html-sanitizer (1.3.0)
119
+ loofah (~> 2.3)
120
+ railties (6.0.3.2)
121
+ actionpack (= 6.0.3.2)
122
+ activesupport (= 6.0.3.2)
123
+ method_source
124
+ rake (>= 0.8.7)
125
+ thor (>= 0.20.3, < 2.0)
126
+ rainbow (3.0.0)
127
+ rake (12.3.3)
128
+ regexp_parser (1.7.1)
129
+ rexml (3.2.4)
130
+ rspec (3.9.0)
131
+ rspec-core (~> 3.9.0)
132
+ rspec-expectations (~> 3.9.0)
133
+ rspec-mocks (~> 3.9.0)
134
+ rspec-core (3.9.2)
135
+ rspec-support (~> 3.9.3)
136
+ rspec-expectations (3.9.2)
137
+ diff-lcs (>= 1.2.0, < 2.0)
138
+ rspec-support (~> 3.9.0)
139
+ rspec-mocks (3.9.1)
140
+ diff-lcs (>= 1.2.0, < 2.0)
141
+ rspec-support (~> 3.9.0)
142
+ rspec-rails (4.0.1)
143
+ actionpack (>= 4.2)
144
+ activesupport (>= 4.2)
145
+ railties (>= 4.2)
146
+ rspec-core (~> 3.9)
147
+ rspec-expectations (~> 3.9)
148
+ rspec-mocks (~> 3.9)
149
+ rspec-support (~> 3.9)
150
+ rspec-support (3.9.3)
151
+ rubocop (0.88.0)
152
+ parallel (~> 1.10)
153
+ parser (>= 2.7.1.1)
154
+ rainbow (>= 2.2.2, < 4.0)
155
+ regexp_parser (>= 1.7)
156
+ rexml
157
+ rubocop-ast (>= 0.1.0, < 1.0)
158
+ ruby-progressbar (~> 1.7)
159
+ unicode-display_width (>= 1.4.0, < 2.0)
160
+ rubocop-ast (0.2.0)
161
+ parser (>= 2.7.0.1)
162
+ rubocop-performance (1.7.1)
163
+ rubocop (>= 0.82.0)
164
+ rubocop-rspec (1.42.0)
165
+ rubocop (>= 0.87.0)
166
+ ruby-progressbar (1.10.1)
167
+ sprockets (4.0.2)
168
+ concurrent-ruby (~> 1.0)
169
+ rack (> 1, < 3)
170
+ sprockets-rails (3.2.1)
171
+ actionpack (>= 4.0)
172
+ activesupport (>= 4.0)
173
+ sprockets (>= 3.0.0)
174
+ sqlite3 (1.4.2)
175
+ thor (1.0.1)
176
+ thread_safe (0.3.6)
177
+ tzinfo (1.2.7)
178
+ thread_safe (~> 0.1)
179
+ unicode-display_width (1.7.0)
180
+ websocket-driver (0.7.3)
181
+ websocket-extensions (>= 0.1.0)
182
+ websocket-extensions (0.1.5)
183
+ zeitwerk (2.4.0)
184
+
185
+ PLATFORMS
186
+ ruby
187
+
188
+ DEPENDENCIES
189
+ combustion
190
+ graphql
191
+ graphql-eager_load!
192
+ rails
193
+ rake
194
+ rspec
195
+ rspec-rails
196
+ rubocop
197
+ rubocop-performance
198
+ rubocop-rspec
199
+ sqlite3
200
+
201
+ BUNDLED WITH
202
+ 2.1.4
@@ -0,0 +1 @@
1
+ Copyright (c) 2020 HOVER Inc.
@@ -0,0 +1,55 @@
1
+ # Graphql::EagerLoad
2
+
3
+ This gem traverses your graphql query looking for fields on types that match
4
+ associations on those types. For each field found to be an association, this
5
+ gem adds those associations to an [ActiveRecord::QueryMethods#includes](https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-includes) hash. That hash can then be used to
6
+ eager load the associations of records returned in your graphql resolver.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'graphql-eager_load'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle install
19
+
20
+ ## Usage
21
+
22
+ ```ruby
23
+ module Resolvers
24
+ class Users < Resolvers::Base
25
+ eager_load_model ::User
26
+
27
+ type Types::User.connection_type, null: false
28
+
29
+ def resolve
30
+ User.active.includes(associations_to_eager_load)
31
+ end
32
+ end
33
+ end
34
+ ```
35
+
36
+ The `.eager_load_model` and `#associations_to_eager_load` methods are provided by this gem.
37
+
38
+ ## Development
39
+
40
+ After checking out the repo, run `bundle` to install dependencies. Then, run `bundle exec rake spec` to run the tests. You can also run `bundle exec bin/console` for an interactive prompt that will allow you to experiment.
41
+
42
+ To release a new version:
43
+
44
+ - Update the version number in `version.rb`
45
+ - Make a PR with your changes and the version number increment
46
+ - After the PR is merged, make the new release https://github.com/hoverinc/graphql-eager-load/releases
47
+
48
+ ## Contributing
49
+
50
+ Bug reports and pull requests are welcome on GitHub at https://github.com/hoverinc/graphql-eager-load.
51
+
52
+
53
+ ## License
54
+
55
+ MIT
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'graphql/eager_load'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+
6
+ Bundler.require :default, :development
7
+
8
+ Combustion.initialize! :all
9
+ run Combustion::Application
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/graphql/eager_load/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'graphql-eager_load'
7
+ spec.version = Graphql::EagerLoad::VERSION
8
+ spec.authors = ['Elise Wood']
9
+ spec.email = ['elise@hover.to']
10
+
11
+ spec.summary = 'Generate ActiveRecord eager loading options from GraphQL Queries.'
12
+ spec.description = ''
13
+ spec.homepage = 'https://github.com/hoverinc/graphql-eager-load'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
16
+
17
+ # spec.metadata['allowed_push_host'] = 'github.com/hoverinc/graphql-eager-load'
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/hoverinc/graphql-eager-load'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/hoverinc/graphql-eager-load/commits'
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
+ end
28
+ spec.bindir = 'exe'
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ['lib']
31
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+ require 'graphql/eager_load/version'
5
+ require 'graphql/eager_load/builder'
6
+ require 'graphql/eager_load/resolver'
7
+
8
+ GraphQL::Schema::Resolver.include Graphql::EagerLoad::Resolver
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Graphql
4
+ module EagerLoad
5
+ #
6
+ # Given
7
+ # - An initial set of includes, a hash
8
+ # - GraphQL query selections
9
+ # - The model corresponding to the type returned by a GraphQL resolver
10
+ #
11
+ # This module will iterate and recurse into the selctions to find out if
12
+ # any of the fields requested for a given type corresponds to an
13
+ # association on the ActiveRecord model associated with the field's type.
14
+ #
15
+ #
16
+ # For example given this query
17
+ #
18
+ # salesOpportunities() {
19
+ # soldEstimateGroup {
20
+ # estimates {
21
+ # lineItems
22
+ # }
23
+ # }
24
+ # }
25
+ #
26
+ # We'd return this "includes" hash
27
+ #
28
+ # {sold_estimate_group: {estimates: {line_items: {}}}}
29
+ #
30
+ # And our resolver will use it like `scope.includes(includes)` to
31
+ # prevent N+1s
32
+ #
33
+ class Builder
34
+ def self.call(selections:, model:)
35
+ selections.each.with_object({}) do |selection, includes|
36
+ builder = new(selection: selection, model: model)
37
+
38
+ if builder.association?
39
+ hash = builder.includes
40
+ hash[:blob] ||= {} if builder.active_storage_attachment?
41
+ includes[builder.association_name] = hash
42
+ else
43
+ includes.merge!(builder.includes)
44
+ end
45
+ end
46
+ end
47
+
48
+ def initialize(selection:, model:)
49
+ @selection = selection
50
+ @model = model
51
+ end
52
+
53
+ def includes
54
+ self.class.call(selections: selection.selections, model: includes_model)
55
+ end
56
+
57
+ def association?
58
+ association.present?
59
+ end
60
+
61
+ def association_name
62
+ association.name
63
+ end
64
+
65
+ def active_storage_attachment?
66
+ model.reflect_on_attachment(field_name).present?
67
+ end
68
+
69
+ private
70
+
71
+ attr_reader :selection, :model
72
+
73
+ def includes_model
74
+ return ActiveStorage::Attachment if active_storage_attachment?
75
+
76
+ association&.klass || model
77
+ end
78
+
79
+ def association
80
+ return if use_custom_method?
81
+ return model.reflect_on_association("#{field_name}_attachment") if active_storage_attachment?
82
+
83
+ model.reflect_on_association(field_name)
84
+ end
85
+
86
+ def use_custom_method?
87
+ custom_method_defined? && !ignore_custom_method?
88
+ end
89
+
90
+ def ignore_custom_method?
91
+ return false unless field_owner.respond_to?(:allow_include_builder_fields)
92
+
93
+ field_owner.allow_include_builder_fields&.include?(field_name.to_sym)
94
+ end
95
+
96
+ def custom_method_defined?
97
+ field_owner.instance_methods.include?(field_name)
98
+ end
99
+
100
+ def field_owner
101
+ selection.field.owner
102
+ end
103
+
104
+ def field_name
105
+ selection.name
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Graphql
4
+ module EagerLoad
5
+ # Automatically included in any subclasses of Resolvers::Base
6
+ module Resolver
7
+ def self.included(klass)
8
+ klass.extend(ClassMethods)
9
+ end
10
+
11
+ def associations_to_include
12
+ graphql_eager_load_options(model: self.class.class_variable_get(:@@eager_load_model))
13
+ end
14
+
15
+ def graphql_eager_load_options(selections: context.query.lookahead.selections, model:)
16
+ Builder.call(selections: selections, model: model)
17
+ end
18
+
19
+ # specify the ActiveRecord model that corresponds to the top level type
20
+ # in the graphql query for this resolver.
21
+ module ClassMethods
22
+ def eager_load_model(model)
23
+ # rubocop:disable Style/ClassVars
24
+ class_variable_set(:@@eager_load_model, model)
25
+ # rubocop:enable Style/ClassVars
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Graphql
4
+ module EagerLoad
5
+ VERSION = '0.2.2'
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphql-eager_load
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Elise Wood
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-08-11 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: ''
14
+ email:
15
+ - elise@hover.to
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".github/workflows/test.yml"
21
+ - ".gitignore"
22
+ - ".rspec"
23
+ - ".rubocop.yml"
24
+ - ".travis.yml"
25
+ - Gemfile
26
+ - Gemfile.lock
27
+ - LICENSE.txt
28
+ - README.md
29
+ - Rakefile
30
+ - bin/console
31
+ - bin/setup
32
+ - config.ru
33
+ - graphql-eager-load.gemspec
34
+ - lib/graphql/eager_load.rb
35
+ - lib/graphql/eager_load/builder.rb
36
+ - lib/graphql/eager_load/resolver.rb
37
+ - lib/graphql/eager_load/version.rb
38
+ homepage: https://github.com/hoverinc/graphql-eager-load
39
+ licenses:
40
+ - MIT
41
+ metadata:
42
+ homepage_uri: https://github.com/hoverinc/graphql-eager-load
43
+ source_code_uri: https://github.com/hoverinc/graphql-eager-load
44
+ changelog_uri: https://github.com/hoverinc/graphql-eager-load/commits
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.3.0
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.1.2
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Generate ActiveRecord eager loading options from GraphQL Queries.
64
+ test_files: []