memoist 0.1.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 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: []