n1_loader 1.4.2 → 1.5.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 +22 -2
- data/.gitignore +2 -1
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +13 -0
- data/README.md +90 -241
- data/activerecord-gemfiles/ar_7_latest.gemfile +3 -0
- data/examples/active_record_integration.rb +33 -0
- data/examples/ar_lazy_integration.rb +37 -0
- data/examples/arguments_support.rb +67 -0
- data/examples/context/service.rb +20 -0
- data/examples/context/setup_ar_lazy.rb +15 -0
- data/examples/context/setup_database.rb +26 -0
- data/examples/core.rb +39 -0
- data/examples/graphql.rb +63 -0
- data/examples/isolated_loader.rb +13 -0
- data/examples/lazy_loading.rb +26 -0
- data/examples/reloading.rb +32 -0
- data/examples/shared_loader.rb +34 -0
- data/examples/single_case.rb +34 -0
- data/lib/n1_loader/active_record/associations_preloader_v7.rb +40 -0
- data/lib/n1_loader/active_record/base.rb +18 -0
- data/lib/n1_loader/active_record/loader_collection.rb +8 -0
- data/lib/n1_loader/active_record.rb +8 -2
- data/lib/n1_loader/core/loader.rb +4 -1
- data/lib/n1_loader/version.rb +1 -1
- data/n1_loader.gemspec +2 -1
- metadata +35 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf26363e90cf5f0b6759776698445183f4abe8442bf366e7109e1cada18b1eaa
|
4
|
+
data.tar.gz: e082a72dc1f910262fbc73040b484ae28e437beee866ca42277a7556d989072a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52993103afc0075d9d71088e6d7e60925ea1740176ee31829acb5d42009a38955b25d8e9124a17fff981b4735f443c04d3532cbea3186850bffc2908f0e40522
|
7
|
+
data.tar.gz: fdcc21a626ff199ccf4a9af7bd860516745693614404c44159414b27601750ea476de8eae593f7bade3782425b638f784db096e6dcdaafa58c71382f3a08b979
|
data/.circleci/config.yml
CHANGED
@@ -106,7 +106,8 @@ workflows:
|
|
106
106
|
]
|
107
107
|
activerecord-gemfile: [
|
108
108
|
"ar_5_latest",
|
109
|
-
"ar_6_latest"
|
109
|
+
"ar_6_latest",
|
110
|
+
"ar_7_latest"
|
110
111
|
]
|
111
112
|
ar_lazy_preload-gemfile: [
|
112
113
|
"ar_lazy_preload_0.6.1",
|
@@ -115,21 +116,40 @@ workflows:
|
|
115
116
|
exclude:
|
116
117
|
# Ruby 2.5 and AR Lazy Preload 1+
|
117
118
|
- ruby-version: "2.5"
|
118
|
-
activerecord-gemfile: "ar_5_latest"
|
119
119
|
ar_lazy_preload-gemfile: "ar_lazy_preload_master"
|
120
|
+
activerecord-gemfile: "ar_5_latest"
|
120
121
|
|
121
122
|
- ruby-version: "2.5"
|
123
|
+
ar_lazy_preload-gemfile: "ar_lazy_preload_master"
|
122
124
|
activerecord-gemfile: "ar_6_latest"
|
125
|
+
|
126
|
+
- ruby-version: "2.5"
|
123
127
|
ar_lazy_preload-gemfile: "ar_lazy_preload_master"
|
128
|
+
activerecord-gemfile: "ar_7_latest"
|
124
129
|
|
125
130
|
# AR 5 and ruby 3+
|
126
131
|
- ruby-version: "latest"
|
127
132
|
activerecord-gemfile: "ar_5_latest"
|
128
133
|
ar_lazy_preload-gemfile: "ar_lazy_preload_0.6.1"
|
134
|
+
|
129
135
|
- ruby-version: "latest"
|
130
136
|
activerecord-gemfile: "ar_5_latest"
|
131
137
|
ar_lazy_preload-gemfile: "ar_lazy_preload_master"
|
132
138
|
|
139
|
+
# AR 7 and ar_lazy_preload < 1
|
140
|
+
- ruby-version: "2.5"
|
141
|
+
activerecord-gemfile: "ar_7_latest"
|
142
|
+
ar_lazy_preload-gemfile: "ar_lazy_preload_0.6.1"
|
143
|
+
|
144
|
+
- ruby-version: "2.7"
|
145
|
+
activerecord-gemfile: "ar_7_latest"
|
146
|
+
ar_lazy_preload-gemfile: "ar_lazy_preload_0.6.1"
|
147
|
+
|
148
|
+
- ruby-version: "latest"
|
149
|
+
activerecord-gemfile: "ar_7_latest"
|
150
|
+
ar_lazy_preload-gemfile: "ar_lazy_preload_0.6.1"
|
151
|
+
|
152
|
+
|
133
153
|
name: ruby-<< matrix.ruby-version >>-<< matrix.activerecord-gemfile >>-<< matrix.ar_lazy_preload-gemfile >>
|
134
154
|
- rubocop:
|
135
155
|
requires:
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## [1.5.0] - 2022/05/01
|
2
|
+
|
3
|
+
- Add support of Rails 7
|
4
|
+
|
5
|
+
## [1.4.4] - 2022/04/29
|
6
|
+
|
7
|
+
- Inject `N1Loader::Loadable` to `ActiveRecord::Base` automatically
|
8
|
+
- Make `reload` to call `n1_clear_cache`
|
9
|
+
|
10
|
+
## [1.4.3] - 2022-04-13
|
11
|
+
|
12
|
+
- Add `default` support to arguments
|
13
|
+
|
1
14
|
## [1.4.2] - 2022-03-01
|
2
15
|
|
3
16
|
- Add n1_clear_cache method which is useful for cases like reload in ActiveRecord
|
data/README.md
CHANGED
@@ -3,298 +3,147 @@
|
|
3
3
|
[![CircleCI][1]][2]
|
4
4
|
[![Gem Version][3]][4]
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
N1Loader is designed to provide a simple way for avoiding [N+1 issues][7] of any kind.
|
7
|
+
For example, it can help with resolving N+1 for:
|
8
|
+
- database querying (most common case)
|
9
|
+
- 3rd party service calls
|
10
|
+
- complex calculations
|
11
|
+
- and many more
|
8
12
|
|
9
|
-
[
|
13
|
+
> [Toptal](https://www.toptal.com#snag-only-shrewd-web-development-experts) is hiring! [Join](https://www.toptal.com#snag-only-shrewd-web-development-experts) as Freelancer or [write me](mailto:lawliet.djez@gmail.com) if you want to join Core team.
|
10
14
|
|
11
|
-
|
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)
|
18
|
-
- it has an integration with [ActiveRecord][5] which makes it brilliant ([example](#activerecord))
|
19
|
-
- it has an integration with [ArLazyPreload][6] which makes it excellent ([example](#arlazypreload))
|
15
|
+
## Killer feature for GraphQL API
|
20
16
|
|
21
|
-
|
17
|
+
N1Loader in combination with [ArLazyPreload][6] is a killer feature for your GraphQL API.
|
18
|
+
Give it a try now and see incredible results instantly! Check out the [example](examples/graphql.rb) and start benefiting from it in your projects!
|
22
19
|
|
23
|
-
## Installation
|
24
|
-
|
25
|
-
Add this line to your application's Gemfile:
|
26
|
-
|
27
|
-
```ruby
|
28
|
-
gem 'n1_loader'
|
29
|
-
```
|
30
|
-
|
31
|
-
You can add integration with [ActiveRecord][5] by:
|
32
|
-
```ruby
|
33
|
-
gem 'n1_loader', require: 'n1_loader/active_record'
|
34
|
-
```
|
35
|
-
|
36
|
-
You can add the integration with [ActiveRecord][5] and [ArLazyPreload][6] by:
|
37
20
|
```ruby
|
38
21
|
gem 'n1_loader', require: 'n1_loader/ar_lazy_preload'
|
39
22
|
```
|
40
23
|
|
41
|
-
##
|
24
|
+
## Enhance [ActiveRecord][5]
|
42
25
|
|
43
|
-
|
44
|
-
class User
|
45
|
-
include N1Loader::Loadable
|
46
|
-
|
47
|
-
# with inline loader
|
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]) }
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# For single object
|
56
|
-
user = User.new
|
57
|
-
user.orders_count
|
26
|
+
Are you working with well-known Rails application? Try it out and see how well N1Loader fulfills missing gaps when you can't define ActiveRecord associations!
|
58
27
|
|
59
|
-
|
60
|
-
|
61
|
-
N1Loader::Preloader.new(users).preload(:orders_count)
|
62
|
-
users.map(&:orders_count)
|
28
|
+
```ruby
|
29
|
+
gem 'n1_loader', require: 'n1_loader/active_record'
|
63
30
|
```
|
64
31
|
|
65
|
-
|
32
|
+
Are you ready to forget about N+1 once and for all? Install [ArLazyPreload][6] and see dreams come true!
|
66
33
|
|
67
34
|
```ruby
|
68
|
-
|
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
|
74
|
-
|
75
|
-
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
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)
|
35
|
+
gem 'n1_loader', require: 'n1_loader/ar_lazy_preload'
|
85
36
|
```
|
86
37
|
|
38
|
+
## Standalone mode
|
87
39
|
|
88
|
-
|
40
|
+
Are you not working with [ActiveRecord][5]? N1Loader is ready to be used as standalone solution! ([full snippet](examples/core.rb))
|
89
41
|
|
90
42
|
```ruby
|
91
|
-
|
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
|
43
|
+
gem 'n1_loader'
|
113
44
|
```
|
114
45
|
|
115
|
-
|
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
|
-
# or
|
133
|
-
user.n1_clear_cache
|
134
|
-
user.orders_count
|
135
|
-
|
136
|
-
users = [User.new, User.new]
|
137
|
-
N1Loader::Preloader.new(users).preload(:orders_count) # => loader was initialized but not yet executed
|
138
|
-
users.map(&:orders_count) # => loader was executed first time without N+1 issue and values were cached
|
46
|
+
## How to use it?
|
139
47
|
|
140
|
-
N1Loader
|
141
|
-
|
142
|
-
```
|
48
|
+
N1Loader provides DSL that allows you to define N+1 ready loaders that can
|
49
|
+
be injected into your objects in a way that you can avoid N+1 issues.
|
143
50
|
|
144
|
-
|
51
|
+
> _Disclaimer_: examples below are working but designed to show N1Loader potentials only.
|
52
|
+
In real live applications, N1Loader can be applied anywhere and in more [elegant way](examples/isolated_loader.rb).
|
145
53
|
|
54
|
+
Let's look at simple example below ([full snippet](examples/active_record_integration.rb)):
|
146
55
|
```ruby
|
147
|
-
class
|
148
|
-
|
149
|
-
|
56
|
+
class User < ActiveRecord::Base
|
57
|
+
has_many :payments
|
58
|
+
|
59
|
+
n1_optimized :payments_total do |users|
|
60
|
+
total_per_user =
|
61
|
+
Payment.group(:user_id)
|
62
|
+
.where(user: users)
|
63
|
+
.sum(:amount)
|
64
|
+
.tap { |h| h.default = 0 }
|
65
|
+
|
66
|
+
users.each do |user|
|
67
|
+
total = total_per_user[user.id]
|
68
|
+
fulfill(user, total)
|
69
|
+
end
|
150
70
|
end
|
151
71
|
end
|
152
72
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
73
|
+
class Payment < ActiveRecord::Base
|
74
|
+
belongs_to :user
|
75
|
+
|
76
|
+
validates :amount, presence: true
|
157
77
|
end
|
158
|
-
```
|
159
78
|
|
160
|
-
|
79
|
+
# A user has many payments.
|
80
|
+
# Assuming, we want to know for group of users, what is a total of their payments, we can do the following:
|
161
81
|
|
162
|
-
|
163
|
-
|
164
|
-
include N1Loader::Loadable
|
82
|
+
# Has N+1 issue
|
83
|
+
p User.all.map { |user| user.payments.sum(&:amount) }
|
165
84
|
|
166
|
-
|
167
|
-
|
168
|
-
orders_per_user = Order.where(user: users).group(:user_id).count
|
85
|
+
# Has no N+1 but we load too many data that we don't actually need
|
86
|
+
p User.all.includes(:payments).map { |user| user.payments.sum(&:amount) }
|
169
87
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
# Optimized for single object loading
|
174
|
-
def single(user)
|
175
|
-
user.orders.count
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
user = User.new
|
181
|
-
user.orders_count # single will be used here
|
182
|
-
|
183
|
-
users = [User.new, User.new]
|
184
|
-
N1Loader::Preloader.new(users).preload(:orders_count)
|
185
|
-
users.map(&:orders_count) # perform will be used once without N+1
|
88
|
+
# Has no N+1 and we load only what we need
|
89
|
+
p User.all.includes(:payments_total).map { |user| user.payments_total }
|
186
90
|
```
|
187
91
|
|
188
|
-
|
92
|
+
Let's assume now, that we want to calculate the total of payments for the given period for a group of users.
|
93
|
+
N1Loader can do that as well! ([full snippet](examples/arguments_support.rb))
|
189
94
|
|
190
95
|
```ruby
|
191
|
-
class User
|
192
|
-
|
193
|
-
|
194
|
-
n1_optimized :orders_count do
|
195
|
-
argument :type
|
196
|
-
|
197
|
-
def perform(users)
|
198
|
-
orders_per_user = Order.where(type: type, user: users).group(:user_id).count
|
199
|
-
|
200
|
-
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
user = User.new
|
206
|
-
user.orders_count(type: :gifts) # The loader will be performed first time for this argument
|
207
|
-
user.orders_count(type: :sales) # The loader will be performed first time for this argument
|
208
|
-
user.orders_count(type: :gifts) # The cached value will be used
|
209
|
-
|
210
|
-
users = [User.new, User.new]
|
211
|
-
N1Loader::Preloader.new(users).preload(:orders_count)
|
212
|
-
users.map { |user| user.orders_count(type: :gifts) } # No N+1 here
|
213
|
-
```
|
96
|
+
class User < ActiveRecord::Base
|
97
|
+
has_many :payments
|
214
98
|
|
215
|
-
|
216
|
-
|
99
|
+
n1_optimized :payments_total do
|
100
|
+
argument :from
|
101
|
+
argument :to
|
217
102
|
|
218
|
-
```ruby
|
219
|
-
class User
|
220
|
-
include N1Loader::Loadable
|
221
|
-
|
222
|
-
n1_optimized :orders_count do
|
223
|
-
argument :sale
|
224
|
-
|
225
|
-
cache_key { sale.id }
|
226
|
-
|
227
103
|
def perform(users)
|
228
|
-
|
229
|
-
|
230
|
-
|
104
|
+
total_per_user =
|
105
|
+
Payment
|
106
|
+
.group(:user_id)
|
107
|
+
.where(created_at: from..to)
|
108
|
+
.where(user: users)
|
109
|
+
.sum(:amount)
|
110
|
+
.tap { |h| h.default = 0 }
|
111
|
+
|
112
|
+
users.each do |user|
|
113
|
+
total = total_per_user[user.id]
|
114
|
+
fulfill(user, total)
|
115
|
+
end
|
231
116
|
end
|
232
117
|
end
|
233
118
|
end
|
234
119
|
|
235
|
-
|
236
|
-
user
|
237
|
-
user.orders_count(sale: Sale.first) # the cached value will be returned
|
238
|
-
```
|
239
|
-
|
240
|
-
|
241
|
-
## Integrations
|
242
|
-
|
243
|
-
### [ActiveRecord][5]
|
244
|
-
|
245
|
-
_Note_: Rails 7 support is coming soon! Stay tuned!
|
246
|
-
|
247
|
-
```ruby
|
248
|
-
class User < ActiveRecord::Base
|
249
|
-
include N1Loader::Loadable
|
250
|
-
|
251
|
-
n1_optimized :orders_count do |users|
|
252
|
-
orders_per_user = Order.where(user: users).group(:user_id).count
|
120
|
+
class Payment < ActiveRecord::Base
|
121
|
+
belongs_to :user
|
253
122
|
|
254
|
-
|
255
|
-
end
|
123
|
+
validates :amount, presence: true
|
256
124
|
end
|
257
125
|
|
258
|
-
#
|
259
|
-
user
|
260
|
-
user.orders_count
|
261
|
-
|
262
|
-
# For many users without N+1
|
263
|
-
User.limit(5).includes(:orders_count).map(&:orders_count)
|
126
|
+
# Has N+1
|
127
|
+
p User.all.map { |user| user.payments.select { |payment| payment.created_at >= from && payment.created_at <= to }.sum(&:amount) }
|
264
128
|
|
265
|
-
#
|
266
|
-
|
267
|
-
N1Loader::Preloader.new(users).preload(:orders_count)
|
129
|
+
# Has no N+1 but we load too many data that we don't need
|
130
|
+
p User.all.includes(:payments).map { |user| user.payments.select { |payment| payment.created_at >= from && payment.created_at <= to }.sum(&:amount) }
|
268
131
|
|
269
|
-
#
|
270
|
-
|
132
|
+
# Has no N+1 and calculation is the most efficient
|
133
|
+
p User.all.includes(:payments_total).map { |user| user.payments_total(from: from, to: to) }
|
271
134
|
```
|
272
135
|
|
273
|
-
|
274
|
-
|
275
|
-
```ruby
|
276
|
-
class User < ActiveRecord::Base
|
277
|
-
include N1Loader::Loadable
|
278
|
-
|
279
|
-
n1_optimized :orders_count do |users|
|
280
|
-
orders_per_user = Order.where(user: users).group(:user_id).count
|
136
|
+
## Features and benefits
|
281
137
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
User.lazy_preload(:orders_count).all.map(&:orders_count)
|
292
|
-
# or
|
293
|
-
User.preload_associations_lazily.all.map(&:orders_count)
|
294
|
-
# or
|
295
|
-
ArLazyPreload.config.auto_preload = true
|
296
|
-
User.all.map(:orders_count)
|
297
|
-
```
|
138
|
+
- N1Loader doesn't use Promises which means it's easy to debug
|
139
|
+
- Doesn't require injection to objects, can be used in [isolation](examples/isolated_loader.rb)
|
140
|
+
- Loads data [lazily](examples/lazy_loading.rb)
|
141
|
+
- Loaders can be [shared](examples/shared_loader.rb) between multiple classes
|
142
|
+
- Loaded data can be [re-fetched](examples/reloading.rb)
|
143
|
+
- Loader can be optimized for [single cases](examples/single_case.rb)
|
144
|
+
- Loader support [arguments](examples/arguments_support.rb)
|
145
|
+
- Has [integration](examples/active_record_integration.rb) with [ActiveRecord][5] which makes it brilliant
|
146
|
+
- Has [integration](examples/ar_lazy_integration.rb) with [ArLazyPreload][6] which makes it excellent
|
298
147
|
|
299
148
|
## Contributing
|
300
149
|
|
@@ -324,4 +173,4 @@ Copyright (c) Evgeniy Demin. See [LICENSE.txt](LICENSE.txt) for further details.
|
|
324
173
|
[5]: https://github.com/rails/rails/tree/main/activerecord
|
325
174
|
[6]: https://github.com/DmitryTsepelev/ar_lazy_preload
|
326
175
|
[7]: https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping
|
327
|
-
[8]: https://github.com/djezzzl/n1_loader
|
176
|
+
[8]: https://github.com/djezzzl/n1_loader
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "n1_loader/active_record"
|
4
|
+
|
5
|
+
require_relative 'context/setup_database'
|
6
|
+
|
7
|
+
class User < ActiveRecord::Base
|
8
|
+
has_many :payments
|
9
|
+
|
10
|
+
n1_optimized :payments_total do |users|
|
11
|
+
total_per_user = Payment.group(:user_id).where(user: users).sum(:amount).tap { |h| h.default = 0 }
|
12
|
+
|
13
|
+
users.each do |user|
|
14
|
+
total = total_per_user[user.id]
|
15
|
+
fulfill(user, total)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Payment < ActiveRecord::Base
|
21
|
+
belongs_to :user
|
22
|
+
|
23
|
+
validates :amount, presence: true
|
24
|
+
end
|
25
|
+
|
26
|
+
fill_database
|
27
|
+
|
28
|
+
# Has N+1
|
29
|
+
p User.all.map { |user| user.payments.sum(&:amount) }
|
30
|
+
# Has no N+1 but we load too many data that we don't need
|
31
|
+
p User.all.includes(:payments).map { |user| user.payments.sum(&:amount) }
|
32
|
+
# Has no N+1 and calculation is the most efficient
|
33
|
+
p User.all.includes(:payments_total).map(&:payments_total)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "n1_loader/ar_lazy_preload"
|
4
|
+
|
5
|
+
require_relative 'context/setup_ar_lazy'
|
6
|
+
require_relative 'context/setup_database'
|
7
|
+
|
8
|
+
class User < ActiveRecord::Base
|
9
|
+
has_many :payments
|
10
|
+
|
11
|
+
n1_optimized :payments_total do |users|
|
12
|
+
total_per_user = Payment.group(:user_id).where(user: users).sum(:amount).tap { |h| h.default = 0 }
|
13
|
+
|
14
|
+
users.each do |user|
|
15
|
+
total = total_per_user[user.id]
|
16
|
+
fulfill(user, total)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Payment < ActiveRecord::Base
|
22
|
+
belongs_to :user
|
23
|
+
|
24
|
+
validates :amount, presence: true
|
25
|
+
end
|
26
|
+
|
27
|
+
fill_database
|
28
|
+
|
29
|
+
# Has N+1
|
30
|
+
p User.all.map { |user| user.payments.sum(&:amount) }
|
31
|
+
|
32
|
+
# Has no N+1 but we load too many data that we don't need
|
33
|
+
p User.preload_associations_lazily.map(&:payments_total)
|
34
|
+
|
35
|
+
# Has no N+1 and calculation is the most efficient
|
36
|
+
ArLazyPreload.config.auto_preload = true
|
37
|
+
User.all.map(&:payments_total)
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "n1_loader/active_record"
|
4
|
+
|
5
|
+
require_relative 'context/setup_database'
|
6
|
+
|
7
|
+
class User < ActiveRecord::Base
|
8
|
+
has_many :payments
|
9
|
+
|
10
|
+
n1_optimized :payments_total do
|
11
|
+
# Arguments can be:
|
12
|
+
# argument :something, optional: true
|
13
|
+
# argument :something, default: -> { 100 }
|
14
|
+
#
|
15
|
+
# Note: do not use mutable (mostly timing related) defaults like:
|
16
|
+
# argument :from, default -> { 2.minutes.from_now }
|
17
|
+
# because such values will be unique for every loader call which will make N+1 issue stay
|
18
|
+
argument :from
|
19
|
+
argument :to
|
20
|
+
|
21
|
+
# This is used to define logic how loaders are compared to each other
|
22
|
+
# default is:
|
23
|
+
# cache_key { *arguments.map(&:object_id) }
|
24
|
+
cache_key { [from, to] }
|
25
|
+
|
26
|
+
def perform(users)
|
27
|
+
total_per_user =
|
28
|
+
Payment
|
29
|
+
.group(:user_id)
|
30
|
+
.where(created_at: from..to)
|
31
|
+
.where(user: users)
|
32
|
+
.sum(:amount)
|
33
|
+
.tap { |h| h.default = 0 }
|
34
|
+
|
35
|
+
users.each do |user|
|
36
|
+
total = total_per_user[user.id]
|
37
|
+
fulfill(user, total)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Payment < ActiveRecord::Base
|
44
|
+
belongs_to :user
|
45
|
+
|
46
|
+
validates :amount, presence: true
|
47
|
+
end
|
48
|
+
|
49
|
+
fill_database
|
50
|
+
|
51
|
+
from = 2.days.ago
|
52
|
+
to = 1.day.ago
|
53
|
+
|
54
|
+
# Has N+1
|
55
|
+
p User.all.map { |user|
|
56
|
+
user.payments.select do |payment|
|
57
|
+
payment.created_at >= from && payment.created_at <= to
|
58
|
+
end.sum(&:amount)
|
59
|
+
}
|
60
|
+
# Has no N+1 but we load too many data that we don't need
|
61
|
+
p User.all.includes(:payments).map { |user|
|
62
|
+
user.payments.select do |payment|
|
63
|
+
payment.created_at >= from && payment.created_at <= to
|
64
|
+
end.sum(&:amount)
|
65
|
+
}
|
66
|
+
# Has no N+1 and calculation is the most efficient
|
67
|
+
p User.all.includes(:payments_total).map { |user| user.payments_total(from: from, to: to) }
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# 3rd party service, or database, or anything else that can perform in batches
|
2
|
+
class Service
|
3
|
+
def self.count
|
4
|
+
@count ||= 0
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.increase!
|
8
|
+
@count = (@count || 0) + 1
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.receive(*users)
|
12
|
+
increase!
|
13
|
+
|
14
|
+
users.flatten.map(&:object_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.single(user)
|
18
|
+
user.object_id
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
ActiveSupport.on_load(:active_record) do
|
2
|
+
ActiveRecord::Base.include(ArLazyPreload::Base)
|
3
|
+
|
4
|
+
ActiveRecord::Relation.prepend(ArLazyPreload::Relation)
|
5
|
+
ActiveRecord::AssociationRelation.prepend(ArLazyPreload::AssociationRelation)
|
6
|
+
ActiveRecord::Relation::Merger.prepend(ArLazyPreload::Merger)
|
7
|
+
|
8
|
+
[
|
9
|
+
ActiveRecord::Associations::CollectionAssociation,
|
10
|
+
ActiveRecord::Associations::Association
|
11
|
+
].each { |klass| klass.prepend(ArLazyPreload::Association) }
|
12
|
+
|
13
|
+
ActiveRecord::Associations::CollectionAssociation.prepend(ArLazyPreload::CollectionAssociation)
|
14
|
+
ActiveRecord::Associations::CollectionProxy.prepend(ArLazyPreload::CollectionProxy)
|
15
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "sqlite3"
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
4
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
5
|
+
ActiveRecord::Base.connection.drop_table(table, force: :cascade)
|
6
|
+
end
|
7
|
+
ActiveRecord::Schema.verbose = false
|
8
|
+
ActiveRecord::Base.logger = Logger.new($stdout)
|
9
|
+
|
10
|
+
ActiveRecord::Schema.define(version: 1) do
|
11
|
+
create_table(:payments) do |t|
|
12
|
+
t.belongs_to :user
|
13
|
+
t.integer :amount
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
create_table(:users)
|
17
|
+
end
|
18
|
+
|
19
|
+
def fill_database
|
20
|
+
10.times do
|
21
|
+
user = User.create!
|
22
|
+
10.times do
|
23
|
+
Payment.create!(user: user, amount: rand(1000))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/examples/core.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "n1_loader"
|
4
|
+
|
5
|
+
require_relative 'context/service'
|
6
|
+
|
7
|
+
# Class that wants to request 3rd party service without N+1
|
8
|
+
class User
|
9
|
+
include N1Loader::Loadable
|
10
|
+
|
11
|
+
def unoptimized_call
|
12
|
+
Service.receive(self)[0]
|
13
|
+
end
|
14
|
+
|
15
|
+
n1_optimized :optimized_call do |users|
|
16
|
+
data = Service.receive(users)
|
17
|
+
|
18
|
+
users.each_with_index do |user, index|
|
19
|
+
fulfill(user, data[index])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# works fine for single case
|
25
|
+
user = User.new
|
26
|
+
p "Works correctly: #{user.unoptimized_call == user.optimized_call}"
|
27
|
+
|
28
|
+
users = [User.new, User.new]
|
29
|
+
|
30
|
+
# Has N+1
|
31
|
+
count_before = Service.count
|
32
|
+
p users.map(&:unoptimized_call)
|
33
|
+
p "Has N+1 #{Service.count == count_before + users.count}"
|
34
|
+
|
35
|
+
# Has no N+1
|
36
|
+
count_before = Service.count
|
37
|
+
N1Loader::Preloader.new(users).preload(:optimized_call)
|
38
|
+
p users.map(&:optimized_call)
|
39
|
+
p "Has no N+1: #{Service.count == count_before + 1}"
|
data/examples/graphql.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "n1_loader/ar_lazy_preload"
|
4
|
+
require 'graphql'
|
5
|
+
|
6
|
+
require_relative 'context/setup_database'
|
7
|
+
require_relative 'context/setup_ar_lazy'
|
8
|
+
|
9
|
+
class User < ActiveRecord::Base
|
10
|
+
has_many :payments
|
11
|
+
|
12
|
+
n1_optimized :payments_total do |users|
|
13
|
+
total_per_user = Payment.group(:user_id).where(user: users).sum(:amount).tap { |h| h.default = 0 }
|
14
|
+
|
15
|
+
users.each do |user|
|
16
|
+
total = total_per_user[user.id]
|
17
|
+
fulfill(user, total)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Payment < ActiveRecord::Base
|
23
|
+
belongs_to :user
|
24
|
+
|
25
|
+
validates :amount, presence: true
|
26
|
+
end
|
27
|
+
|
28
|
+
10.times do
|
29
|
+
user = User.create!
|
30
|
+
10.times do
|
31
|
+
Payment.create!(user: user, amount: rand(1000))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
ArLazyPreload.config.auto_preload = true
|
36
|
+
# Or use +preload_associations_lazily+ when loading objects from database
|
37
|
+
|
38
|
+
class UserType < GraphQL::Schema::Object
|
39
|
+
field :payments_total, Integer
|
40
|
+
end
|
41
|
+
|
42
|
+
class QueryType < GraphQL::Schema::Object
|
43
|
+
field :users, [UserType]
|
44
|
+
|
45
|
+
def users
|
46
|
+
User.all
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Schema < GraphQL::Schema
|
51
|
+
query QueryType
|
52
|
+
end
|
53
|
+
|
54
|
+
query_string = <<~GQL
|
55
|
+
{
|
56
|
+
users {
|
57
|
+
paymentsTotal
|
58
|
+
}
|
59
|
+
}
|
60
|
+
GQL
|
61
|
+
|
62
|
+
# No N+1. And never will be!
|
63
|
+
p Schema.execute(query_string)['data']
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'n1_loader'
|
2
|
+
|
3
|
+
class IsolatedLoader < N1Loader::Loader
|
4
|
+
def perform(elements)
|
5
|
+
elements.each { |element| fulfill(element, [element]) }
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
objects = [1, 2, 3, 4]
|
10
|
+
loader = IsolatedLoader.new(objects)
|
11
|
+
objects.each do |object|
|
12
|
+
loader.for(object) # => it has no N+1 and it doesn't require to be injected in the class
|
13
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'n1_loader'
|
2
|
+
|
3
|
+
require_relative 'context/service'
|
4
|
+
|
5
|
+
# Class that wants to request 3rd party service without N+1
|
6
|
+
class User
|
7
|
+
include N1Loader::Loadable
|
8
|
+
|
9
|
+
n1_optimized :optimized_call do |users|
|
10
|
+
data = Service.receive(users)
|
11
|
+
|
12
|
+
users.each_with_index do |user, index|
|
13
|
+
fulfill(user, data[index])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
users = [User.new, User.new, User.new]
|
19
|
+
|
20
|
+
# Initialized loader but didn't perform it yet
|
21
|
+
N1Loader::Preloader.new(users).preload(:optimized_call)
|
22
|
+
p "No calls yet: #{Service.count == 0}"
|
23
|
+
|
24
|
+
# First time loading
|
25
|
+
users.map(&:optimized_call)
|
26
|
+
p "First time loaded: #{Service.count == 1}"
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'n1_loader'
|
2
|
+
|
3
|
+
require_relative 'context/service'
|
4
|
+
|
5
|
+
class User
|
6
|
+
include N1Loader::Loadable
|
7
|
+
|
8
|
+
n1_optimized :optimized_call do |users|
|
9
|
+
data = Service.receive(users)
|
10
|
+
|
11
|
+
users.each_with_index do |user, index|
|
12
|
+
fulfill(user, data[index])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
users = [User.new, User.new, User.new]
|
18
|
+
|
19
|
+
# Initialized loader but didn't perform it yet
|
20
|
+
N1Loader::Preloader.new(users).preload(:optimized_call)
|
21
|
+
p "No calls yet: #{Service.count == 0}"
|
22
|
+
|
23
|
+
# First time loading
|
24
|
+
users.map(&:optimized_call)
|
25
|
+
p "First time loaded: #{Service.count == 1}"
|
26
|
+
|
27
|
+
users.first.optimized_call(reload: true)
|
28
|
+
p "Reloaded for this object only: #{Service.count == 2}"
|
29
|
+
|
30
|
+
users.first.n1_clear_cache
|
31
|
+
users.first.optimized_call
|
32
|
+
p "Reloaded for this object only: #{Service.count == 3}"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'n1_loader'
|
2
|
+
|
3
|
+
require_relative 'context/service'
|
4
|
+
|
5
|
+
# Loader that will be shared between multiple classes
|
6
|
+
class SharedLoader < N1Loader::Loader
|
7
|
+
def perform(objects)
|
8
|
+
data = Service.receive(objects)
|
9
|
+
|
10
|
+
objects.each_with_index do |user, index|
|
11
|
+
fulfill(user, data[index])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class User
|
17
|
+
include N1Loader::Loadable
|
18
|
+
|
19
|
+
n1_optimized :optimized_call, SharedLoader
|
20
|
+
end
|
21
|
+
|
22
|
+
class Payment
|
23
|
+
include N1Loader::Loadable
|
24
|
+
|
25
|
+
n1_optimized :optimized_call, SharedLoader
|
26
|
+
end
|
27
|
+
|
28
|
+
objects = [User.new, Payment.new, User.new, Payment.new]
|
29
|
+
|
30
|
+
N1Loader::Preloader.new(objects).preload(:optimized_call)
|
31
|
+
|
32
|
+
# First time loading for all objects
|
33
|
+
objects.map(&:optimized_call)
|
34
|
+
p "Loaded for all once: #{Service.count == 1}"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'n1_loader'
|
2
|
+
|
3
|
+
require_relative 'context/service'
|
4
|
+
|
5
|
+
# Loader that will be shared between multiple classes
|
6
|
+
class OptimizedLoader < N1Loader::Loader
|
7
|
+
def perform(objects)
|
8
|
+
data = Service.receive(objects)
|
9
|
+
|
10
|
+
objects.each_with_index do |user, index|
|
11
|
+
fulfill(user, data[index])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def single(object)
|
16
|
+
Service.single(object)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class User
|
21
|
+
include N1Loader::Loadable
|
22
|
+
|
23
|
+
n1_optimized :optimized_call, OptimizedLoader
|
24
|
+
end
|
25
|
+
|
26
|
+
objects = [User.new, User.new]
|
27
|
+
|
28
|
+
N1Loader::Preloader.new(objects).preload(:optimized_call)
|
29
|
+
|
30
|
+
objects.map(&:optimized_call)
|
31
|
+
p "Used multi-case perform: #{Service.count == 1}"
|
32
|
+
|
33
|
+
User.new.optimized_call
|
34
|
+
p "Used single-case perform: #{Service.count == 1}"
|
@@ -0,0 +1,40 @@
|
|
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_reflection(reflection, records)
|
14
|
+
return super unless reflection.is_a?(N1LoaderReflection)
|
15
|
+
|
16
|
+
N1Loader::Preloader.new(records).preload(reflection.key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def grouped_records # rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
20
|
+
n1_load_records, records = source_records.partition do |record|
|
21
|
+
record.class.respond_to?(:n1_loader_defined?) && record.class.n1_loader_defined?(association)
|
22
|
+
end
|
23
|
+
|
24
|
+
h = n1_load_records.group_by do |record|
|
25
|
+
N1LoaderReflection.new(association, record.class.n1_loader(association))
|
26
|
+
end
|
27
|
+
|
28
|
+
polymorphic_parent = !root? && parent.polymorphic?
|
29
|
+
records.each do |record|
|
30
|
+
reflection = record.class._reflect_on_association(association)
|
31
|
+
next if polymorphic_parent && !reflection || !record.association(association).klass
|
32
|
+
|
33
|
+
(h[reflection] ||= []) << record
|
34
|
+
end
|
35
|
+
h
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module N1Loader
|
4
|
+
module ActiveRecord
|
5
|
+
# Extension module for ActiveRecord::Base
|
6
|
+
module Base
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
include N1Loader::Loadable
|
10
|
+
|
11
|
+
# Clear N1Loader cache on reloading the object
|
12
|
+
def reload(*)
|
13
|
+
n1_clear_cache
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -16,13 +16,19 @@ end
|
|
16
16
|
ActiveSupport.on_load(:active_record) do
|
17
17
|
require_relative "active_record/loader"
|
18
18
|
require_relative "active_record/loader_collection"
|
19
|
+
require_relative "active_record/base"
|
19
20
|
|
20
21
|
case ActiveRecord::VERSION::MAJOR
|
21
22
|
when 6
|
22
23
|
require_relative "active_record/associations_preloader_v6"
|
23
|
-
|
24
|
+
ActiveRecord::Associations::Preloader.prepend(N1Loader::ActiveRecord::Associations::Preloader)
|
25
|
+
when 5
|
24
26
|
require_relative "active_record/associations_preloader_v5"
|
27
|
+
ActiveRecord::Associations::Preloader.prepend(N1Loader::ActiveRecord::Associations::Preloader)
|
28
|
+
else
|
29
|
+
require_relative "active_record/associations_preloader_v7"
|
30
|
+
ActiveRecord::Associations::Preloader::Branch.prepend(N1Loader::ActiveRecord::Associations::Preloader)
|
25
31
|
end
|
26
32
|
|
27
|
-
ActiveRecord::
|
33
|
+
ActiveRecord::Base.include(N1Loader::ActiveRecord::Base)
|
28
34
|
end
|
@@ -17,10 +17,13 @@ module N1Loader
|
|
17
17
|
# @param name [Symbol]
|
18
18
|
# @param opts [Hash]
|
19
19
|
# @option opts [Boolean] optional false by default
|
20
|
+
# @option opts [Proc] default
|
20
21
|
def argument(name, **opts)
|
22
|
+
opts[:optional] = true if opts[:default]
|
23
|
+
|
21
24
|
@arguments ||= []
|
22
25
|
|
23
|
-
define_method(name) { args[name] }
|
26
|
+
define_method(name) { args[name] ||= opts[:default]&.call }
|
24
27
|
|
25
28
|
@arguments << opts.merge(name: name)
|
26
29
|
end
|
data/lib/n1_loader/version.rb
CHANGED
data/n1_loader.gemspec
CHANGED
@@ -24,7 +24,8 @@ Gem::Specification.new do |spec|
|
|
24
24
|
|
25
25
|
spec.add_development_dependency "activerecord", ">= 5"
|
26
26
|
spec.add_development_dependency "ar_lazy_preload", ">= 0.6"
|
27
|
-
spec.add_development_dependency "db-query-matchers", "~> 0.
|
27
|
+
spec.add_development_dependency "db-query-matchers", "~> 0.11"
|
28
|
+
spec.add_development_dependency "graphql", "~> 2.0"
|
28
29
|
spec.add_development_dependency "rails", ">= 5"
|
29
30
|
spec.add_development_dependency "rspec", "~> 3.0"
|
30
31
|
spec.add_development_dependency "rspec_junit_formatter", "~> 0.4"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: n1_loader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.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: 2022-
|
11
|
+
date: 2022-05-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -44,14 +44,28 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0.
|
47
|
+
version: '0.11'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '0.
|
54
|
+
version: '0.11'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: graphql
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rails
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -141,14 +155,30 @@ files:
|
|
141
155
|
- Rakefile
|
142
156
|
- activerecord-gemfiles/ar_5_latest.gemfile
|
143
157
|
- activerecord-gemfiles/ar_6_latest.gemfile
|
158
|
+
- activerecord-gemfiles/ar_7_latest.gemfile
|
144
159
|
- ar_lazy_preload-gemfiles/ar_lazy_preload_0.6.1.gemfile
|
145
160
|
- ar_lazy_preload-gemfiles/ar_lazy_preload_master.gemfile
|
146
161
|
- bin/console
|
147
162
|
- bin/setup
|
163
|
+
- examples/active_record_integration.rb
|
164
|
+
- examples/ar_lazy_integration.rb
|
165
|
+
- examples/arguments_support.rb
|
166
|
+
- examples/context/service.rb
|
167
|
+
- examples/context/setup_ar_lazy.rb
|
168
|
+
- examples/context/setup_database.rb
|
169
|
+
- examples/core.rb
|
170
|
+
- examples/graphql.rb
|
171
|
+
- examples/isolated_loader.rb
|
172
|
+
- examples/lazy_loading.rb
|
173
|
+
- examples/reloading.rb
|
174
|
+
- examples/shared_loader.rb
|
175
|
+
- examples/single_case.rb
|
148
176
|
- lib/n1_loader.rb
|
149
177
|
- lib/n1_loader/active_record.rb
|
150
178
|
- lib/n1_loader/active_record/associations_preloader_v5.rb
|
151
179
|
- lib/n1_loader/active_record/associations_preloader_v6.rb
|
180
|
+
- lib/n1_loader/active_record/associations_preloader_v7.rb
|
181
|
+
- lib/n1_loader/active_record/base.rb
|
152
182
|
- lib/n1_loader/active_record/loader.rb
|
153
183
|
- lib/n1_loader/active_record/loader_collection.rb
|
154
184
|
- lib/n1_loader/ar_lazy_preload.rb
|
@@ -186,7 +216,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
216
|
- !ruby/object:Gem::Version
|
187
217
|
version: '0'
|
188
218
|
requirements: []
|
189
|
-
rubygems_version: 3.
|
219
|
+
rubygems_version: 3.1.6
|
190
220
|
signing_key:
|
191
221
|
specification_version: 4
|
192
222
|
summary: Loader to solve N+1 issue for good.
|