memoist 0.2.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
+