n1_loader 1.3.0 → 1.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +18 -11
- data/lib/n1_loader/active_record/loader_collection.rb +1 -3
- data/lib/n1_loader/ar_lazy_preload/loader_collection_patch.rb +1 -1
- data/lib/n1_loader/core/loadable.rb +15 -3
- data/lib/n1_loader/core/loader.rb +44 -12
- data/lib/n1_loader/core/loader_collection.rb +2 -2
- data/lib/n1_loader/core/preloader.rb +12 -2
- data/lib/n1_loader/version.rb +1 -1
- data/lib/n1_loader.rb +1 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f72c7428f78489c25222fb2ab8d56fde519c680fdc010115b952637c2291ca93
|
4
|
+
data.tar.gz: 7149852481ffed8d1fd0fc25e28fc045eda0a0f2936e748461dd7f43d4f709f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d0802914b84d4c25a97c8c328ff2ae4453cf8fcd4eee87de1955b06b810c874d9982498b5fc80ecf1fa77238971e51ef5f162f809914876a1811084b366ddfe
|
7
|
+
data.tar.gz: fa001ab34b7165ee35ba31ef449c837418d538e52977ee984b38e35b767df06c05ef0cb01949c9290c17ae40bcd3e3644f00374cf7f66d7e6811f094515c37da
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
## [1.4.2] - 2022-03-01
|
2
|
+
|
3
|
+
- Add n1_clear_cache method which is useful for cases like reload in ActiveRecord
|
4
|
+
|
5
|
+
## [1.4.1] - 2022-02-24
|
6
|
+
|
7
|
+
- Fix preloading of invalid objects
|
8
|
+
|
9
|
+
## [1.4.0] - 2022-02-22
|
10
|
+
|
11
|
+
- add support of optional arguments
|
12
|
+
|
13
|
+
BREAKING CHANGES:
|
14
|
+
- rework arguments to use single definition through `argument <name>` only
|
15
|
+
- use keyword arguments
|
16
|
+
|
1
17
|
## [1.3.0] - 2022-02-22
|
2
18
|
|
3
19
|
- add support of named arguments with `argument <name>`
|
data/README.md
CHANGED
@@ -129,6 +129,9 @@ end
|
|
129
129
|
user = User.new
|
130
130
|
user.orders_count # => loader is executed first time and value was cached
|
131
131
|
user.orders_count(reload: true) # => loader is executed again and a new value was cached
|
132
|
+
# or
|
133
|
+
user.n1_clear_cache
|
134
|
+
user.orders_count
|
132
135
|
|
133
136
|
users = [User.new, User.new]
|
134
137
|
N1Loader::Preloader.new(users).preload(:orders_count) # => loader was initialized but not yet executed
|
@@ -188,21 +191,25 @@ users.map(&:orders_count) # perform will be used once without N+1
|
|
188
191
|
class User
|
189
192
|
include N1Loader::Loadable
|
190
193
|
|
191
|
-
n1_optimized :orders_count do
|
192
|
-
|
193
|
-
|
194
|
-
|
194
|
+
n1_optimized :orders_count do
|
195
|
+
argument :type
|
196
|
+
|
197
|
+
def perform(users)
|
198
|
+
orders_per_user = Order.where(type: type, user: users).group(:user_id).count
|
199
|
+
|
200
|
+
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
201
|
+
end
|
195
202
|
end
|
196
203
|
end
|
197
204
|
|
198
205
|
user = User.new
|
199
|
-
user.orders_count(:gifts) # The loader will be performed first time for this argument
|
200
|
-
user.orders_count(:sales) # The loader will be performed first time for this argument
|
201
|
-
user.orders_count(:gifts) # The cached value will be used
|
206
|
+
user.orders_count(type: :gifts) # The loader will be performed first time for this argument
|
207
|
+
user.orders_count(type: :sales) # The loader will be performed first time for this argument
|
208
|
+
user.orders_count(type: :gifts) # The cached value will be used
|
202
209
|
|
203
210
|
users = [User.new, User.new]
|
204
211
|
N1Loader::Preloader.new(users).preload(:orders_count)
|
205
|
-
users.map { |user| user.orders_count(:gifts) } # No N+1 here
|
212
|
+
users.map { |user| user.orders_count(type: :gifts) } # No N+1 here
|
206
213
|
```
|
207
214
|
|
208
215
|
_Note_: By default, we use `arguments.map(&:object_id)` to identify arguments but in some cases,
|
@@ -217,7 +224,7 @@ class User
|
|
217
224
|
|
218
225
|
cache_key { sale.id }
|
219
226
|
|
220
|
-
def perform(users
|
227
|
+
def perform(users)
|
221
228
|
orders_per_user = Order.where(sale: sale, user: users).group(:user_id).count
|
222
229
|
|
223
230
|
users.each { |user| fulfill(user, orders_per_user[user.id]) }
|
@@ -226,8 +233,8 @@ class User
|
|
226
233
|
end
|
227
234
|
|
228
235
|
user = User.new
|
229
|
-
user.orders_count(Sale.first) # perform will be executed and value will be cached
|
230
|
-
user.orders_count(Sale.first) # the cached value will be returned
|
236
|
+
user.orders_count(sale: Sale.first) # perform will be executed and value will be cached
|
237
|
+
user.orders_count(sale: Sale.first) # the cached value will be returned
|
231
238
|
```
|
232
239
|
|
233
240
|
|
@@ -1,9 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
N1Loader::LoaderCollection.define_method :preloaded_records do
|
4
|
-
|
5
|
-
raise N1Loader::ActiveRecord::InvalidPreloading, "Cannot preload loader with arguments"
|
6
|
-
end
|
4
|
+
raise N1Loader::ActiveRecord::InvalidPreloading, "Cannot preload loader with arguments" if loader_class.arguments
|
7
5
|
|
8
6
|
with.preloaded_records
|
9
7
|
end
|
@@ -32,6 +32,12 @@ module N1Loader
|
|
32
32
|
send("#{name}_loader=", loader_collection)
|
33
33
|
end
|
34
34
|
|
35
|
+
def n1_clear_cache
|
36
|
+
self.class.n1_loaders.each do |name|
|
37
|
+
n1_loader_set(name, nil)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
35
41
|
def self.included(base)
|
36
42
|
base.extend(ClassMethods)
|
37
43
|
end
|
@@ -45,9 +51,13 @@ module N1Loader
|
|
45
51
|
respond_to?("#{name}_loader")
|
46
52
|
end
|
47
53
|
|
54
|
+
def n1_loaders
|
55
|
+
@n1_loaders ||= superclass.respond_to?(:n1_loaders) ? superclass.n1_loaders.dup : []
|
56
|
+
end
|
57
|
+
|
48
58
|
def n1_optimized(name, loader = nil, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
49
59
|
loader ||= Class.new(N1Loader::Loader) do
|
50
|
-
if block
|
60
|
+
if block.arity == 1
|
51
61
|
define_method(:perform, &block)
|
52
62
|
else
|
53
63
|
class_eval(&block)
|
@@ -56,6 +66,8 @@ module N1Loader
|
|
56
66
|
loader_name = "#{name}_loader"
|
57
67
|
loader_variable_name = "@#{loader_name}"
|
58
68
|
|
69
|
+
n1_loaders << name
|
70
|
+
|
59
71
|
define_singleton_method(loader_name) do
|
60
72
|
loader
|
61
73
|
end
|
@@ -73,10 +85,10 @@ module N1Loader
|
|
73
85
|
instance_variable_get(loader_variable_name) || send("#{loader_name}_reload")
|
74
86
|
end
|
75
87
|
|
76
|
-
define_method(name) do
|
88
|
+
define_method(name) do |reload: false, **args|
|
77
89
|
send("#{loader_name}_reload") if reload
|
78
90
|
|
79
|
-
send(loader_name).with(
|
91
|
+
send(loader_name).with(**args).for(self)
|
80
92
|
end
|
81
93
|
|
82
94
|
[name, loader_name, loader_variable_name]
|
@@ -13,11 +13,16 @@ module N1Loader
|
|
13
13
|
#
|
14
14
|
# First defined argument will have the value of first passed argument,
|
15
15
|
# meaning the order is important.
|
16
|
-
|
16
|
+
#
|
17
|
+
# @param name [Symbol]
|
18
|
+
# @param opts [Hash]
|
19
|
+
# @option opts [Boolean] optional false by default
|
20
|
+
def argument(name, **opts)
|
17
21
|
@arguments ||= []
|
18
|
-
|
19
|
-
define_method(name) { args[
|
20
|
-
|
22
|
+
|
23
|
+
define_method(name) { args[name] }
|
24
|
+
|
25
|
+
@arguments << opts.merge(name: name)
|
21
26
|
end
|
22
27
|
|
23
28
|
# Defines a custom cache key that is calculated for passed arguments.
|
@@ -29,7 +34,7 @@ module N1Loader
|
|
29
34
|
end
|
30
35
|
end
|
31
36
|
|
32
|
-
def initialize(elements,
|
37
|
+
def initialize(elements, **args)
|
33
38
|
@elements = elements
|
34
39
|
@args = args
|
35
40
|
end
|
@@ -44,18 +49,45 @@ module N1Loader
|
|
44
49
|
end
|
45
50
|
|
46
51
|
def cache_key
|
47
|
-
|
52
|
+
check_arguments!
|
53
|
+
args.values.map(&:object_id)
|
48
54
|
end
|
49
55
|
|
50
56
|
private
|
51
57
|
|
52
58
|
attr_reader :elements, :args
|
53
59
|
|
60
|
+
def check_missing_arguments! # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
61
|
+
return unless (arguments = self.class.arguments)
|
62
|
+
|
63
|
+
min = arguments.count { |argument| !argument[:optional] }
|
64
|
+
max = arguments.count
|
65
|
+
|
66
|
+
return if args.size >= min && args.size <= max
|
67
|
+
|
68
|
+
str =
|
69
|
+
if min == max
|
70
|
+
max.to_s
|
71
|
+
else
|
72
|
+
"#{min}..#{max}"
|
73
|
+
end
|
74
|
+
|
75
|
+
raise MissingArgument, "Loader requires #{str} arguments but #{args.size} were given"
|
76
|
+
end
|
77
|
+
|
54
78
|
def check_arguments!
|
55
|
-
|
56
|
-
|
79
|
+
check_missing_arguments!
|
80
|
+
check_invalid_arguments!
|
81
|
+
end
|
57
82
|
|
58
|
-
|
83
|
+
def check_invalid_arguments!
|
84
|
+
return unless (arguments = self.class.arguments)
|
85
|
+
|
86
|
+
args.each_key do |arg|
|
87
|
+
next if arguments.find { |argument| argument[:name] == arg }
|
88
|
+
|
89
|
+
raise InvalidArgument, "Loader doesn't define #{arg} argument"
|
90
|
+
end
|
59
91
|
end
|
60
92
|
|
61
93
|
def perform(_elements)
|
@@ -66,7 +98,7 @@ module N1Loader
|
|
66
98
|
@loaded[element] = value
|
67
99
|
end
|
68
100
|
|
69
|
-
def loaded
|
101
|
+
def loaded
|
70
102
|
return @loaded if @loaded
|
71
103
|
|
72
104
|
check_arguments!
|
@@ -74,9 +106,9 @@ module N1Loader
|
|
74
106
|
@loaded = {}.compare_by_identity
|
75
107
|
|
76
108
|
if elements.size == 1 && respond_to?(:single)
|
77
|
-
fulfill(elements.first, single(elements.first
|
109
|
+
fulfill(elements.first, single(elements.first))
|
78
110
|
elsif elements.any?
|
79
|
-
perform(elements
|
111
|
+
perform(elements)
|
80
112
|
end
|
81
113
|
|
82
114
|
@loaded
|
@@ -17,13 +17,23 @@ module N1Loader
|
|
17
17
|
def preload(*keys)
|
18
18
|
keys.flatten(1).flat_map do |key|
|
19
19
|
elements
|
20
|
-
.group_by { |element| element
|
20
|
+
.group_by { |element| loader_class(element, key) }
|
21
21
|
.map do |loader_class, grouped_elements|
|
22
|
+
next unless loader_class
|
23
|
+
|
22
24
|
loader_collection = N1Loader::LoaderCollection.new(loader_class, grouped_elements)
|
23
25
|
grouped_elements.each { |grouped_element| grouped_element.n1_loader_set(key, loader_collection) }
|
24
26
|
loader_collection
|
25
|
-
end
|
27
|
+
end.compact
|
26
28
|
end
|
27
29
|
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def loader_class(element, key)
|
34
|
+
element.class.respond_to?(:n1_loader_defined?) &&
|
35
|
+
element.class.n1_loader_defined?(key) &&
|
36
|
+
element.class.n1_loader(key)
|
37
|
+
end
|
28
38
|
end
|
29
39
|
end
|
data/lib/n1_loader/version.rb
CHANGED
data/lib/n1_loader.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.4.2
|
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-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|