n1_loader 0.1.2 → 1.0.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 +7 -0
- data/CHANGELOG.md +4 -2
- data/README.md +134 -4
- data/gemfiles/ar_5_latest.gemfile +7 -0
- data/lib/n1_loader/active_record/associations_preloader_v5.rb +39 -0
- data/lib/n1_loader/active_record/{associations_preloader.rb → associations_preloader_v6.rb} +0 -0
- data/lib/n1_loader/active_record.rb +7 -2
- data/lib/n1_loader/ar_lazy_preload/context_adapter.rb +1 -1
- data/lib/n1_loader/core/loadable.rb +14 -5
- data/lib/n1_loader/core/loader.rb +5 -1
- data/lib/n1_loader/version.rb +1 -1
- data/n1_loader.gemspec +2 -2
- metadata +13 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e86d093c173eed577feb029f483c8b283f516bba1611391f9036e5675dec91d5
|
4
|
+
data.tar.gz: 626e0305af7e75fd6a5dbb23d6970a8972ca292a9449f2f32d61cf7849c57ec9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b450b20c25c935d3644003bf320e50644376cb6fc77cbda414522947a50857c3b28c5607bbcfd484dd9626a64523726cb4c6dd98b402a9ed3b73f1d6a40785a
|
7
|
+
data.tar.gz: 9d07c4a64e89f2c2e9aa230d86c780388aaf720002db5fac37fe9b98ffd6297b914ab49ec54306c728c3457ea7a323a52d66c3c9e78c40ccbaed0a511be1f012
|
data/.circleci/config.yml
CHANGED
@@ -86,8 +86,15 @@ workflows:
|
|
86
86
|
"latest"
|
87
87
|
]
|
88
88
|
gemfile: [
|
89
|
+
"gemfiles/ar_5_latest.gemfile",
|
89
90
|
"gemfiles/ar_6_latest.gemfile"
|
90
91
|
]
|
92
|
+
exclude:
|
93
|
+
- ruby-version: "3.0"
|
94
|
+
gemfile: "gemfiles/ar_5_latest.gemfile"
|
95
|
+
- ruby-version: "latest"
|
96
|
+
gemfile: "gemfiles/ar_5_latest.gemfile"
|
97
|
+
|
91
98
|
name: << matrix.gemfile >>-build-ruby-<< matrix.ruby-version >>
|
92
99
|
- rubocop:
|
93
100
|
requires:
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -9,11 +9,15 @@ We have a solution for you!
|
|
9
9
|
[N1Loader][8] is designed to solve the issue for good!
|
10
10
|
|
11
11
|
It has many benefits:
|
12
|
-
- it
|
13
|
-
- it
|
12
|
+
- it can be [isolated](#isolated-loaders)
|
13
|
+
- it loads data [lazily](#lazy-loading)
|
14
|
+
- it supports [shareable loaders](#shareable-loaders) between multiple classes
|
15
|
+
- it supports [reloading](#reloading)
|
16
|
+
- it supports optimized [single object loading](#optimized-single-case)
|
14
17
|
- it has an integration with [ActiveRecord][5] which makes it brilliant ([example](#activerecord))
|
15
18
|
- it has an integration with [ArLazyPreload][6] which makes it excellent ([example](#arlazypreload))
|
16
19
|
|
20
|
+
... and even more features to come! Stay tuned!
|
17
21
|
|
18
22
|
## Installation
|
19
23
|
|
@@ -25,16 +29,18 @@ gem 'n1_loader'
|
|
25
29
|
|
26
30
|
You can add integration with [ActiveRecord][5] by:
|
27
31
|
```ruby
|
28
|
-
require 'n1_loader/active_record'
|
32
|
+
gem 'n1_loader', require: 'n1_loader/active_record'
|
29
33
|
```
|
30
34
|
|
31
35
|
You can add the integration with [ActiveRecord][5] and [ArLazyPreload][6] by:
|
32
36
|
```ruby
|
33
|
-
require 'n1_loader/ar_lazy_preload'
|
37
|
+
gem 'n1_loader', require: 'n1_loader/ar_lazy_preload'
|
34
38
|
```
|
35
39
|
|
36
40
|
## Usage
|
37
41
|
|
42
|
+
**Supported Ruby version:** 2.5, 2.6, 2.7, 3.0, and latest.
|
43
|
+
|
38
44
|
```ruby
|
39
45
|
class Example
|
40
46
|
include N1Loader::Loadable
|
@@ -67,8 +73,132 @@ N1Loader::Preloader.new(objects).preload(:anything)
|
|
67
73
|
objects.map(&:anything)
|
68
74
|
```
|
69
75
|
|
76
|
+
### Lazy loading
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
class Example
|
80
|
+
include N1Loader::Loadable
|
81
|
+
|
82
|
+
n1_loader :anything do |elements|
|
83
|
+
# Has to return a hash that has keys as element from elements
|
84
|
+
elements.group_by(&:itself)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
object = Example.new # => nothing was done for loading
|
89
|
+
object.anything # => first time loading
|
90
|
+
|
91
|
+
objects = [Example.new, Example.new] # => nothing was done for loading
|
92
|
+
N1Loader::Preloader.new([objects]).preload(:anything) # => we only initial loader but didn't perform it yet
|
93
|
+
objects.map(&:anything) # => loading happen for the first time (without N+1)
|
94
|
+
```
|
95
|
+
|
96
|
+
|
97
|
+
### Shareable loaders
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
class MyLoader < N1Loader::Loader
|
101
|
+
def perform(elements)
|
102
|
+
# Has to return a hash that has keys as element from elements
|
103
|
+
elements.group_by(&:itself)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class A
|
108
|
+
include N1Loader::Loadable
|
109
|
+
|
110
|
+
n1_loader :anything, MyLoader
|
111
|
+
end
|
112
|
+
|
113
|
+
class B
|
114
|
+
include N1Loader::Loadable
|
115
|
+
|
116
|
+
n1_loader :something, MyLoader
|
117
|
+
end
|
118
|
+
|
119
|
+
A.new.anything # => works
|
120
|
+
B.new.something # => works
|
121
|
+
```
|
122
|
+
|
123
|
+
### Reloading
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
class Example
|
127
|
+
include N1Loader::Loadable
|
128
|
+
|
129
|
+
# with inline loader
|
130
|
+
n1_loader :anything do |elements|
|
131
|
+
# Has to return a hash that has keys as element from elements
|
132
|
+
elements.group_by(&:itself)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
object = Example.new
|
137
|
+
object.anything # => loader is executed first time and value was cached
|
138
|
+
object.anything(reload: true) # => loader is executed again and a new value was cached
|
139
|
+
|
140
|
+
objects = [Example.new, Example.new]
|
141
|
+
|
142
|
+
N1Loader::Preloader.new(objects).preload(:anything) # => loader was initialized but not yet executed
|
143
|
+
objects.map(&:anything) # => loader was executed first time without N+1 issue and values were cached
|
144
|
+
|
145
|
+
N1Loader::Preloader.new(objects).preload(:anything) # => loader was initialized again but not yet executed
|
146
|
+
objects.map(&:anything) # => new loader was executed first time without N+1 issue and new values were cached
|
147
|
+
```
|
148
|
+
|
149
|
+
### Isolated loaders
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
class MyLoader < N1Loader::Loader
|
153
|
+
def perform(elements)
|
154
|
+
# Has to return a hash that has keys as element from elements
|
155
|
+
elements.group_by(&:itself)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
objects = [1, 2, 3, 4]
|
160
|
+
loader = MyLoader.new(objects)
|
161
|
+
objects.each do |object|
|
162
|
+
loader.for(object) # => it has no N+1 and it doesn't require to be injected in the class
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
### Optimized single case
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
class Example
|
170
|
+
include N1Loader::Loadable
|
171
|
+
|
172
|
+
n1_loader :something do # no arguments passed to the block, so we can override both perform and single.
|
173
|
+
def perform(elements)
|
174
|
+
# Has to return a hash that has keys as element from elements
|
175
|
+
elements.group_by(&:itself)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Optimized for single object loading
|
179
|
+
def single(element)
|
180
|
+
# Just return a value you want to have for this element
|
181
|
+
element
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
object = Example.new
|
187
|
+
object.something # single will be used here
|
188
|
+
|
189
|
+
objects = [Example.new, Example.new]
|
190
|
+
N1Loader::Preloader.new(objects).preload(:something)
|
191
|
+
objects.map(&:something) # perform will be used once without N+1
|
192
|
+
```
|
193
|
+
|
194
|
+
## Integrations
|
195
|
+
|
70
196
|
### [ActiveRecord][5]
|
71
197
|
|
198
|
+
**Supported versions**: 5, 6.
|
199
|
+
|
200
|
+
_Note_: Please open an issue if you interested in support of version 7 or other.
|
201
|
+
|
72
202
|
```ruby
|
73
203
|
class User < ActiveRecord::Base
|
74
204
|
include N1Loader::Loadable
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module N1Loader
|
4
|
+
module ActiveRecord
|
5
|
+
module Associations
|
6
|
+
module Preloader # :nodoc:
|
7
|
+
N1LoaderReflection = Struct.new(:key, :loader) do
|
8
|
+
def options
|
9
|
+
{}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def preloaders_for_one(association, records, scope)
|
14
|
+
grouped_records(association, records).flat_map do |reflection, klasses|
|
15
|
+
next N1Loader::Preloader.new(records).preload(reflection.key) if reflection.is_a?(N1LoaderReflection)
|
16
|
+
|
17
|
+
klasses.map do |rhs_klass, rs|
|
18
|
+
loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
|
19
|
+
loader.run self
|
20
|
+
loader
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def grouped_records(association, records)
|
26
|
+
n1_load_records, records = records.partition do |record|
|
27
|
+
record.class.respond_to?(:n1_loader_defined?) && record.class.n1_loader_defined?(association)
|
28
|
+
end
|
29
|
+
|
30
|
+
hash = n1_load_records.group_by do |record|
|
31
|
+
N1LoaderReflection.new(association, record.class.n1_loader(association))
|
32
|
+
end
|
33
|
+
|
34
|
+
hash.merge(super)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
File without changes
|
@@ -4,8 +4,13 @@ require_relative "../n1_loader"
|
|
4
4
|
|
5
5
|
require "active_record"
|
6
6
|
|
7
|
-
require_relative "active_record/associations_preloader"
|
8
|
-
|
9
7
|
ActiveSupport.on_load(:active_record) do
|
8
|
+
case ActiveRecord::VERSION::MAJOR
|
9
|
+
when 6
|
10
|
+
require_relative "active_record/associations_preloader_v6"
|
11
|
+
else
|
12
|
+
require_relative "active_record/associations_preloader_v5"
|
13
|
+
end
|
14
|
+
|
10
15
|
ActiveRecord::Associations::Preloader.prepend(N1Loader::ActiveRecord::Associations::Preloader)
|
11
16
|
end
|
@@ -47,9 +47,13 @@ module N1Loader
|
|
47
47
|
respond_to?("#{name}_loader")
|
48
48
|
end
|
49
49
|
|
50
|
-
def n1_load(name, loader = nil, &block) # rubocop:disable Metrics/MethodLength
|
50
|
+
def n1_load(name, loader = nil, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
51
51
|
loader ||= Class.new(N1Loader::Loader) do
|
52
|
-
|
52
|
+
if block && block.arity == 1
|
53
|
+
define_method(:perform, &block)
|
54
|
+
else
|
55
|
+
class_eval(&block)
|
56
|
+
end
|
53
57
|
end
|
54
58
|
|
55
59
|
loader_name = "#{name}_loader"
|
@@ -59,16 +63,21 @@ module N1Loader
|
|
59
63
|
loader
|
60
64
|
end
|
61
65
|
|
66
|
+
define_method("#{loader_name}_reload") do
|
67
|
+
instance_variable_set(loader_variable_name, self.class.send(loader_name).new([self]))
|
68
|
+
end
|
69
|
+
|
62
70
|
define_method("#{loader_name}=") do |loader_instance|
|
63
71
|
instance_variable_set(loader_variable_name, loader_instance)
|
64
72
|
end
|
65
73
|
|
66
74
|
define_method(loader_name) do
|
67
|
-
instance_variable_get(loader_variable_name) ||
|
68
|
-
instance_variable_set(loader_variable_name, self.class.send(loader_name).new([self]))
|
75
|
+
instance_variable_get(loader_variable_name) || send("#{loader_name}_reload")
|
69
76
|
end
|
70
77
|
|
71
|
-
define_method(name) do
|
78
|
+
define_method(name) do |reload: false|
|
79
|
+
send("#{loader_name}_reload") if reload
|
80
|
+
|
72
81
|
send(loader_name).for(self)
|
73
82
|
end
|
74
83
|
|
@@ -15,7 +15,11 @@ module N1Loader
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def loaded
|
18
|
-
@loaded ||=
|
18
|
+
@loaded ||= if elements.size == 1 && respond_to?(:single)
|
19
|
+
{ elements.first => single(elements.first) }
|
20
|
+
else
|
21
|
+
perform(elements)
|
22
|
+
end
|
19
23
|
end
|
20
24
|
|
21
25
|
def preloaded_records
|
data/lib/n1_loader/version.rb
CHANGED
data/n1_loader.gemspec
CHANGED
@@ -22,10 +22,10 @@ Gem::Specification.new do |spec|
|
|
22
22
|
end
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
|
25
|
-
spec.add_development_dependency "activerecord", "
|
25
|
+
spec.add_development_dependency "activerecord", ">= 5"
|
26
26
|
spec.add_development_dependency "ar_lazy_preload", "~> 0.7"
|
27
27
|
spec.add_development_dependency "db-query-matchers", "~> 0.10"
|
28
|
-
spec.add_development_dependency "rails", "
|
28
|
+
spec.add_development_dependency "rails", ">= 5"
|
29
29
|
spec.add_development_dependency "rspec", "~> 3.0"
|
30
30
|
spec.add_development_dependency "rspec_junit_formatter", "~> 0.4"
|
31
31
|
spec.add_development_dependency "rubocop", "~> 1.7"
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: n1_loader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.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: 2021-12-
|
11
|
+
date: 2021-12-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '5'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: ar_lazy_preload
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,16 +56,16 @@ dependencies:
|
|
56
56
|
name: rails
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '5'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '5'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -141,10 +141,12 @@ files:
|
|
141
141
|
- Rakefile
|
142
142
|
- bin/console
|
143
143
|
- bin/setup
|
144
|
+
- gemfiles/ar_5_latest.gemfile
|
144
145
|
- gemfiles/ar_6_latest.gemfile
|
145
146
|
- lib/n1_loader.rb
|
146
147
|
- lib/n1_loader/active_record.rb
|
147
|
-
- lib/n1_loader/active_record/
|
148
|
+
- lib/n1_loader/active_record/associations_preloader_v5.rb
|
149
|
+
- lib/n1_loader/active_record/associations_preloader_v6.rb
|
148
150
|
- lib/n1_loader/ar_lazy_preload.rb
|
149
151
|
- lib/n1_loader/ar_lazy_preload/associated_context_builder.rb
|
150
152
|
- lib/n1_loader/ar_lazy_preload/context_adapter.rb
|