1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Improved Memoizable test coverage and added support for multiple arguments

This commit is contained in:
Joshua Peek 2008-07-22 10:26:44 -05:00
parent 8b858782fa
commit 8a87d8a6c2
3 changed files with 186 additions and 82 deletions

View file

@ -5,4 +5,9 @@ class Object
self
end
end
# If class_eval is called on an object, add those methods to its metaclass
def class_eval(*args, &block)
metaclass.class_eval(*args, &block)
end
end

View file

@ -1,32 +1,43 @@
module ActiveSupport
module Memoizable #:nodoc:
module Memoizable
module Freezable
def self.included(base)
base.class_eval do
unless base.method_defined?(:freeze_without_memoizable)
alias_method_chain :freeze, :memoizable
end
end
end
def freeze_with_memoizable
methods.each do |method|
if m = method.to_s.match(/^_unmemoized_(.*)/)
send(m[1])
end
end
freeze_without_memoizable
end
end
def memoize(*symbols)
symbols.each do |symbol|
original_method = "unmemoized_#{symbol}"
memoized_ivar = "@#{symbol}"
original_method = "_unmemoized_#{symbol}"
memoized_ivar = "@_memoized_#{symbol}"
klass = respond_to?(:class_eval) ? self : self.metaclass
raise "Already memoized #{symbol}" if klass.instance_methods.map(&:to_s).include?(original_method)
class_eval <<-EOS, __FILE__, __LINE__
include Freezable
klass.class_eval <<-EOS, __FILE__, __LINE__
unless instance_methods.map(&:to_s).include?("freeze_without_memoizable")
alias_method :freeze_without_memoizable, :freeze
def freeze
methods.each do |method|
if m = method.to_s.match(/^unmemoized_(.*)/)
send(m[1])
end
end
freeze_without_memoizable
end
end
raise "Already memoized #{symbol}" if method_defined?(:#{original_method})
alias #{original_method} #{symbol}
alias_method :#{original_method}, :#{symbol}
def #{symbol}(reload = false)
if !reload && defined? #{memoized_ivar}
#{memoized_ivar}
def #{symbol}(*args)
#{memoized_ivar} ||= {}
reload = args.pop if args.last == true || args.last == :reload
if !reload && #{memoized_ivar} && #{memoized_ivar}.has_key?(args)
#{memoized_ivar}[args]
else
#{memoized_ivar} = #{original_method}.freeze
#{memoized_ivar}[args] = #{original_method}(*args).freeze
end
end
EOS

View file

@ -5,86 +5,174 @@ uses_mocha 'Memoizable' do
class Person
extend ActiveSupport::Memoizable
def name
fetch_name_from_floppy
attr_reader :name_calls, :age_calls
def initialize
@name_calls = 0
@age_calls = 0
end
memoize :name
def name
@name_calls += 1
"Josh"
end
def age
@age_calls += 1
nil
end
def counter
@counter ||= 0
@counter += 1
end
memoize :age, :counter
private
def fetch_name_from_floppy
"Josh"
end
end
def setup
@person = Person.new
end
def test_memoization
assert_equal "Josh", @person.name
@person.expects(:fetch_name_from_floppy).never
2.times { assert_equal "Josh", @person.name }
end
def test_reloadable
counter = @person.counter
assert_equal 1, @person.counter
assert_equal 2, @person.counter(:reload)
end
def test_memoized_methods_are_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.freeze
assert_equal nil, @person.age
end
def test_double_memoization
assert_raise(RuntimeError) { Person.memoize :name }
memoize :name, :age
end
class Company
def name
lookup_name
attr_reader :name_calls
def initialize
@name_calls = 0
end
def lookup_name
def name
@name_calls += 1
"37signals"
end
end
module Rates
extend ActiveSupport::Memoizable
attr_reader :sales_tax_calls
def sales_tax(price)
@sales_tax_calls ||= 0
@sales_tax_calls += 1
price * 0.1025
end
memoize :sales_tax
end
class Calculator
extend ActiveSupport::Memoizable
include Rates
attr_reader :fib_calls
def initialize
@fib_calls = 0
end
def fib(n)
@fib_calls += 1
if n == 0 || n == 1
n
else
fib(n - 1) + fib(n - 2)
end
end
memoize :fib
def counter
@count ||= 0
@count += 1
end
memoize :counter
end
def setup
@person = Person.new
@calculator = Calculator.new
end
def test_memoization
assert_equal "Josh", @person.name
assert_equal 1, @person.name_calls
3.times { assert_equal "Josh", @person.name }
assert_equal 1, @person.name_calls
end
def test_memoization_with_nil_value
assert_equal nil, @person.age
assert_equal 1, @person.age_calls
3.times { assert_equal nil, @person.age }
assert_equal 1, @person.age_calls
end
def test_reloadable
counter = @calculator.counter
assert_equal 1, @calculator.counter
assert_equal 2, @calculator.counter(:reload)
assert_equal 2, @calculator.counter
assert_equal 3, @calculator.counter(true)
assert_equal 3, @calculator.counter
end
def test_memoization_cache_is_different_for_each_instance
assert_equal 1, @calculator.counter
assert_equal 2, @calculator.counter(:reload)
assert_equal 1, Calculator.new.counter
end
def test_memoized_is_not_affected_by_freeze
@person.freeze
assert_equal "Josh", @person.name
end
def test_memoization_with_args
assert_equal 55, @calculator.fib(10)
assert_equal 11, @calculator.fib_calls
end
def test_reloadable_with_args
assert_equal 55, @calculator.fib(10)
assert_equal 11, @calculator.fib_calls
assert_equal 55, @calculator.fib(10, :reload)
assert_equal 12, @calculator.fib_calls
assert_equal 55, @calculator.fib(10, true)
assert_equal 13, @calculator.fib_calls
end
def test_object_memoization
[Company.new, Company.new, Company.new].each do |company|
company.extend ActiveSupport::Memoizable
company.memoize :name
assert_equal "37signals", company.name
assert_equal 1, company.name_calls
assert_equal "37signals", company.name
assert_equal 1, company.name_calls
end
end
def test_memoized_module_methods
assert_equal 1.025, @calculator.sales_tax(10)
assert_equal 1, @calculator.sales_tax_calls
assert_equal 1.025, @calculator.sales_tax(10)
assert_equal 1, @calculator.sales_tax_calls
assert_equal 2.5625, @calculator.sales_tax(25)
assert_equal 2, @calculator.sales_tax_calls
end
def test_object_memoized_module_methods
company = Company.new
company.extend(Rates)
assert_equal 1.025, company.sales_tax(10)
assert_equal 1, company.sales_tax_calls
assert_equal 1.025, company.sales_tax(10)
assert_equal 1, company.sales_tax_calls
assert_equal 2.5625, company.sales_tax(25)
assert_equal 2, company.sales_tax_calls
end
def test_double_memoization
assert_raise(RuntimeError) { Person.memoize :name }
person = Person.new
person.extend ActiveSupport::Memoizable
assert_raise(RuntimeError) { person.memoize :name }
company = Company.new
company.extend ActiveSupport::Memoizable
company.memoize :name
assert_equal "37signals", company.name
# Mocha doesn't play well with frozen objects
company.metaclass.instance_eval { define_method(:lookup_name) { b00m } }
assert_equal "37signals", company.name
assert_equal true, company.name.frozen?
company.freeze
assert_equal true, company.name.frozen?
assert_raise(RuntimeError) { company.memoize :name }
end
end
end