n1_loader 1.5.1 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rubocop.yml +23 -0
- data/.github/workflows/tests.yml +62 -0
- data/.rubocop.yml +2 -1
- data/CHANGELOG.md +4 -0
- data/README.md +11 -0
- data/examples/ar_lazy_integration.rb +3 -4
- data/examples/ar_lazy_integration_with_isolated_loader.rb +39 -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/version.rb +1 -1
- metadata +8 -3
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
|
@@ -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,7 @@
|
|
1
|
+
## [1.6.0] - 2022/10/24
|
2
|
+
|
3
|
+
- Add support of ArLazyPreload context for isolated loaders.
|
4
|
+
|
1
5
|
## [1.5.1] - 2022/09/20
|
2
6
|
|
3
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)!
|
data/README.md
CHANGED
@@ -149,6 +149,17 @@ p User.all.includes(:payments_total).map { |user| user.payments_total(from: from
|
|
149
149
|
- Has [integration](examples/active_record_integration.rb) with [ActiveRecord][5] which makes it brilliant
|
150
150
|
- Has [integration](examples/ar_lazy_integration.rb) with [ArLazyPreload][6] which makes it excellent
|
151
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
|
+
|
152
163
|
## Funding
|
153
164
|
|
154
165
|
### Open Collective Backers
|
@@ -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,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
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
|
@@ -184,8 +187,10 @@ files:
|
|
184
187
|
- lib/n1_loader/active_record/loader_collection.rb
|
185
188
|
- lib/n1_loader/ar_lazy_preload.rb
|
186
189
|
- lib/n1_loader/ar_lazy_preload/associated_context_builder.rb
|
190
|
+
- lib/n1_loader/ar_lazy_preload/context.rb
|
187
191
|
- lib/n1_loader/ar_lazy_preload/context_adapter.rb
|
188
192
|
- lib/n1_loader/ar_lazy_preload/loadable.rb
|
193
|
+
- lib/n1_loader/ar_lazy_preload/loader.rb
|
189
194
|
- lib/n1_loader/ar_lazy_preload/loader_collection_patch.rb
|
190
195
|
- lib/n1_loader/ar_lazy_preload/loader_patch.rb
|
191
196
|
- lib/n1_loader/ar_lazy_preload/preloader_patch.rb
|
@@ -217,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
217
222
|
- !ruby/object:Gem::Version
|
218
223
|
version: '0'
|
219
224
|
requirements: []
|
220
|
-
rubygems_version: 3.2.
|
225
|
+
rubygems_version: 3.2.33
|
221
226
|
signing_key:
|
222
227
|
specification_version: 4
|
223
228
|
summary: Loader to solve N+1 issue for good.
|