fume-aloader 0.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: e76d23af085408f974ed12200e21710eb7e41ed838f1e1f4906b28cda813967e
4
+ data.tar.gz: f8ca566f229ef016c0f25a47d4018ec2b82fbb2e9c3c04c1a760d297fcf22edc
5
+ SHA512:
6
+ metadata.gz: 3cced0b48ce3022d0467db55bbc4f6b474546bf3990991d531a479d99914afaa568de687d608dc0f8deccc3cbea338eb0218d65656207b85e935e18f8dcc6f7a
7
+ data.tar.gz: d2897f943c2cbceb053a2580190faa83e42fab3700d07999bd1aa18f675238fd0c864ee97168a8686b7b48a3962ad9ba80865048be730fc5bf98af91eb12ba05
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-04-27
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fume-aloader.gemspec
4
+ gemspec
5
+
6
+ gem "pry"
7
+
8
+ # gem "rubocop", "~> 1.21"
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 sunteya
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 sunteya
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,31 @@
1
+ # Fume::Aloader
2
+
3
+ [fume-aloader](https://github.com/sunteya/fume-aloader) is a configurable eager loading plugin for rails
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add fume-aloader
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install fume-aloader
14
+
15
+ ## Usage
16
+
17
+ TODO: Please read spec code.
18
+
19
+ ## Development
20
+
21
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
22
+
23
+ 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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
24
+
25
+ ## Contributing
26
+
27
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/fume-aloader.
28
+
29
+ ## License
30
+
31
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/config.ru ADDED
@@ -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,235 @@
1
+ module Fume::Aloader
2
+ class AssociationLoader
3
+ attr_accessor :klass
4
+ attr_accessor :records
5
+ attr_accessor :presets
6
+ attr_accessor :profile
7
+
8
+ attr_accessor :cached_values
9
+ attr_accessor :preload_values
10
+
11
+ def initialize(records, klass = nil, &block)
12
+ self.profile = :default
13
+ self.presets = {}
14
+ self.klass = klass || records.klass
15
+
16
+ self.cached_values = {}
17
+ self.preload_values = {}
18
+ self.records = records
19
+ instance_exec(&block) if block
20
+ end
21
+
22
+ def find_cached_value(record, name)
23
+ association = record.association(name)
24
+ reflection = association.reflection
25
+
26
+ key = reflection.collection? ? record.send(:id) : record.send(reflection.join_foreign_key)
27
+
28
+ unless self.cached_values.key?(name)
29
+ init_records_value(name)
30
+ end
31
+
32
+ self.cached_values[name][key]
33
+ end
34
+
35
+ def load(record, name)
36
+ association = record.association(name)
37
+ if association.loaded? && !self.cached_values.key?(name)
38
+ init_records_value(name, ->(scope) {
39
+ values = self.records.flat_map(&name.to_sym).compact
40
+ scope.send(:load_records, values)
41
+ scope.al_init_records
42
+ })
43
+ else
44
+ value = find_cached_value(record, name)
45
+ association.target = value
46
+ end
47
+ end
48
+
49
+ def preload_all(*path, values)
50
+ path = [ path ].flatten
51
+ name = path.shift
52
+
53
+ if path.size.zero?
54
+ fill_records_value(name, values)
55
+ else
56
+ self.preload_values[name] ||= []
57
+ self.preload_values[name] << [ path, values ]
58
+ end
59
+ end
60
+
61
+ def init_records_value(name, callback = nil)
62
+ association = klass.new.association(name)
63
+ values = build_association_values_scope(name, association)
64
+ callback&.(values)
65
+
66
+ if self.preload_values.key?(name)
67
+ preload_values[name].each do |args|
68
+ values.al_preload_all(*args)
69
+ end
70
+ end
71
+
72
+ fill_records_value(name, values)
73
+ end
74
+
75
+ def fill_records_value(name, values)
76
+ association = klass.new.association(name)
77
+ reflection = association.reflection
78
+
79
+ if reflection.collection?
80
+ self.cached_values[name] = values.each_with_object(Hash.new { [] }) do |it, result|
81
+ key = it.send(reflection.join_primary_key)
82
+ result[key] += [ it ]
83
+ end
84
+ elsif reflection.belongs_to?
85
+ self.cached_values[name] = values.index_by(&:id)
86
+ else
87
+ self.cached_values[name] = values.index_by { |it| it.send(reflection.join_primary_key) }
88
+ end
89
+ end
90
+
91
+ def build_association_values_scope(name, association)
92
+ reflection = association.reflection
93
+
94
+ # HACK: 重写第一次取值,升级后可能会报错
95
+ # 不能使用子查询 select, 可能内存占用过多
96
+ hack_values = [ records.map { |item| item.read_attribute(reflection.join_foreign_key) }.uniq ]
97
+
98
+ value_transformation = ->(val) {
99
+ hack_values.shift || val
100
+ }
101
+
102
+ association_scope = ActiveRecord::Associations::AssociationScope.new(value_transformation)
103
+ values_scope = association.send(:target_scope).merge(association_scope.scope(association))
104
+ values_scope = apply_profile_attribute_includes(values_scope, name)
105
+ values_scope = values_scope.limit(nil).offset(0)
106
+ values_scope
107
+ end
108
+
109
+ def apply_profile_attribute_includes(base, name)
110
+ preset = self.active_preset
111
+ attribute = preset.dig(:attributes, name) || {}
112
+
113
+ if (attr_preset_name = attribute[:preset])
114
+ return base.al_to_scope(attr_preset_name)
115
+ end
116
+
117
+ includes = find_attribute_includes(preset, name) || []
118
+ return base if includes.empty?
119
+
120
+
121
+ except = (self.preload_values[name] || []).map(&:first)
122
+ columns = simplify_includes_hash(convert_to_includes_hash(includes, [], except))
123
+
124
+ if columns.any?
125
+ base = base.includes(columns).references(columns)
126
+ else
127
+ base
128
+ end
129
+ end
130
+
131
+ def build_profile_scope_includes
132
+ preset = self.active_preset
133
+ except = self.cached_values.keys.map { |it| [ it] }.to_set
134
+ self.preload_values.each do |name, items|
135
+ items.each do |item|
136
+ except << ([ name ] + item.first)
137
+ end
138
+ end
139
+
140
+ result = {}
141
+ roots = [ preset[:scope_includes] || [] ].flatten
142
+ roots.each do |root|
143
+ if root.is_a?(Hash)
144
+ root.each do |(name, value)|
145
+ result.update convert_to_includes_hash({ name => value }, [], except)
146
+ end
147
+ else
148
+ includes = find_attribute_includes(preset, root) || {}
149
+ result.update convert_to_includes_hash({ root => includes }, [], except)
150
+ end
151
+ end
152
+
153
+ simplify_includes_hash(result)
154
+ end
155
+
156
+ def simplify_includes_hash(includes)
157
+ array = []
158
+ hash = {}
159
+
160
+ includes.each do |(key, value)|
161
+ if value.empty?
162
+ array << key
163
+ else
164
+ hash[key] = simplify_includes_hash(value)
165
+ end
166
+ end
167
+
168
+ hash.empty? ? array : array + [ hash ]
169
+ end
170
+
171
+ def convert_to_includes_hash(item, prefix = [], except = [])
172
+ case item
173
+ when Hash
174
+ item.each_with_object({}) do |(name, value), result|
175
+ path = prefix + [ name ]
176
+ if !except.include?(path)
177
+ result[name] = convert_to_includes_hash(value, path, except)
178
+ end
179
+ end
180
+ when Array
181
+ item.each_with_object({}) do |name, result|
182
+ path = prefix + [ name ]
183
+
184
+ if !except.include?(path)
185
+ result[name] = convert_to_includes_hash({}, path, except)
186
+ end
187
+ end
188
+ else
189
+ path = prefix + [ item ]
190
+ except.include?(path) ? {} : { item => Hash.new }
191
+ end
192
+ end
193
+
194
+ def apply_profile_scope_includes(base)
195
+ names = build_profile_scope_includes
196
+
197
+ if names.any?
198
+ base.includes(*names).references(*names)
199
+ else
200
+ base
201
+ end
202
+ end
203
+
204
+ def find_attribute_includes(preset, name)
205
+ attribute = preset.dig(:attributes, name) || {}
206
+
207
+ attr_preset_name = attribute[:preset]
208
+ return attribute[:scope_includes] || [] if attr_preset_name.nil?
209
+
210
+ loader = build_attribute_aloader(name, attr_preset_name)
211
+ loader.build_profile_scope_includes
212
+ end
213
+
214
+ def build_attribute_aloader(name, profile)
215
+ association = klass.new.association(name)
216
+ reflection = association.reflection
217
+ loader = reflection.klass.al_build([])
218
+ loader.active(profile)
219
+ loader
220
+ end
221
+
222
+ def active(name)
223
+ self.profile = name
224
+ end
225
+
226
+ def active_preset
227
+ self.presets[self.profile] || {}
228
+ end
229
+
230
+ def spawn_from(parent)
231
+ self.preload_values = parent.preload_values.dup
232
+ self.profile = parent.profile
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,40 @@
1
+ module Fume::Aloader
2
+ class DSL
3
+ attr_accessor :config
4
+
5
+ def initialize(&block)
6
+ self.config = {}
7
+ instance_exec &block if block
8
+ end
9
+
10
+ def preset(name, &block)
11
+ name = name.to_sym
12
+ dsl = self.class.new(&block)
13
+ self.config[:presets] ||= {}
14
+ self.config[:presets][name] = { scope_includes: [] }.merge(dsl.config)
15
+ end
16
+
17
+ def scope_includes(columns)
18
+ self.config[:scope_includes] = columns
19
+ end
20
+
21
+ def attribute(name, options = {}, &block)
22
+ self.config[:attributes] ||= {}
23
+ self.config[:attributes][name] = options
24
+
25
+ if block
26
+ dsl = self.class.new(&block)
27
+ self.config[:attributes][name][:scope_includes] = dsl.config[:scope_includes]
28
+ end
29
+ end
30
+
31
+ def apply_config(loader)
32
+ loader.presets = self.config[:presets] || {}
33
+ loader.presets[nil] = {
34
+ scope_includes: self.config[:scope_includes] || [],
35
+ attributes: self.config[:attributes] || {}
36
+ }
37
+ loader
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,14 @@
1
+ require "rails/railtie"
2
+ require_relative "relation_addons"
3
+ require_relative "record_addons"
4
+
5
+ require_relative "association_loader"
6
+
7
+ module Fume::Aloader
8
+ class Railtie < ::Rails::Railtie
9
+ initializer 'fume-aloader.configure_rails_initialization' do |app|
10
+ ::ActiveRecord::Base.include(RecordAddons)
11
+ ::ActiveRecord::Relation.prepend(RelationAddons)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ require "active_support/concern"
2
+ require_relative "dsl"
3
+
4
+ module Fume::Aloader
5
+ module RecordAddons
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attr_accessor :aloader
10
+ end
11
+
12
+ def al_load(*path)
13
+ path = [ path ].flatten
14
+ name = path.shift
15
+ self.aloader.load(self, name)
16
+
17
+ if path.any?
18
+ value = self.send(name)
19
+ value&.al_load(*path)
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ def aloader_init(&block)
25
+ define_singleton_method :al_build do |records|
26
+ dsl = DSL.new(&block)
27
+ dsl.apply_config(AssociationLoader.new(records, self))
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,55 @@
1
+ module Fume::Aloader
2
+ module RelationAddons
3
+
4
+ attr_accessor :aloader
5
+
6
+ def load(*args, &block)
7
+ return super if loaded?
8
+ result = super
9
+ al_init_records
10
+ result
11
+ end
12
+
13
+ def spawn(*args)
14
+ result = super
15
+ result.aloader = nil
16
+ result.al_init_loader
17
+ result.aloader&.spawn_from(self.aloader) if self.aloader
18
+ result
19
+ end
20
+
21
+ def al_init_records
22
+ al_init_loader
23
+
24
+ if self.aloader
25
+ @records.each do |record|
26
+ record.aloader = self.aloader
27
+ end
28
+ end
29
+ end
30
+
31
+ def al_init_loader
32
+ return if self.aloader
33
+ return unless klass.respond_to?(:al_build)
34
+
35
+ self.aloader = klass.al_build(self)
36
+ end
37
+
38
+ def al_load(*args)
39
+ records.each { |it| it.al_load(*args) }
40
+ self
41
+ end
42
+
43
+ def al_preload_all(*args)
44
+ al_init_loader
45
+ self.aloader.preload_all(*args)
46
+ self
47
+ end
48
+
49
+ def al_to_scope(preset = :default)
50
+ al_init_loader
51
+ self.aloader.active(preset)
52
+ self.aloader.apply_profile_scope_includes(self)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,5 @@
1
+ module Fume
2
+ module Aloader
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "aloader/version"
2
+ require_relative "aloader/railtie"
3
+ require_relative "aloader/dsl"
4
+ require_relative "aloader/association_loader"
5
+
6
+ module Fume
7
+ module Aloader
8
+ def self.dsl(*args, &block)
9
+ dsl = DSL.new(&block)
10
+ loader = AssociationLoader.new(*args)
11
+ dsl.apply_config(loader)
12
+ loader
13
+ end
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fume-aloader
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - sunteya
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-04-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: combustion
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 1.4.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 1.4.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-do_action
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.0.7
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.0.7
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.21.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.21.2
97
+ - !ruby/object:Gem::Dependency
98
+ name: factory_bot
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 6.2.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 6.2.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: faker
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 2.20.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 2.20.0
125
+ description:
126
+ email:
127
+ - sunteya@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".rspec"
133
+ - ".rubocop.yml"
134
+ - CHANGELOG.md
135
+ - Gemfile
136
+ - LICENSE
137
+ - LICENSE.txt
138
+ - README.md
139
+ - config.ru
140
+ - lib/fume/aloader.rb
141
+ - lib/fume/aloader/association_loader.rb
142
+ - lib/fume/aloader/dsl.rb
143
+ - lib/fume/aloader/railtie.rb
144
+ - lib/fume/aloader/record_addons.rb
145
+ - lib/fume/aloader/relation_addons.rb
146
+ - lib/fume/aloader/version.rb
147
+ homepage: https://github.com/sunteya/fume-aloader
148
+ licenses:
149
+ - MIT
150
+ metadata: {}
151
+ post_install_message:
152
+ rdoc_options: []
153
+ require_paths:
154
+ - lib
155
+ required_ruby_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: 2.6.0
160
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ requirements: []
166
+ rubygems_version: 3.1.6
167
+ signing_key:
168
+ specification_version: 4
169
+ summary: a configurable eager loading plugin for rails
170
+ test_files: []