n1_loader 1.3.0 → 1.4.0

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: 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