memoized 1.0.1 → 1.1.1

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: 9ab7a8649195530ae1e3ace893313e3731c1dcf3af93f6ebeaebad4a715efa65
4
- data.tar.gz: 543fd2584dbf7a34fd45c23394888cf8935a8f93f93be9a293b684e1054c3eed
3
+ metadata.gz: 38881d8fc70ecdbf1920e7bbb40d684403d88c4225e53d7a3e426f97ef5f41ac
4
+ data.tar.gz: 750f0290f9258b48bcabdf993e0c0a11f9919643b2a944efa5046cdd930d05a8
5
5
  SHA512:
6
- metadata.gz: 13673e0131ff6560848e8dcac44ea746db4c18602b3fa68d3072dbefeaca95bf93ea1bbd4159ce49c8be30cd7361e616237c1ef61d19bc6e2d72efaba6a5d9fe
7
- data.tar.gz: 99f27fd8d150aaf3283d29cd159f16ed31beb13c6a293338709f15397c562294caf55a971b7764847895919aaf46c1a87ea77c123f2680691b1765b361b129b8
6
+ metadata.gz: 35dd34457b8c984200f9fe3eefc72539cf6dcf4661d6ade672197a9c0760439fb27d7b577400eb9416db66fb2f69fc8b6302f78f36f6dbb25d79350f5b7ca521
7
+ data.tar.gz: 94360c1c038704a77d06258aa76971bdcb875064cbec5a26b090f5764c50eecccddb01c42ecd67b3a15b3c577768a2740160163111900543582ab4677577d994
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: Tests
3
+ 'on':
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+ branches:
9
+ - master
10
+ jobs:
11
+ test:
12
+ runs-on: ubuntu-20.04
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ include:
17
+ - ruby: 2.5.3
18
+ gemfile: Gemfile
19
+ - ruby: 2.6.7
20
+ gemfile: Gemfile
21
+ - ruby: 2.7.3
22
+ gemfile: Gemfile
23
+ - ruby: 3.0.1
24
+ gemfile: Gemfile
25
+ env:
26
+ BUNDLE_GEMFILE: "${{ matrix.gemfile }}"
27
+ steps:
28
+ - uses: actions/checkout@v2
29
+ - name: Install ruby
30
+ uses: ruby/setup-ruby@v1
31
+ with:
32
+ ruby-version: "${{ matrix.ruby }}"
33
+ - name: Bundle
34
+ run: |
35
+ gem install bundler:2.2.21
36
+ bundle install --no-deployment
37
+ - name: Run tests
38
+ run: bundle exec rspec
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.1
1
+ 2.6.7
data/CHANGELOG.md CHANGED
@@ -13,6 +13,32 @@ 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
+
27
+ ## 1.1.0 - 2022-03-16
28
+
29
+ ### Breaking changes
30
+
31
+ - Remove no longer supported ruby versions (2.3.8, 2.4.5)
32
+
33
+ ### Compatible changes
34
+
35
+ - Activate rubygems MFA
36
+
37
+ ## 1.0.2 - 2019-05-22
38
+
39
+ ### Compatible changes
40
+
41
+ - Preserve arity of methods with optional arguments
16
42
 
17
43
  ## 1.0.1 - 2019-02-27
18
44
 
data/Gemfile CHANGED
@@ -1,2 +1,3 @@
1
1
  source 'https://rubygems.org'
2
+
2
3
  gemspec
data/Gemfile.lock CHANGED
@@ -1,12 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- memoized (1.0.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)
11
+ gemika (0.7.1)
12
+ prop_check (0.14.1)
13
+ amazing_print (~> 1.2)
10
14
  rake (10.4.2)
11
15
  rspec (3.5.0)
12
16
  rspec-core (~> 3.5.0)
@@ -27,10 +31,12 @@ PLATFORMS
27
31
  ruby
28
32
 
29
33
  DEPENDENCIES
34
+ gemika
30
35
  memoized!
36
+ prop_check (~> 0.14.1)
31
37
  rake (~> 10.4.2)
32
38
  rspec (~> 3.5.0)
33
39
  timecop (~> 0.8.0)
34
40
 
35
41
  BUNDLED WITH
