n1_loader 1.5.1 → 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/.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.
|