n1_loader 0.1.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|