memoist 0.2.0 → 0.9.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/README.md +34 -2
- data/lib/memoist.rb +159 -76
- data/lib/memoist/core_ext/singleton_class.rb +1 -6
- data/test/memoist_test.rb +63 -10
- metadata +40 -22
data/README.md
CHANGED
@@ -24,7 +24,17 @@ Just extend with the Memoist module
|
|
24
24
|
|
25
25
|
And person.social_security will only be calculated once.
|
26
26
|
|
27
|
-
|
27
|
+
Every memoized function (which initially was not accepting any arguments) has a ```(reload)```
|
28
|
+
argument you can pass in to bypass and reset the memoization:
|
29
|
+
|
30
|
+
def some_method
|
31
|
+
Time.now
|
32
|
+
end
|
33
|
+
memoize :some_method
|
34
|
+
|
35
|
+
Calling ```some_method``` will be memoized, but calling ```some_method(true)``` will rememoize each time.
|
36
|
+
|
37
|
+
You can even memoize method that takes arguments.
|
28
38
|
|
29
39
|
|
30
40
|
class Person
|
@@ -36,6 +46,23 @@ You can even do it with a method that takes arguments.
|
|
36
46
|
|
37
47
|
This will only be calculated once per value of income.
|
38
48
|
|
49
|
+
Reload
|
50
|
+
------
|
51
|
+
|
52
|
+
Each memoized function comes with a way to flush the existing value.
|
53
|
+
|
54
|
+
person.social_security # returns the memoized value
|
55
|
+
person.social_security(true) # bypasses the memoized value and rememoizes it
|
56
|
+
|
57
|
+
This also works with a memoized method with arguments
|
58
|
+
|
59
|
+
person.taxes_due(100_000) # returns the memoized value
|
60
|
+
person.taxes_due(100_000, true) # bypasses the memoized value and rememoizes it
|
61
|
+
|
62
|
+
If you want to flush the entire memoization cache for an object
|
63
|
+
|
64
|
+
person.flush_cache
|
65
|
+
|
39
66
|
Authors
|
40
67
|
===========
|
41
68
|
|
@@ -51,4 +78,9 @@ Everyone who contributed to it in the rails repository.
|
|
51
78
|
* jeem
|
52
79
|
* Jay Pignata
|
53
80
|
* Damien Mathieu
|
54
|
-
* José Valim
|
81
|
+
* José Valim
|
82
|
+
|
83
|
+
License
|
84
|
+
=======
|
85
|
+
|
86
|
+
Released under the [MIT License](http://www.opensource.org/licenses/MIT), just as Ruby on Rails is.
|
data/lib/memoist.rb
CHANGED
@@ -1,27 +1,43 @@
|
|
1
|
-
# require 'active_support/core_ext/kernel/singleton_class'
|
2
1
|
require 'memoist/core_ext/singleton_class'
|
3
2
|
|
4
3
|
module Memoist
|
5
4
|
|
6
|
-
def self.memoized_ivar_for(
|
7
|
-
"
|
5
|
+
def self.memoized_ivar_for(method_name, identifier=nil)
|
6
|
+
["@#{memoized_prefix(identifier)}", escape_punctuation(method_name.to_s)].join("_")
|
8
7
|
end
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
def self.unmemoized_method_for(method_name, identifier=nil)
|
10
|
+
[unmemoized_prefix(identifier), method_name].join("_").to_sym
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.memoized_prefix(identifier=nil)
|
14
|
+
["_memoized", identifier].compact.join("_")
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.unmemoized_prefix(identifier=nil)
|
18
|
+
["_unmemoized", identifier].compact.join("_")
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.escape_punctuation(string)
|
22
|
+
string.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.memoist_eval(klass, *args, &block)
|
26
|
+
if klass.respond_to?(:class_eval)
|
27
|
+
klass.class_eval(*args, &block)
|
28
|
+
else
|
29
|
+
klass.singleton_class.class_eval(*args, &block)
|
18
30
|
end
|
31
|
+
end
|
19
32
|
|
20
|
-
|
21
|
-
|
22
|
-
|
33
|
+
def self.extract_reload!(method, args)
|
34
|
+
if args.length == method.arity + 1 && (args.last == true || args.last == :reload)
|
35
|
+
reload = args.pop
|
23
36
|
end
|
37
|
+
reload
|
38
|
+
end
|
24
39
|
|
40
|
+
module InstanceMethods
|
25
41
|
def memoize_all
|
26
42
|
prime_cache
|
27
43
|
end
|
@@ -30,85 +46,152 @@ module Memoist
|
|
30
46
|
flush_cache
|
31
47
|
end
|
32
48
|
|
33
|
-
def prime_cache(*
|
34
|
-
if
|
35
|
-
|
36
|
-
|
49
|
+
def prime_cache(*method_names)
|
50
|
+
if method_names.empty?
|
51
|
+
prefix = Memoist.unmemoized_prefix+"_"
|
52
|
+
method_names = methods.collect do |method_name|
|
53
|
+
if method_name.to_s.start_with?(prefix)
|
54
|
+
method_name[prefix.length..-1]
|
55
|
+
end
|
37
56
|
end.compact
|
38
57
|
end
|
39
58
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
__send__(sym)
|
59
|
+
method_names.each do |method_name|
|
60
|
+
if method(Memoist.unmemoized_method_for(method_name)).arity == 0
|
61
|
+
__send__(method_name)
|
44
62
|
else
|
45
|
-
ivar = Memoist.memoized_ivar_for(
|
63
|
+
ivar = Memoist.memoized_ivar_for(method_name)
|
46
64
|
instance_variable_set(ivar, {})
|
47
65
|
end
|
48
66
|
end
|
49
67
|
end
|
50
68
|
|
51
|
-
def flush_cache(*
|
52
|
-
if
|
53
|
-
|
54
|
-
|
69
|
+
def flush_cache(*method_names)
|
70
|
+
if method_names.empty?
|
71
|
+
prefix = Memoist.unmemoized_prefix+"_"
|
72
|
+
method_names = (methods + private_methods + protected_methods).collect do |method_name|
|
73
|
+
if method_name.to_s.start_with?(prefix)
|
74
|
+
method_name[prefix.length..-1]
|
75
|
+
end
|
55
76
|
end.compact
|
56
77
|
end
|
57
78
|
|
58
|
-
|
59
|
-
ivar = Memoist.memoized_ivar_for(
|
79
|
+
method_names.each do |method_name|
|
80
|
+
ivar = Memoist.memoized_ivar_for(method_name)
|
60
81
|
instance_variable_get(ivar).clear if instance_variable_defined?(ivar)
|
61
82
|
end
|
62
83
|
end
|
63
84
|
end
|
64
85
|
|
65
|
-
def memoize(*
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
86
|
+
def memoize(*method_names)
|
87
|
+
if method_names.last.is_a?(Hash)
|
88
|
+
identifier = method_names.pop[:identifier]
|
89
|
+
end
|
90
|
+
|
91
|
+
method_names.each do |method_name|
|
92
|
+
unmemoized_method = Memoist.unmemoized_method_for(method_name, identifier)
|
93
|
+
memoized_ivar = Memoist.memoized_ivar_for(method_name, identifier)
|
94
|
+
|
95
|
+
Memoist.memoist_eval(self) do
|
96
|
+
include InstanceMethods
|
97
|
+
|
98
|
+
if method_defined?(unmemoized_method)
|
99
|
+
raise "Already memoized #{method_name}"
|
100
|
+
end
|
101
|
+
alias_method unmemoized_method, method_name
|
102
|
+
|
103
|
+
if instance_method(method_name).arity == 0
|
104
|
+
|
105
|
+
# define a method like this;
|
106
|
+
|
107
|
+
# def mime_type(reload=true)
|
108
|
+
# skip_cache = reload || !memoized?(:abc)
|
109
|
+
# set_cache = skip_cache && !frozen?
|
110
|
+
#
|
111
|
+
# if skip_cache
|
112
|
+
# value = _unmemoized_mime_type
|
113
|
+
# else
|
114
|
+
# value = @_memoized_mime_type[0]
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# if set_cache
|
118
|
+
# @_memoized_mime_type = [value]
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# value
|
122
|
+
# end
|
123
|
+
|
124
|
+
module_eval <<-EOS, __FILE__, __LINE__ + 1
|
125
|
+
def #{method_name}(reload = false)
|
126
|
+
skip_cache = reload || !defined?(#{memoized_ivar}) || #{memoized_ivar}.empty?
|
127
|
+
set_cache = skip_cache && !frozen?
|
128
|
+
|
129
|
+
if skip_cache
|
130
|
+
value = #{unmemoized_method}
|
131
|
+
else
|
132
|
+
value = #{memoized_ivar}[0]
|
133
|
+
end
|
134
|
+
|
135
|
+
if set_cache
|
136
|
+
#{memoized_ivar} = [value]
|
137
|
+
end
|
138
|
+
|
139
|
+
value
|
140
|
+
end
|
141
|
+
EOS
|
142
|
+
else
|
143
|
+
|
144
|
+
# define a method like this;
|
145
|
+
|
146
|
+
# def mime_type(*args)
|
147
|
+
# reload = Memoist.extract_reload!(method(:_unmemoized_mime_type), args)
|
148
|
+
#
|
149
|
+
# skip_cache = reload || !memoized_with_args?(:mime_type, args)
|
150
|
+
# set_cache = skip_cache && !frozen
|
151
|
+
#
|
152
|
+
# if skip_cache
|
153
|
+
# value = _unmemoized_mime_type(*args)
|
154
|
+
# else
|
155
|
+
# value = @_memoized_mime_type[args]
|
156
|
+
# end
|
157
|
+
#
|
158
|
+
# if set_cache
|
159
|
+
# @_memoized_mime_type ||= {}
|
160
|
+
# @_memoized_mime_type[args] = value
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
# value
|
164
|
+
# end
|
165
|
+
|
166
|
+
module_eval <<-EOS, __FILE__, __LINE__ + 1
|
167
|
+
def #{method_name}(*args)
|
168
|
+
reload = Memoist.extract_reload!(method(#{unmemoized_method.inspect}), args)
|
169
|
+
|
170
|
+
skip_cache = reload || !(defined?(#{memoized_ivar}) && #{memoized_ivar} && #{memoized_ivar}.has_key?(args))
|
171
|
+
set_cache = skip_cache && !frozen?
|
172
|
+
|
173
|
+
if skip_cache
|
174
|
+
value = #{unmemoized_method}(*args)
|
175
|
+
else
|
176
|
+
value = #{memoized_ivar}[args]
|
177
|
+
end
|
178
|
+
|
179
|
+
if set_cache
|
180
|
+
#{memoized_ivar} ||= {}
|
181
|
+
#{memoized_ivar}[args] = value
|
182
|
+
end
|
183
|
+
|
184
|
+
value
|
185
|
+
end
|
186
|
+
EOS
|
187
|
+
end
|
188
|
+
|
189
|
+
if private_method_defined?(unmemoized_method)
|
190
|
+
private method_name
|
191
|
+
elsif protected_method_defined?(unmemoized_method)
|
192
|
+
protected method_name
|
193
|
+
end
|
194
|
+
end
|
112
195
|
end
|
113
196
|
end
|
114
197
|
end
|
data/test/memoist_test.rb
CHANGED
@@ -2,25 +2,58 @@ require 'test_helper'
|
|
2
2
|
require 'memoist'
|
3
3
|
|
4
4
|
class MemoistTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
class CallCounter
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@calls = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(method_name)
|
13
|
+
@calls[method_name] ||= 0
|
14
|
+
@calls[method_name] += 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def count(method_name)
|
18
|
+
@calls[method_name] ||= 0
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
5
23
|
class Person
|
6
24
|
extend Memoist
|
7
25
|
|
8
|
-
attr_reader :name_calls, :age_calls, :is_developer_calls, :name_query_calls
|
9
|
-
|
10
26
|
def initialize
|
11
|
-
@
|
12
|
-
|
13
|
-
|
14
|
-
|
27
|
+
@counter = CallCounter.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def name_calls
|
31
|
+
@counter.count(:name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def student_name_calls
|
35
|
+
@counter.count(:student_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def name_query_calls
|
39
|
+
@counter.count(:name?)
|
40
|
+
end
|
41
|
+
|
42
|
+
def is_developer_calls
|
43
|
+
@counter.count(:is_developer?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def age_calls
|
47
|
+
@counter.count(:age)
|
15
48
|
end
|
16
49
|
|
17
50
|
def name
|
18
|
-
@
|
51
|
+
@counter.call(:name)
|
19
52
|
"Josh"
|
20
53
|
end
|
21
54
|
|
22
55
|
def name?
|
23
|
-
@
|
56
|
+
@counter.call(:name?)
|
24
57
|
true
|
25
58
|
end
|
26
59
|
memoize :name?
|
@@ -31,7 +64,7 @@ class MemoistTest < Test::Unit::TestCase
|
|
31
64
|
memoize :update
|
32
65
|
|
33
66
|
def age
|
34
|
-
@
|
67
|
+
@counter.call(:age)
|
35
68
|
nil
|
36
69
|
end
|
37
70
|
|
@@ -47,12 +80,20 @@ class MemoistTest < Test::Unit::TestCase
|
|
47
80
|
private
|
48
81
|
|
49
82
|
def is_developer?
|
50
|
-
@
|
83
|
+
@counter.call(:is_developer?)
|
51
84
|
"Yes"
|
52
85
|
end
|
53
86
|
memoize :is_developer?
|
54
87
|
end
|
55
88
|
|
89
|
+
class Student < Person
|
90
|
+
def name
|
91
|
+
@counter.call(:student_name)
|
92
|
+
"Student #{super}"
|
93
|
+
end
|
94
|
+
memoize :name, :identifier => :student
|
95
|
+
end
|
96
|
+
|
56
97
|
class Company
|
57
98
|
attr_reader :name_calls
|
58
99
|
def initialize
|
@@ -259,6 +300,18 @@ class MemoistTest < Test::Unit::TestCase
|
|
259
300
|
assert_raise(RuntimeError) { company.memoize :name }
|
260
301
|
end
|
261
302
|
|
303
|
+
def test_double_memoization_with_identifier
|
304
|
+
assert_nothing_raised { Person.memoize :name, :identifier => :again }
|
305
|
+
end
|
306
|
+
|
307
|
+
def test_memoization_with_a_subclass
|
308
|
+
student = Student.new
|
309
|
+
student.name
|
310
|
+
student.name
|
311
|
+
assert_equal 1, student.student_name_calls
|
312
|
+
assert_equal 1, student.name_calls
|
313
|
+
end
|
314
|
+
|
262
315
|
def test_protected_method_memoization
|
263
316
|
person = Person.new
|
264
317
|
|
metadata
CHANGED
@@ -1,12 +1,17 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: memoist
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 59
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 0
|
10
|
+
version: 0.9.0
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Joshua Peek
|
9
|
-
- Tarmo
|
14
|
+
- "Tarmo T\xC3\xA4nav"
|
10
15
|
- Jeremy Kemper
|
11
16
|
- Eugene Pimenov
|
12
17
|
- Xavier Noria
|
@@ -15,14 +20,16 @@ authors:
|
|
15
20
|
- jeem
|
16
21
|
- Jay Pignata
|
17
22
|
- Damien Mathieu
|
18
|
-
-
|
23
|
+
- "Jos\xC3\xA9 Valim"
|
19
24
|
autorequire:
|
20
25
|
bindir: bin
|
21
26
|
cert_chain: []
|
22
|
-
|
27
|
+
|
28
|
+
date: 2013-03-20 00:00:00 Z
|
23
29
|
dependencies: []
|
30
|
+
|
24
31
|
description:
|
25
|
-
email:
|
32
|
+
email:
|
26
33
|
- josh@joshpeek.com
|
27
34
|
- tarmo@itech.ee
|
28
35
|
- jeremy@bitsweat.net
|
@@ -35,10 +42,12 @@ email:
|
|
35
42
|
- 42@dmathieu.com
|
36
43
|
- jose.valim@gmail.com
|
37
44
|
executables: []
|
45
|
+
|
38
46
|
extensions: []
|
39
|
-
|
47
|
+
|
48
|
+
extra_rdoc_files:
|
40
49
|
- README.md
|
41
|
-
files:
|
50
|
+
files:
|
42
51
|
- README.md
|
43
52
|
- test/memoist_test.rb
|
44
53
|
- test/test_helper.rb
|
@@ -46,28 +55,37 @@ files:
|
|
46
55
|
- lib/memoist.rb
|
47
56
|
homepage: https://github.com/matthewrudy/memoist
|
48
57
|
licenses: []
|
58
|
+
|
49
59
|
post_install_message:
|
50
|
-
rdoc_options:
|
60
|
+
rdoc_options:
|
51
61
|
- --main
|
52
62
|
- README.md
|
53
|
-
require_paths:
|
63
|
+
require_paths:
|
54
64
|
- lib
|
55
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
66
|
none: false
|
57
|
-
requirements:
|
58
|
-
- -
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
|
61
|
-
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
75
|
none: false
|
63
|
-
requirements:
|
64
|
-
- -
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
hash: 3
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
67
83
|
requirements: []
|
84
|
+
|
68
85
|
rubyforge_project:
|
69
86
|
rubygems_version: 1.8.24
|
70
87
|
signing_key:
|
71
88
|
specification_version: 3
|
72
89
|
summary: memoize methods invocation
|
73
90
|
test_files: []
|
91
|
+
|