Skip to content

Commit

Permalink
Allow memoized methods to be reloaded and allow multiple symbols
Browse files Browse the repository at this point in the history
  • Loading branch information
josh committed Jul 18, 2008
1 parent 7430c41 commit e1f23da
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 28 deletions.
32 changes: 17 additions & 15 deletions activesupport/lib/active_support/memoizable.rb
Expand Up @@ -5,28 +5,30 @@ def self.included(base) #:nodoc:
end

module ClassMethods
def memoize(symbol)
original_method = "_unmemoized_#{symbol}"
memoized_ivar = "@_memoized_#{symbol}"
raise "Already memoized #{symbol}" if instance_methods.map(&:to_s).include?(original_method)
def memoize(*symbols)
symbols.each do |symbol|
original_method = "unmemoized_#{symbol}"
memoized_ivar = "@#{symbol}"
raise "Already memoized #{symbol}" if instance_methods.map(&:to_s).include?(original_method)

alias_method original_method, symbol
class_eval <<-EOS, __FILE__, __LINE__
def #{symbol}
if defined? #{memoized_ivar}
#{memoized_ivar}
else
#{memoized_ivar} = #{original_method}
alias_method original_method, symbol
class_eval <<-EOS, __FILE__, __LINE__
def #{symbol}(reload = false)
if !reload && defined? #{memoized_ivar}
#{memoized_ivar}
else
#{memoized_ivar} = #{original_method}.freeze
end
end
end
EOS
EOS
end
end
end

def freeze
methods.each do |method|
if m = method.to_s.match(/\A_unmemoized_(.*)/)
send(m[1]).freeze
if m = method.to_s.match(/\Aunmemoized_(.*)/)
send(m[1])
end
end
super
Expand Down
39 changes: 26 additions & 13 deletions activesupport/test/memoizable_test.rb
Expand Up @@ -8,38 +8,51 @@ class Person
def name
fetch_name_from_floppy
end
memoize :name

def age
nil
end
memoize :age

def random
rand(0)
end

memoize :name, :age, :random

private
def fetch_name_from_floppy
"Josh"
end
end

def setup
@person = Person.new
end

def test_memoization
person = Person.new
assert_equal "Josh", person.name
assert_equal "Josh", @person.name

@person.expects(:fetch_name_from_floppy).never
2.times { assert_equal "Josh", @person.name }
end

person.expects(:fetch_name_from_floppy).never
2.times { assert_equal "Josh", person.name }
def test_reloadable
random = @person.random
assert_equal random, @person.random
assert_not_equal random, @person.random(:reload)

This comment has been minimized.

Copy link
@DefV

DefV Jul 18, 2008

Contributor

This test will not always pass :-)

end

def test_memoized_methods_are_frozen
person = Person.new
person.freeze
assert_equal "Josh", person.name
assert_equal true, person.name.frozen?
assert_equal true, @person.name.frozen?

@person.freeze
assert_equal "Josh", @person.name
assert_equal true, @person.name.frozen?
end

def test_memoization_frozen_with_nil_value
person = Person.new
person.freeze
assert_equal nil, person.age
@person.freeze
assert_equal nil, @person.age
end

def test_double_memoization
Expand Down

2 comments on commit e1f23da

@NZKoz
Copy link
Member

@NZKoz NZKoz commented on e1f23da Jul 18, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless it’s run on debian

@nbibler
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would probably be better to replace the random calls with an incrementing instance variable. For instance:

class Person
  def current_count
    @count ||= 0
    @count += 1
  end
  memoize :current_count

You can then reliably test expected values against the memoization.

Please sign in to comment.