36
- 1.17.2
42
+ 2.2.21
data/README.md CHANGED
@@ -1,6 +1,4 @@
1
- [![Build Status](https://travis-ci.org/makandra/memoized.svg?branch=master)](https://travis-ci.org/makandra/memoized)
2
-
3
- # Memoized
1
+ # Memoized [![Tests](https://github.com/makandra/memoized/workflows/Tests/badge.svg)](https://github.com/makandra/memoized/actions)
4
2
 
5
3
  Memoized will memoize the results of your methods. It acts much like
6
4
  `ActiveSupport::Memoizable` without all of that freezing business. The API for
@@ -14,7 +12,7 @@ $ gem install memoized
14
12
 
15
13
  ## Usage
16
14
 
17
- To define a memoized instance method, use `memoized def``:
15
+ To define a memoized instance method, use `memoize def`:
18
16
 
19
17
  ```ruby
20
18
  class A
@@ -78,6 +76,64 @@ instance.goodbye # the goodbye method is now memoized
78
76
  instance.unmemoize_all # neither hello nor goodbye are memoized anymore
79
77
  ```
80
78
 
79
+ ## Limitations
80
+
81
+ When you are using Memoized with default arguments or default keyword arguments, there are some edge cased you have to
82
+ keep in mind.
83
+
84
+ When you memoize a method with (keyword) arguments that have an expression as default value, you should be aware
85
+ that the expression is evaluated only once.
86
+
87
+ ```ruby
88
+ memoize def print_time(time = Time.now)
89
+ time
90
+ end
91
+
92
+ print_time
93
+ => 2021-07-23 14:23:18 +0200
94
+
95
+ sleep(1.minute)
96
+ print_time
97
+ => 2021-07-23 14:23:18 +0200
98
+ ```
99
+
100
+ When you memoize a method with (keyword) arguments that have default values, you should be aware that Memoized
101
+ differentiates between a method call without arguments and the default values.
102
+
103
+ ```ruby
104
+ def true_or_false(default = true)
105
+ puts 'calculate value ...'
106
+ default
107
+ end
108
+
109
+ true_or_false
110
+ calculate value ...
111
+ => true
112
+
113
+ true_or_false
114
+ => true
115
+
116
+ true_or_false(true)
117
+ calculate value ...
118
+ => true
119
+ ```
120
+
121
+ ## Development
122
+
123
+ There are tests in `spec`. We only accept PRs with tests. To run tests:
124
+
125
+ - Install Ruby 2.6.1
126
+ - Install development dependencies using `bundle install`
127
+ - Run tests using `bundle exec rake current_rspec`
128
+
129
+ We recommend to test large changes against multiple versions of Ruby. Supported combinations are configured in `.github/workflows/test.yml`. We provide some rake tasks to help with this:
130
+
131
+ - Install development dependencies using `bundle exec rake matrix:install`
132
+ - Run tests using `bundle exec rake matrix:spec`
133
+
134
+ Note that we have configured GitHub Actions to automatically run tests in all supported Ruby versions and dependency sets after each push. We will only merge pull requests after a green GitHub Actions run.
135
+
136
+ I'm very eager to keep this gem leightweight and on topic. If you're unsure whether a change would make it into the gem, [talk to me beforehand](mailto:henning.koch@makandra.de).
81
137
 
82
138
  ## License
83
139
 
data/Rakefile CHANGED
@@ -1,2 +1,10 @@
1
1
  require 'rake'
2
2
  require 'bundler/gem_tasks'
3
+
4
+ begin
5
+ require 'gemika/tasks'
6
+ rescue LoadError
7
+ puts 'Run `gem install gemika` for additional tasks'
8
+ end
9
+
10
+ task :default => 'matrix:spec'
@@ -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.0.1'
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,45 +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
- case arity
26
- when 0
27
- module_eval(<<-RUBY)
28
- def #{method_name}()
29
- #{memoized_ivar_name} ||= [#{unmemoized_method}()]
30
- #{memoized_ivar_name}.first
31
- end
32
- RUBY
33
-
34
- when -1
35
- module_eval(<<-RUBY)
36
- def #{method_name}(*args)
37
- #{memoized_ivar_name} ||= {}
38
- if #{memoized_ivar_name}.has_key?(args)
39
- #{memoized_ivar_name}[args]
40
- else
41
- #{memoized_ivar_name}[args] = #{unmemoized_method}(*args)
42
- end
43
- end
44
- RUBY
27
+ parameters = Parameters.new(instance_method(unmemoized_method).parameters)
45
28
 
46
- else
47
- arg_names = (0..(arity - 1)).map { |i| "arg#{i}" }
48
- args_ruby = arg_names.join(', ')
29
+ module_eval(<<-RUBY)
30
+ def #{method_name}(#{parameters.signature})
31
+ #{parameters.cache_key}
49
32
 
50
- module_eval(<<-RUBY)
51
- def #{method_name}(#{args_ruby})
52
- args = [#{args_ruby}]
53
- #{memoized_ivar_name} ||= {}
54
- if #{memoized_ivar_name}.has_key?(args)
55
- #{memoized_ivar_name}[args]
56
- else
57
- #{memoized_ivar_name}[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)
58
42
  end
43
+ #{memoized_ivar_name}[cache_key] = live_result
44
+ live_result
59
45
  end
60
- RUBY
61
- end
46
+ end
47
+ RUBY
62
48
 
63
49
  if self.private_method_defined?(unmemoized_method)
64
50
  private method_name
data/memoized.gemspec CHANGED
@@ -8,6 +8,12 @@ Gem::Specification.new do |s|
8
8
  s.homepage = "https://github.com/makandra/memoized"
9
9
  s.summary = "Memoized caches the results of your method calls"
10
10
  s.description = s.summary
11
+ s.metadata = {
12
+ 'source_code_uri' => s.homepage,
13
+ 'bug_tracker_uri' => 'https://github.com/makandra/memoized/issues',
14
+ 'changelog_uri' => 'https://github.com/makandra/memoized/blob/master/CHANGELOG.md',
15
+ 'rubygems_mfa_required' => 'true',
16
+ }
11
17
 
12
18
  s.files = `git ls-files`.split("\n").reject { |path| File.lstat(path).symlink? }
13
19
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n").reject { |path| File.lstat(path).symlink? }
@@ -19,4 +25,6 @@ Gem::Specification.new do |s|
19
25
  s.add_development_dependency('rake', '~> 10.4.2')
20
26
  s.add_development_dependency('rspec', '~> 3.5.0')
21
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')
22
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,7 +180,92 @@ 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
183
+ end
145
184
 
185
+ context 'for methods with a required and an optional arg' do
186
+ class ArityRequiredAndOptional < MemoizedSpecClass
187
+ def foo(a, b = 'default')
188
+ return [a, b]
189
+ end
190
+
191
+ memoize :foo
192
+ end
193
+
194
+ it 'creates a memoized method with a arity of -2' do
195
+ expect(ArityRequiredAndOptional.instance_method(:foo).arity).to eq(-2)
196
+ end
197
+
198
+ it "preserves the optional arg's default value" do
199
+ instance = ArityRequiredAndOptional.new
200
+ expect(instance.foo('foo')).to eq ['foo', 'default']
201
+ end
202
+ end
203
+
204
+ context 'for methods with a required arg and splat args' do
205
+ class ArityArgAndOptional < MemoizedSpecClass
206
+ def foo(a, *args)
207
+ return [a, args]
208
+ end
209
+
210
+ memoize :foo
211
+ end
212
+
213
+ it 'creates a memoized method with a arity of -2' do
214
+ expect(ArityArgAndOptional.instance_method(:foo).arity).to eq(-2)
215
+ end
216
+
217
+ it "passes the splat args to the memoized method" do
218
+ instance = ArityArgAndOptional.new
219
+ expect(instance.foo('foo', 'bar', 'baz')).to eq ['foo', ['bar', 'baz']]
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
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
146
269
  end
147
270
 
148
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.0.1
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: 2019-02-27 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,16 +53,44 @@ 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: []
59
87
  extensions: []
60
88
  extra_rdoc_files: []
61
89
  files:
90
+ - ".github/workflows/test.yml"
62
91
  - ".gitignore"
63
92
  - ".rspec"
64
93
  - ".ruby-version"
65
- - ".travis.yml"
66
94
  - CHANGELOG.md
67
95
  - Gemfile
68
96
  - Gemfile.lock
@@ -70,14 +98,20 @@ 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:
79
109
  - MIT
80
- metadata: {}
110
+ metadata:
111
+ source_code_uri: https://github.com/makandra/memoized
112
+ bug_tracker_uri: https://github.com/makandra/memoized/issues
113
+ changelog_uri: https://github.com/makandra/memoized/blob/master/CHANGELOG.md
114
+ rubygems_mfa_required: 'true'
81
115
  post_install_message:
82
116
  rdoc_options: []
83
117
  require_paths:
@@ -93,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
127
  - !ruby/object:Gem::Version
94
128
  version: '0'
95
129
  requirements: []
96
- rubygems_version: 3.0.1
130
+ rubygems_version: 3.0.3.1
97
131
  signing_key:
98
132
  specification_version: 4
99
133
  summary: Memoized caches the results of your method calls
data/.travis.yml DELETED
@@ -1,26 +0,0 @@
1
- language: ruby
2
-
3
- rvm:
4
- - 2.1.8
5
- - 2.3.8
6
- - 2.4.5
7
- - 2.5.3
8
- - 2.6.1
9
-
10
- gemfile:
11
- - Gemfile
12
-
13
- script: bundle exec rspec spec
14
-
15
- sudo: false
16
-
17
- cache: bundler
18
-
19
- notifications:
20
- email:
21
- - fail@makandra.de
22
-
23
- install:
24
- # Replace default Travis CI bundler script with a version that doesn't
25
- # explode when lockfile doesn't match recently bumped version
26
- - bundle install --no-deployment --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}