Skip to content
This repository
Browse code

Add CachingTools::HashCaching to simplify the creation of nested, aut…

…ofilling hashes.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4059 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit 24403498ba8582d29f55aab16ffd5920dec1c669 1 parent 3f496c6
Nicholas Seckar authored March 27, 2006
2  activesupport/CHANGELOG
... ...
@@ -1,5 +1,7 @@
1 1
 *SVN*
2 2
 
  3
+* Add CachingTools::HashCaching to simplify the creation of nested, autofilling hashes. [Nicholas Seckar]
  4
+
3 5
 * Remove a hack intended to avoid unloading the same class twice, but which would not work anyways. [Nicholas Seckar]
4 6
 
5 7
 * Update Object.subclasses_of to locate nested classes. This affects Object.remove_subclasses_of in that nested classes will now be unloaded. [Nicholas Seckar]
62  activesupport/lib/active_support/caching_tools.rb
... ...
@@ -0,0 +1,62 @@
  1
+module ActiveSupport
  2
+  module CachingTools #:nodoc:
  3
+    
  4
+    # Provide shortcuts to simply the creation of nested default hashes. This
  5
+    # pattern is useful, common practice, and unsightly when done manually.
  6
+    module HashCaching
  7
+      # Dynamically create a nested hash structure used to cache calls to +method_name+
  8
+      # The cache method is named +#{method_name}_cache+ unless :as => :alternate_name
  9
+      # is given.
  10
+      #
  11
+      # The hash structure is created using nested Hash.new. For example:
  12
+      # 
  13
+      #   def slow_method(a, b) a ** b end
  14
+      # 
  15
+      # can be cached using hash_cache :slow_method, which will define the method
  16
+      # slow_method_cache. We can then find the result of a ** b using:
  17
+      # 
  18
+      #   slow_method_cache[a][b]
  19
+      # 
  20
+      # The hash structure returned by slow_method_cache would look like this:
  21
+      # 
  22
+      #   Hash.new do |as, a|
  23
+      #     as[a] = Hash.new do |bs, b|
  24
+      #       bs[b] = slow_method(a, b)
  25
+      #     end
  26
+      #   end
  27
+      # 
  28
+      # The generated code is actually compressed onto a single line to maintain
  29
+      # sensible backtrace signatures.
  30
+      #
  31
+      def hash_cache(method_name, options = {})
  32
+        selector = options[:as] || "#{method_name}_cache"
  33
+        method = self.instance_method(method_name)
  34
+        
  35
+        args = []
  36
+        code = "def #{selector}(); @#{selector} ||= "
  37
+        
  38
+        (1..method.arity).each do |n|
  39
+          args << "v#{n}"
  40
+          code << "Hash.new {|h#{n}, v#{n}| h#{n}[v#{n}] = "
  41
+        end
  42
+        
  43
+        # Add the method call with arguments, followed by closing braces and end.
  44
+        code << "#{method_name}(#{args * ', '}) #{'}' * method.arity} end"
  45
+        
  46
+        # Extract the line number information from the caller. Exceptions arising
  47
+        # in the generated code should point to the +hash_cache :...+ line.
  48
+        if caller[0] && /^(.*):(\d+)$/ =~ caller[0]
  49
+          file, line_number = $1, $2.to_i
  50
+        else # We can't give good trackback info; fallback to this line:
  51
+          file, line_number = __FILE__, __LINE__
  52
+        end
  53
+        
  54
+        # We use eval rather than building proc's because it allows us to avoid
  55
+        # linking the Hash's to this method's binding. Experience has shown that
  56
+        # doing so can cause obtuse memory leaks.
  57
+        class_eval code, file, line_number
  58
+      end
  59
+    end
  60
+    
  61
+  end
  62
+end
81  activesupport/test/caching_tools_test.rb
... ...
@@ -0,0 +1,81 @@
  1
+require 'test/unit'
  2
+require File.dirname(__FILE__)+'/../lib/active_support/caching_tools'
  3
+
  4
+class HashCachingTests < Test::Unit::TestCase
  5
+  
  6
+  def cached(&proc)
  7
+    return @cached if @cached
  8
+    
  9
+    @cached_class = Class.new(&proc)
  10
+    @cached_class.class_eval do
  11
+      extend ActiveSupport::CachingTools::HashCaching
  12
+      hash_cache :slow_method
  13
+    end
  14
+    @cached = @cached_class.new
  15
+  end
  16
+  
  17
+  def test_cache_access_should_call_method
  18
+    cached do
  19
+      def slow_method(a) raise "I should be here: #{a}"; end
  20
+    end
  21
+    assert_raises(RuntimeError) { cached.slow_method_cache[1] }
  22
+  end
  23
+  
  24
+  def test_cache_access_should_actually_cache
  25
+    cached do
  26
+      def slow_method(a)
  27
+        (@x ||= [])
  28
+        if @x.include?(a) then raise "Called twice for #{a}!"
  29
+        else
  30
+          @x << a
  31
+          a + 1
  32
+        end
  33
+      end
  34
+    end
  35
+    assert_equal 11, cached.slow_method_cache[10]
  36
+    assert_equal 12, cached.slow_method_cache[11]
  37
+    assert_equal 11, cached.slow_method_cache[10]
  38
+    assert_equal 12, cached.slow_method_cache[11]
  39
+  end
  40
+  
  41
+  def test_cache_should_be_clearable
  42
+    cached do
  43
+      def slow_method(a)
  44
+        @x ||= 0
  45
+        @x += 1
  46
+      end
  47
+    end
  48
+    assert_equal 1, cached.slow_method_cache[:a]
  49
+    assert_equal 2, cached.slow_method_cache[:b]
  50
+    assert_equal 3, cached.slow_method_cache[:c]
  51
+    
  52
+    assert_equal 1, cached.slow_method_cache[:a]
  53
+    assert_equal 2, cached.slow_method_cache[:b]
  54
+    assert_equal 3, cached.slow_method_cache[:c]
  55
+    
  56
+    cached.slow_method_cache.clear
  57
+    
  58
+    assert_equal 4, cached.slow_method_cache[:a]
  59
+    assert_equal 5, cached.slow_method_cache[:b]
  60
+    assert_equal 6, cached.slow_method_cache[:c]
  61
+  end
  62
+  
  63
+  def test_deep_caches_should_work_too
  64
+    cached do
  65
+      def slow_method(a, b, c)
  66
+        a + b + c
  67
+      end
  68
+    end
  69
+    assert_equal 3, cached.slow_method_cache[1][1][1]
  70
+    assert_equal 7, cached.slow_method_cache[1][2][4]
  71
+    assert_equal 7, cached.slow_method_cache[1][2][4]
  72
+    assert_equal 7, cached.slow_method_cache[4][2][1]
  73
+    
  74
+    assert_equal({
  75
+      1 => {1 => {1 => 3}, 2 => {4 => 7}},
  76
+      4 => {2 => {1 => 7}}},
  77
+      cached.slow_method_cache
  78
+    )
  79
+  end
  80
+  
  81
+end

0 notes on commit 2440349

Please sign in to comment.
Something went wrong with that request. Please try again.