memoized 1.1.0 → 1.1.1

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: 1b5c64274f2f14822e1da8bd04450fcaba727b7dd7310ab704e6d418651df057
4
- data.tar.gz: f9afb8a81796f1841a5504eabeeb7a67956fc8c1b14e8e82cf89676c59449378
3
+ metadata.gz: 38881d8fc70ecdbf1920e7bbb40d684403d88c4225e53d7a3e426f97ef5f41ac
4
+ data.tar.gz: 750f0290f9258b48bcabdf993e0c0a11f9919643b2a944efa5046cdd930d05a8
5
5
  SHA512:
6
- metadata.gz: 39b17f38a9ffa32e4d3369e1d2e0f2d5db75e522bc412a63d984e0fffa0c3e17310e6dd7a50094c6554fea1192de13a5b5b290823f26a5ee5af62bc94e2e8911
7
- data.tar.gz: 0e8696b57c1dee349ef43b40b1949af22ec3f826dbbf1d0fe8b39ddd7a751ba5ca5b0893335620cb21ef9e1fabf1f9e094c61524b97cd3a3ef0c14f26f3b455b
6
+ metadata.gz: 35dd34457b8c984200f9fe3eefc72539cf6dcf4661d6ade672197a9c0760439fb27d7b577400eb9416db66fb2f69fc8b6302f78f36f6dbb25d79350f5b7ca521
7
+ data.tar.gz: 94360c1c038704a77d06258aa76971bdcb875064cbec5a26b090f5764c50eecccddb01c42ecd67b3a15b3c577768a2740160163111900543582ab4677577d994
data/CHANGELOG.md CHANGED
@@ -13,6 +13,17 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html
13
13
 
14
14
  -
15
15
 
16
+ ## 1.1.1 - 2022-06-22
17
+
18
+ ### Breaking changes
19
+
20
+ - (The `.parameters` of a memoized method are no longer renamed to `arg1` ... `argn` and instead retain their original names)
21
+
22
+ ### Compatible changes
23
+
24
+ - Methods with keyword arguments can now be properly memoized
25
+ - In addition to a methods `.arity`, memoized now also preserves its `.parameters`
26
+
16
27
  ## 1.1.0 - 2022-03-16
17
28
 
18
29
  ### Breaking changes
