Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

in the middle of a rewrite for rails 3, not relying on anything but R…

…ack::Cache (and other middlewares) in future.
  • Loading branch information...
commit 8eb0a2e8f86024f332b552c0c6dee91e68f5b2d1 1 parent 024583b
Sven Fuchs authored August 29, 2010
2  MIT-LICENSE
... ...
@@ -1,4 +1,4 @@
1  
-Copyright (c) 2008 Sven Fuchs
  1
+Copyright (c) 2008-2010 Sven Fuchs
2 2
 
3 3
 Permission is hereby granted, free of charge, to any person obtaining
4 4
 a copy of this software and associated documentation files (the
9  README.markdown
Source Rendered
... ...
@@ -1,4 +1,11 @@
1  
-## Page Cache Tagging
  1
+## Cache Reference Tagging
  2
+
  3
+BIG REWRITE FOR RAILS 3 AHEAD. 
  4
+
  5
+Won't rely on anything but Rack::Cache in future, i.e. no Rails page caching.
  6
+
  7
+
  8
+THE FOLLOWING ONLY APPLIES TO THE TAG rails-2.x
2 9
 
3 10
 Largely inspired by Rick Olson's [Referenced Page Caching](http://svn.techno-weenie.net/projects/plugins/referenced_page_caching/)
4 11
 this plugin uses a more normalized database schema for better performance 
14  db/migrate/20080401000020_create_cached_page_references.rb
... ...
@@ -1,14 +0,0 @@
1  
-class CreateCachedPageReferences < ActiveRecord::Migration
2  
-  def self.up
3  
-    create_table :cached_page_references, :force => true do |t|
4  
-      t.references :cached_page
5  
-      t.integer    :object_id
6  
-      t.string     :object_type
7  
-      t.string     :method
8  
-    end
9  
-  end
10  
-  
11  
-  def self.down
12  
-    drop_table :cached_page_references
13  
-  end
14  
-end
15  db/migrate/20080401000021_create_cached_pages.rb
... ...
@@ -1,15 +0,0 @@
1  
-class CreateCachedPages < ActiveRecord::Migration
2  
-  def self.up
3  
-    create_table :cached_pages, :force => true do |t|
4  
-      t.references :site
5  
-      t.references :section
6  
-      t.string     :url
7  
-      t.datetime   :updated_at
8  
-      t.datetime   :cleared_at
9  
-    end
10  
-  end
11  
-  
12  
-  def self.down
13  
-    drop_table :cached_pages
14  
-  end
15  
-end
3  lib/cache_references.rb
... ...
@@ -0,0 +1,3 @@
  1
+module CacheReferences
  2
+  autoload :MethodCallTracking, 'cache_references/method_call_tracking'
  3
+end
42  lib/cache_references/method_call_tracking.rb
@@ -8,16 +8,20 @@
8 8
 # the) first call. The given array will then equal [[the_object, :foo]].
9 9
 module CacheReferences
10 10
   module MethodCallTracking
11  
-    def track_method_calls(tracker, *methods)
  11
+    def track_method_calls(tracker, methods)
12 12
       meta_class = (class << self; self; end)
13 13
       methods.each do |method|
14  
-        meta_class.send :define_method, method do |*args|
  14
+        meta_class.send(:define_method, method) do |*args|
15 15
           tracker << [self, method]
16  
-          meta_class.send :remove_method, method
  16
+          meta_class.send(:remove_method, method)
17 17
           super
18 18
         end
19 19
       end
20 20
     end
  21
+    
  22
+    def reference_tag
  23
+      "#{self.class.name.underscore}-#{id}"
  24
+    end
21 25
 
22 26
     # Tracks method access on trackable objects. Trackables can be given as 
23 27
     #
@@ -35,44 +39,44 @@ def track_method_calls(tracker, *methods)
35 39
     class Tracker
36 40
       attr_reader :references
37 41
       
38  
-      def initialize
  42
+      def initialize(owner = nil, trackables = nil)
39 43
         @references = []
  44
+        track(owner, trackables) if owner
40 45
       end
41 46
       
42  
-      def track(owner, *trackables)
  47
+      def track(owner, trackables)
43 48
         trackables.each do |trackable|
44  
-          trackable = { trackable => nil } unless trackable.is_a? Hash
  49
+          trackable = { trackable => nil } unless trackable.is_a?(Hash)
45 50
           trackable.each do |trackable, methods|
46 51
             trackable = resolve_trackable(owner, trackable)
47 52
             track_methods(trackable, methods) unless trackable.nil?
48 53
           end
49 54
         end
50 55
       end
  56
+      
  57
+      def tags
  58
+        references.map { |reference|reference.first.reference_tag }.uniq.join(',')
  59
+      end
51 60
     
52 61
       protected
53 62
     
54  
-        # Resolves the trackable by looking it up on the owner. Trackables  will be 
  63
+        # Resolves the trackable by looking it up on the owner. Trackables will be 
55 64
         # interpreted as instance variables when they start with an @ and as method
56 65
         # names otherwise.
57 66
         def resolve_trackable(owner, trackable)
58  
-          case trackable.to_s
59  
-            when /^@/   then owner.instance_variable_get(trackable.to_sym)
60  
-            else             owner.send(trackable.to_sym)
61  
-          end
  67
+          trackable.to_s[0, 1] == '@' ? 
  68
+            owner.instance_variable_get(trackable.to_sym) :
  69
+            owner.send(trackable.to_sym)
62 70
         end
63 71
       
64 72
         # Wraps the trackable into a MethodReadObserver and registers itself as an observer. 
65 73
         # Sets up tracking for the read_attribute method when the methods argument is nil. 
66 74
         # Sets up tracking for any other given methods otherwise.
67 75
         def track_methods(trackable, methods)
68  
-          methods ||= :read_attribute
69  
-          methods = [methods] if methods && !methods.is_a?(Array)
70  
-
71  
-          if trackable.is_a? Array
72  
-            trackable.each { |trackable| track_methods trackable, methods }
73  
-          else
74  
-            trackable.track_method_calls(references, *methods) unless methods.empty?
75  
-          end
  76
+          methods = Array(methods || :read_attribute)
  77
+          Array(trackable).each do |trackable|
  78
+            trackable.track_method_calls(references, methods)
  79
+          end unless methods.empty?
76 80
         end
77 81
     end
78 82
   end
99  lib/cache_references/page_caching.rb
... ...
@@ -1,99 +0,0 @@
1  
-require 'cache_references/method_call_tracking'
2  
-
3  
-module CacheReferences
4  
-  module PageCaching
5  
-    module ActMacro
6  
-    
7  
-      # Caches the actions using the page-caching approach and sets up reference 
8  
-      # tracking for given actions and objects
9  
-      # 
10  
-      #   caches_page_with_references :index, :show, :track => ['@article', '@articles', {'@site' => :tag_counts}]
11  
-      #
12  
-      def caches_page_with_references(*actions)
13  
-        tracks_cache_references(*actions)
14  
-      
15  
-        unless caches_page_with_references?
16  
-          alias_method_chain :caching_allowed, :skipping
17  
-        end
18  
-
19  
-        options = actions.extract_options!
20  
-        caches_page *actions
21  
-      end
22  
-    
23  
-      # Sets up reference tracking for given actions and objects
24  
-      # 
25  
-      #   tracks_cache_references :index, :show, :track => ['@article', '@articles', {'@site' => :tag_counts}]
26  
-      #
27  
-      def tracks_cache_references(*actions)
28  
-        unless tracks_cache_references?
29  
-          include CacheReferences::PageCaching
30  
-      
31  
-          helper_method :cached_references
32  
-          attr_writer :cached_references
33  
-          alias_method_chain :render, :cache_reference_tracking 
34  
-
35  
-          class_inheritable_accessor :track_options
36  
-          self.track_options ||= {}
37  
-        end
38  
-    
39  
-        options = actions.extract_options!
40  
-        actions.map(&:to_sym).each do |action|
41  
-          self.track_options[action] = options[:track]
42  
-        end
43  
-      end
44  
-  
45  
-      def caches_page_with_references?
46  
-        method_defined? :caching_allowed_without_skipping
47  
-      end
48  
-  
49  
-      def tracks_cache_references?
50  
-        method_defined? :render_without_cache_reference_tracking
51  
-      end
52  
-    end
53  
-      
54  
-    def skip_caching!
55  
-      @skip_caching = true
56  
-    end
57  
-    
58  
-    def skip_caching?
59  
-      @skip_caching == true
60  
-    end
61  
-  
62  
-    protected
63  
-
64  
-      def render_with_cache_reference_tracking(*args)
65  
-        options = args.last.is_a?(Hash) ? args.last : {}
66  
-        # skips caching if :skip_caching => true was passed or action is not configured to be cached
67  
-        skip_caching! if options.delete(:skip_caching) || !(track_options.has_key?(params[:action].to_sym))
68  
-
69  
-        setup_method_call_tracking if track_method_calls?
70  
-        returning render_without_cache_reference_tracking(*args) do
71  
-          save_cache_references if track_method_calls?
72  
-        end
73  
-      end
74  
-      
75  
-      def track_method_calls?
76  
-        perform_caching and not skip_caching?
77  
-      end
78  
-
79  
-      def setup_method_call_tracking
80  
-        @method_call_tracker ||= MethodCallTracking::Tracker.new 
81  
-        @method_call_tracker.track(self, *method_call_trackables) # FIXME pass the controller when self === Component
82  
-      end
83  
-      
84  
-      def method_call_trackables
85  
-        trackables = self.class.track_options[params[:action].to_sym] || {}
86  
-        trackables.clone
87  
-      end
88  
-
89  
-      def save_cache_references
90  
-        CachedPage.create_with_references(@site, @section, request.path, @method_call_tracker.references)
91  
-      end
92  
-
93  
-      def caching_allowed_with_skipping
94  
-        caching_allowed_without_skipping && !skip_caching?
95  
-      end
96  
-  end
97  
-end
98  
-
99  
-ActionController::Base.send :extend, CacheReferences::PageCaching::ActMacro
56  lib/cache_references/reference_tracking.rb
... ...
@@ -0,0 +1,56 @@
  1
+require 'action_controller'
  2
+require 'cache_references/method_call_tracking'
  3
+
  4
+module CacheReferences
  5
+  module ReferenceTracking
  6
+    TAGS_HEADER = 'rack-cache.tags'
  7
+    
  8
+    module ActMacro
  9
+      # Sets up reference tracking for given actions and objects
  10
+      # 
  11
+      #   tracks_references :index, :show, :track => [:article, :@articles, { :@site => :tag_counts }]
  12
+      #
  13
+      def tracks_references(*actions)
  14
+        unless tracks_cache_references?
  15
+          include ReferenceTracking
  16
+          
  17
+          class_inheritable_accessor :reference_tracking_options
  18
+          self.reference_tracking_options = { :header => TAGS_HEADER }
  19
+        end
  20
+    
  21
+        options = actions.extract_options!
  22
+        actions.map(&:to_sym).each do |action|
  23
+          self.reference_tracking_options[action] = options[:track]
  24
+        end
  25
+      end
  26
+  
  27
+      def tracks_cache_references?
  28
+        included_modules.include?(ReferenceTracking)
  29
+      end
  30
+    end
  31
+  
  32
+    protected
  33
+    
  34
+      def render(*)
  35
+        setup_reference_tracking
  36
+        result = super
  37
+        add_reference_headers
  38
+        result
  39
+      end
  40
+      
  41
+      def add_reference_headers
  42
+        headers[reference_tracking_options[:header]] = @reference_tracker.tags
  43
+      end
  44
+
  45
+      def setup_reference_tracking
  46
+        @reference_tracker = MethodCallTracking::Tracker.new(self, reference_trackables)
  47
+      end
  48
+      
  49
+      def reference_trackables
  50
+        trackables = reference_tracking_options[params[:action].to_sym] || {}
  51
+        trackables.clone
  52
+      end
  53
+  end
  54
+end
  55
+
  56
+ActionController::Base.send(:extend, CacheReferences::ReferenceTracking::ActMacro)
25  lib/cache_references/sweeper.rb
... ...
@@ -1,25 +0,0 @@
1  
-module CacheReferences
2  
-  class Sweeper < ActionController::Caching::Sweeper
3  
-    def expire_cached_pages_by_site(site)
4  
-      expire_cached_pages site, CachedPage.find_all_by_site_id(site.id)
5  
-    end
6  
-
7  
-    def expire_cached_pages_by_section(section)
8  
-      expire_cached_pages section, CachedPage.find_all_by_section_id(section.id)
9  
-    end
10  
-
11  
-    def expire_cached_pages_by_reference(record, method = nil)
12  
-      expire_cached_pages record, CachedPage.find_by_reference(record, method)
13  
-    end
14  
-
15  
-    def expire_cached_pages(record, pages)
16  
-      record.logger.warn cached_log_message_for(record, pages) if Site.cache_sweeper_logging
17  
-      controller.expire_pages(pages) if controller # TODO wtf ... why is controller sometimes nil here??
18  
-    end
19  
-
20  
-    def cached_log_message_for(record, pages)
21  
-      msg = ["Expired pages referenced by #{record.class} ##{record.id}", "Expiring #{pages.size} page(s)"]
22  
-      pages.inject(msg) { |msg, page| msg << " - #{page.url}" }.join("\n")
23  
-    end
24  
-  end
25  
-end
41  lib/cached_page.rb
... ...
@@ -1,41 +0,0 @@
1  
-# Represents a cached page in the database.  Has one or more references that expire it.
2  
-
3  
-class CachedPage < ActiveRecord::Base
4  
-  belongs_to :site
5  
-  validates_uniqueness_of :url, :scope => :site_id
6  
-  
7  
-  has_many :references, :class_name => "CachedPageReference", :dependent => :destroy
8  
-
9  
-  class << self
10  
-    def find_by_reference(object, method = nil)
11  
-      sql = 'cached_page_references.object_type = ? AND cached_page_references.object_id = ?'      
12  
-      sql << ' AND cached_page_references.method = ?' if method
13  
-
14  
-      conditions = [sql, object.class.name, object.id]
15  
-      conditions << method.to_s if method
16  
-
17  
-      find :all, :conditions => conditions, :include => :references
18  
-    end
19  
-    
20  
-    def create_with_references(site, section, url, references)
21  
-      returning find_or_initialize_by_site_id_and_url(site.id, url, :include => :references) do |page|
22  
-        [:compact!, :uniq!].each { |method| references.send method }
23  
-        references.each do |object, method|
24  
-          reference = CachedPageReference.initialize_with(object, method)
25  
-          page.references << reference unless page.references.detect {|r| r == reference }
26  
-        end
27  
-        page.section_id = section.id
28  
-        page.cleared_at = nil
29  
-        page.save!
30  
-      end
31  
-    end
32  
-
33  
-    def expire_pages(pages)
34  
-      destroy pages.collect(&:id) unless pages.empty?
35  
-    end
36  
-    
37  
-    def delete_all_by_site_id(site_id)
38  
-      delete_all "site_id = #{site_id}"
39  
-    end
40  
-  end
41  
-end
16  lib/cached_page_reference.rb
... ...
@@ -1,16 +0,0 @@
1  
-class CachedPageReference < ActiveRecord::Base
2  
-  belongs_to :cached_page
3  
-  
4  
-  class << self
5  
-    def initialize_with(object, method = nil)
6  
-      new :object_type => object.class.name, :object_id => object.id, :method => method.to_s
7  
-    end
8  
-  end
9  
-  
10  
-  def ==(other)
11  
-    self.cached_page_id == other.cached_page_id &&
12  
-    self.object_type == other.object_type &&
13  
-    self.object_id == other.object_id &&
14  
-    self.method == other.method
15  
-  end
16  
-end
54  test/cache_references_test.rb
... ...
@@ -1,54 +0,0 @@
1  
-require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2  
-
3  
-class CacheReferencesTest < Test::Unit::TestCase
4  
-  def setup
5  
-    @article = Article.new
6  
-    @comment = Comment.new
7  
-
8  
-    @controller = ArticlesController.new
9  
-    @controller.stubs(:save_cache_references)
10  
-    @controller.instance_variable_set(:@article, @article)
11  
-    @controller.instance_variable_set(:@comments, [@comment])
12  
-  end
13  
-  
14  
-  def tracker
15  
-    @controller.instance_variable_get(:@method_call_tracker)
16  
-  end
17  
-  
18  
-  def test_access_to_an_attribute_on_an_observed_object_records_the_reference
19  
-    @controller.send :render
20  
-    @article.title
21  
-    assert tracker.references.include?([@article, :read_attribute])
22  
-  end
23  
-  
24  
-  def test_access_to_a_registered_method_on_an_observed_object_records_the_reference
25  
-    @controller.send :render
26  
-    @article.section
27  
-    assert tracker.references.include?([@article, :section])
28  
-  end
29  
-  
30  
-  def test_access_to_an_attribute_on_an_observed_array_of_objects_records_the_reference
31  
-    @controller.send :render
32  
-    @comment.body
33  
-    assert tracker.references.include?([@comment, :read_attribute])
34  
-  end
35  
-  
36  
-  def test_access_to_a_registered_method_on_an_observed_array_of_objects_records_the_reference
37  
-    @controller.send :render
38  
-    @comment.section
39  
-    assert tracker.references.include?([@comment, :section])
40  
-  end
41  
-  
42  
-  def test_does_not_setup_method_call_tracking_if_skip_caching_is_passed_as_option
43  
-    @controller.send :render, :skip_caching => true
44  
-    @article.title
45  
-    assert_equal nil, tracker
46  
-  end
47  
-  
48  
-  def test_does_not_setup_method_call_tracking_if_skip_caching_is_called_on_controller
49  
-    @controller.skip_caching!
50  
-    @controller.send :render
51  
-    @article.title
52  
-    assert_equal nil, tracker
53  
-  end
54  
-end
145  test/method_call_tracking_test.rb
... ...
@@ -1,108 +1,105 @@
1  
-$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
  1
+require File.expand_path('../test_helper', __FILE__)
2 2
 
3  
-require 'rubygems'
4  
-require 'mocha'
5  
-require 'cache_references/method_call_tracking'
6  
-
7  
-class MethodCallTrackerTest < Test::Unit::TestCase
  3
+class MethodCallTrackingTest < Test::Unit::TestCase
8 4
   include CacheReferences
9 5
 
  6
+  attr_reader :object, :tracker, :references
  7
+
  8
+  class Object
  9
+    include CacheReferences::MethodCallTracking
  10
+
  11
+    def title; end
  12
+    def foo(bar, baz); end
  13
+  end
  14
+
10 15
   def setup
11  
-    @controller = mock('controller')
12 16
     @tracker = MethodCallTracking::Tracker.new
  17
+    @object = Object.new
  18
+    @references = []
13 19
   end
14 20
 
15  
-  def test_resolve_trackable_resolves_ivars_and_method_names_given_as_symbols_or_strings
16  
-    @controller.expects(:instance_variable_get).with(:@foo)
17  
-    @tracker.send(:resolve_trackable, @controller, :@foo)
18  
-  
19  
-    @controller.expects(:instance_variable_get).with(:@foo)
20  
-    @tracker.send(:resolve_trackable, @controller, '@foo')
21  
-    
22  
-    @controller.expects(:foo)
23  
-    @tracker.send(:resolve_trackable, @controller, :foo)
24  
-    
25  
-    @controller.expects(:foo)
26  
-    @tracker.send(:resolve_trackable, @controller, 'foo')
  21
+  test 'Tracker#resolve_trackable resolves ivars given as symbols' do
  22
+    object.expects(:instance_variable_get).with(:@foo)
  23
+    tracker.send(:resolve_trackable, object, :@foo)
27 24
   end
28  
-  
29  
-  def test_initialize_resolves_trackables_given_as_symbol_string_or_hash_key
30  
-    @controller.expects(:instance_variable_get).with(:@foo)
31  
-    @controller.expects(:bar)
32  
-    @controller.expects(:baz)
33  
-  
34  
-    @tracker.track @controller, :@foo, :bar, { :baz => nil }
  25
+
  26
+  test 'Tracker#resolve_trackable resolves ivars given as strings' do
  27
+    object.expects(:instance_variable_get).with(:@foo)
  28
+    tracker.send(:resolve_trackable, object, '@foo')
35 29
   end
36  
-  
37  
-  def test_tracks_read_attribute_method_when_given_method_is_an_attribute
38  
-    foo = stub('foo', :has_attribute? => true)
39  
-    @controller.expects(:foo).returns foo
40  
-  
41  
-    foo.expects(:track_method_calls).with([], :read_attribute)
42  
-    @tracker.track @controller, :foo
  30
+
  31
+  test 'Tracker#resolve_trackable resolves method_names given as symbols' do
  32
+    object.expects(:foo)
  33
+    tracker.send(:resolve_trackable, object, :foo)
43 34
   end
44  
-  
45  
-  def test_tracks_method_when_given_method_is_not_an_attribute
46  
-    foo = stub('foo', :has_attribute? => false)
47  
-    @controller.expects(:foo).returns foo
48  
-  
49  
-    foo.expects(:track_method_calls).with([], :bar)
50  
-    @tracker.track @controller, :foo => :bar
  35
+
  36
+  test 'Tracker#resolve_trackable resolves method_names given as strings' do
  37
+    object.expects(:foo)
  38
+    tracker.send(:resolve_trackable, object, 'foo')
51 39
   end
52  
-end
53 40
 
54  
-class MethodReadTrackingTest < Test::Unit::TestCase
55  
-  class Record
56  
-    include CacheReferences::MethodCallTracking
  41
+  test 'Tracker#track resolves trackables given as a symbol, string or hash key' do
  42
+    [:@foo, :@bar].each { |ivar| object.expects(:instance_variable_get).with(ivar) }
  43
+    [:baz, :buz, :bum].each { |method| object.expects(method) }
57 44
 
58  
-    def title; end
59  
-    def foo(bar, baz); end
  45
+    tracker.track(object, [:@foo, '@bar', :baz, 'buz', { :bum => nil }])
60 46
   end
61 47
 
62  
-  def setup
63  
-    @record = Record.new
64  
-    @references = []
  48
+  test 'Tracker#track tracks read_attribute method when the given method is an attribute' do
  49
+    foo = stub('foo', :has_attribute? => true)
  50
+    object.expects(:foo).returns(foo)
  51
+
  52
+    foo.expects(:track_method_calls).with([], [:read_attribute])
  53
+    tracker.track(object, [:foo])
  54
+  end
  55
+
  56
+  test 'Tracker#track tracks the given method when it is not an attribute' do
  57
+    foo = stub('foo', :has_attribute? => false)
  58
+    object.expects(:foo).returns(foo)
  59
+
  60
+    foo.expects(:track_method_calls).with([], [:bar])
  61
+    tracker.track(object, [{ :foo => :bar }])
65 62
   end
66 63
 
67  
-  def test_installs_on_methods_without_arguments
  64
+  test 'track_method_calls installs on methods that do no take any arguments' do
68 65
     assert_nothing_raised {
69  
-      @record.track_method_calls(@references, :title)
70  
-      @record.title
  66
+      object.track_method_calls(references, [:title])
  67
+      object.title
71 68
     }
72 69
   end
73 70
 
74  
-  def test_installs_on_methods_with_arguments
  71
+  test 'track_method_calls installs on methods that take arguments' do
75 72
     assert_nothing_raised {
76  
-      @record.track_method_calls(@references, :foo)
77  
-      @record.foo(:bar, :baz)
  73
+      object.track_method_calls(references, [:foo])
  74
+      object.foo(:bar, :baz)
78 75
     }
79 76
   end
80 77
 
81  
-  def test_installs_method_on_metaclass
82  
-    @record.track_method_calls(@references, :title)
83  
-    assert (class << @record; self; end).method_defined?(:title)
  78
+  test "track_method_calls installs a new method on the object's meta class" do
  79
+    object.track_method_calls(references, [:title])
  80
+    assert (class << object; self; end).method_defined?(:title)
84 81
   end
85 82
 
86  
-  def test_adds_method_call_reference_to_given_references_array
87  
-    @record.track_method_calls(@references, :title)
88  
-    @record.title
89  
-    assert_equal [@record, :title], @references.first
  83
+  test "a call to a tracked method adds a reference to the given references array" do
  84
+    object.track_method_calls(references, [:title])
  85
+    object.title
  86
+    assert_equal [object, :title], references.first
90 87
   end
91 88
 
92  
-  def test_does_not_add_multiple_references_on_subsequent_calls
93  
-    @record.track_method_calls(@references, :title)
94  
-    @record.title
95  
-    @record.title
96  
-    assert_equal [@record, :title], @references.first
  89
+  test "subsequent calls to the same tracked method do not add multiple references" do
  90
+    object.track_method_calls(references, [:title])
  91
+    object.title
  92
+    object.title
  93
+    assert_equal [object, :title], references.first
97 94
   end
98  
-  
99  
-  def test_allows_to_setup_tracking_multiple_times_for_the_same_method
  95
+
  96
+  test "tracking can be set up multiple times for the same method" do
100 97
     assert_nothing_raised {
101  
-      @record.track_method_calls(@references, :title)
102  
-      @record.track_method_calls(@references, :title)
  98
+      object.track_method_calls(references, [:title])
  99
+      object.track_method_calls(references, [:title])
103 100
     }
104  
-    @record.title
105  
-    @record.title
106  
-    assert_equal [@record, :title], @references.first
  101
+    object.title
  102
+    object.title
  103
+    assert_equal [object, :title], references.first
107 104
   end
108 105
 end
43  test/reference_tracking_test.rb
... ...
@@ -0,0 +1,43 @@
  1
+require File.expand_path('../test_helper', __FILE__)
  2
+
  3
+class ReferenceTrackingTest < Test::Unit::TestCase
  4
+  attr_reader :controller, :article, :comment
  5
+  
  6
+  def setup
  7
+    @article = Article.new
  8
+    @comment = Comment.new
  9
+
  10
+    @controller = ArticlesController.new
  11
+    @controller.instance_variable_set(:@article, @article)
  12
+    @controller.instance_variable_set(:@comments, [@comment])
  13
+  end
  14
+  
  15
+  def tracker
  16
+    controller.instance_variable_get(:@reference_tracker)
  17
+  end
  18
+  
  19
+  test 'accessing an attribute on an observed object records the reference' do
  20
+    controller.process { article.title }
  21
+    assert tracker.references.include?([article, :read_attribute])
  22
+  end
  23
+  
  24
+  test 'accessing a registered method on an observed object records the reference' do
  25
+    controller.process { article.section }
  26
+    assert tracker.references.include?([article, :section])
  27
+  end
  28
+  
  29
+  test 'accessing an attribute on an observed array of objects records the reference' do
  30
+    controller.process { comment.body }
  31
+    assert tracker.references.include?([comment, :read_attribute])
  32
+  end
  33
+  
  34
+  test 'accessing a registered method on an observed array of objects records the reference' do
  35
+    controller.process { comment.section }
  36
+    assert tracker.references.include?([comment, :section])
  37
+  end
  38
+  
  39
+  test 'adds reference tags to the headers hash' do
  40
+    controller.process { article.title; comment.section; comment.body }
  41
+    assert_equal 'article-1,comment-2', controller.headers[CacheReferences::ReferenceTracking::TAGS_HEADER]
  42
+  end
  43
+end
38  test/test_helper.rb
... ...
@@ -1,33 +1,41 @@
1 1
 $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
2 2
 
3 3
 require 'rubygems'
4  
-require 'actionpack'
5  
-require 'action_controller'
  4
+require 'test/unit'
6 5
 require 'mocha'
  6
+require 'test_declarative'
7 7
 
8  
-require 'cache_references/page_caching'
  8
+require 'cache_references/reference_tracking'
  9
+require 'cache_references'
  10
+
  11
+class ActionController::Base
  12
+  def render(*)
  13
+    yield
  14
+  end
  15
+end
9 16
 
10 17
 class ArticlesController < ActionController::Base
11  
-  caches_page_with_references :show, :track => [:@article, :@comments, { :@article => :section, :@comments => :section }]
12  
-  
  18
+  tracks_references :show, :track => [:@article, :@comments, { :@article => :section, :@comments => :section }]
  19
+
  20
+  def process(&block)
  21
+    self.response = ActionDispatch::Response.new
  22
+    run_callbacks(:process_action, :show) { render(&block) }
  23
+  end
  24
+
13 25
   def params
14 26
     { :action => :show }
15 27
   end
16  
-  
17  
-  def render_without_cache_reference_tracking(*args)
18  
-  end
19 28
 end
20 29
 
21 30
 class Record
22 31
   include CacheReferences::MethodCallTracking
23 32
   
24  
-  def section
25  
-  end
  33
+  def section; end
26 34
 
27 35
   def read_attribute(name)
28 36
     @attributes[name]
29 37
   end
30  
-  
  38
+
31 39
   def method_missing(name)
32 40
     read_attribute(name)
33 41
   end
@@ -37,11 +45,19 @@ class Article < Record
37 45
   def initialize
38 46
     @attributes = {:title => '', :body => ''}
39 47
   end
  48
+  
  49
+  def id
  50
+    1
  51
+  end
40 52
 end
41 53
 
42 54
 class Comment < Record
43 55
   def initialize
44 56
     @attributes = {:body => ''}
45 57
   end
  58
+  
  59
+  def id
  60
+    2
  61
+  end
46 62
 end
47 63
 

0 notes on commit 8eb0a2e

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