graphql-eager_load 0.2.2

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: 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: []