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 +4 -4
- data/.github/workflows/test.yml +38 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +26 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +8 -2
- data/README.md +60 -4
- data/Rakefile +8 -0
- data/lib/memoized/parameters.rb +98 -0
- data/lib/memoized/version.rb +1 -1
- data/lib/memoized.rb +21 -35
- data/memoized.gemspec +8 -0
- data/spec/memoized_spec.rb +126 -3
- data/spec/properties_spec.rb +222 -0
- data/spec/spec_helper.rb +79 -0
- metadata +39 -5
- data/.travis.yml +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38881d8fc70ecdbf1920e7bbb40d684403d88c4225e53d7a3e426f97ef5f41ac
|
4
|
+
data.tar.gz: 750f0290f9258b48bcabdf993e0c0a11f9919643b2a944efa5046cdd930d05a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
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
data/Gemfile.lock
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
memoized (1.
|
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
|
-
|
42
|
+
2.2.21
|
data/README.md
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
[](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 `
|
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
@@ -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
|
data/lib/memoized/version.rb
CHANGED
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
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
29
|
+
module_eval(<<-RUBY)
|
30
|
+
def #{method_name}(#{parameters.signature})
|
31
|
+
#{parameters.cache_key}
|
49
32
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
#{memoized_ivar_name}
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
61
|
-
|
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
|
data/spec/memoized_spec.rb
CHANGED
@@ -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
|
-
|
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.
|
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:
|
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}
|