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 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
- You can even do it with a method that takes arguments.
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.
@@ -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(symbol)
7
- "@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym
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
- module InstanceMethods
11
- def self.included(base)
12
- base.class_eval do
13
- unless base.method_defined?(:freeze_without_memoizable)
14
- alias_method :freeze_without_memoizable, :freeze
15
- alias_method :freeze, :freeze_with_memoizable
16
- end
17
- end
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
- def freeze_with_memoizable
21
- memoize_all unless frozen?
22
- freeze_without_memoizable
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(*syms)
34
- if syms.empty?
35
- syms = methods.collect do |m|
36
- m[12..-1] if m.to_s.start_with?("_unmemoized_")
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
- syms.each do |sym|
41
- m = method(:"_unmemoized_#{sym}") rescue next
42
- if m.arity == 0
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(sym)
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(*syms)
52
- if syms.empty?
53
- syms = (methods + private_methods + protected_methods).collect do |m|
54
- m[12..-1] if m.to_s.start_with?("_unmemoized_")
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
- syms.each do |sym|
59
- ivar = Memoist.memoized_ivar_for(sym)
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(*symbols)
66
- symbols.each do |symbol|
67
- original_method = :"_unmemoized_#{symbol}"
68
- memoized_ivar = Memoist.memoized_ivar_for(symbol)
69
-
70
- class_eval <<-EOS, __FILE__, __LINE__ + 1
71
- include InstanceMethods # include InstanceMethods
72
- #
73
- if method_defined?(:#{original_method}) # if method_defined?(:_unmemoized_mime_type)
74
- raise "Already memoized #{symbol}" # raise "Already memoized mime_type"
75
- end # end
76
- alias #{original_method} #{symbol} # alias _unmemoized_mime_type mime_type
77
- #
78
- if instance_method(:#{symbol}).arity == 0 # if instance_method(:mime_type).arity == 0
79
- def #{symbol}(reload = false) # def mime_type(reload = false)
80
- if reload || !defined?(#{memoized_ivar}) || #{memoized_ivar}.empty? # if reload || !defined?(@_memoized_mime_type) || @_memoized_mime_type.empty?
81
- #{memoized_ivar} = [#{original_method}] # @_memoized_mime_type = [_unmemoized_mime_type]
82
- end # end
83
- #{memoized_ivar}[0] # @_memoized_mime_type[0]
84
- end # end
85
- else # else
86
- def #{symbol}(*args) # def mime_type(*args)
87
- #{memoized_ivar} ||= {} unless frozen? # @_memoized_mime_type ||= {} unless frozen?
88
- args_length = method(:#{original_method}).arity # args_length = method(:_unmemoized_mime_type).arity
89
- if args.length == args_length + 1 && # if args.length == args_length + 1 &&
90
- (args.last == true || args.last == :reload) # (args.last == true || args.last == :reload)
91
- reload = args.pop # reload = args.pop
92
- end # end
93
- #
94
- if defined?(#{memoized_ivar}) && #{memoized_ivar} # if defined?(@_memoized_mime_type) && @_memoized_mime_type
95
- if !reload && #{memoized_ivar}.has_key?(args) # if !reload && @_memoized_mime_type.has_key?(args)
96
- #{memoized_ivar}[args] # @_memoized_mime_type[args]
97
- elsif #{memoized_ivar} # elsif @_memoized_mime_type
98
- #{memoized_ivar}[args] = #{original_method}(*args) # @_memoized_mime_type[args] = _unmemoized_mime_type(*args)
99
- end # end
100
- else # else
101
- #{original_method}(*args) # _unmemoized_mime_type(*args)
102
- end # end
103
- end # end
104
- end # end
105
- #
106
- if private_method_defined?(#{original_method.inspect}) # if private_method_defined?(:_unmemoized_mime_type)
107
- private #{symbol.inspect} # private :mime_type
108
- elsif protected_method_defined?(#{original_method.inspect}) # elsif protected_method_defined?(:_unmemoized_mime_type)
109
- protected #{symbol.inspect} # protected :mime_type
110
- end # end
111
- EOS
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
@@ -5,9 +5,4 @@ module Kernel
5
5
  self
6
6
  end
7
7
  end unless respond_to?(:singleton_class) # exists in 1.9.2
8
-
9
- # class_eval on an object acts like singleton_class.class_eval.
10
- def class_eval(*args, &block)
11
- singleton_class.class_eval(*args, &block)
12
- end
13
- end
8
+ end
@@ -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
- @name_calls = 0
12
- @age_calls = 0
13
- @is_developer_calls = 0
14
- @name_query_calls = 0
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
- @name_calls += 1
51
+ @counter.call(:name)
19
52
  "Josh"
20
53
  end
21
54
 
22
55
  def name?
23
- @name_query_calls += 1
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
- @age_calls += 1
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
- @is_developer_calls += 1
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
- version: 0.2.0
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 Tänav
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
- - José Valim
23
+ - "Jos\xC3\xA9 Valim"
19
24
  autorequire:
20
25
  bindir: bin
21
26
  cert_chain: []
22
- date: 2012-08-15 00:00:00.000000000 Z
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
- extra_rdoc_files:
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
- version: '0'
61
- required_rubygems_version: !ruby/object:Gem::Requirement
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
- version: '0'
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
+