n1_loader 0.1.2 → 1.3.0
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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +56 -14
- data/CHANGELOG.md +20 -2
- data/Gemfile +9 -0
- data/README.md +199 -39
- data/activerecord-gemfiles/ar_5_latest.gemfile +3 -0
- data/{gemfiles → activerecord-gemfiles}/ar_6_latest.gemfile +0 -4
- data/ar_lazy_preload-gemfiles/ar_lazy_preload_0.6.1.gemfile +3 -0
- data/ar_lazy_preload-gemfiles/ar_lazy_preload_master.gemfile +3 -0
- data/lib/n1_loader/active_record/associations_preloader_v5.rb +39 -0
- data/lib/n1_loader/active_record/{associations_preloader.rb → associations_preloader_v6.rb} +0 -0
- data/lib/n1_loader/active_record/loader.rb +5 -0
- data/lib/n1_loader/active_record/loader_collection.rb +9 -0
- data/lib/n1_loader/active_record.rb +18 -1
- data/lib/n1_loader/ar_lazy_preload/associated_context_builder.rb +8 -1
- data/lib/n1_loader/ar_lazy_preload/context_adapter.rb +9 -6
- data/lib/n1_loader/ar_lazy_preload/loadable.rb +3 -3
- data/lib/n1_loader/ar_lazy_preload/loader_collection_patch.rb +18 -0
- data/lib/n1_loader/ar_lazy_preload/loader_patch.rb +20 -0
- data/lib/n1_loader/ar_lazy_preload/preloader_patch.rb +23 -0
- data/lib/n1_loader/ar_lazy_preload.rb +9 -0
- data/lib/n1_loader/core/loadable.rb +24 -17
- data/lib/n1_loader/core/loader.rb +62 -12
- data/lib/n1_loader/core/loader_collection.rb +25 -0
- data/lib/n1_loader/core/preloader.rb +3 -3
- data/lib/n1_loader/version.rb +1 -1
- data/lib/n1_loader.rb +3 -0
- data/n1_loader.gemspec +4 -4
- metadata +27 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc47894a27d425f70a38dc43b9b40053d823f4815fe8e135a7cb1cf5afbde0f3
|
4
|
+
data.tar.gz: 132d5851c19763db2f1910ebeeb6a368437aba6d3340c2c69ad2ffe348791011
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 945258069b57bc7cd0fe26416411322886d734db0ed4eead1c88a3acced70bdb14ffc92eb9403e5d346c0853d64dc09e5afb8039a9026c9b4c09a1cc6f5fc9e8
|
7
|
+
data.tar.gz: 036e5a8a4b2d63bed2aee558164fdd042ac8c0c68b0ecdb1d5ec9f64231ca9f6841dc56cd6cdc63b87fdb9bf964e4f42089f89d9783cf2513cf81243a473150a
|
data/.circleci/config.yml
CHANGED
@@ -11,8 +11,8 @@ executors:
|
|
11
11
|
docker:
|
12
12
|
- image: circleci/ruby:<< parameters.tag >>
|
13
13
|
environment:
|
14
|
-
|
15
|
-
|
14
|
+
BUNDLE_JOBS: 4
|
15
|
+
BUNDLE_RETRY: 3
|
16
16
|
working_directory: ~/n1_loader
|
17
17
|
|
18
18
|
jobs:
|
@@ -29,28 +29,49 @@ jobs:
|
|
29
29
|
parameters:
|
30
30
|
ruby-version:
|
31
31
|
type: string
|
32
|
-
gemfile:
|
32
|
+
activerecord-gemfile:
|
33
33
|
type: string
|
34
|
+
ar_lazy_preload-gemfile:
|
35
|
+
type: string
|
36
|
+
environment:
|
37
|
+
ACTIVERECORD_GEMFILE: << parameters.activerecord-gemfile >>
|
38
|
+
AR_LAZY_PRELOAD_GEMFILE: << parameters.ar_lazy_preload-gemfile >>
|
34
39
|
executor:
|
35
40
|
name: ruby
|
36
41
|
tag: << parameters.ruby-version >>
|
37
42
|
steps:
|
38
43
|
- attach_workspace:
|
39
44
|
at: ~/n1_loader
|
40
|
-
- run:
|
41
|
-
name: Use << parameters.gemfile >> as the Gemfile
|
42
|
-
command: bundle config --global gemfile << parameters.gemfile >>
|
43
45
|
- run:
|
44
46
|
name: Install the gems specified by the Gemfile
|
45
47
|
command: bundle install
|
46
48
|
- run:
|
47
|
-
name: Run RSpec
|
49
|
+
name: Run Core RSpec
|
48
50
|
command: |
|
49
51
|
bundle exec rspec --profile 10 \
|
50
52
|
--format RspecJunitFormatter \
|
51
|
-
--out test_results/
|
53
|
+
--out test_results/core.xml \
|
52
54
|
--format progress \
|
53
|
-
|
55
|
+
spec/n1_loader_spec.rb
|
56
|
+
- run:
|
57
|
+
name: Run ActiveRecord integration RSpec
|
58
|
+
command: |
|
59
|
+
bundle exec rspec --profile 10 \
|
60
|
+
--format RspecJunitFormatter \
|
61
|
+
--out test_results/activerecord-integration.xml \
|
62
|
+
--format progress \
|
63
|
+
spec/n1_loader_spec.rb \
|
64
|
+
spec/activerecord_spec.rb
|
65
|
+
- run:
|
66
|
+
name: Run ActiveRecord integration RSpec
|
67
|
+
command: |
|
68
|
+
bundle exec rspec --profile 10 \
|
69
|
+
--format RspecJunitFormatter \
|
70
|
+
--out test_results/ar-lazy-preload-integration.xml \
|
71
|
+
--format progress \
|
72
|
+
spec/n1_loader_spec.rb \
|
73
|
+
spec/activerecord_spec.rb \
|
74
|
+
spec/ar_lazy_preload_spec.rb
|
54
75
|
- store_test_results:
|
55
76
|
path: test_results
|
56
77
|
|
@@ -80,15 +101,36 @@ workflows:
|
|
80
101
|
parameters:
|
81
102
|
ruby-version: [
|
82
103
|
"2.5",
|
83
|
-
"2.6",
|
84
104
|
"2.7",
|
85
|
-
"3.0",
|
86
105
|
"latest"
|
87
106
|
]
|
88
|
-
gemfile: [
|
89
|
-
"
|
107
|
+
activerecord-gemfile: [
|
108
|
+
"ar_5_latest",
|
109
|
+
"ar_6_latest"
|
90
110
|
]
|
91
|
-
|
111
|
+
ar_lazy_preload-gemfile: [
|
112
|
+
"ar_lazy_preload_0.6.1",
|
113
|
+
"ar_lazy_preload_master"
|
114
|
+
]
|
115
|
+
exclude:
|
116
|
+
# Ruby 2.5 and AR Lazy Preload 1+
|
117
|
+
- ruby-version: "2.5"
|
118
|
+
activerecord-gemfile: "ar_5_latest"
|
119
|
+
ar_lazy_preload-gemfile: "ar_lazy_preload_master"
|
120
|
+
|
121
|
+
- ruby-version: "2.5"
|
122
|
+
activerecord-gemfile: "ar_6_latest"
|
123
|
+
ar_lazy_preload-gemfile: "ar_lazy_preload_master"
|
124
|
+
|
125
|
+
# AR 5 and ruby 3+
|
126
|
+
- ruby-version: "latest"
|
127
|
+
activerecord-gemfile: "ar_5_latest"
|
128
|
+
ar_lazy_preload-gemfile: "ar_lazy_preload_0.6.1"
|
129
|
+
- ruby-version: "latest"
|
130
|
+
activerecord-gemfile: "ar_5_latest"
|
131
|
+
ar_lazy_preload-gemfile: "ar_lazy_preload_master"
|
132
|
+
|
133
|
+
name: ruby-<< matrix.ruby-version >>-<< matrix.activerecord-gemfile >>-<< matrix.ar_lazy_preload-gemfile >>
|
92
134
|
- rubocop:
|
93
135
|
requires:
|
94
136
|
- checkout
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,23 @@
|
|
1
|
-
## [
|
1
|
+
## [1.3.0] - 2022-02-22
|
2
|
+
|
3
|
+
- add support of named arguments with `argument <name>`
|
4
|
+
|
5
|
+
BREAKING CHANGES:
|
6
|
+
- rename `n1_load` to `n1_optimized`
|
7
|
+
- rework `def self.arguments_key` to `cache_key`
|
8
|
+
|
9
|
+
## [1.2.0] - 2022-01-14
|
10
|
+
|
11
|
+
- Introduce arguments support.
|
12
|
+
|
13
|
+
## [1.1.0] - 2021-12-27
|
14
|
+
|
15
|
+
- Introduce `fulfill` method to abstract the storage.
|
16
|
+
|
17
|
+
## [1.0.0] - 2021-12-26
|
18
|
+
|
19
|
+
- Various of great features.
|
2
20
|
|
3
21
|
## [0.1.0] - 2021-12-16
|
4
22
|
|
5
|
-
- Initial release
|
23
|
+
- Initial release.
|
data/Gemfile
CHANGED
@@ -4,3 +4,12 @@ source "https://rubygems.org"
|
|
4
4
|
|
5
5
|
# Specify your gem's dependencies in n1_loader.gemspec
|
6
6
|
gemspec
|
7
|
+
|
8
|
+
# Hack to make Github work with Circle CI job names with slashes
|
9
|
+
gemfiles = []
|
10
|
+
gemfiles << "activerecord-gemfiles/#{ENV["ACTIVERECORD_GEMFILE"]}.gemfile" if ENV["ACTIVERECORD_GEMFILE"]
|
11
|
+
gemfiles << "ar_lazy_preload-gemfiles/#{ENV["AR_LAZY_PRELOAD_GEMFILE"]}.gemfile" if ENV["AR_LAZY_PRELOAD_GEMFILE"]
|
12
|
+
|
13
|
+
gemfiles.each do |path|
|
14
|
+
eval(File.read(path)) # rubocop:disable Security/Eval
|
15
|
+
end
|
data/README.md
CHANGED
@@ -9,11 +9,16 @@ We have a solution for you!
|
|
9
9
|
[N1Loader][8] is designed to solve the issue for good!
|
10
10
|
|
11
11
|
It has many benefits:
|
12
|
-
- it
|
13
|
-
- it
|
12
|
+
- it can be [isolated](#isolated-loaders)
|
13
|
+
- it loads data [lazily](#lazy-loading)
|
14
|
+
- it supports [shareable loaders](#shareable-loaders) between multiple classes
|
15
|
+
- it supports [reloading](#reloading)
|
16
|
+
- it supports optimized [single object loading](#optimized-single-case)
|
17
|
+
- it supports [arguments](#arguments)
|
14
18
|
- it has an integration with [ActiveRecord][5] which makes it brilliant ([example](#activerecord))
|
15
19
|
- it has an integration with [ArLazyPreload][6] which makes it excellent ([example](#arlazypreload))
|
16
20
|
|
21
|
+
... and even more features to come! Stay tuned!
|
17
22
|
|
18
23
|
## Installation
|
19
24
|
|
@@ -25,59 +30,221 @@ gem 'n1_loader'
|
|
25
30
|
|
26
31
|
You can add integration with [ActiveRecord][5] by:
|
27
32
|
```ruby
|
28
|
-
require 'n1_loader/active_record'
|
33
|
+
gem 'n1_loader', require: 'n1_loader/active_record'
|
29
34
|
```
|
30
35
|
|
31
36
|
You can add the integration with [ActiveRecord][5] and [ArLazyPreload][6] by:
|
32
37
|
```ruby
|
33
|
-
require 'n1_loader/ar_lazy_preload'
|
38
|
+
gem 'n1_loader', require: 'n1_loader/ar_lazy_preload'
|
34
39
|
```
|
35
40
|
|
36
41
|
## Usage
|
37
42
|
|
38
43
|
```ruby
|
39
|
-
class
|
44
|
+
class User
|
40
45
|
include N1Loader::Loadable
|
41
46
|
|
42
47
|
# with inline loader
|
43
|
-
|
44
|
-
|
45
|
-
|
48
|
+
n1_optimized :orders_count do |users|
|
49
|
+
orders_per_user = Order.where(user: users).group(:user_id).count
|
50
|
+
|
51
|
+
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
46
52
|
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# For single object
|
56
|
+
user = User.new
|
57
|
+
user.orders_count
|
58
|
+
|
59
|
+
# For multiple objects without N+1
|
60
|
+
users = [User.new, User.new]
|
61
|
+
N1Loader::Preloader.new(users).preload(:orders_count)
|
62
|
+
users.map(&:orders_count)
|
63
|
+
```
|
64
|
+
|
65
|
+
### Lazy loading
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
class User
|
69
|
+
include N1Loader::Loadable
|
70
|
+
|
71
|
+
# with inline loader
|
72
|
+
n1_optimized :orders_count do |users|
|
73
|
+
orders_per_user = Order.where(user: users).group(:user_id).count
|
47
74
|
|
48
|
-
|
49
|
-
|
75
|
+
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
76
|
+
end
|
50
77
|
end
|
51
78
|
|
52
|
-
|
53
|
-
|
54
|
-
|
79
|
+
user = User.new # => nothing was done for loading
|
80
|
+
user.orders_count # => first time loading
|
81
|
+
|
82
|
+
users = [User.new, User.new] # => nothing was done for loading
|
83
|
+
N1Loader::Preloader.new([users]).preload(:orders_count) # => we only initialized loader but didn't perform it yet
|
84
|
+
users.map(&:orders_count) # => loading has happen for the first time (without N+1)
|
85
|
+
```
|
86
|
+
|
87
|
+
|
88
|
+
### Shareable loaders
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
class OrdersCountLoader < N1Loader::Loader
|
92
|
+
def perform(users)
|
93
|
+
orders_per_user = Order.where(user: users).group(:user_id).count
|
94
|
+
|
95
|
+
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class User
|
100
|
+
include N1Loader::Loadable
|
101
|
+
|
102
|
+
n1_optimized :orders_count, OrdersCountLoader
|
103
|
+
end
|
104
|
+
|
105
|
+
class Customer
|
106
|
+
include N1Loader::Loadable
|
107
|
+
|
108
|
+
n1_optimized :orders_count, OrdersCountLoader
|
109
|
+
end
|
110
|
+
|
111
|
+
User.new.orders_count # => works
|
112
|
+
Customer.new.orders_count # => works
|
113
|
+
```
|
114
|
+
|
115
|
+
### Reloading
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
class User
|
119
|
+
include N1Loader::Loadable
|
120
|
+
|
121
|
+
# with inline loader
|
122
|
+
n1_optimized :orders_count do |users|
|
123
|
+
orders_per_user = Order.where(user: users).group(:user_id).count
|
124
|
+
|
125
|
+
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
user = User.new
|
130
|
+
user.orders_count # => loader is executed first time and value was cached
|
131
|
+
user.orders_count(reload: true) # => loader is executed again and a new value was cached
|
132
|
+
|
133
|
+
users = [User.new, User.new]
|
134
|
+
N1Loader::Preloader.new(users).preload(:orders_count) # => loader was initialized but not yet executed
|
135
|
+
users.map(&:orders_count) # => loader was executed first time without N+1 issue and values were cached
|
136
|
+
|
137
|
+
N1Loader::Preloader.new(users).preload(:orders_count) # => loader was initialized again but not yet executed
|
138
|
+
users.map(&:orders_count) # => new loader was executed first time without N+1 issue and new values were cached
|
139
|
+
```
|
140
|
+
|
141
|
+
### Isolated loaders
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
class IsolatedLoader < N1Loader::Loader
|
55
145
|
def perform(elements)
|
56
|
-
elements.
|
146
|
+
elements.each { |element| fulfill(element, [element]) }
|
57
147
|
end
|
58
148
|
end
|
59
149
|
|
60
|
-
|
61
|
-
|
62
|
-
|
150
|
+
objects = [1, 2, 3, 4]
|
151
|
+
loader = IsolatedLoader.new(objects)
|
152
|
+
objects.each do |object|
|
153
|
+
loader.for(object) # => it has no N+1 and it doesn't require to be injected in the class
|
154
|
+
end
|
155
|
+
```
|
63
156
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
157
|
+
### Optimized single case
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
class User
|
161
|
+
include N1Loader::Loadable
|
162
|
+
|
163
|
+
n1_optimized :orders_count do # no arguments passed to the block, so we can override both perform and single.
|
164
|
+
def perform(users)
|
165
|
+
orders_per_user = Order.where(user: users).group(:user_id).count
|
166
|
+
|
167
|
+
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
168
|
+
end
|
169
|
+
|
170
|
+
# Optimized for single object loading
|
171
|
+
def single(user)
|
172
|
+
user.orders.count
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
user = User.new
|
178
|
+
user.orders_count # single will be used here
|
179
|
+
|
180
|
+
users = [User.new, User.new]
|
181
|
+
N1Loader::Preloader.new(users).preload(:orders_count)
|
182
|
+
users.map(&:orders_count) # perform will be used once without N+1
|
68
183
|
```
|
69
184
|
|
185
|
+
### Arguments
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
class User
|
189
|
+
include N1Loader::Loadable
|
190
|
+
|
191
|
+
n1_optimized :orders_count do |users, type|
|
192
|
+
orders_per_user = Order.where(type: type, user: users).group(:user_id).count
|
193
|
+
|
194
|
+
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
user = User.new
|
199
|
+
user.orders_count(:gifts) # The loader will be performed first time for this argument
|
200
|
+
user.orders_count(:sales) # The loader will be performed first time for this argument
|
201
|
+
user.orders_count(:gifts) # The cached value will be used
|
202
|
+
|
203
|
+
users = [User.new, User.new]
|
204
|
+
N1Loader::Preloader.new(users).preload(:orders_count)
|
205
|
+
users.map { |user| user.orders_count(:gifts) } # No N+1 here
|
206
|
+
```
|
207
|
+
|
208
|
+
_Note_: By default, we use `arguments.map(&:object_id)` to identify arguments but in some cases,
|
209
|
+
you may want to override it, for example:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
class User
|
213
|
+
include N1Loader::Loadable
|
214
|
+
|
215
|
+
n1_optimized :orders_count do
|
216
|
+
argument :sale
|
217
|
+
|
218
|
+
cache_key { sale.id }
|
219
|
+
|
220
|
+
def perform(users, sale)
|
221
|
+
orders_per_user = Order.where(sale: sale, user: users).group(:user_id).count
|
222
|
+
|
223
|
+
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
user = User.new
|
229
|
+
user.orders_count(Sale.first) # perform will be executed and value will be cached
|
230
|
+
user.orders_count(Sale.first) # the cached value will be returned
|
231
|
+
```
|
232
|
+
|
233
|
+
|
234
|
+
## Integrations
|
235
|
+
|
70
236
|
### [ActiveRecord][5]
|
71
237
|
|
238
|
+
_Note_: Rails 7 support is coming soon! Stay tuned!
|
239
|
+
|
72
240
|
```ruby
|
73
241
|
class User < ActiveRecord::Base
|
74
242
|
include N1Loader::Loadable
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
hash.transform_keys! { |key| users.find { |user| user.id == key } }
|
243
|
+
|
244
|
+
n1_optimized :orders_count do |users|
|
245
|
+
orders_per_user = Order.where(user: users).group(:user_id).count
|
246
|
+
|
247
|
+
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
81
248
|
end
|
82
249
|
end
|
83
250
|
|
@@ -101,12 +268,11 @@ users.map(&:orders_count)
|
|
101
268
|
```ruby
|
102
269
|
class User < ActiveRecord::Base
|
103
270
|
include N1Loader::Loadable
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
hash.transform_keys! { |key| users.find { |user| user.id == key } }
|
271
|
+
|
272
|
+
n1_optimized :orders_count do |users|
|
273
|
+
orders_per_user = Order.where(user: users).group(:user_id).count
|
274
|
+
|
275
|
+
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
110
276
|
end
|
111
277
|
end
|
112
278
|
|
@@ -123,12 +289,6 @@ ArLazyPreload.config.auto_preload = true
|
|
123
289
|
User.all.map(:orders_count)
|
124
290
|
```
|
125
291
|
|
126
|
-
## Development
|
127
|
-
|
128
|
-
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.
|
129
|
-
|
130
|
-
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).
|
131
|
-
|
132
292
|
## Contributing
|
133
293
|
|
134
294
|
Bug reports and pull requests are welcome on GitHub at https://github.com/djezzzl/n1_loader.
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module N1Loader
|
4
|
+
module ActiveRecord
|
5
|
+
module Associations
|
6
|
+
module Preloader # :nodoc:
|
7
|
+
N1LoaderReflection = Struct.new(:key, :loader) do
|
8
|
+
def options
|
9
|
+
{}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def preloaders_for_one(association, records, scope)
|
14
|
+
grouped_records(association, records).flat_map do |reflection, klasses|
|
15
|
+
next N1Loader::Preloader.new(records).preload(reflection.key) if reflection.is_a?(N1LoaderReflection)
|
16
|
+
|
17
|
+
klasses.map do |rhs_klass, rs|
|
18
|
+
loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
|
19
|
+
loader.run self
|
20
|
+
loader
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def grouped_records(association, records)
|
26
|
+
n1_load_records, records = records.partition do |record|
|
27
|
+
record.class.respond_to?(:n1_loader_defined?) && record.class.n1_loader_defined?(association)
|
28
|
+
end
|
29
|
+
|
30
|
+
hash = n1_load_records.group_by do |record|
|
31
|
+
N1LoaderReflection.new(association, record.class.n1_loader(association))
|
32
|
+
end
|
33
|
+
|
34
|
+
hash.merge(super)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
File without changes
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
N1Loader::LoaderCollection.define_method :preloaded_records do
|
4
|
+
unless loader_class.instance_method(:perform).arity == 1
|
5
|
+
raise N1Loader::ActiveRecord::InvalidPreloading, "Cannot preload loader with arguments"
|
6
|
+
end
|
7
|
+
|
8
|
+
with.preloaded_records
|
9
|
+
end
|
@@ -1,11 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Load core library
|
3
4
|
require_relative "../n1_loader"
|
4
5
|
|
6
|
+
# Load integration dependency
|
5
7
|
require "active_record"
|
6
8
|
|
7
|
-
|
9
|
+
module N1Loader
|
10
|
+
module ActiveRecord
|
11
|
+
class InvalidPreloading < N1Loader::Error; end
|
12
|
+
end
|
13
|
+
end
|
8
14
|
|
15
|
+
# Library integration
|
9
16
|
ActiveSupport.on_load(:active_record) do
|
17
|
+
require_relative "active_record/loader"
|
18
|
+
require_relative "active_record/loader_collection"
|
19
|
+
|
20
|
+
case ActiveRecord::VERSION::MAJOR
|
21
|
+
when 6
|
22
|
+
require_relative "active_record/associations_preloader_v6"
|
23
|
+
else
|
24
|
+
require_relative "active_record/associations_preloader_v5"
|
25
|
+
end
|
26
|
+
|
10
27
|
ActiveRecord::Associations::Preloader.prepend(N1Loader::ActiveRecord::Associations::Preloader)
|
11
28
|
end
|
@@ -4,9 +4,16 @@ module N1Loader
|
|
4
4
|
module ArLazyPreload
|
5
5
|
# Context builder for N1Loader
|
6
6
|
class AssociatedContextBuilder < ::ArLazyPreload::AssociatedContextBuilder
|
7
|
+
attr_reader :records
|
8
|
+
|
9
|
+
def initialize(parent_context:, association_name:, records:)
|
10
|
+
super(parent_context: parent_context, association_name: association_name)
|
11
|
+
@records = records
|
12
|
+
end
|
13
|
+
|
7
14
|
def perform
|
8
15
|
::ArLazyPreload::Context.register(
|
9
|
-
records:
|
16
|
+
records: records.flatten(1).select { |record| record.respond_to?(:lazy_preload_context=) },
|
10
17
|
association_tree: child_association_tree,
|
11
18
|
auto_preload: parent_context.auto_preload?
|
12
19
|
)
|
@@ -6,7 +6,7 @@ module N1Loader
|
|
6
6
|
class ContextAdapter
|
7
7
|
attr_reader :context
|
8
8
|
|
9
|
-
|
9
|
+
delegate_missing_to :context
|
10
10
|
|
11
11
|
def initialize(context)
|
12
12
|
@context = context
|
@@ -19,12 +19,15 @@ module N1Loader
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def perform_preloading(association_name)
|
22
|
-
|
22
|
+
context_setup = lambda { |records|
|
23
|
+
AssociatedContextBuilder.prepare(
|
24
|
+
parent_context: self,
|
25
|
+
association_name: association_name,
|
26
|
+
records: records
|
27
|
+
)
|
28
|
+
}
|
23
29
|
|
24
|
-
|
25
|
-
parent_context: self,
|
26
|
-
association_name: association_name
|
27
|
-
)
|
30
|
+
N1Loader::Preloader.new(records, context_setup).preload(association_name)
|
28
31
|
end
|
29
32
|
end
|
30
33
|
end
|
@@ -4,18 +4,18 @@ module N1Loader
|
|
4
4
|
module ArLazyPreload
|
5
5
|
module Loadable
|
6
6
|
module ClassMethods # :nodoc:
|
7
|
-
def
|
7
|
+
def n1_optimized(name, loader = nil, &block)
|
8
8
|
name, loader_name, loader_variable_name = super
|
9
9
|
|
10
10
|
define_method(loader_name) do
|
11
11
|
loader = instance_variable_get(loader_variable_name)
|
12
|
-
|
13
12
|
return loader if loader
|
13
|
+
|
14
14
|
if respond_to?(:lazy_preload_context) && ContextAdapter.new(lazy_preload_context).try_preload_lazily(name)
|
15
15
|
return instance_variable_get(loader_variable_name)
|
16
16
|
end
|
17
17
|
|
18
|
-
|
18
|
+
send("#{loader_name}_reload")
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module N1Loader
|
4
|
+
module ArLazyPreload
|
5
|
+
# A patch to {N1Loader::LoaderCollection} to setup lazy context lazily.
|
6
|
+
module LoaderCollectionPatch
|
7
|
+
attr_accessor :context_setup
|
8
|
+
|
9
|
+
def with(*args)
|
10
|
+
result = super
|
11
|
+
|
12
|
+
result.context_setup = context_setup if context_setup && result.context_setup.nil?
|
13
|
+
|
14
|
+
result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module N1Loader
|
4
|
+
module ArLazyPreload
|
5
|
+
# A patch to {N1Loader::Loader} to setup lazy context lazily.
|
6
|
+
module LoaderPatch
|
7
|
+
attr_accessor :context_setup
|
8
|
+
|
9
|
+
def loaded
|
10
|
+
return @loaded if @loaded
|
11
|
+
|
12
|
+
super
|
13
|
+
|
14
|
+
context_setup&.call(preloaded_records)
|
15
|
+
|
16
|
+
@loaded
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module N1Loader
|
4
|
+
module ArLazyPreload
|
5
|
+
# A patch to {N1Loader::Preloader} setup lazy context lazily.
|
6
|
+
module PreloaderPatch
|
7
|
+
def initialize(elements, context_setup = nil)
|
8
|
+
super(elements)
|
9
|
+
@context_setup = context_setup
|
10
|
+
end
|
11
|
+
|
12
|
+
def preload(*keys)
|
13
|
+
super.each do |loader_collection|
|
14
|
+
loader_collection.context_setup = context_setup
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :context_setup
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,12 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Load core library
|
3
4
|
require_relative "active_record"
|
4
5
|
|
6
|
+
# Load integration dependency
|
5
7
|
require "rails"
|
6
8
|
require "ar_lazy_preload"
|
7
9
|
|
10
|
+
# Library integration
|
8
11
|
require_relative "ar_lazy_preload/loadable"
|
9
12
|
require_relative "ar_lazy_preload/context_adapter"
|
10
13
|
require_relative "ar_lazy_preload/associated_context_builder"
|
14
|
+
require_relative "ar_lazy_preload/loader_collection_patch"
|
15
|
+
require_relative "ar_lazy_preload/preloader_patch"
|
16
|
+
require_relative "ar_lazy_preload/loader_patch"
|
11
17
|
|
12
18
|
N1Loader::Loadable::ClassMethods.prepend(N1Loader::ArLazyPreload::Loadable::ClassMethods)
|
19
|
+
N1Loader::Preloader.prepend(N1Loader::ArLazyPreload::PreloaderPatch)
|
20
|
+
N1Loader::Loader.prepend(N1Loader::ArLazyPreload::LoaderPatch)
|
21
|
+
N1Loader::LoaderCollection.prepend(N1Loader::ArLazyPreload::LoaderCollectionPatch)
|
@@ -7,9 +7,9 @@ module N1Loader
|
|
7
7
|
# include N1Loader::Loadable
|
8
8
|
#
|
9
9
|
# # with inline loader
|
10
|
-
# n1_loader :something do
|
11
|
-
# elements
|
12
|
-
#
|
10
|
+
# n1_loader :something do
|
11
|
+
# def perform(elements)
|
12
|
+
# elements.each { |element| fulfill(element,, element.calculate_something) }
|
13
13
|
# end
|
14
14
|
# end
|
15
15
|
#
|
@@ -20,9 +20,7 @@ module N1Loader
|
|
20
20
|
# # custom loader
|
21
21
|
# class MyLoader < N1Loader::Loader
|
22
22
|
# def perform(elements)
|
23
|
-
# elements.
|
24
|
-
# hash[element] = element.calculate_something
|
25
|
-
# end
|
23
|
+
# elements.each { |element| fulfill(element,, element.calculate_something) }
|
26
24
|
# end
|
27
25
|
# end
|
28
26
|
module Loadable
|
@@ -30,8 +28,8 @@ module N1Loader
|
|
30
28
|
send("#{name}_loader")
|
31
29
|
end
|
32
30
|
|
33
|
-
def n1_loader_set(name,
|
34
|
-
send("#{name}_loader=",
|
31
|
+
def n1_loader_set(name, loader_collection)
|
32
|
+
send("#{name}_loader=", loader_collection)
|
35
33
|
end
|
36
34
|
|
37
35
|
def self.included(base)
|
@@ -47,11 +45,14 @@ module N1Loader
|
|
47
45
|
respond_to?("#{name}_loader")
|
48
46
|
end
|
49
47
|
|
50
|
-
def
|
48
|
+
def n1_optimized(name, loader = nil, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
51
49
|
loader ||= Class.new(N1Loader::Loader) do
|
52
|
-
|
50
|
+
if block&.arity&.positive?
|
51
|
+
define_method(:perform, &block)
|
52
|
+
else
|
53
|
+
class_eval(&block)
|
54
|
+
end
|
53
55
|
end
|
54
|
-
|
55
56
|
loader_name = "#{name}_loader"
|
56
57
|
loader_variable_name = "@#{loader_name}"
|
57
58
|
|
@@ -59,17 +60,23 @@ module N1Loader
|
|
59
60
|
loader
|
60
61
|
end
|
61
62
|
|
62
|
-
define_method("#{loader_name}
|
63
|
-
instance_variable_set(loader_variable_name,
|
63
|
+
define_method("#{loader_name}_reload") do
|
64
|
+
instance_variable_set(loader_variable_name,
|
65
|
+
N1Loader::LoaderCollection.new(self.class.send(loader_name), [self]))
|
66
|
+
end
|
67
|
+
|
68
|
+
define_method("#{loader_name}=") do |loader_collection_instance|
|
69
|
+
instance_variable_set(loader_variable_name, loader_collection_instance)
|
64
70
|
end
|
65
71
|
|
66
72
|
define_method(loader_name) do
|
67
|
-
instance_variable_get(loader_variable_name) ||
|
68
|
-
instance_variable_set(loader_variable_name, self.class.send(loader_name).new([self]))
|
73
|
+
instance_variable_get(loader_variable_name) || send("#{loader_name}_reload")
|
69
74
|
end
|
70
75
|
|
71
|
-
define_method(name) do
|
72
|
-
send(loader_name)
|
76
|
+
define_method(name) do |*args, reload: false|
|
77
|
+
send("#{loader_name}_reload") if reload
|
78
|
+
|
79
|
+
send(loader_name).with(*args).for(self)
|
73
80
|
end
|
74
81
|
|
75
82
|
[name, loader_name, loader_variable_name]
|
@@ -6,30 +6,80 @@ module N1Loader
|
|
6
6
|
# Subclasses must define +perform+ method that accepts single argument
|
7
7
|
# and returns hash where key is the element and value is what we want to load.
|
8
8
|
class Loader
|
9
|
-
|
9
|
+
class << self
|
10
|
+
attr_reader :arguments
|
11
|
+
|
12
|
+
# Defines an argument that can be accessed within the loader.
|
13
|
+
#
|
14
|
+
# First defined argument will have the value of first passed argument,
|
15
|
+
# meaning the order is important.
|
16
|
+
def argument(name)
|
17
|
+
@arguments ||= []
|
18
|
+
index = @arguments.size
|
19
|
+
define_method(name) { args[index] }
|
20
|
+
@arguments << name
|
21
|
+
end
|
22
|
+
|
23
|
+
# Defines a custom cache key that is calculated for passed arguments.
|
24
|
+
def cache_key(&block)
|
25
|
+
define_method(:cache_key) do
|
26
|
+
check_arguments!
|
27
|
+
instance_exec(&block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(elements, *args)
|
10
33
|
@elements = elements
|
34
|
+
@args = args
|
11
35
|
end
|
12
36
|
|
13
|
-
def
|
14
|
-
|
37
|
+
def for(element)
|
38
|
+
if loaded.empty? && elements.any?
|
39
|
+
raise NotFilled, "Nothing was preloaded, perhaps you forgot to use fulfill method"
|
40
|
+
end
|
41
|
+
raise NotLoaded, "The data was not preloaded for the given element" unless loaded.key?(element)
|
42
|
+
|
43
|
+
loaded[element]
|
15
44
|
end
|
16
45
|
|
17
|
-
def
|
18
|
-
|
46
|
+
def cache_key
|
47
|
+
args.map(&:object_id)
|
19
48
|
end
|
20
49
|
|
21
|
-
|
22
|
-
|
50
|
+
private
|
51
|
+
|
52
|
+
attr_reader :elements, :args
|
53
|
+
|
54
|
+
def check_arguments!
|
55
|
+
return unless (required = self.class.arguments)
|
56
|
+
return if required.size == args.size
|
57
|
+
|
58
|
+
raise MissingArgument, "Loader defined #{required.size} arguments but #{args.size} were given"
|
23
59
|
end
|
24
60
|
|
25
|
-
def
|
26
|
-
raise
|
61
|
+
def perform(_elements)
|
62
|
+
raise NotImplemented, "Subclasses have to implement the method"
|
63
|
+
end
|
27
64
|
|
28
|
-
|
65
|
+
def fulfill(element, value)
|
66
|
+
@loaded[element] = value
|
29
67
|
end
|
30
68
|
|
31
|
-
|
69
|
+
def loaded # rubocop:disable Metrics/AbcSize
|
70
|
+
return @loaded if @loaded
|
71
|
+
|
72
|
+
check_arguments!
|
73
|
+
|
74
|
+
@loaded = {}.compare_by_identity
|
32
75
|
|
33
|
-
|
76
|
+
if elements.size == 1 && respond_to?(:single)
|
77
|
+
fulfill(elements.first, single(elements.first, *args))
|
78
|
+
elsif elements.any?
|
79
|
+
perform(elements, *args)
|
80
|
+
end
|
81
|
+
|
82
|
+
@loaded
|
83
|
+
end
|
34
84
|
end
|
35
85
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module N1Loader
|
4
|
+
# The class is used for storing collections of loaders for elements per set of arguments.
|
5
|
+
class LoaderCollection
|
6
|
+
attr_reader :loader_class, :elements
|
7
|
+
|
8
|
+
def initialize(loader_class, elements)
|
9
|
+
@loader_class = loader_class
|
10
|
+
@elements = elements
|
11
|
+
end
|
12
|
+
|
13
|
+
def with(*args)
|
14
|
+
loader = loader_class.new(elements, *args)
|
15
|
+
|
16
|
+
loaders[loader.cache_key] ||= loader
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def loaders
|
22
|
+
@loaders ||= {}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -19,9 +19,9 @@ module N1Loader
|
|
19
19
|
elements
|
20
20
|
.group_by { |element| element.class.n1_loader(key) }
|
21
21
|
.map do |loader_class, grouped_elements|
|
22
|
-
|
23
|
-
grouped_elements.each { |grouped_element| grouped_element.n1_loader_set(key,
|
24
|
-
|
22
|
+
loader_collection = N1Loader::LoaderCollection.new(loader_class, grouped_elements)
|
23
|
+
grouped_elements.each { |grouped_element| grouped_element.n1_loader_set(key, loader_collection) }
|
24
|
+
loader_collection
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
data/lib/n1_loader/version.rb
CHANGED
data/lib/n1_loader.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require_relative "n1_loader/version"
|
4
4
|
|
5
5
|
require_relative "n1_loader/core/loader"
|
6
|
+
require_relative "n1_loader/core/loader_collection"
|
6
7
|
require_relative "n1_loader/core/loadable"
|
7
8
|
require_relative "n1_loader/core/preloader"
|
8
9
|
|
@@ -10,4 +11,6 @@ module N1Loader # :nodoc:
|
|
10
11
|
class Error < StandardError; end
|
11
12
|
class NotImplemented < Error; end
|
12
13
|
class NotLoaded < Error; end
|
14
|
+
class NotFilled < Error; end
|
15
|
+
class MissingArgument < Error; end
|
13
16
|
end
|
data/n1_loader.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.authors = ["Evgeniy Demin"]
|
9
9
|
spec.email = ["lawliet.djez@gmail.com"]
|
10
10
|
|
11
|
-
spec.summary = "
|
11
|
+
spec.summary = "Loader to solve N+1 issue for good."
|
12
12
|
spec.homepage = "https://github.com/djezzzl/n1_loader"
|
13
13
|
spec.license = "MIT"
|
14
14
|
spec.required_ruby_version = ">= 2.5.0"
|
@@ -22,10 +22,10 @@ Gem::Specification.new do |spec|
|
|
22
22
|
end
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
|
25
|
-
spec.add_development_dependency "activerecord", "
|
26
|
-
spec.add_development_dependency "ar_lazy_preload", "
|
25
|
+
spec.add_development_dependency "activerecord", ">= 5"
|
26
|
+
spec.add_development_dependency "ar_lazy_preload", ">= 0.6"
|
27
27
|
spec.add_development_dependency "db-query-matchers", "~> 0.10"
|
28
|
-
spec.add_development_dependency "rails", "
|
28
|
+
spec.add_development_dependency "rails", ">= 5"
|
29
29
|
spec.add_development_dependency "rspec", "~> 3.0"
|
30
30
|
spec.add_development_dependency "rspec_junit_formatter", "~> 0.4"
|
31
31
|
spec.add_development_dependency "rubocop", "~> 1.7"
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: n1_loader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evgeniy Demin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '5'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: ar_lazy_preload
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0.
|
33
|
+
version: '0.6'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0.
|
40
|
+
version: '0.6'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: db-query-matchers
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,16 +56,16 @@ dependencies:
|
|
56
56
|
name: rails
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '5'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '5'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -139,18 +139,28 @@ files:
|
|
139
139
|
- LICENSE.txt
|
140
140
|
- README.md
|
141
141
|
- Rakefile
|
142
|
+
- activerecord-gemfiles/ar_5_latest.gemfile
|
143
|
+
- activerecord-gemfiles/ar_6_latest.gemfile
|
144
|
+
- ar_lazy_preload-gemfiles/ar_lazy_preload_0.6.1.gemfile
|
145
|
+
- ar_lazy_preload-gemfiles/ar_lazy_preload_master.gemfile
|
142
146
|
- bin/console
|
143
147
|
- bin/setup
|
144
|
-
- gemfiles/ar_6_latest.gemfile
|
145
148
|
- lib/n1_loader.rb
|
146
149
|
- lib/n1_loader/active_record.rb
|
147
|
-
- lib/n1_loader/active_record/
|
150
|
+
- lib/n1_loader/active_record/associations_preloader_v5.rb
|
151
|
+
- lib/n1_loader/active_record/associations_preloader_v6.rb
|
152
|
+
- lib/n1_loader/active_record/loader.rb
|
153
|
+
- lib/n1_loader/active_record/loader_collection.rb
|
148
154
|
- lib/n1_loader/ar_lazy_preload.rb
|
149
155
|
- lib/n1_loader/ar_lazy_preload/associated_context_builder.rb
|
150
156
|
- lib/n1_loader/ar_lazy_preload/context_adapter.rb
|
151
157
|
- lib/n1_loader/ar_lazy_preload/loadable.rb
|
158
|
+
- lib/n1_loader/ar_lazy_preload/loader_collection_patch.rb
|
159
|
+
- lib/n1_loader/ar_lazy_preload/loader_patch.rb
|
160
|
+
- lib/n1_loader/ar_lazy_preload/preloader_patch.rb
|
152
161
|
- lib/n1_loader/core/loadable.rb
|
153
162
|
- lib/n1_loader/core/loader.rb
|
163
|
+
- lib/n1_loader/core/loader_collection.rb
|
154
164
|
- lib/n1_loader/core/preloader.rb
|
155
165
|
- lib/n1_loader/version.rb
|
156
166
|
- n1_loader.gemspec
|
@@ -179,5 +189,5 @@ requirements: []
|
|
179
189
|
rubygems_version: 3.2.22
|
180
190
|
signing_key:
|
181
191
|
specification_version: 4
|
182
|
-
summary:
|
192
|
+
summary: Loader to solve N+1 issue for good.
|
183
193
|
test_files: []
|