n1_loader 1.3.0 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc47894a27d425f70a38dc43b9b40053d823f4815fe8e135a7cb1cf5afbde0f3
4
- data.tar.gz: 132d5851c19763db2f1910ebeeb6a368437aba6d3340c2c69ad2ffe348791011
3
+ metadata.gz: 2d0f96166ae91c856012ea26c933cc56c7d33f68065b753a18adc3c6b9da7e87
4
+ data.tar.gz: adccc88c382d9283c2af9b41059298fa15563e47615678ef20629a0183a51253
5
5
  SHA512:
6
- metadata.gz: 945258069b57bc7cd0fe26416411322886d734db0ed4eead1c88a3acced70bdb14ffc92eb9403e5d346c0853d64dc09e5afb8039a9026c9b4c09a1cc6f5fc9e8
7
- data.tar.gz: 036e5a8a4b2d63bed2aee558164fdd042ac8c0c68b0ecdb1d5ec9f64231ca9f6841dc56cd6cdc63b87fdb9bf964e4f42089f89d9783cf2513cf81243a473150a
6
+ metadata.gz: ad3b1273627cf7abb5922cc7139c34e5e9543dc91d296d7e44deea690367d32082dd1ddc028ba40a4ed7fa402a65eb8f5aed36b6d8df0436937e59aaa6cef65a
7
+ data.tar.gz: fde03e325f2b780a070786f1648a98d278933c84b3266cedf4db50cd268b029839ff06290416846a9ab2a85a25a0795c30684845e4da15909417389dbd1a7a82
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [1.4.0] - 2022-02-22
2
+
3
+ - add support of optional arguments
4
+
5
+ BREAKING CHANGES:
6
+ - rework arguments to use single definition through `argument <name>` only
7
+ - use keyword arguments
8
+
1
9
  ## [1.3.0] - 2022-02-22
2
10
 
3
11
  - add support of named arguments with `argument <name>`
data/README.md CHANGED
@@ -188,21 +188,25 @@ users.map(&:orders_count) # perform will be used once without N+1
188
188
  class User
189
189
  include N1Loader::Loadable
190
190
 
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]) }
191
+ n1_optimized :orders_count do
192
+ argument :type
193
+
194
+ def perform(users)
195
+ orders_per_user = Order.where(type: type, user: users).group(:user_id).count
196
+
197
+ users.each { |user| fulfill(user, orders_per_user[user.id]) }
198
+ end
195
199
  end
196
200
  end
197
201
 
198
202
  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
203
+ user.orders_count(type: :gifts) # The loader will be performed first time for this argument
204
+ user.orders_count(type: :sales) # The loader will be performed first time for this argument
205
+ user.orders_count(type: :gifts) # The cached value will be used
202
206
 
203
207
  users = [User.new, User.new]
204
208
  N1Loader::Preloader.new(users).preload(:orders_count)
205
- users.map { |user| user.orders_count(:gifts) } # No N+1 here
209
+ users.map { |user| user.orders_count(type: :gifts) } # No N+1 here
206
210
  ```
207
211
 
208
212
  _Note_: By default, we use `arguments.map(&:object_id)` to identify arguments but in some cases,
@@ -217,7 +221,7 @@ class User
217
221
 
218
222
  cache_key { sale.id }
219
223
 
220
- def perform(users, sale)
224
+ def perform(users)
221
225
  orders_per_user = Order.where(sale: sale, user: users).group(:user_id).count
222
226
 
223
227
  users.each { |user| fulfill(user, orders_per_user[user.id]) }
@@ -226,8 +230,8 @@ class User
226
230
  end
227
231
 
228
232
  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
233
+ user.orders_count(sale: Sale.first) # perform will be executed and value will be cached
234
+ user.orders_count(sale: Sale.first) # the cached value will be returned
231
235
  ```
232
236
 
233
237
 
@@ -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?
@@ -47,7 +47,7 @@ module N1Loader
47
47
 
48
48
  def n1_optimized(name, loader = nil, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
49
49
  loader ||= Class.new(N1Loader::Loader) do
50
- if block&.arity&.positive?
50
+ if block.arity == 1
51
51
  define_method(:perform, &block)
52
52
  else
53
53
  class_eval(&block)
@@ -73,10 +73,10 @@ module N1Loader
73
73
  instance_variable_get(loader_variable_name) || send("#{loader_name}_reload")
74
74
  end
75
75
 
76
- define_method(name) do |*args, reload: false|
76
+ define_method(name) do |reload: false, **args|
77
77
  send("#{loader_name}_reload") if reload
78
78
 
79
- send(loader_name).with(*args).for(self)
79
+ send(loader_name).with(**args).for(self)
80
80
  end
81
81
 
82
82
  [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
@@ -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.0"
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,7 +1,7 @@
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.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgeniy Demin