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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc47894a27d425f70a38dc43b9b40053d823f4815fe8e135a7cb1cf5afbde0f3
4
- data.tar.gz: 132d5851c19763db2f1910ebeeb6a368437aba6d3340c2c69ad2ffe348791011
3
+ metadata.gz: f72c7428f78489c25222fb2ab8d56fde519c680fdc010115b952637c2291ca93
4
+ data.tar.gz: 7149852481ffed8d1fd0fc25e28fc045eda0a0f2936e748461dd7f43d4f709f1
5
5
  SHA512:
6
- metadata.gz: 945258069b57bc7cd0fe26416411322886d734db0ed4eead1c88a3acced70bdb14ffc92eb9403e5d346c0853d64dc09e5afb8039a9026c9b4c09a1cc6f5fc9e8
7
- data.tar.gz: 036e5a8a4b2d63bed2aee558164fdd042ac8c0c68b0ecdb1d5ec9f64231ca9f6841dc56cd6cdc63b87fdb9bf964e4f42089f89d9783cf2513cf81243a473150a
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 |users, type|
192
- orders_per_user = Order.where(type: type, user: users).group(:user_id).count
193
-
194
- users.each { |user| fulfill(user, orders_per_user[user.id]) }
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, sale)
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
- unless loader_class.instance_method(:perform).arity == 1
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
@@ -6,7 +6,7 @@ module N1Loader
6
6
  module LoaderCollectionPatch
7
7
  attr_accessor :context_setup
8
8
 
9
- def with(*args)
9
+ def with(**args)
10
10
  result = super
11
11
 
12
12
  result.context_setup = context_setup if context_setup && result.context_setup.nil?
@@ -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&.arity&.positive?
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 |*args, reload: false|
88
+ define_method(name) do |reload: false, **args|
77
89
  send("#{loader_name}_reload") if reload
78
90
 
79
- send(loader_name).with(*args).for(self)
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
- def argument(name)
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
- index = @arguments.size
19
- define_method(name) { args[index] }
20
- @arguments << name
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, *args)
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
- args.map(&:object_id)
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
- return unless (required = self.class.arguments)
56
- return if required.size == args.size
79
+ check_missing_arguments!
80
+ check_invalid_arguments!
81
+ end
57
82
 
58
- raise MissingArgument, "Loader defined #{required.size} arguments but #{args.size} were given"
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 # rubocop:disable Metrics/AbcSize
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, *args))
109
+ fulfill(elements.first, single(elements.first))
78
110
  elsif elements.any?
79
- perform(elements, *args)
111
+ perform(elements)
80
112
  end
81
113
 
82
114
  @loaded
@@ -10,8 +10,8 @@ module N1Loader
10
10
  @elements = elements
11
11
  end
12
12
 
13
- def with(*args)
14
- loader = loader_class.new(elements, *args)
13
+ def with(**args)
14
+ loader = loader_class.new(elements, **args)
15
15
 
16
16
  loaders[loader.cache_key] ||= loader
17
17
  end
@@ -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.class.n1_loader(key) }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module N1Loader
4
- VERSION = "1.3.0"
4
+ VERSION = "1.4.2"
5
5
  end
data/lib/n1_loader.rb CHANGED
@@ -13,4 +13,5 @@ module N1Loader # :nodoc:
13
13
  class NotLoaded < Error; end
14
14
  class NotFilled < Error; end
15
15
  class MissingArgument < Error; end
16
+ class InvalidArgument < Error; end
16
17
  end
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.3.0
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-02-22 00:00:00.000000000 Z
11
+ date: 2022-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord