memoist 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ Memoist
2
+ =============
3
+
4
+ Memoist is an extraction of ActiveSupport::Memoizable.
5
+
6
+ Since June 2011 ActiveSupport::Memoizable has been deprecated.
7
+ But I love it,
8
+ and so I plan to keep it alive.
9
+
10
+ Usage
11
+ -----
12
+
13
+ Just extend with the Memoist module
14
+
15
+ require 'memoist'
16
+ class Person
17
+ extend Memoist
18
+
19
+ def social_security
20
+ decrypt_social_security
21
+ end
22
+ memoize :social_security
23
+ end
24
+
25
+ And person.social_security will only be calculated once.
26
+
27
+ You can even do it with a method that takes arguments.
28
+
29
+
30
+ class Person
31
+ def taxes_due(income)
32
+ income * 0.40
33
+ end
34
+ memoize :taxes_due
35
+ end
36
+
37
+ This will only be calculated once per value of income.
38
+
39
+ Authors
40
+ ===========
41
+
42
+ Everyone who contributed to it in the rails repository.
43
+
44
+ * Joshua Peek
45
+ * Tarmo Tänav
46
+ * Jeremy Kemper
47
+ * Eugene Pimenov
48
+ * Xavier Noria
49
+ * Niels Ganser
50
+ * Carl Lerche & Yehuda Katz
51
+ * jeem
52
+ * Jay Pignata
53
+ * Damien Mathieu
54
+ * José Valim
data/lib/memoist.rb ADDED
@@ -0,0 +1,109 @@
1
+ # require 'active_support/core_ext/kernel/singleton_class'
2
+ require 'memoist/core_ext/singleton_class'
3
+
4
+ module Memoist
5
+
6
+ def self.memoized_ivar_for(symbol)
7
+ "@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym
8
+ end
9
+
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
18
+ end
19
+
20
+ def freeze_with_memoizable
21
+ memoize_all unless frozen?
22
+ freeze_without_memoizable
23
+ end
24
+
25
+ def memoize_all
26
+ prime_cache ".*"
27
+ end
28
+
29
+ def unmemoize_all
30
+ flush_cache ".*"
31
+ end
32
+
33
+ def prime_cache(*syms)
34
+ syms.each do |sym|
35
+ methods.each do |m|
36
+ if m.to_s =~ /^_unmemoized_(#{sym})/
37
+ if method(m).arity == 0
38
+ __send__($1)
39
+ else
40
+ ivar = Memoist.memoized_ivar_for($1)
41
+ instance_variable_set(ivar, {})
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def flush_cache(*syms)
49
+ syms.each do |sym|
50
+ (methods + private_methods + protected_methods).each do |m|
51
+ if m.to_s =~ /^_unmemoized_(#{sym.to_s.gsub(/\?\Z/, '\?')})/
52
+ ivar = Memoist.memoized_ivar_for($1)
53
+ instance_variable_get(ivar).clear if instance_variable_defined?(ivar)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def memoize(*symbols)
61
+ symbols.each do |symbol|
62
+ original_method = :"_unmemoized_#{symbol}"
63
+ memoized_ivar = Memoist.memoized_ivar_for(symbol)
64
+
65
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
66
+ include InstanceMethods # include InstanceMethods
67
+ #
68
+ if method_defined?(:#{original_method}) # if method_defined?(:_unmemoized_mime_type)
69
+ raise "Already memoized #{symbol}" # raise "Already memoized mime_type"
70
+ end # end
71
+ alias #{original_method} #{symbol} # alias _unmemoized_mime_type mime_type
72
+ #
73
+ if instance_method(:#{symbol}).arity == 0 # if instance_method(:mime_type).arity == 0
74
+ def #{symbol}(reload = false) # def mime_type(reload = false)
75
+ if reload || !defined?(#{memoized_ivar}) || #{memoized_ivar}.empty? # if reload || !defined?(@_memoized_mime_type) || @_memoized_mime_type.empty?
76
+ #{memoized_ivar} = [#{original_method}] # @_memoized_mime_type = [_unmemoized_mime_type]
77
+ end # end
78
+ #{memoized_ivar}[0] # @_memoized_mime_type[0]
79
+ end # end
80
+ else # else
81
+ def #{symbol}(*args) # def mime_type(*args)
82
+ #{memoized_ivar} ||= {} unless frozen? # @_memoized_mime_type ||= {} unless frozen?
83
+ args_length = method(:#{original_method}).arity # args_length = method(:_unmemoized_mime_type).arity
84
+ if args.length == args_length + 1 && # if args.length == args_length + 1 &&
85
+ (args.last == true || args.last == :reload) # (args.last == true || args.last == :reload)
86
+ reload = args.pop # reload = args.pop
87
+ end # end
88
+ #
89
+ if defined?(#{memoized_ivar}) && #{memoized_ivar} # if defined?(@_memoized_mime_type) && @_memoized_mime_type
90
+ if !reload && #{memoized_ivar}.has_key?(args) # if !reload && @_memoized_mime_type.has_key?(args)
91
+ #{memoized_ivar}[args] # @_memoized_mime_type[args]
92
+ elsif #{memoized_ivar} # elsif @_memoized_mime_type
93
+ #{memoized_ivar}[args] = #{original_method}(*args) # @_memoized_mime_type[args] = _unmemoized_mime_type(*args)
94
+ end # end
95
+ else # else
96
+ #{original_method}(*args) # _unmemoized_mime_type(*args)
97
+ end # end
98
+ end # end
99
+ end # end
100
+ #
101
+ if private_method_defined?(#{original_method.inspect}) # if private_method_defined?(:_unmemoized_mime_type)
102
+ private #{symbol.inspect} # private :mime_type
103
+ elsif protected_method_defined?(#{original_method.inspect}) # elsif protected_method_defined?(:_unmemoized_mime_type)
104
+ protected #{symbol.inspect} # protected :mime_type
105
+ end # end
106
+ EOS
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,13 @@
1
+ module Kernel
2
+ # Returns the object's singleton class.
3
+ def singleton_class
4
+ class << self
5
+ self
6
+ end
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
@@ -0,0 +1,279 @@
1
+ require 'test_helper'
2
+ require 'memoist'
3
+
4
+ class MemoistTest < Test::Unit::TestCase
5
+ class Person
6
+ extend Memoist
7
+
8
+ attr_reader :name_calls, :age_calls, :is_developer_calls, :name_query_calls
9
+
10
+ def initialize
11
+ @name_calls = 0
12
+ @age_calls = 0
13
+ @is_developer_calls = 0
14
+ @name_query_calls = 0
15
+ end
16
+
17
+ def name
18
+ @name_calls += 1
19
+ "Josh"
20
+ end
21
+
22
+ def name?
23
+ @name_query_calls += 1
24
+ true
25
+ end
26
+ memoize :name?
27
+
28
+ def update(name)
29
+ "Joshua"
30
+ end
31
+ memoize :update
32
+
33
+ def age
34
+ @age_calls += 1
35
+ nil
36
+ end
37
+
38
+ memoize :name, :age
39
+
40
+ protected
41
+
42
+ def memoize_protected_test
43
+ 'protected'
44
+ end
45
+ memoize :memoize_protected_test
46
+
47
+ private
48
+
49
+ def is_developer?
50
+ @is_developer_calls += 1
51
+ "Yes"
52
+ end
53
+ memoize :is_developer?
54
+ end
55
+
56
+ class Company
57
+ attr_reader :name_calls
58
+ def initialize
59
+ @name_calls = 0
60
+ end
61
+
62
+ def name
63
+ @name_calls += 1
64
+ "37signals"
65
+ end
66
+ end
67
+
68
+ module Rates
69
+ extend Memoist
70
+
71
+ attr_reader :sales_tax_calls
72
+ def sales_tax(price)
73
+ @sales_tax_calls ||= 0
74
+ @sales_tax_calls += 1
75
+ price * 0.1025
76
+ end
77
+ memoize :sales_tax
78
+ end
79
+
80
+ class Calculator
81
+ extend Memoist
82
+ include Rates
83
+
84
+ attr_reader :fib_calls
85
+ def initialize
86
+ @fib_calls = 0
87
+ end
88
+
89
+ def fib(n)
90
+ @fib_calls += 1
91
+
92
+ if n == 0 || n == 1
93
+ n
94
+ else
95
+ fib(n - 1) + fib(n - 2)
96
+ end
97
+ end
98
+ memoize :fib
99
+
100
+ def add_or_subtract(i, j, add)
101
+ if add
102
+ i + j
103
+ else
104
+ i - j
105
+ end
106
+ end
107
+ memoize :add_or_subtract
108
+
109
+ def counter
110
+ @count ||= 0
111
+ @count += 1
112
+ end
113
+ memoize :counter
114
+ end
115
+
116
+ def setup
117
+ @person = Person.new
118
+ @calculator = Calculator.new
119
+ end
120
+
121
+ def test_memoization
122
+ assert_equal "Josh", @person.name
123
+ assert_equal 1, @person.name_calls
124
+
125
+ 3.times { assert_equal "Josh", @person.name }
126
+ assert_equal 1, @person.name_calls
127
+ end
128
+
129
+ def test_memoization_with_punctuation
130
+ assert_equal true, @person.name?
131
+
132
+ assert_nothing_raised(NameError) do
133
+ @person.memoize_all
134
+ @person.unmemoize_all
135
+ end
136
+ end
137
+
138
+ def test_memoization_flush_with_punctuation
139
+ assert_equal true, @person.name?
140
+ @person.flush_cache(:name?)
141
+ 3.times { assert_equal true, @person.name? }
142
+ assert_equal 2, @person.name_query_calls
143
+ end
144
+
145
+ def test_memoization_with_nil_value
146
+ assert_equal nil, @person.age
147
+ assert_equal 1, @person.age_calls
148
+
149
+ 3.times { assert_equal nil, @person.age }
150
+ assert_equal 1, @person.age_calls
151
+ end
152
+
153
+ def test_reloadable
154
+ assert_equal 1, @calculator.counter
155
+ assert_equal 2, @calculator.counter(:reload)
156
+ assert_equal 2, @calculator.counter
157
+ assert_equal 3, @calculator.counter(true)
158
+ assert_equal 3, @calculator.counter
159
+ end
160
+
161
+ def test_flush_cache
162
+ assert_equal 1, @calculator.counter
163
+
164
+ assert @calculator.instance_variable_get(:@_memoized_counter).any?
165
+ @calculator.flush_cache(:counter)
166
+ assert @calculator.instance_variable_get(:@_memoized_counter).empty?
167
+
168
+ assert_equal 2, @calculator.counter
169
+ end
170
+
171
+ def test_unmemoize_all
172
+ assert_equal 1, @calculator.counter
173
+
174
+ assert @calculator.instance_variable_get(:@_memoized_counter).any?
175
+ @calculator.unmemoize_all
176
+ assert @calculator.instance_variable_get(:@_memoized_counter).empty?
177
+
178
+ assert_equal 2, @calculator.counter
179
+ end
180
+
181
+ def test_memoize_all
182
+ @calculator.memoize_all
183
+ assert @calculator.instance_variable_defined?(:@_memoized_counter)
184
+ end
185
+
186
+ def test_memoization_cache_is_different_for_each_instance
187
+ assert_equal 1, @calculator.counter
188
+ assert_equal 2, @calculator.counter(:reload)
189
+ assert_equal 1, Calculator.new.counter
190
+ end
191
+
192
+ def test_memoized_is_not_affected_by_freeze
193
+ @person.freeze
194
+ assert_equal "Josh", @person.name
195
+ assert_equal "Joshua", @person.update("Joshua")
196
+ end
197
+
198
+ def test_memoization_with_args
199
+ assert_equal 55, @calculator.fib(10)
200
+ assert_equal 11, @calculator.fib_calls
201
+ end
202
+
203
+ def test_reloadable_with_args
204
+ assert_equal 55, @calculator.fib(10)
205
+ assert_equal 11, @calculator.fib_calls
206
+ assert_equal 55, @calculator.fib(10, :reload)
207
+ assert_equal 12, @calculator.fib_calls
208
+ assert_equal 55, @calculator.fib(10, true)
209
+ assert_equal 13, @calculator.fib_calls
210
+ end
211
+
212
+ def test_memoization_with_boolean_arg
213
+ assert_equal 4, @calculator.add_or_subtract(2, 2, true)
214
+ assert_equal 2, @calculator.add_or_subtract(4, 2, false)
215
+ end
216
+
217
+ def test_object_memoization
218
+ [Company.new, Company.new, Company.new].each do |company|
219
+ company.extend Memoist
220
+ company.memoize :name
221
+
222
+ assert_equal "37signals", company.name
223
+ assert_equal 1, company.name_calls
224
+ assert_equal "37signals", company.name
225
+ assert_equal 1, company.name_calls
226
+ end
227
+ end
228
+
229
+ def test_memoized_module_methods
230
+ assert_equal 1.025, @calculator.sales_tax(10)
231
+ assert_equal 1, @calculator.sales_tax_calls
232
+ assert_equal 1.025, @calculator.sales_tax(10)
233
+ assert_equal 1, @calculator.sales_tax_calls
234
+ assert_equal 2.5625, @calculator.sales_tax(25)
235
+ assert_equal 2, @calculator.sales_tax_calls
236
+ end
237
+
238
+ def test_object_memoized_module_methods
239
+ company = Company.new
240
+ company.extend(Rates)
241
+
242
+ assert_equal 1.025, company.sales_tax(10)
243
+ assert_equal 1, company.sales_tax_calls
244
+ assert_equal 1.025, company.sales_tax(10)
245
+ assert_equal 1, company.sales_tax_calls
246
+ assert_equal 2.5625, company.sales_tax(25)
247
+ assert_equal 2, company.sales_tax_calls
248
+ end
249
+
250
+ def test_double_memoization
251
+ assert_raise(RuntimeError) { Person.memoize :name }
252
+ person = Person.new
253
+ person.extend Memoist
254
+ assert_raise(RuntimeError) { person.memoize :name }
255
+
256
+ company = Company.new
257
+ company.extend Memoist
258
+ company.memoize :name
259
+ assert_raise(RuntimeError) { company.memoize :name }
260
+ end
261
+
262
+ def test_protected_method_memoization
263
+ person = Person.new
264
+
265
+ assert_raise(NoMethodError) { person.memoize_protected_test }
266
+ assert_equal "protected", person.send(:memoize_protected_test)
267
+ end
268
+
269
+ def test_private_method_memoization
270
+ person = Person.new
271
+
272
+ assert_raise(NoMethodError) { person.is_developer? }
273
+ assert_equal "Yes", person.send(:is_developer?)
274
+ assert_equal 1, person.is_developer_calls
275
+ assert_equal "Yes", person.send(:is_developer?)
276
+ assert_equal 1, person.is_developer_calls
277
+ end
278
+
279
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ $:.unshift File.expand_path(File.dirname(__FILE__)+"/../lib")
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: memoist
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Joshua Peek
9
+ - Tarmo Tänav
10
+ - Jeremy Kemper
11
+ - Eugene Pimenov
12
+ - Xavier Noria
13
+ - Niels Ganser
14
+ - Carl Lerche & Yehuda Katz
15
+ - jeem
16
+ - Jay Pignata
17
+ - Damien Mathieu
18
+ - José Valim
19
+ autorequire:
20
+ bindir: bin
21
+ cert_chain: []
22
+ date: 2012-01-24 00:00:00.000000000 Z
23
+ dependencies: []
24
+ description:
25
+ email:
26
+ - josh@joshpeek.com
27
+ - tarmo@itech.ee
28
+ - jeremy@bitsweat.net
29
+ - libc@mac.com
30
+ - fxn@hashref.com
31
+ - niels@herimedia.co
32
+ - wycats@gmail.com
33
+ - jeem@hughesorama.com
34
+ - john.pignata@gmail.com
35
+ - 42@dmathieu.com
36
+ - jose.valim@gmail.com
37
+ executables: []
38
+ extensions: []
39
+ extra_rdoc_files:
40
+ - README.md
41
+ files:
42
+ - README.md
43
+ - test/memoist_test.rb
44
+ - test/test_helper.rb
45
+ - lib/memoist/core_ext/singleton_class.rb
46
+ - lib/memoist.rb
47
+ homepage: https://github.com/matthewrudy/memoist
48
+ licenses: []
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --main
52
+ - README.md
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 1.8.10
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: memoize methods invocation
73
+ test_files: []