n1_loader 1.5.0 → 1.6.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 +0 -14
- data/.github/workflows/rubocop.yml +23 -0
- data/.github/workflows/tests.yml +62 -0
- data/.rubocop.yml +2 -1
- data/CHANGELOG.md +8 -0
- data/README.md +89 -0
- data/examples/ar_lazy_integration.rb +3 -4
- data/examples/ar_lazy_integration_with_isolated_loader.rb +39 -0
- data/guides/enhanced-activerecord.md +266 -0
- data/lib/n1_loader/ar_lazy_preload/context.rb +7 -0
- data/lib/n1_loader/ar_lazy_preload/context_adapter.rb +4 -1
- data/lib/n1_loader/ar_lazy_preload/loader.rb +31 -0
- data/lib/n1_loader/ar_lazy_preload.rb +2 -0
- data/lib/n1_loader/core/loader.rb +3 -1
- data/lib/n1_loader/version.rb +1 -1
- data/n1_loader.gemspec +2 -2
- metadata +11 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d914dd09225515a579945ab616916bf1ad446101c5051c4cf1b72c46d38c107
|
|
4
|
+
data.tar.gz: a304c32e35294c5646fafe5c65a5de6c637e80e080d80ac16f957d77a3ebe4c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 738d8ca021bca48a41486689c74506af32dc2bca8a542398272edaa72aa57dd49247249d04afe8d9b834cc28cd9b725f0a9b1d8d7f5dd80176cfd3c9ca711f08
|
|
7
|
+
data.tar.gz: c73ddc81593e0cbd0450457143726b9d57fbce0a27c09ede70ea3f269816b7ae7374e5dc1c06dda4e8b0ee8c21a0c96248ed4afb9e3d7f26ec5b5ac46e038d16
|
data/.circleci/config.yml
CHANGED
|
@@ -100,7 +100,6 @@ workflows:
|
|
|
100
100
|
matrix:
|
|
101
101
|
parameters:
|
|
102
102
|
ruby-version: [
|
|
103
|
-
"2.5",
|
|
104
103
|
"2.7",
|
|
105
104
|
"latest"
|
|
106
105
|
]
|
|
@@ -114,19 +113,6 @@ workflows:
|
|
|
114
113
|
"ar_lazy_preload_master"
|
|
115
114
|
]
|
|
116
115
|
exclude:
|
|
117
|
-
# Ruby 2.5 and AR Lazy Preload 1+
|
|
118
|
-
- ruby-version: "2.5"
|
|
119
|
-
ar_lazy_preload-gemfile: "ar_lazy_preload_master"
|
|
120
|
-
activerecord-gemfile: "ar_5_latest"
|
|
121
|
-
|
|
122
|
-
- ruby-version: "2.5"
|
|
123
|
-
ar_lazy_preload-gemfile: "ar_lazy_preload_master"
|
|
124
|
-
activerecord-gemfile: "ar_6_latest"
|
|
125
|
-
|
|
126
|
-
- ruby-version: "2.5"
|
|
127
|
-
ar_lazy_preload-gemfile: "ar_lazy_preload_master"
|
|
128
|
-
activerecord-gemfile: "ar_7_latest"
|
|
129
|
-
|
|
130
116
|
# AR 5 and ruby 3+
|
|
131
117
|
- ruby-version: "latest"
|
|
132
118
|
activerecord-gemfile: "ar_5_latest"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Rubocop
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
- pull_request
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
tests:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v3
|
|
12
|
+
|
|
13
|
+
- name: Set up Ruby
|
|
14
|
+
uses: ruby/setup-ruby@v1
|
|
15
|
+
with:
|
|
16
|
+
ruby-version: 2.7
|
|
17
|
+
bundler-cache: true
|
|
18
|
+
|
|
19
|
+
- name: Install dependencies
|
|
20
|
+
run: bundle install
|
|
21
|
+
|
|
22
|
+
- name: Run Rubocop
|
|
23
|
+
run: bundle exec rubocop
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: RSpec tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
- pull_request
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
tests:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
ruby-version:
|
|
13
|
+
- '2.7'
|
|
14
|
+
- '3.0'
|
|
15
|
+
- 'head'
|
|
16
|
+
activerecord-gemfile:
|
|
17
|
+
- 'ar_5_latest'
|
|
18
|
+
- 'ar_6_latest'
|
|
19
|
+
- 'ar_7_latest'
|
|
20
|
+
ar_lazy_preload-gemfile:
|
|
21
|
+
- 'ar_lazy_preload_0.6.1'
|
|
22
|
+
- 'ar_lazy_preload_master'
|
|
23
|
+
exclude:
|
|
24
|
+
- ruby-version: 'head'
|
|
25
|
+
activerecord-gemfile: 'ar_5_latest'
|
|
26
|
+
|
|
27
|
+
- ruby-version: 'head'
|
|
28
|
+
activerecord-gemfile: 'ar_5_latest'
|
|
29
|
+
|
|
30
|
+
- ruby-version: '3.0'
|
|
31
|
+
activerecord-gemfile: 'ar_5_latest'
|
|
32
|
+
|
|
33
|
+
- ruby-version: '3.0'
|
|
34
|
+
activerecord-gemfile: 'ar_5_latest'
|
|
35
|
+
|
|
36
|
+
- activerecord-gemfile: 'ar_7_latest'
|
|
37
|
+
ar_lazy_preload-gemfile: 'ar_lazy_preload_0.6.1'
|
|
38
|
+
|
|
39
|
+
env:
|
|
40
|
+
ACTIVERECORD_GEMFILE: ${{ matrix.activerecord-gemfile }}
|
|
41
|
+
AR_LAZY_PRELOAD_GEMFILE: ${{ matrix.ar_lazy_preload-gemfile }}
|
|
42
|
+
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v3
|
|
45
|
+
|
|
46
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
47
|
+
uses: ruby/setup-ruby@v1
|
|
48
|
+
with:
|
|
49
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
50
|
+
bundler-cache: true
|
|
51
|
+
|
|
52
|
+
- name: Install dependencies
|
|
53
|
+
run: bundle install
|
|
54
|
+
|
|
55
|
+
- name: Run Core tests
|
|
56
|
+
run: bundle exec rspec spec/n1_loader_spec.rb
|
|
57
|
+
|
|
58
|
+
- name: Run ActiveRecord tests
|
|
59
|
+
run: bundle exec rspec spec/n1_loader_spec.rb spec/activerecord_spec.rb
|
|
60
|
+
|
|
61
|
+
- name: Run ArLazyPreload tests
|
|
62
|
+
run: bundle exec rspec spec/n1_loader_spec.rb spec/activerecord_spec.rb spec/ar_lazy_preload_spec.rb
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [1.6.0] - 2022/10/24
|
|
2
|
+
|
|
3
|
+
- Add support of ArLazyPreload context for isolated loaders.
|
|
4
|
+
|
|
5
|
+
## [1.5.1] - 2022/09/20
|
|
6
|
+
|
|
7
|
+
- Fix support of falsey value of arguments. Thanks [Aitor Lopez Beltran](https://github.com/aitorlb) for the [contribution](https://github.com/djezzzl/n1_loader/pull/23)!
|
|
8
|
+
|
|
1
9
|
## [1.5.0] - 2022/05/01
|
|
2
10
|
|
|
3
11
|
- Add support of Rails 7
|
data/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[![CircleCI][1]][2]
|
|
4
4
|
[![Gem Version][3]][4]
|
|
5
|
+
[![][9]][10]
|
|
5
6
|
|
|
6
7
|
N1Loader is designed to provide a simple way for avoiding [N+1 issues][7] of any kind.
|
|
7
8
|
For example, it can help with resolving N+1 for:
|
|
@@ -12,6 +13,8 @@ For example, it can help with resolving N+1 for:
|
|
|
12
13
|
|
|
13
14
|
> [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.
|
|
14
15
|
|
|
16
|
+
___Support:___ ActiveRecord 5, 6, and 7.
|
|
17
|
+
|
|
15
18
|
## Killer feature for GraphQL API
|
|
16
19
|
|
|
17
20
|
N1Loader in combination with [ArLazyPreload][6] is a killer feature for your GraphQL API.
|
|
@@ -24,6 +27,7 @@ gem 'n1_loader', require: 'n1_loader/ar_lazy_preload'
|
|
|
24
27
|
## Enhance [ActiveRecord][5]
|
|
25
28
|
|
|
26
29
|
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!
|
|
30
|
+
Check out the detailed [guide](guides/enhanced-activerecord.md) with examples or its [short version](examples/active_record_integration.rb).
|
|
27
31
|
|
|
28
32
|
```ruby
|
|
29
33
|
gem 'n1_loader', require: 'n1_loader/active_record'
|
|
@@ -145,6 +149,89 @@ p User.all.includes(:payments_total).map { |user| user.payments_total(from: from
|
|
|
145
149
|
- Has [integration](examples/active_record_integration.rb) with [ActiveRecord][5] which makes it brilliant
|
|
146
150
|
- Has [integration](examples/ar_lazy_integration.rb) with [ArLazyPreload][6] which makes it excellent
|
|
147
151
|
|
|
152
|
+
### Feature killer for [ArLazyPreload][6] integration with isolated loaders
|
|
153
|
+
|
|
154
|
+
In [version 1.6.0](CHANGELOG.md#160---20221019) isolated loaders were integrated with [ArLazyPreload][6] context.
|
|
155
|
+
This means, it isn't required to inject `N1Loader` into your [ActiveRecord][5] models to avoid N+1 issues out of the box.
|
|
156
|
+
It is especially great as many engineers are trying to avoid extra coupling between their models/services when it's possible.
|
|
157
|
+
And this feature was designed exactly for this without losing an out of a box solution for N+1.
|
|
158
|
+
|
|
159
|
+
Without further ado, please have a look at the [example](examples/ar_lazy_integration_with_isolated_loader.rb).
|
|
160
|
+
|
|
161
|
+
_Spoiler:_ as soon as you have your loader defined, it will be as simple as `Loader.for(element)` to get your data efficiently and without N+1.
|
|
162
|
+
|
|
163
|
+
## Funding
|
|
164
|
+
|
|
165
|
+
### Open Collective Backers
|
|
166
|
+
|
|
167
|
+
You're an individual who wants to support the project with a monthly donation. Your logo will be available on the Github page. [[Become a backer](https://opencollective.com/n1_loader#backer)]
|
|
168
|
+
|
|
169
|
+
<a href="https://opencollective.com/n1_loader/backer/0/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/0/avatar.svg"></a>
|
|
170
|
+
<a href="https://opencollective.com/n1_loader/backer/1/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/1/avatar.svg"></a>
|
|
171
|
+
<a href="https://opencollective.com/n1_loader/backer/2/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/2/avatar.svg"></a>
|
|
172
|
+
<a href="https://opencollective.com/n1_loader/backer/3/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/3/avatar.svg"></a>
|
|
173
|
+
<a href="https://opencollective.com/n1_loader/backer/4/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/4/avatar.svg"></a>
|
|
174
|
+
<a href="https://opencollective.com/n1_loader/backer/5/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/5/avatar.svg"></a>
|
|
175
|
+
<a href="https://opencollective.com/n1_loader/backer/6/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/6/avatar.svg"></a>
|
|
176
|
+
<a href="https://opencollective.com/n1_loader/backer/7/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/7/avatar.svg"></a>
|
|
177
|
+
<a href="https://opencollective.com/n1_loader/backer/8/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/8/avatar.svg"></a>
|
|
178
|
+
<a href="https://opencollective.com/n1_loader/backer/9/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/9/avatar.svg"></a>
|
|
179
|
+
<a href="https://opencollective.com/n1_loader/backer/10/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/10/avatar.svg"></a>
|
|
180
|
+
<a href="https://opencollective.com/n1_loader/backer/11/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/11/avatar.svg"></a>
|
|
181
|
+
<a href="https://opencollective.com/n1_loader/backer/12/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/12/avatar.svg"></a>
|
|
182
|
+
<a href="https://opencollective.com/n1_loader/backer/13/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/13/avatar.svg"></a>
|
|
183
|
+
<a href="https://opencollective.com/n1_loader/backer/14/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/14/avatar.svg"></a>
|
|
184
|
+
<a href="https://opencollective.com/n1_loader/backer/15/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/15/avatar.svg"></a>
|
|
185
|
+
<a href="https://opencollective.com/n1_loader/backer/16/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/16/avatar.svg"></a>
|
|
186
|
+
<a href="https://opencollective.com/n1_loader/backer/17/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/17/avatar.svg"></a>
|
|
187
|
+
<a href="https://opencollective.com/n1_loader/backer/18/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/18/avatar.svg"></a>
|
|
188
|
+
<a href="https://opencollective.com/n1_loader/backer/19/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/19/avatar.svg"></a>
|
|
189
|
+
<a href="https://opencollective.com/n1_loader/backer/20/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/20/avatar.svg"></a>
|
|
190
|
+
<a href="https://opencollective.com/n1_loader/backer/21/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/21/avatar.svg"></a>
|
|
191
|
+
<a href="https://opencollective.com/n1_loader/backer/22/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/22/avatar.svg"></a>
|
|
192
|
+
<a href="https://opencollective.com/n1_loader/backer/23/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/23/avatar.svg"></a>
|
|
193
|
+
<a href="https://opencollective.com/n1_loader/backer/24/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/24/avatar.svg"></a>
|
|
194
|
+
<a href="https://opencollective.com/n1_loader/backer/25/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/25/avatar.svg"></a>
|
|
195
|
+
<a href="https://opencollective.com/n1_loader/backer/26/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/26/avatar.svg"></a>
|
|
196
|
+
<a href="https://opencollective.com/n1_loader/backer/27/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/27/avatar.svg"></a>
|
|
197
|
+
<a href="https://opencollective.com/n1_loader/backer/28/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/28/avatar.svg"></a>
|
|
198
|
+
<a href="https://opencollective.com/n1_loader/backer/29/website" target="_blank"><img src="https://opencollective.com/n1_loader/backer/29/avatar.svg"></a>
|
|
199
|
+
|
|
200
|
+
### Open Collective Sponsors
|
|
201
|
+
|
|
202
|
+
You're an organization that wants to support the project with a monthly donation. Your logo will be available on the Github page. [[Become a sponsor](https://opencollective.com/n1_loader#sponsor)]
|
|
203
|
+
|
|
204
|
+
<a href="https://opencollective.com/n1_loader/sponsor/0/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/0/avatar.svg"></a>
|
|
205
|
+
<a href="https://opencollective.com/n1_loader/sponsor/1/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/1/avatar.svg"></a>
|
|
206
|
+
<a href="https://opencollective.com/n1_loader/sponsor/2/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/2/avatar.svg"></a>
|
|
207
|
+
<a href="https://opencollective.com/n1_loader/sponsor/3/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/3/avatar.svg"></a>
|
|
208
|
+
<a href="https://opencollective.com/n1_loader/sponsor/4/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/4/avatar.svg"></a>
|
|
209
|
+
<a href="https://opencollective.com/n1_loader/sponsor/5/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/5/avatar.svg"></a>
|
|
210
|
+
<a href="https://opencollective.com/n1_loader/sponsor/6/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/6/avatar.svg"></a>
|
|
211
|
+
<a href="https://opencollective.com/n1_loader/sponsor/7/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/7/avatar.svg"></a>
|
|
212
|
+
<a href="https://opencollective.com/n1_loader/sponsor/8/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/8/avatar.svg"></a>
|
|
213
|
+
<a href="https://opencollective.com/n1_loader/sponsor/9/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/9/avatar.svg"></a>
|
|
214
|
+
<a href="https://opencollective.com/n1_loader/sponsor/10/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/10/avatar.svg"></a>
|
|
215
|
+
<a href="https://opencollective.com/n1_loader/sponsor/11/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/11/avatar.svg"></a>
|
|
216
|
+
<a href="https://opencollective.com/n1_loader/sponsor/12/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/12/avatar.svg"></a>
|
|
217
|
+
<a href="https://opencollective.com/n1_loader/sponsor/13/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/13/avatar.svg"></a>
|
|
218
|
+
<a href="https://opencollective.com/n1_loader/sponsor/14/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/14/avatar.svg"></a>
|
|
219
|
+
<a href="https://opencollective.com/n1_loader/sponsor/15/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/15/avatar.svg"></a>
|
|
220
|
+
<a href="https://opencollective.com/n1_loader/sponsor/16/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/16/avatar.svg"></a>
|
|
221
|
+
<a href="https://opencollective.com/n1_loader/sponsor/17/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/17/avatar.svg"></a>
|
|
222
|
+
<a href="https://opencollective.com/n1_loader/sponsor/18/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/18/avatar.svg"></a>
|
|
223
|
+
<a href="https://opencollective.com/n1_loader/sponsor/19/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/19/avatar.svg"></a>
|
|
224
|
+
<a href="https://opencollective.com/n1_loader/sponsor/20/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/20/avatar.svg"></a>
|
|
225
|
+
<a href="https://opencollective.com/n1_loader/sponsor/21/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/21/avatar.svg"></a>
|
|
226
|
+
<a href="https://opencollective.com/n1_loader/sponsor/22/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/22/avatar.svg"></a>
|
|
227
|
+
<a href="https://opencollective.com/n1_loader/sponsor/23/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/23/avatar.svg"></a>
|
|
228
|
+
<a href="https://opencollective.com/n1_loader/sponsor/24/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/24/avatar.svg"></a>
|
|
229
|
+
<a href="https://opencollective.com/n1_loader/sponsor/25/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/25/avatar.svg"></a>
|
|
230
|
+
<a href="https://opencollective.com/n1_loader/sponsor/26/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/26/avatar.svg"></a>
|
|
231
|
+
<a href="https://opencollective.com/n1_loader/sponsor/27/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/27/avatar.svg"></a>
|
|
232
|
+
<a href="https://opencollective.com/n1_loader/sponsor/28/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/28/avatar.svg"></a>
|
|
233
|
+
<a href="https://opencollective.com/n1_loader/sponsor/29/website" target="_blank"><img src="https://opencollective.com/n1_loader/sponsor/29/avatar.svg"></a>
|
|
234
|
+
|
|
148
235
|
## Contributing
|
|
149
236
|
|
|
150
237
|
Bug reports and pull requests are welcome on GitHub at https://github.com/djezzzl/n1_loader.
|
|
@@ -174,3 +261,5 @@ Copyright (c) Evgeniy Demin. See [LICENSE.txt](LICENSE.txt) for further details.
|
|
|
174
261
|
[6]: https://github.com/DmitryTsepelev/ar_lazy_preload
|
|
175
262
|
[7]: https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping
|
|
176
263
|
[8]: https://github.com/djezzzl/n1_loader
|
|
264
|
+
[9]: https://opencollective.com/n1_loader/tiers/badge.svg
|
|
265
|
+
[10]: https://opencollective.com/n1_loader#support
|
|
@@ -29,9 +29,8 @@ fill_database
|
|
|
29
29
|
# Has N+1
|
|
30
30
|
p User.all.map { |user| user.payments.sum(&:amount) }
|
|
31
31
|
|
|
32
|
-
# Has no N+1
|
|
32
|
+
# Has no N+1 and loads only required data
|
|
33
33
|
p User.preload_associations_lazily.map(&:payments_total)
|
|
34
|
-
|
|
35
|
-
# Has no N+1 and calculation is the most efficient
|
|
34
|
+
# or
|
|
36
35
|
ArLazyPreload.config.auto_preload = true
|
|
37
|
-
User.all.map(&:payments_total)
|
|
36
|
+
User.all.map(&:payments_total)
|
|
@@ -0,0 +1,39 @@
|
|
|
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 Loader < N1Loader::Loader
|
|
9
|
+
def perform(users)
|
|
10
|
+
total_per_user = Payment.group(:user_id).where(user: users).sum(:amount).tap { |h| h.default = 0 }
|
|
11
|
+
|
|
12
|
+
users.each do |user|
|
|
13
|
+
total = total_per_user[user.id]
|
|
14
|
+
fulfill(user, total)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class User < ActiveRecord::Base
|
|
20
|
+
has_many :payments
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Payment < ActiveRecord::Base
|
|
24
|
+
belongs_to :user
|
|
25
|
+
|
|
26
|
+
validates :amount, presence: true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
fill_database
|
|
30
|
+
|
|
31
|
+
# Has N+1 and loads redundant data
|
|
32
|
+
p User.all.map { |user| user.payments.sum(&:amount) }
|
|
33
|
+
|
|
34
|
+
# Has no N+1 and loads only required data
|
|
35
|
+
p User.preload_associations_lazily.all.map { |user| Loader.for(user) }
|
|
36
|
+
|
|
37
|
+
# or
|
|
38
|
+
ArLazyPreload.config.auto_preload = true
|
|
39
|
+
p User.all.map { |user| Loader.for(user) }
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# Enhanced ActiveRecord
|
|
2
|
+
|
|
3
|
+
- Do you like `ActiveRecord` preloading?
|
|
4
|
+
- How many times have you resolved your N+1 issues with `includes` or `preload`?
|
|
5
|
+
- Do you know that preloading has limitations?
|
|
6
|
+
|
|
7
|
+
In this guide, I'd like to share with you tips and tricks about ActiveRecord
|
|
8
|
+
preloading and how you can enhance it to the next level.
|
|
9
|
+
|
|
10
|
+
Let's start by describing the models.
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
# The model represents users in our application.
|
|
14
|
+
class User < ActiveRecord::Base
|
|
15
|
+
# Every user may have from 0 to many payments.
|
|
16
|
+
has_many :payments
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The model represents payments in our application.
|
|
20
|
+
class Payment < ActiveRecord::Base
|
|
21
|
+
# Every payment belongs to a user.
|
|
22
|
+
belongs_to :user
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Assuming we want to iterate over a group of users and check how many payments they have, we may do:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
# The query we want to use to fetch users from the database.
|
|
30
|
+
users = User.all
|
|
31
|
+
# Iteration over selected users.
|
|
32
|
+
users.each do |user|
|
|
33
|
+
# Print amount of user's payments.
|
|
34
|
+
# This query will be called for every user, bringing an N+1 issue.
|
|
35
|
+
p user.payments.count
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
We can fix the N+1 issue above in a second.
|
|
40
|
+
We need to add ActiveRecord's `includes` to the query that fetches users.
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
# The query to fetch users with preload payments for every selected user.
|
|
44
|
+
users = User.includes(:payments).all
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Then, we can iterate over the group again without the N+1 issue.
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
users.each do |user|
|
|
51
|
+
p user.payments.count
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Experienced with ActiveRecord person may notice that the iteration above still will have an N+1 issue.
|
|
56
|
+
The reason is the `.count` method and its behavior.
|
|
57
|
+
This issue brings us to the first tip.
|
|
58
|
+
|
|
59
|
+
### Tip 1. `count` vs `size` vs `length`
|
|
60
|
+
|
|
61
|
+
- `count` - always queries the database with `COUNT` query;
|
|
62
|
+
- `size` - queries the database with `COUNT` only when there is no preloaded data, returns array length otherwise;
|
|
63
|
+
- `length` - always returns array length, in case there is no data, load it first.
|
|
64
|
+
|
|
65
|
+
_Note:_ be careful with `size` as ordering is critical.
|
|
66
|
+
|
|
67
|
+
Meaning, for `user = User.first`
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# Does `COUNT` query
|
|
71
|
+
user.payments.size
|
|
72
|
+
# Does `SELECT` query
|
|
73
|
+
user.payments.each { |payment| }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
is different from
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
# Does `SELECT` query
|
|
80
|
+
user.payments.each { |payment| }
|
|
81
|
+
# No query
|
|
82
|
+
user.payments.size
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
You may notice that the above solution loads all payment information when the amount is only needed.
|
|
86
|
+
There is a well-known solution for this case called [counter_cache](https://guides.rubyonrails.org/association_basics.html#options-for-belongs-to-counter-cache).
|
|
87
|
+
|
|
88
|
+
To use that, you need to add `payments_count` field to `users` table and adjust `Payment` model.
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
# Migration to add `payments_count` to `users` table.
|
|
92
|
+
class AddPaymentsCountToUsers < ActiveRecord::Migration
|
|
93
|
+
def change
|
|
94
|
+
add_column :users, :payments_count, :integer, default: 0, null: false
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Change `belongs_to` to have `counter_cache` option.
|
|
99
|
+
class Payment < ActiveRecord::Base
|
|
100
|
+
belongs_to :user, counter_cache: true
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
_Note:_ avoid adding or removing payments from the database directly or through `insert_all`/`delete`/`delete_all` as
|
|
105
|
+
`counter_cache` is using ActiveRecord callbacks to update the field's value.
|
|
106
|
+
|
|
107
|
+
It's worth mentioning [counter_culture](https://github.com/magnusvk/counter_culture) alternative that has many features compared with the built-in `counter_cache`
|
|
108
|
+
|
|
109
|
+
## Associations with arguments
|
|
110
|
+
|
|
111
|
+
Now, let's assume we want to fetch the number of payments in a time frame for every user in a group.
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
from = 1.months.ago
|
|
115
|
+
to = Time.current
|
|
116
|
+
|
|
117
|
+
# Query to fetch users.
|
|
118
|
+
users = User.all
|
|
119
|
+
|
|
120
|
+
users.each do |user|
|
|
121
|
+
# Print the number of payments in a time frame for every user.
|
|
122
|
+
# Database query will be triggered for every user, meaning it has an N+1 issue.
|
|
123
|
+
p user.payments.where(created_at: from...to).count
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
ActiveRecord supports defining associations with arguments.
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
class User < ActiveRecord::Base
|
|
131
|
+
has_many :payments, -> (from, to) { where(created_at: from...to) }
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Unfortunately, such associations are not possible to preload with `includes`.
|
|
136
|
+
Gladly, there is a solution with [N1Loader](https://github.com/djezzzl/n1_loader/).
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
# Install gem dependencies.
|
|
140
|
+
require 'n1_loader/active_record'
|
|
141
|
+
|
|
142
|
+
class User < ActiveRecord::Base
|
|
143
|
+
n1_optimized :payments_count do
|
|
144
|
+
argument :from
|
|
145
|
+
argument :to
|
|
146
|
+
|
|
147
|
+
def perform(users)
|
|
148
|
+
# Fetch the payment number once for all users.
|
|
149
|
+
payments = Payment.where(user: users).where(created_at: from...to).group(:user_id).count
|
|
150
|
+
|
|
151
|
+
users.each do |user|
|
|
152
|
+
# Assign preloaded data to every user.
|
|
153
|
+
# Note: it doesn't use any promises.
|
|
154
|
+
fulfill(user, payments[user.id])
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
from = 1.month.ago
|
|
161
|
+
to = Time.current
|
|
162
|
+
|
|
163
|
+
# Preload `payments` N1Loader "association". Doesn't query the database yet.
|
|
164
|
+
users = User.includes(:payments_count).all
|
|
165
|
+
|
|
166
|
+
users.each do |user|
|
|
167
|
+
# Queries the database once, meaning has no N+1 issues.
|
|
168
|
+
p user.payments_count(from, to)
|
|
169
|
+
end
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Let's look at another example. Assuming we want to fetch the last payment for every user.
|
|
173
|
+
We can try to define scoped `has_one` association and use that.
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
class User < ActiveRecord::Base
|
|
177
|
+
has_one :last_payment, -> { order(id: :desc) }, class_name: 'Payment'
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
We can see that preloading is working.
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
users = User.includes(:last_payment)
|
|
185
|
+
|
|
186
|
+
users.each do |user|
|
|
187
|
+
# No N+1. Last payment was returned.
|
|
188
|
+
p user.last_payment
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
At first glance, we may think everything is alright. Unfortunately, it is not.
|
|
193
|
+
|
|
194
|
+
### Tip 2. Enforce `has_one` associations on the database level
|
|
195
|
+
|
|
196
|
+
ActiveRecord, fetches all available payments for every user with provided order and then assigns only first payment to the association.
|
|
197
|
+
First, such querying is inefficient as we load many redundant information.
|
|
198
|
+
But most importantly, this association may lead to big issues. Other engineers may use it, for example,
|
|
199
|
+
for `joins(:last_payment)`. Assuming that association has strict agreement on the database level that
|
|
200
|
+
a user may have none or a single payment in the database. Apparently, it may not be the case, and some queries
|
|
201
|
+
will return unexpected data.
|
|
202
|
+
|
|
203
|
+
Described issues may be found with [DatabaseConsistency](https://github.com/djezzzl/database_consistency).
|
|
204
|
+
|
|
205
|
+
Back to the task, we can solve it with [N1Loader](https://github.com/djezzzl/n1_loader) in the following way
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
require 'n1_loader/active_record'
|
|
209
|
+
|
|
210
|
+
class User < ActiveRecord::Base
|
|
211
|
+
n1_optimized :last_payment do |users|
|
|
212
|
+
subquery = Payment.select('MAX(id)').where(user: users)
|
|
213
|
+
payments = Payment.where(id: subquery).index_by(&:user_id)
|
|
214
|
+
|
|
215
|
+
users.each do |user|
|
|
216
|
+
fulfill(user, payments[user.id])
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
users = User.includes(:last_payment).all
|
|
222
|
+
|
|
223
|
+
users.each do |user|
|
|
224
|
+
# Queries the database once, meaning no N+1.
|
|
225
|
+
p user.last_payment
|
|
226
|
+
end
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Attentive reader could notice that in every described case, it was a requirement to explicitly list data that we want to preload for a group of users.
|
|
230
|
+
Gladly, there is a simple solution! [ArLazyPreload](https://github.com/DmitryTsepelev/ar_lazy_preload) will make N+1 disappear just by enabling it.
|
|
231
|
+
As soon as you need to load association for any record, it will load it once for all records that were fetched along this one.
|
|
232
|
+
And it works with ActiveRecord and N1Loader perfectly!
|
|
233
|
+
|
|
234
|
+
Let's look at the example.
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
# Require N1Loader with ArLazyPreload integration
|
|
238
|
+
require 'n1_loader/ar_lazy_preload'
|
|
239
|
+
|
|
240
|
+
# Enable ArLazyPreload globally, so you don't need to care about `includes` anymore
|
|
241
|
+
ArLazyPreload.config.auto_preload = true
|
|
242
|
+
|
|
243
|
+
class User < ActiveRecord::Base
|
|
244
|
+
has_many :payments
|
|
245
|
+
|
|
246
|
+
n1_optimized :last_payment do |users|
|
|
247
|
+
subquery = Payment.select('MAX(id)').where(user: users)
|
|
248
|
+
payments = Payment.where(id: subquery).index_by(&:user_id)
|
|
249
|
+
|
|
250
|
+
users.each do |user|
|
|
251
|
+
fulfill(user, payments[user.id])
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# no need to specify `includes`
|
|
257
|
+
users = User.all
|
|
258
|
+
|
|
259
|
+
users.each do |user|
|
|
260
|
+
p user.payments # no N+1
|
|
261
|
+
p user.last_payment # no N+1
|
|
262
|
+
end
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
As you can see, there is no need to even remember about resolving N+1 when you have both [ArLazyPreload](https://github.com/DmitryTsepelev/ar_lazy_preload) and [N1Loader](https://github.com/djezzzl/n1_loader) in your pocket.
|
|
266
|
+
It works great with GraphQL API too. Give it and try and share your feedback!
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Returns cached N1Loader::LoaderCollection from context for a loader.
|
|
4
|
+
# In case there is none yet, saves passed block to a cache.
|
|
5
|
+
ArLazyPreload::Contexts::BaseContext.define_method :fetch_n1_loader_collection do |loader, &block|
|
|
6
|
+
(@n1_loader_collections ||= {})[loader] ||= block.call
|
|
7
|
+
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module N1Loader
|
|
4
4
|
module ArLazyPreload
|
|
5
|
-
# Context adapter for N1Loader
|
|
5
|
+
# Context adapter for injected N1Loader loaders.
|
|
6
6
|
class ContextAdapter
|
|
7
7
|
attr_reader :context
|
|
8
8
|
|
|
@@ -12,12 +12,15 @@ module N1Loader
|
|
|
12
12
|
@context = context
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# Assign initialized preloader to +association_name+ in case it wasn't yet preloaded within the given context.
|
|
15
16
|
def try_preload_lazily(association_name)
|
|
16
17
|
return unless context&.send(:association_needs_preload?, association_name)
|
|
17
18
|
|
|
18
19
|
perform_preloading(association_name)
|
|
19
20
|
end
|
|
20
21
|
|
|
22
|
+
# Initialize preloader for +association_name+ with context builder callback.
|
|
23
|
+
# The callback will be executed when on records load.
|
|
21
24
|
def perform_preloading(association_name)
|
|
22
25
|
context_setup = lambda { |records|
|
|
23
26
|
AssociatedContextBuilder.prepare(
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Raised when a single object without ArLazyPreload context was passed to an isolated loader.
|
|
4
|
+
N1Loader::Loader::MissingArLazyPreloadContext = Class.new(StandardError)
|
|
5
|
+
|
|
6
|
+
# Defines a singleton method method that allows isolated loaders
|
|
7
|
+
# to use ArLazyPreload context without passing sibling records.
|
|
8
|
+
N1Loader::Loader.define_singleton_method(:for) do |element, **args|
|
|
9
|
+
# It is required to have an ArLazyPreload context defined
|
|
10
|
+
if !element.respond_to?(:lazy_preload_context) || element.lazy_preload_context.nil?
|
|
11
|
+
raise N1Loader::Loader::MissingArLazyPreloadContext
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Fetch or initialize loader from ArLazyPreload context
|
|
15
|
+
loader_collection = element.lazy_preload_context.fetch_n1_loader_collection(self) do
|
|
16
|
+
context_setup = lambda { |records|
|
|
17
|
+
N1Loader::ArLazyPreload::AssociatedContextBuilder.prepare(
|
|
18
|
+
parent_context: element.lazy_preload_context,
|
|
19
|
+
association_name: "cached_n1_loader_collection_#{self}".downcase.to_sym,
|
|
20
|
+
records: records
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
N1Loader::LoaderCollection.new(self, element.lazy_preload_context.records).tap do |collection|
|
|
25
|
+
collection.context_setup = context_setup
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Fetch value from loader
|
|
30
|
+
loader_collection.with(**args).for(element)
|
|
31
|
+
end
|
|
@@ -14,6 +14,8 @@ require_relative "ar_lazy_preload/associated_context_builder"
|
|
|
14
14
|
require_relative "ar_lazy_preload/loader_collection_patch"
|
|
15
15
|
require_relative "ar_lazy_preload/preloader_patch"
|
|
16
16
|
require_relative "ar_lazy_preload/loader_patch"
|
|
17
|
+
require_relative "ar_lazy_preload/loader"
|
|
18
|
+
require_relative "ar_lazy_preload/context"
|
|
17
19
|
|
|
18
20
|
N1Loader::Loadable::ClassMethods.prepend(N1Loader::ArLazyPreload::Loadable::ClassMethods)
|
|
19
21
|
N1Loader::Preloader.prepend(N1Loader::ArLazyPreload::PreloaderPatch)
|
data/lib/n1_loader/version.rb
CHANGED
data/n1_loader.gemspec
CHANGED
|
@@ -14,8 +14,8 @@ Gem::Specification.new do |spec|
|
|
|
14
14
|
spec.required_ruby_version = ">= 2.5.0"
|
|
15
15
|
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
|
-
spec.metadata["source_code_uri"] = "https://github.com/djezzzl/
|
|
18
|
-
spec.metadata["changelog_uri"] = "https://github.com/djezzzl/
|
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/djezzzl/n1_loader"
|
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/djezzzl/n1_loader/master/CHANGELOG.md"
|
|
19
19
|
|
|
20
20
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
21
21
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
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.6.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-10-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -144,6 +144,8 @@ extensions: []
|
|
|
144
144
|
extra_rdoc_files: []
|
|
145
145
|
files:
|
|
146
146
|
- ".circleci/config.yml"
|
|
147
|
+
- ".github/workflows/rubocop.yml"
|
|
148
|
+
- ".github/workflows/tests.yml"
|
|
147
149
|
- ".gitignore"
|
|
148
150
|
- ".rspec"
|
|
149
151
|
- ".rubocop.yml"
|
|
@@ -162,6 +164,7 @@ files:
|
|
|
162
164
|
- bin/setup
|
|
163
165
|
- examples/active_record_integration.rb
|
|
164
166
|
- examples/ar_lazy_integration.rb
|
|
167
|
+
- examples/ar_lazy_integration_with_isolated_loader.rb
|
|
165
168
|
- examples/arguments_support.rb
|
|
166
169
|
- examples/context/service.rb
|
|
167
170
|
- examples/context/setup_ar_lazy.rb
|
|
@@ -173,6 +176,7 @@ files:
|
|
|
173
176
|
- examples/reloading.rb
|
|
174
177
|
- examples/shared_loader.rb
|
|
175
178
|
- examples/single_case.rb
|
|
179
|
+
- guides/enhanced-activerecord.md
|
|
176
180
|
- lib/n1_loader.rb
|
|
177
181
|
- lib/n1_loader/active_record.rb
|
|
178
182
|
- lib/n1_loader/active_record/associations_preloader_v5.rb
|
|
@@ -183,8 +187,10 @@ files:
|
|
|
183
187
|
- lib/n1_loader/active_record/loader_collection.rb
|
|
184
188
|
- lib/n1_loader/ar_lazy_preload.rb
|
|
185
189
|
- lib/n1_loader/ar_lazy_preload/associated_context_builder.rb
|
|
190
|
+
- lib/n1_loader/ar_lazy_preload/context.rb
|
|
186
191
|
- lib/n1_loader/ar_lazy_preload/context_adapter.rb
|
|
187
192
|
- lib/n1_loader/ar_lazy_preload/loadable.rb
|
|
193
|
+
- lib/n1_loader/ar_lazy_preload/loader.rb
|
|
188
194
|
- lib/n1_loader/ar_lazy_preload/loader_collection_patch.rb
|
|
189
195
|
- lib/n1_loader/ar_lazy_preload/loader_patch.rb
|
|
190
196
|
- lib/n1_loader/ar_lazy_preload/preloader_patch.rb
|
|
@@ -199,8 +205,8 @@ licenses:
|
|
|
199
205
|
- MIT
|
|
200
206
|
metadata:
|
|
201
207
|
homepage_uri: https://github.com/djezzzl/n1_loader
|
|
202
|
-
source_code_uri: https://github.com/djezzzl/
|
|
203
|
-
changelog_uri: https://github.com/djezzzl/
|
|
208
|
+
source_code_uri: https://github.com/djezzzl/n1_loader
|
|
209
|
+
changelog_uri: https://github.com/djezzzl/n1_loader/master/CHANGELOG.md
|
|
204
210
|
post_install_message:
|
|
205
211
|
rdoc_options: []
|
|
206
212
|
require_paths:
|
|
@@ -216,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
216
222
|
- !ruby/object:Gem::Version
|
|
217
223
|
version: '0'
|
|
218
224
|
requirements: []
|
|
219
|
-
rubygems_version: 3.
|
|
225
|
+
rubygems_version: 3.2.33
|
|
220
226
|
signing_key:
|
|
221
227
|
specification_version: 4
|
|
222
228
|
summary: Loader to solve N+1 issue for good.
|