memo_wise 1.0.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.
data/lib/memo_wise.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
3
5
  require "memo_wise/internal_api"
4
6
  require "memo_wise/version"
5
7
 
@@ -23,7 +25,7 @@ require "memo_wise/version"
23
25
  # - {.memo_wise} for API and usage examples.
24
26
  # - {file:README.md} for general project information.
25
27
  #
26
- module MemoWise # rubocop:disable Metrics/ModuleLength
28
+ module MemoWise
27
29
  # Constructor to set up memoization state before
28
30
  # [calling the original](https://medium.com/@jeremy_96642/ruby-method-auditing-using-module-prepend-4f4e69aacd95)
29
31
  # constructor.
@@ -54,7 +56,7 @@ module MemoWise # rubocop:disable Metrics/ModuleLength
54
56
  # :nocov:
55
57
  all_args = RUBY_VERSION < "2.7" ? "*" : "..."
56
58
  # :nocov:
57
- class_eval <<-END_OF_METHOD, __FILE__, __LINE__ + 1
59
+ class_eval <<~HEREDOC, __FILE__, __LINE__ + 1
58
60
  # On Ruby 2.7 or greater:
59
61
  #
60
62
  # def initialize(...)
@@ -73,7 +75,7 @@ module MemoWise # rubocop:disable Metrics/ModuleLength
73
75
  MemoWise::InternalAPI.create_memo_wise_state!(self)
74
76
  super
75
77
  end
76
- END_OF_METHOD
78
+ HEREDOC
77
79
 
78
80
  # @private
79
81
  #
@@ -89,7 +91,7 @@ module MemoWise # rubocop:disable Metrics/ModuleLength
89
91
  # prepend MemoWise
90
92
  # end
91
93
  #
92
- def self.prepended(target) # rubocop:disable Metrics/PerceivedComplexity
94
+ def self.prepended(target)
93
95
  class << target
94
96
  # Allocator to set up memoization state before
95
97
  # [calling the original](https://medium.com/@jeremy_96642/ruby-method-auditing-using-module-prepend-4f4e69aacd95)
@@ -109,7 +111,7 @@ module MemoWise # rubocop:disable Metrics/ModuleLength
109
111
  end
110
112
 
111
113
  # NOTE: See YARD docs for {.memo_wise} directly below this method!
112
- def memo_wise(method_name_or_hash) # rubocop:disable Metrics/PerceivedComplexity
114
+ def memo_wise(method_name_or_hash)
113
115
  klass = self
114
116
  case method_name_or_hash
115
117
  when Symbol
@@ -120,6 +122,23 @@ module MemoWise # rubocop:disable Metrics/ModuleLength
120
122
  MemoWise::InternalAPI.original_class_from_singleton(klass)
121
123
  )
122
124
  end
125
+
126
+ # Ensures a module extended by another class/module still works
127
+ # e.g. rails `ClassMethods` module
128
+ if klass.is_a?(Module) && !klass.is_a?(Class)
129
+ # Using `extended` without `included` & `prepended`
130
+ # As a call to `create_memo_wise_state!` is already included in
131
+ # `.allocate`/`#initialize`
132
+ #
133
+ # But a module/class extending another module with memo_wise
134
+ # would not call `.allocate`/`#initialize` before calling methods
135
+ #
136
+ # On method call `@_memo_wise` would still be `nil`
137
+ # causing error when fetching cache from `@_memo_wise`
138
+ def klass.extended(base)
139
+ MemoWise::InternalAPI.create_memo_wise_state!(base)
140
+ end
141
+ end
123
142
  when Hash
124
143
  unless method_name_or_hash.keys == [:self]
125
144
  raise ArgumentError,
@@ -137,92 +156,159 @@ module MemoWise # rubocop:disable Metrics/ModuleLength
137
156
  klass = klass.singleton_class
138
157
  end
139
158
 