data/Gemfile CHANGED
@@ -1,10 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Development dependencies
4
- gem 'rake', '~> 10.4.2'
5
- gem 'rspec', '~> 3.5.0'
6
- gem 'timecop', '~> 0.8.0'
7
- gem 'gemika'
8
-
9
- # Gem under test
10
- gem 'memoized', :path => '.'
3
+ gemspec
data/Gemfile.lock CHANGED
@@ -1,13 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- memoized (1.1.0)
4
+ memoized (1.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ amazing_print (1.4.0)
9
10
  diff-lcs (1.3)
10
11
  gemika (0.7.1)
12
+ prop_check (0.14.1)
13
+ amazing_print (~> 1.2)
11
14
  rake (10.4.2)
12
15
  rspec (3.5.0)
13
16
  rspec-core (~> 3.5.0)
@@ -30,6 +33,7 @@ PLATFORMS
30
33
  DEPENDENCIES
31
34
  gemika
32
35
  memoized!
36
+ prop_check (~> 0.14.1)
33
37
  rake (~> 10.4.2)
34
38
  rspec (~> 3.5.0)
35
39
  timecop (~> 0.8.0)
@@ -0,0 +1,98 @@
1
+ module Memoized
2
+ class Parameters
3
+ UNIQUE = 42.freeze
4
+
5
+ attr_accessor :req_params, :opt_params, :rest_params, :keyreq_params, :key_params, :keyrest_params
6
+
7
+ def initialize(parameters = [])
8
+ # This constructor does not check, whether the parameters were ordered correctly
9
+ # with respect to the Ruby language specification. However, all outputs will be sorted correctly.
10
+ @req_params = []
11
+ @opt_params = []
12
+ @rest_params = []
13
+ @keyreq_params = []
14
+ @key_params = []
15
+ @keyrest_params = []
16
+
17
+ parameters.each do |(param_type, param_name)|
18
+ case param_type
19
+ when :req
20
+ @req_params << [param_type, param_name]
21
+ when :opt
22
+ @opt_params << [param_type, param_name]
23
+ when :rest
24
+ @rest_params << [param_type, param_name]
25
+ when :keyreq
26
+ @keyreq_params << [param_type, param_name]
27
+ when :key
28
+ @key_params << [param_type, param_name]
29
+ when :keyrest
30
+ @keyrest_params << [param_type, param_name]
31
+ when :block
32
+ raise Memoized::CannotMemoize, 'Cannot memoize a method that takes a block!'
33
+ else
34
+ raise Memoized::CannotMemoize, 'Unknown parameter type!'
35
+ end
36
+ end
37
+
38
+ if @rest_params.size > 1 || @keyrest_params.size > 1
39
+ raise Memoized::CannotMemoize "Multiple rest or keyrest parameters, invalid signature!"
40
+ end
41
+ end
42
+
43
+ def params
44
+ @req_params + @opt_params + @rest_params + @keyreq_params + @key_params + @keyrest_params
45
+ end
46
+
47
+ def signature
48
+ params.map(&Parameters.method(:to_signature)).join(', ')
49
+ end
50
+
51
+ def self.to_signature((param_type, param_name))
52
+ case param_type
53
+ when :req
54
+ "#{param_name}"
55
+ when :opt
56
+ "#{param_name} = Memoized::Parameters::UNIQUE"
57
+ when :rest
58
+ "*#{param_name}"
59
+ when :keyreq
60
+ "#{param_name}:"
61
+ when :key
62
+ "#{param_name}: Memoized::Parameters::UNIQUE"
63
+ when :keyrest
64
+ "**#{param_name}"
65
+ else raise "unknown parameter type"
66
+ end
67
+ end
68
+
69
+ def cache_key
70
+ <<-STRING
71
+ all_args = []
72
+ all_kwargs = {}
73
+
74
+ #{params.map(&Parameters.method(:to_cache_key)).join("\n")}
75
+
76
+ cache_key = [all_args, all_kwargs]
77
+ STRING
78
+ end
79
+
80
+ def self.to_cache_key((param_type, param_name))
81
+ case param_type
82
+ when :req
83
+ "all_args.push(#{param_name})"
84
+ when :opt
85
+ "all_args.push(#{param_name}) unless #{param_name}.equal?(Memoized::Parameters::UNIQUE)"
86
+ when :rest
87
+ "all_args.push(*#{param_name})"
88
+ when :keyreq
89
+ "all_kwargs[:#{param_name}] = #{param_name}"
90
+ when :key
91
+ "all_kwargs[:#{param_name}] = #{param_name} unless #{param_name}.equal?(Memoized::Parameters::UNIQUE)"
92
+ when :keyrest
93
+ "all_kwargs.merge!(#{param_name})"
94
+ else raise "unknown parameter type"
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,3 +1,3 @@
1
1
  module Memoized
2
- VERSION = '1.1.0'
2
+ VERSION = '1.1.1'
3
3
  end
data/lib/memoized.rb CHANGED
@@ -1,4 +1,8 @@
1
+ require 'memoized/parameters'
2
+
1
3
  module Memoized
4
+ class CannotMemoize < ::StandardError; end
5
+
2
6
  def self.included(base)
3
7
  base.extend ClassMethods
4
8
  base.send :include, InstanceMethods
@@ -20,62 +24,27 @@ module Memoized
20
24
 
21
25
  alias_method unmemoized_method, method_name
22
26
 
23
- arity = instance_method(unmemoized_method).arity
24
-
25
- if arity == 0
26
- module_eval(<<-RUBY)
27
- def #{method_name}()
28
- #{memoized_ivar_name} ||= [#{unmemoized_method}()]
29
- #{memoized_ivar_name}.first
30
- end
31
- RUBY
32
-
33
- elsif arity == -1
34
- module_eval(<<-RUBY)
35
- def #{method_name}(*args)
36
- #{memoized_ivar_name} ||= {}
37
- if #{memoized_ivar_name}.has_key?(args)
38
- #{memoized_ivar_name}[args]
39
- else
40
- #{memoized_ivar_name}[args] = #{unmemoized_method}(*args)
41
- end
42
- end
43
- RUBY
44
-
45
- elsif arity < -1
46
- # For Ruby methods that take a variable number of arguments,
47
- # Method#arity returns -n-1, where n is the number of required arguments
48
- required_arg_names = (1..(-arity - 1)).map { |i| "arg#{i}" }
49
- required_args_ruby = required_arg_names.join(', ')
50
-
51
- module_eval(<<-RUBY)
52
- def #{method_name}(#{required_args_ruby}, *optional_args)
53
- all_args = [#{required_args_ruby}, *optional_args]
54
- #{memoized_ivar_name} ||= {}
55
- if #{memoized_ivar_name}.has_key?(all_args)
56
- #{memoized_ivar_name}[all_args]
57
- else
58
- #{memoized_ivar_name}[all_args] = #{unmemoized_method}(*all_args)
59
- end
60
- end
61
- RUBY
27
+ parameters = Parameters.new(instance_method(unmemoized_method).parameters)
62
28
 
63
- else # positive arity
64
- arg_names = (1..arity).map { |i| "arg#{i}" }
65
- args_ruby = arg_names.join(', ')
29
+ module_eval(<<-RUBY)
30
+ def #{method_name}(#{parameters.signature})
31
+ #{parameters.cache_key}
66
32
 
67
- module_eval(<<-RUBY)
68
- def #{method_name}(#{args_ruby})
69
- all_args = [#{args_ruby}]
70
- #{memoized_ivar_name} ||= {}
71
- if #{memoized_ivar_name}.has_key?(all_args)
72
- #{memoized_ivar_name}[all_args]
73
- else
74
- #{memoized_ivar_name}[all_args] = #{unmemoized_method}(#{args_ruby})
33
+ #{memoized_ivar_name} ||= {}
34
+
35
+ if #{memoized_ivar_name}.key?(cache_key)
36
+ #{memoized_ivar_name}[cache_key]
37
+ else
38
+ live_result = if all_kwargs.empty?
39
+ #{unmemoized_method}(*all_args)
40
+ else
41
+ #{unmemoized_method}(*all_args, **all_kwargs)
75
42
  end
43
+ #{memoized_ivar_name}[cache_key] = live_result
44
+ live_result
76
45
  end
77
- RUBY
78
- end
46
+ end
47
+ RUBY
79
48
 
80
49
  if self.private_method_defined?(unmemoized_method)
81
50
  private method_name
data/memoized.gemspec CHANGED
@@ -25,4 +25,6 @@ Gem::Specification.new do |s|
25
25
  s.add_development_dependency('rake', '~> 10.4.2')
26
26
  s.add_development_dependency('rspec', '~> 3.5.0')
27
27
  s.add_development_dependency('timecop', '~> 0.8.0')
28
+ s.add_development_dependency('prop_check', '~> 0.14.1')
29
+ s.add_development_dependency('gemika')
28
30
  end
@@ -3,7 +3,13 @@ class MemoizedSpecClass
3
3
  def no_params() Date.today; end
4
4
  def with_params?(ndays, an_array) Date.today + ndays + an_array.length; end
5
5
  def returning_nil!() Date.today; nil; end
6
- memoize :no_params, :with_params?, :returning_nil!
6
+ def all_param_types(req, opt = 3, *rest, keyreq:, key: 11, **keyrest)
7
+ Date.today + (req * opt * rest.inject(&:*) * keyreq * key * keyrest.values.inject(&:*))
8
+ end
9
+ def only_kwargs(**keyrest)
10
+ Date.today + keyrest.values.inject(&:*)
11
+ end
12
+ memoize :no_params, :with_params?, :returning_nil!, :all_param_types, :only_kwargs
7
13
  end
8
14
  class Beepbop < MemoizedSpecClass; end
9
15
 
@@ -49,6 +55,40 @@ describe Memoized do
49
55
  end
50
56
  end
51
57
 
58
+ context "for a method with all param types" do
59
+ it "stores memoized value" do
60
+ Timecop.freeze(today)
61
+ expect(object.all_param_types(2, 3, 5, 5, keyreq: 7, key: 11, **{ first: 13, second: 13 })).to eq(today + 1951950)
62
+ Timecop.freeze(tomorrow)
63
+ expect(object.all_param_types(2, 3, 5, 5, keyreq: 7, key: 11, **{ first: 13, second: 13 })).to eq(today + 1951950)
64
+ end
65
+ it "does not confuse one set of inputs for another" do
66
+ Timecop.freeze(today)
67
+ expect(object.all_param_types(2, 3, 5, 5, keyreq: 7, key: 11, **{ first: 13, second: 13 })).to eq(today + 1951950)
68
+ expect(object.all_param_types(2, 9, 5, keyreq: 7, key: 121, **{ first: 13 })).to eq(today + 990990)
69
+ Timecop.freeze(tomorrow)
70
+ expect(object.all_param_types(2, 3, 5, 5, keyreq: 7, key: 11, **{ first: 13, second: 13 })).to eq(today + 1951950)
71
+ expect(object.all_param_types(2, 9, 5, keyreq: 7, key: 121, **{ first: 13 })).to eq(today + 990990)
72
+ end
73
+ end
74
+
75
+ context "for a method with only keyword rest arguments" do
76
+ it "stores memoized value" do
77
+ Timecop.freeze(today)
78
+ expect(object.only_kwargs(**{ first: 2, second: 3 })).to eq(today + 6)
79
+ Timecop.freeze(tomorrow)
80
+ expect(object.only_kwargs(**{ first: 2, second: 3 })).to eq(today + 6)
81
+ end
82
+ it "does not confuse one set of inputs for another" do
83
+ Timecop.freeze(today)
84
+ expect(object.only_kwargs(**{ first: 2, second: 3 })).to eq(today + 6)
85
+ expect(object.only_kwargs(**{ first: 7 })).to eq(today + 7)
86
+ Timecop.freeze(tomorrow)
87
+ expect(object.only_kwargs(**{ first: 2, second: 3 })).to eq(today + 6)
88
+ expect(object.only_kwargs(**{ first: 7 })).to eq(today + 7)
89
+ end
90
+ end
91
+
52
92
  context "for subclasses" do
53
93
  let(:object) { Beepbop.new }
54
94
  it "still memoizes things" do
@@ -114,7 +154,6 @@ describe Memoized do
114
154
  it 'creates a memoized method with an arity of 0' do
115
155
  expect(Arity0.instance_method(:foo).arity).to eq(0)
116
156
  end
117
-
118
157
  end
119
158
 
120
159
  context 'for methods with an arity of 2' do
@@ -128,7 +167,6 @@ describe Memoized do
128
167
  it 'creates a memoized method with an arity of 2' do
129
168
  expect(Arity2.instance_method(:foo).arity).to eq(2)
130
169
  end
131
-
132
170
  end
133
171
 
134
172
  context 'for methods with splat args' do
@@ -142,11 +180,9 @@ describe Memoized do
142
180
  it 'creates a memoized method with an arity of -1' do
143
181
  expect(AritySplat.instance_method(:foo).arity).to eq(-1)
144
182
  end
145
-
146
183
  end
147
184
 
148
185
  context 'for methods with a required and an optional arg' do
149
-
150
186
  class ArityRequiredAndOptional < MemoizedSpecClass
151
187
  def foo(a, b = 'default')
152
188
  return [a, b]
@@ -163,11 +199,9 @@ describe Memoized do
163
199
  instance = ArityRequiredAndOptional.new
164
200
  expect(instance.foo('foo')).to eq ['foo', 'default']
165
201
  end
166
-
167
202
  end
168
203
 
169
204
  context 'for methods with a required arg and splat args' do
170
-
171
205
  class ArityArgAndOptional < MemoizedSpecClass
172
206
  def foo(a, *args)
173
207
  return [a, args]
@@ -184,7 +218,54 @@ describe Memoized do
184
218
  instance = ArityArgAndOptional.new
185
219
  expect(instance.foo('foo', 'bar', 'baz')).to eq ['foo', ['bar', 'baz']]
186
220
  end
221
+ end
222
+
223
+ context 'for methods with all types of args' do
224
+ class AllArgTypes < MemoizedSpecClass
225
+ def foo(required, optional = 3, *rest, req_keyword:, opt_keyword: 11, **keyrest)
226
+ return [required, optional, rest, req_keyword, opt_keyword, keyrest]
227
+ end
228
+
229
+ memoize :foo
230
+ end
231
+
232
+ it 'the memoized method has the same arity as the original method' do
233
+ expect(AllArgTypes.instance_method(:_unmemoized_foo).arity).to eq(-3)
234
+ expect(AllArgTypes.instance_method(:foo).arity).to eq(-3)
235
+ end
187
236
 
237
+ it 'the memoized method has the same parameters as the original method' do
238
+ expect(AllArgTypes.instance_method(:_unmemoized_foo).parameters)
239
+ .to eq([
240
+ [:req, :required],
241
+ [:opt, :optional],
242
+ [:rest, :rest],
243
+ [:keyreq, :req_keyword],
244
+ [:key, :opt_keyword],
245
+ [:keyrest, :keyrest]
246
+ ])
247
+ expect(AllArgTypes.instance_method(:foo).parameters)
248
+ .to eq([
249
+ [:req, :required],
250
+ [:opt, :optional],
251
+ [:rest, :rest],
252
+ [:keyreq, :req_keyword],
253
+ [:key, :opt_keyword],
254
+ [:keyrest, :keyrest]
255
+ ])
256
+ end
257
+
258
+ it "passes all args to the original method correctly" do
259
+ instance = AllArgTypes.new
260
+ expect(instance.foo(2, 333, 5, 5, req_keyword: 7, opt_keyword: 1111, first: 13, second: 17))
261
+ .to eq [2, 333, [5, 5], 7, 1111, { first: 13, second: 17 }]
262
+ end
263
+
264
+ it "preserves the original method's default values" do
265
+ instance = AllArgTypes.new
266
+ expect(instance.foo(2, req_keyword: 7, third: 19))
267
+ .to eq [2, 3, [], 7, 11, { third: 19 }]
268
+ end
188
269
  end
189
270
 
190
271
  end
@@ -0,0 +1,222 @@
1
+ unless RUBY_VERSION == '2.5.3'
2
+ describe "#memoize" do
3
+ include PropCheck
4
+ include PropCheck::Generators
5
+ include Memoized
6
+
7
+ before do
8
+ PropCheck::Property.configure do |config|
9
+ # CAUTION:
10
+ # 100 (default) takes 8 seconds
11
+ # 300 takes 11 minutes
12
+ config.n_runs = 100
13
+ end
14
+ end
15
+
16
+ it "does not change the method's arity" do
17
+ forall(
18
+ array(
19
+ tuple(
20
+ one_of(
21
+ constant(:req), constant(:opt), constant(:rest), constant(:keyreq), constant(:key), constant(:keyrest)
22
+ ),
23
+ simple_symbol.map do |sym|
24
+ "param_#{sym}".to_sym
25
+ end
26
+ )
27
+ )
28
+ ) do |parameters|
29
+ # params now have proper names (no :"", no Ruby keywords) due to the .map in the generator above
30
+ unique_names = parameters.uniq { |v| v[1] }
31
+ single_args_and_kwargs = unique_names.uniq do |v|
32
+ if [:rest, :keyrest].include?(v[0])
33
+ v[0]
34
+ else
35
+ v[1]
36
+ end
37
+ end
38
+
39
+ mp = Memoized::Parameters.new(single_args_and_kwargs)
40
+ puts mp.debug_info if ENV['DEBUG'] == 'true'
41
+
42
+ eval(<<-RUBY)
43
+ class MemoizedPropertyClass
44
+ include Memoized
45
+
46
+ def parameter_dummy(#{mp.signature})
47
+ 42
48
+ end
49
+
50
+ @old_arity = new.method(:parameter_dummy).arity
51
+ memoize :parameter_dummy
52
+ @new_arity = new.method(:parameter_dummy).arity
53
+
54
+ # cleanup to get rid of warnings
55
+ remove_method :_unmemoized_parameter_dummy
56
+ remove_method :parameter_dummy
57
+ end
58
+ RUBY
59
+
60
+ expect(MemoizedPropertyClass.instance_variable_get(:@new_arity))
61
+ .to eq(MemoizedPropertyClass.instance_variable_get(:@old_arity))
62
+ end
63
+ end
64
+
65
+ it "does not change the method's parameters" do
66
+ forall(
67
+ array(
68
+ tuple(
69
+ one_of(
70
+ constant(:req), constant(:opt), constant(:rest), constant(:keyreq), constant(:key), constant(:keyrest)
71
+ ),
72
+ simple_symbol.map do |sym|
73
+ "param_#{sym}".to_sym
74
+ end
75
+ )
76
+ )
77
+ ) do |parameters|
78
+ # params now have proper names (no :"", no Ruby keywords) due to the .map in the generator above
79
+ unique_names = parameters.uniq { |v| v[1] }
80
+ single_args_and_kwargs = unique_names.uniq do |v|
81
+ if [:rest, :keyrest].include?(v[0])
82
+ v[0]
83
+ else
84
+ v[1]
85
+ end
86
+ end
87
+
88
+ mp = Memoized::Parameters.new(single_args_and_kwargs)
89
+ puts mp.debug_info if ENV['DEBUG'] == 'true'
90
+
91
+ eval(<<-RUBY)
92
+ class MemoizedPropertyClass
93
+ include Memoized
94
+
95
+ def parameter_dummy(#{mp.signature})
96
+ 42
97
+ end
98
+
99
+ @old_parameters = new.method(:parameter_dummy).parameters
100
+ memoize :parameter_dummy
101
+ @new_parameters = new.method(:parameter_dummy).parameters
102
+
103
+ # cleanup to get rid of warnings
104
+ remove_method :_unmemoized_parameter_dummy
105
+ remove_method :parameter_dummy
106
+ end
107
+ RUBY
108
+
109
+ expect(MemoizedPropertyClass.instance_variable_get(:@new_parameters))
110
+ .to eq(MemoizedPropertyClass.instance_variable_get(:@old_parameters))
111
+ end
112
+ end
113
+
114
+ it "does not change the method's value" do
115
+ forall(
116
+ array(
117
+ tuple(
118
+ one_of(
119
+ constant(:req), constant(:opt), constant(:rest), constant(:keyreq), constant(:key), constant(:keyrest)
120
+ ),
121
+ simple_symbol.map do |sym|
122
+ "param_#{sym}".to_sym
123
+ end
124
+ )
125
+ )
126
+ ) do |parameters|
127
+ # params now have proper names (no :"", no Ruby keywords) due to the .map in the generator above
128
+ unique_names = parameters.uniq { |v| v[1] }
129
+ single_args_and_kwargs = unique_names.uniq do |v|
130
+ if [:rest, :keyrest].include?(v[0])
131
+ v[0]
132
+ else
133
+ v[1]
134
+ end
135
+ end
136
+
137
+ mp = Memoized::Parameters.new(single_args_and_kwargs)
138
+ puts mp.debug_info if ENV['DEBUG'] == 'true'
139
+
140
+ eval(<<-RUBY)
141
+ class MemoizedPropertyClass
142
+ include Memoized
143
+
144
+ def parameter_dummy(#{mp.signature})
145
+ #{mp.test_body}
146
+ end
147
+
148
+ @old_value = new.parameter_dummy(#{mp.test_arguments})
149
+ memoize :parameter_dummy
150
+ @new_value = new.parameter_dummy(#{mp.test_arguments})
151
+
152
+ # cleanup to get rid of warnings
153
+ remove_method :_unmemoized_parameter_dummy
154
+ remove_method :parameter_dummy
155
+ end
156
+ RUBY
157
+
158
+ expect(MemoizedPropertyClass.instance_variable_get(:@new_value))
159
+ .to eq(MemoizedPropertyClass.instance_variable_get(:@old_value))
160
+ end
161
+ end
162
+
163
+ it "computes the correct value" do
164
+ forall(array(choose(15), min: 6, max: 6)) do |parameter_multiplicities|
165
+
166
+ params = []
167
+
168
+ parameter_multiplicities[0].times.with_index do |counter|
169
+ params << [:req, "required_#{counter}".to_sym]
170
+ end
171
+
172
+ parameter_multiplicities[1].times.with_index do |counter|
173
+ params << [:opt, "optional_#{counter}".to_sym]
174
+ end
175
+
176
+ if parameter_multiplicities[2] != 0
177
+ params << [:rest, :args]
178
+ end
179
+
180
+ parameter_multiplicities[3].times.with_index do |counter|
181
+ params << [:keyreq, "required_kw_#{counter}".to_sym]
182
+ end
183
+
184
+ parameter_multiplicities[4].times.with_index do |counter|
185
+ params << [:key, "optional_kw_#{counter}".to_sym]
186
+ end
187
+
188
+ if parameter_multiplicities[5] != 0
189
+ params << [:keyrest, :kwargs]
190
+ end
191
+
192
+ mp = Memoized::Parameters.new(params, parameter_multiplicities[2], parameter_multiplicities[5])
193
+
194
+ puts mp.debug_info if ENV['DEBUG'] == 'true'
195
+
196
+ eval(<<-RUBY)
197
+ class MemoizedPropertyClass
198
+ include Memoized
199
+
200
+ def parameter_dummy(#{mp.signature})
201
+ #{mp.test_body}
202
+ end
203
+
204
+ memoize :parameter_dummy
205
+ @value = new.parameter_dummy(#{mp.test_arguments})
206
+
207
+ # cleanup to get rid of warnings
208
+ remove_method :_unmemoized_parameter_dummy
209
+ remove_method :parameter_dummy
210
+ end
211
+ RUBY
212
+
213
+ expected_result = [2, 3, 5, 7, 11, 13].zip(parameter_multiplicities).map do |base, exponent|
214
+ base ** exponent
215
+ end.inject(&:*)
216
+
217
+ expect(MemoizedPropertyClass.instance_variable_get(:@value))
218
+ .to eq(expected_result)
219
+ end
220
+ end
221
+ end
222
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,88 @@
1
1
  require 'date'
2
2
  require 'timecop'
3
3
  require 'memoized'
4
+ if Gem::Version.new(RUBY_VERSION) > Gem::Version.new('2.5.3')
5
+ require 'prop_check'
6
+ end
4
7
 
5
8
  RSpec.configure do |config|
6
9
  config.warnings = true
7
10
  config.order = :random
8
11
  Kernel.srand config.seed
9
12
  end
13
+
14
+ module Memoized
15
+ class Parameters
16
+ attr_accessor :args_count, :kwargs_count
17
+
18
+ def test_body
19
+ params.map(&Parameters.method(:to_test_body)).join(" * ")
20
+ end
21
+
22
+ def self.to_test_body((param_type, param_name))
23
+ case param_type
24
+ when :req
25
+ "#{param_name}"
26
+ when :opt
27
+ "#{param_name}"
28
+ when :rest
29
+ "#{param_name}.inject(&:*)"
30
+ when :keyreq
31
+ "#{param_name}"
32
+ when :key
33
+ "#{param_name}"
34
+ when :keyrest
35
+ "#{param_name}.values.inject(&:*)"
36
+ else raise "unknown parameter type"
37
+ end
38
+ end
39
+
40
+ def test_arguments
41
+ params.map(&Parameters.new([], @args_count, @kwargs_count).method(:to_test_arguments)).join(", ")
42
+ end
43
+
44
+ def to_test_arguments((param_type, param_name))
45
+ case param_type
46
+ when :req
47
+ "2"
48
+ when :opt
49
+ "3"
50
+ when :rest
51
+ if @args_count.nil?
52
+ "5, 5, 5"
53
+ else
54
+ (["5"] * @args_count).join(', ')
55
+ end
56
+ when :keyreq
57
+ "#{param_name}: 7"
58
+ when :key
59
+ "#{param_name}: 11"
60
+ when :keyrest
61
+ if @kwargs_count.nil?
62
+ "**{ first: 13, second: 13 }"
63
+ else
64
+ kwargs_list = (1..@kwargs_count).map.with_index do |counter|
65
+ "kwarg_#{counter}: 13"
66
+ end
67
+ "**{ #{kwargs_list.join(', ')} }"
68
+ end
69
+ else raise "unknown parameter type"
70
+ end
71
+ end
72
+
73
+ def debug_info
74
+ "#{@req_params.size} - #{@opt_params.size} - #{@args_count || @rest_params.size} " \
75
+ "| #{@keyreq_params.size} - #{@key_params.size} - #{@kwargs_count || @keyrest_params.size}"
76
+ end
77
+ end
78
+ end
79
+
80
+ module ParametersTestExtension
81
+ def initialize(parameters = [], args_count = nil, kwargs_count = nil)
82
+ super(parameters)
83
+ @args_count = args_count
84
+ @kwargs_count = kwargs_count
85
+ end
86
+ end
87
+
88
+ Memoized::Parameters.prepend(ParametersTestExtension)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memoized
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Barun Singh
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-03-16 00:00:00.000000000 Z
12
+ date: 2022-06-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -53,6 +53,34 @@ dependencies:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
55
  version: 0.8.0
56
+ - !ruby/object:Gem::Dependency
57
+ name: prop_check
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 0.14.1
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.14.1
70
+ - !ruby/object:Gem::Dependency
71
+ name: gemika
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
56
84
  description: Memoized caches the results of your method calls
57
85
  email:
58
86
  executables: []
@@ -70,9 +98,11 @@ files:
70
98
  - README.md
71
99
  - Rakefile
72
100
  - lib/memoized.rb
101
+ - lib/memoized/parameters.rb
73
102
  - lib/memoized/version.rb
74
103
  - memoized.gemspec
75
104
  - spec/memoized_spec.rb
105
+ - spec/properties_spec.rb
76
106
  - spec/spec_helper.rb
77
107
  homepage: https://github.com/makandra/memoized
78
108
  licenses:
@@ -97,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
127
  - !ruby/object:Gem::Version
98
128
  version: '0'
99
129
  requirements: []
100
- rubygems_version: 3.2.21
130
+ rubygems_version: 3.0.3.1
101
131
  signing_key:
102
132
  specification_version: 4
103
133
  summary: Memoized caches the results of your method calls