140
- unless method_name.is_a?(Symbol)
141
- raise ArgumentError, "#{method_name.inspect} must be a Symbol"
159
+ if klass.singleton_class?
160
+ # This ensures that a memoized method defined on a parent class can
161
+ # still be used in a child class.
162
+ klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
163
+ def inherited(subclass)
164
+ super
165
+ MemoWise::InternalAPI.create_memo_wise_state!(subclass)
166
+ end
167
+ HEREDOC
142
168
  end
143
169
 
144
- api = MemoWise::InternalAPI.new(klass)
145
- visibility = api.method_visibility(method_name)
146
- original_memo_wised_name =
147
- MemoWise::InternalAPI.original_memo_wised_name(method_name)
170
+ raise ArgumentError, "#{method_name.inspect} must be a Symbol" unless method_name.is_a?(Symbol)
171
+
172
+ visibility = MemoWise::InternalAPI.method_visibility(klass, method_name)
173
+ original_memo_wised_name = MemoWise::InternalAPI.original_memo_wised_name(method_name)
148
174
  method = klass.instance_method(method_name)
149
175
 
150
176
  klass.send(:alias_method, original_memo_wised_name, method_name)
151
177
  klass.send(:private, original_memo_wised_name)
152
178
 
153
- # Zero-arg methods can use simpler/more performant logic because the
154
- # hash key is just the method name.
155
- if method.arity.zero?
156
- klass.module_eval <<-END_OF_METHOD, __FILE__, __LINE__ + 1
157
- # def foo
158
- # @_memo_wise.fetch(:foo) do
159
- # @_memo_wise[:foo] = _memo_wise_original_foo
160
- # end
161
- # end
162
-
163
- def #{method_name}
164
- @_memo_wise.fetch(:#{method_name}) do
165
- @_memo_wise[:#{method_name}] = #{original_memo_wised_name}
179
+ method_arguments = MemoWise::InternalAPI.method_arguments(method)
180
+
181
+ case method_arguments
182
+ when MemoWise::InternalAPI::NONE
183
+ # Zero-arg methods can use simpler/more performant logic because the
184
+ # hash key is just the method name.
185
+ klass.send(:define_method, method_name) do # Ruby 2.4's `define_method` is private in some cases
186
+ index = MemoWise::InternalAPI.index(self, method_name)
187
+ klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
188
+ def #{method_name}
189
+ if @_memo_wise_sentinels[#{index}]
190
+ @_memo_wise[#{index}]
191
+ else
192
+ ret = @_memo_wise[#{index}] = #{original_memo_wised_name}
193
+ @_memo_wise_sentinels[#{index}] = true
194
+ ret
195
+ end
166
196
  end
197
+ HEREDOC
198
+
199
+ klass.send(visibility, method_name)
200
+ send(method_name)
201
+ end
202
+ when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL, MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD
203
+ key = method.parameters.first.last
204
+ # NOTE: Ruby 2.6 and below, and TruffleRuby 3.0, break when we use
205
+ # `define_method(...) do |*args, **kwargs|`. Instead we must use the
206
+ # simpler `|*args|` pattern. We can't just do this always though
207
+ # because Ruby 2.7 and above require `|*args, **kwargs|` to work
208
+ # correctly.
209
+ # See: https://blog.saeloun.com/2019/10/07/ruby-2-7-keyword-arguments-redesign.html#ruby-26
210
+ # :nocov:
211
+ if RUBY_VERSION < "2.7" || RUBY_ENGINE == "truffleruby"
212
+ klass.send(:define_method, method_name) do |*args| # Ruby 2.4's `define_method` is private in some cases
213
+ index = MemoWise::InternalAPI.index(self, method_name)
214
+ klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
215
+ def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
216
+ _memo_wise_hash = (@_memo_wise[#{index}] ||= {})
217
+ _memo_wise_output = _memo_wise_hash[#{key}]
218
+ if _memo_wise_output || _memo_wise_hash.key?(#{key})
219
+ _memo_wise_output
220
+ else
221
+ _memo_wise_hash[#{key}] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})
222
+ end
223
+ end
224
+ HEREDOC
225
+
226
+ klass.send(visibility, method_name)
227
+ send(method_name, *args)
167
228
  end
168
- END_OF_METHOD
169
- else
170
- if MemoWise::InternalAPI.has_only_required_args?(method)
171
- args_str = method.parameters.map do |type, name|
172
- "#{name}#{':' if type == :keyreq}"
173
- end.join(", ")
174
- args_str = "(#{args_str})"
175
- call_str = method.parameters.map do |type, name|
176
- type == :req ? name : "#{name}: #{name}"
177
- end.join(", ")
178
- call_str = "(#{call_str})"
179
- fetch_key = method.parameters.map(&:last)
180
- fetch_key = if fetch_key.size > 1
181
- "[#{fetch_key.join(', ')}].freeze"
182
- else
183
- fetch_key.first.to_s
184
- end
229
+ # :nocov:
185
230
  else
186
- # If our method has arguments, we need to separate out our handling
187
- # of normal args vs. keyword args due to the changes in Ruby 3.
188
- # See: <link>
189
- # By only including logic for *args, **kwargs when they are used in
190
- # the method, we can avoid allocating unnecessary arrays and hashes.
191
- has_arg = MemoWise::InternalAPI.has_arg?(method)
192
-
193
- if has_arg && MemoWise::InternalAPI.has_kwarg?(method)
194
- args_str = "(*args, **kwargs)"
195
- fetch_key = "[args, kwargs].freeze"
196
- elsif has_arg
197
- args_str = "(*args)"
198
- fetch_key = "args"
199
- else
200
- args_str = "(**kwargs)"
201
- fetch_key = "kwargs"
231
+ klass.define_method(method_name) do |*args, **kwargs|
232
+ index = MemoWise::InternalAPI.index(self, method_name)
233
+ klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
234
+ def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
235
+ _memo_wise_hash = (@_memo_wise[#{index}] ||= {})
236
+ _memo_wise_output = _memo_wise_hash[#{key}]
237
+ if _memo_wise_output || _memo_wise_hash.key?(#{key})
238
+ _memo_wise_output
239
+ else
240
+ _memo_wise_hash[#{key}] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})
241
+ end
242
+ end
243
+ HEREDOC
244
+
245
+ klass.send(visibility, method_name)
246
+ send(method_name, *args, **kwargs)
202
247
  end
203
248
  end
204
-
205
- # Note that we don't need to freeze args before using it as a hash key
206
- # because Ruby always copies argument arrays when splatted.
207
- klass.module_eval <<-END_OF_METHOD, __FILE__, __LINE__ + 1
208
- # def foo(*args, **kwargs)
209
- # hash = @_memo_wise.fetch(:foo) do
210
- # @_memo_wise[:foo] = {}
211
- # end
212
- # hash.fetch([args, kwargs].freeze) do
213
- # hash[[args, kwargs].freeze] = _memo_wise_original_foo(*args, **kwargs)
214
- # end
215
- # end
216
-
217
- def #{method_name}#{args_str}
218
- hash = @_memo_wise.fetch(:#{method_name}) do
219
- @_memo_wise[:#{method_name}] = {}
220
- end
221
- hash.fetch(#{fetch_key}) do
222
- hash[#{fetch_key}] = #{original_memo_wised_name}#{call_str || args_str}
223
- end
249
+ # MemoWise::InternalAPI::MULTIPLE_REQUIRED, MemoWise::InternalAPI::SPLAT,
250
+ # MemoWise::InternalAPI::DOUBLE_SPLAT, MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
251
+ else
252
+ # NOTE: When benchmarking this implementation against something like:
253
+ #
254
+ # @_memo_wise.fetch(key) do
255
+ # ...
256
+ # end
257
+ #
258
+ # this implementation may sometimes perform worse than the above. This
259
+ # is because this case uses a more complex hash key (see
260
+ # `MemoWise::InternalAPI.key_str`), and hashing that key has less
261
+ # consistent performance. In general, this should still be faster for
262
+ # truthy results because `Hash#[]` generally performs hash lookups
263
+ # faster than `Hash#fetch`.
264
+ #
265
+ # NOTE: Ruby 2.6 and below, and TruffleRuby 3.0, break when we use
266
+ # `define_method(...) do |*args, **kwargs|`. Instead we must use the
267
+ # simpler `|*args|` pattern. We can't just do this always though
268
+ # because Ruby 2.7 and above require `|*args, **kwargs|` to work
269
+ # correctly.
270
+ # See: https://blog.saeloun.com/2019/10/07/ruby-2-7-keyword-arguments-redesign.html#ruby-26
271
+ # :nocov:
272
+ if RUBY_VERSION < "2.7" || RUBY_ENGINE == "truffleruby"
273
+ klass.send(:define_method, method_name) do |*args| # Ruby 2.4's `define_method` is private in some cases
274
+ index = MemoWise::InternalAPI.index(self, method_name)
275
+ klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
276
+ def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
277
+ _memo_wise_hash = (@_memo_wise[#{index}] ||= {})
278
+ _memo_wise_key = #{MemoWise::InternalAPI.key_str(method)}
279
+ _memo_wise_output = _memo_wise_hash[_memo_wise_key]
280
+ if _memo_wise_output || _memo_wise_hash.key?(_memo_wise_key)
281
+ _memo_wise_output
282
+ else
283
+ _memo_wise_hash[_memo_wise_key] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})
284
+ end
285
+ end
286
+ HEREDOC
287
+
288
+ klass.send(visibility, method_name)
289
+ send(method_name, *args)
290
+ end
291
+ # :nocov:
292
+ else # Ruby 2.7 and above break with (*args)
293
+ klass.define_method(method_name) do |*args, **kwargs|
294
+ index = MemoWise::InternalAPI.index(self, method_name)
295
+ klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
296
+ def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
297
+ _memo_wise_hash = (@_memo_wise[#{index}] ||= {})
298
+ _memo_wise_key = #{MemoWise::InternalAPI.key_str(method)}
299
+ _memo_wise_output = _memo_wise_hash[_memo_wise_key]
300
+ if _memo_wise_output || _memo_wise_hash.key?(_memo_wise_key)
301
+ _memo_wise_output
302
+ else
303
+ _memo_wise_hash[_memo_wise_key] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})
304
+ end
305
+ end
306
+ HEREDOC
307
+
308
+ klass.send(visibility, method_name)
309
+ send(method_name, *args, **kwargs)
224
310
  end
225
- END_OF_METHOD
311
+ end
226
312
  end
227
313
 
228
314
  klass.send(visibility, method_name)
@@ -248,8 +334,7 @@ module MemoWise # rubocop:disable Metrics/ModuleLength
248
334
  # (`...` or `*args, **kwargs`), making reflection on method parameters
249
335
  # useless without this.
250
336
  def target.instance_method(symbol)
251
- # TODO: Extract this method naming pattern
252
- original_memo_wised_name = :"_memo_wise_original_#{symbol}"
337
+ original_memo_wised_name = MemoWise::InternalAPI.original_memo_wised_name(symbol)
253
338
 
254
339
  super.tap do |curr_method|
255
340
  # Start with calling the original `instance_method` on `symbol`,
@@ -341,7 +426,6 @@ module MemoWise # rubocop:disable Metrics/ModuleLength
341
426
  # Example.method_called_times #=> nil
342
427
  ##
343
428
 
344
- # rubocop:disable Layout/LineLength
345
429
  ##
346
430
  # @!method self.reset_memo_wise(method_name = nil, *args, **kwargs)
347
431
  # Implementation of {#reset_memo_wise} for class methods.
@@ -375,7 +459,6 @@ module MemoWise # rubocop:disable Metrics/ModuleLength
375
459
  #
376
460
  # Example.reset_memo_wise # reset "all methods" mode
377
461
  ##
378
- # rubocop:enable Layout/LineLength
379
462
 
380
463
  # Presets the memoized result for the given method to the result of the given
381
464
  # block.
@@ -428,21 +511,34 @@ module MemoWise # rubocop:disable Metrics/ModuleLength
428
511
  # ex.method_called_times #=> nil
429
512
  #
430
513
  def preset_memo_wise(method_name, *args, **kwargs)
431
- unless block_given?
432
- raise ArgumentError,
433
- "Pass a block as the value to preset for #{method_name}, #{args}"
514
+ raise ArgumentError, "Pass a block as the value to preset for #{method_name}, #{args}" unless block_given?
515
+
516
+ MemoWise::InternalAPI.validate_memo_wised!(self, method_name)
517
+
518
+ method = method(MemoWise::InternalAPI.original_memo_wised_name(method_name))
519
+ method_arguments = MemoWise::InternalAPI.method_arguments(method)
520
+ index = MemoWise::InternalAPI.index(self, method_name)
521
+
522
+ if method_arguments == MemoWise::InternalAPI::NONE
523
+ @_memo_wise_sentinels[index] = true
524
+ @_memo_wise[index] = yield
525
+ return
434
526
  end
435
527
 
436
- api = MemoWise::InternalAPI.new(self)
437
- api.validate_memo_wised!(method_name)
528
+ hash = (@_memo_wise[index] ||= {})
438
529
 
439
- if method(method_name).arity.zero?
440
- @_memo_wise[method_name] = yield
441
- else
442
- hash = @_memo_wise.fetch(method_name) do
443
- @_memo_wise[method_name] = {}
530
+ case method_arguments
531
+ when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL then hash[args.first] = yield
532
+ when MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD then hash[kwargs.first.last] = yield
533
+ when MemoWise::InternalAPI::SPLAT then hash[args] = yield
534
+ when MemoWise::InternalAPI::DOUBLE_SPLAT then hash[kwargs] = yield
535
+ when MemoWise::InternalAPI::MULTIPLE_REQUIRED
536
+ key = method.parameters.map.with_index do |(type, name), idx|
537
+ type == :req ? args[idx] : kwargs[name]
444
538
  end
445
- hash[api.fetch_key(method_name, *args, **kwargs)] = yield
539
+ hash[key] = yield
540
+ else # MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
541
+ hash[[args, kwargs]] = yield
446
542
  end
447
543
  end
448
544
 
@@ -513,33 +609,64 @@ module MemoWise # rubocop:disable Metrics/ModuleLength
513
609
  #
514
610
  def reset_memo_wise(method_name = nil, *args, **kwargs)
515
611
  if method_name.nil?
516
- unless args.empty?
517
- raise ArgumentError, "Provided args when method_name = nil"
518
- end
612
+ raise ArgumentError, "Provided args when method_name = nil" unless args.empty?
613
+ raise ArgumentError, "Provided kwargs when method_name = nil" unless kwargs.empty?
519
614
 
520
- unless kwargs.empty?
521
- raise ArgumentError, "Provided kwargs when method_name = nil"
522
- end
523
-
524
- return @_memo_wise.clear
615
+ @_memo_wise.clear
616
+ @_memo_wise_sentinels.clear
617
+ return
525
618
  end
526
619
 
527
- unless method_name.is_a?(Symbol)
528
- raise ArgumentError, "#{method_name.inspect} must be a Symbol"
529
- end
620
+ raise ArgumentError, "#{method_name.inspect} must be a Symbol" unless method_name.is_a?(Symbol)
621
+ raise ArgumentError, "#{method_name} is not a defined method" unless respond_to?(method_name, true)
530
622
 
531
- unless respond_to?(method_name, true)
532
- raise ArgumentError, "#{method_name} is not a defined method"
533
- end
623
+ MemoWise::InternalAPI.validate_memo_wised!(self, method_name)
534
624
 
535
- api = MemoWise::InternalAPI.new(self)
536
- api.validate_memo_wised!(method_name)
625
+ method = method(MemoWise::InternalAPI.original_memo_wised_name(method_name))
626
+ method_arguments = MemoWise::InternalAPI.method_arguments(method)
627
+ index = MemoWise::InternalAPI.index(self, method_name)
537
628
 
538
- if args.empty? && kwargs.empty?
539
- @_memo_wise.delete(method_name)
540
- else
541
- @_memo_wise[method_name]&.
542
- delete(api.fetch_key(method_name, *args, **kwargs))
629
+ case method_arguments
630
+ when MemoWise::InternalAPI::NONE
631
+ @_memo_wise_sentinels[index] = nil
632
+ @_memo_wise[index] = nil
633
+ when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL
634
+ if args.empty?
635
+ @_memo_wise[index]&.clear
636
+ else
637
+ @_memo_wise[index]&.delete(args.first)
638
+ end
639
+ when MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD
640
+ if kwargs.empty?
641
+ @_memo_wise[index]&.clear
642
+ else
643
+ @_memo_wise[index]&.delete(kwargs.first.last)
644
+ end
645
+ when MemoWise::InternalAPI::SPLAT
646
+ if args.empty?
647
+ @_memo_wise[index]&.clear
648
+ else
649
+ @_memo_wise[index]&.delete(args)
650
+ end
651
+ when MemoWise::InternalAPI::DOUBLE_SPLAT
652
+ if kwargs.empty?
653
+ @_memo_wise[index]&.clear
654
+ else
655
+ @_memo_wise[index]&.delete(kwargs)
656
+ end
657
+ else # MemoWise::InternalAPI::MULTIPLE_REQUIRED, MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
658
+ if args.empty? && kwargs.empty?
659
+ @_memo_wise[index]&.clear
660
+ else
661
+ key = if method_arguments == MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
662
+ [args, kwargs]
663
+ else
664
+ method.parameters.map.with_index do |(type, name), i|
665
+ type == :req ? args[i] : kwargs[name] # rubocop:disable Metrics/BlockNesting
666
+ end
667
+ end
668
+ @_memo_wise[index]&.delete(key)
669
+ end
543
670
  end
544
671
  end
545
672
  end
data/memo_wise.gemspec CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
36
36
  spec.require_paths = ["lib"]
37
37
 
38
38
  spec.metadata = {
39
+ "rubygems_mfa_required" => "true",
39
40
  "changelog_uri" => "https://github.com/panorama-ed/memo_wise/blob/main/CHANGELOG.md",
40
41
  "source_code_uri" => "https://github.com/panorama-ed/memo_wise"
41
42
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memo_wise
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Panorama Education
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2021-06-24 00:00:00.000000000 Z
14
+ date: 2021-12-10 00:00:00.000000000 Z
15
15
  dependencies: []
16
16
  description:
17
17
  email:
@@ -39,9 +39,7 @@ files:
39
39
  - LICENSE.txt
40
40
  - README.md
41
41
  - Rakefile
42
- - benchmarks/.ruby-version
43
42
  - benchmarks/Gemfile
44
- - benchmarks/Gemfile.lock
45
43
  - benchmarks/benchmarks.rb
46
44
  - bin/console
47
45
  - bin/setup
@@ -54,6 +52,7 @@ homepage: https://github.com/panorama-ed/memo_wise
54
52
  licenses:
55
53
  - MIT
56
54
  metadata:
55
+ rubygems_mfa_required: 'true'
57
56
  changelog_uri: https://github.com/panorama-ed/memo_wise/blob/main/CHANGELOG.md
58
57
  source_code_uri: https://github.com/panorama-ed/memo_wise
59
58
  post_install_message:
@@ -1 +0,0 @@
1
- 3.0.0
@@ -1,29 +0,0 @@
1
- PATH
2
- remote: ..
3
- specs:
4
- memo_wise (0.4.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- benchmark-ips (2.9.1)
10
- memoist (0.16.2)
11
- memoized (1.0.2)
12
- memoizer (1.0.3)
13
-
14
- PLATFORMS
15
- x86_64-darwin-19
16
- x86_64-linux
17
-
18
- DEPENDENCIES
19
- benchmark-ips (= 2.9.1)
20
- memo_wise!
21
- memoist (= 0.16.2)
22
- memoized (= 1.0.2)
23
- memoizer (= 1.0.3)
24
-
25
- RUBY VERSION
26
- ruby 3.0.0p0
27
-
28
- BUNDLED WITH
29
- 2.2.3