cache_digests 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 035fdb8a71c48c1440431e11341a79de3127a055
4
+ data.tar.gz: 5e4bbc22c9ab89f076a28075273291830c11856c
5
+ SHA512:
6
+ metadata.gz: 4353fed562cd8a8b595e7fbd15b5f79ac04a457bedb15e01349b0164054f87eb41ce67bc8f1bcba2dfd2e9837f30553656129f83e3932d8bc085b25a8310dc1a
7
+ data.tar.gz: bb811acb72bf76db5da85c1169205b8a064de985bc84813c605034a34cdbfcf2de020d1e94ab471f29dd5a2c623068ea8887d12b45fa7b61e156ad9b3d570424
data/Gemfile.lock CHANGED
@@ -1,45 +1,50 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cache_digests (0.1.0)
4
+ cache_digests (0.2.0)
5
5
  actionpack (>= 3.2)
6
+ thread_safe
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
10
- actionpack (3.2.3)
11
- activemodel (= 3.2.3)
12
- activesupport (= 3.2.3)
11
+ actionpack (3.2.12)
12
+ activemodel (= 3.2.12)
13
+ activesupport (= 3.2.12)
13
14
  builder (~> 3.0.0)
14
15
  erubis (~> 2.7.0)
15
- journey (~> 1.0.1)
16
- rack (~> 1.4.0)
16
+ journey (~> 1.0.4)
17
+ rack (~> 1.4.5)
17
18
  rack-cache (~> 1.2)
18
19
  rack-test (~> 0.6.1)
19
- sprockets (~> 2.1.2)
20
- activemodel (3.2.3)
21
- activesupport (= 3.2.3)
20
+ sprockets (~> 2.2.1)
21
+ activemodel (3.2.12)
22
+ activesupport (= 3.2.12)
22
23
  builder (~> 3.0.0)
23
- activesupport (3.2.3)
24
+ activesupport (3.2.12)
24
25
  i18n (~> 0.6)
25
26
  multi_json (~> 1.0)
26
- builder (3.0.0)
27
+ atomic (1.0.1)
28
+ builder (3.0.4)
27
29
  erubis (2.7.0)
28
30
  hike (1.2.1)
29
- i18n (0.6.0)
30
- journey (1.0.3)
31
+ i18n (0.6.1)
32
+ journey (1.0.4)
31
33
  minitest (2.12.1)
32
- multi_json (1.3.2)
33
- rack (1.4.1)
34
+ multi_json (1.6.1)
35
+ rack (1.4.5)
34
36
  rack-cache (1.2)
35
37
  rack (>= 0.4)
36
- rack-test (0.6.1)
38
+ rack-test (0.6.2)
37
39
  rack (>= 1.0)
38
40
  rake (0.9.2.2)
39
- sprockets (2.1.2)
41
+ sprockets (2.2.2)
40
42
  hike (~> 1.2)
43
+ multi_json (~> 1.0)
41
44
  rack (~> 1.0)
42
45
  tilt (~> 1.1, != 1.3.0)
46
+ thread_safe (0.1.0)
47
+ atomic
43
48
  tilt (1.3.3)
44
49
 
45
50
  PLATFORMS
data/README.md CHANGED
@@ -63,6 +63,9 @@ Our code from above can just look like:
63
63
 
64
64
  The caching key for app/views/projects/show.html.erb will be something like `views/projects/605816632-20120810191209/d9fb66b120b61f46707c67ab41d93cb2`. That last bit is a MD5 of the template file itself and all of its dependencies. It'll change if you change either the template or any of the dependencies, and thus allow the cache to expire automatically.
65
65
 
66
+ Note that if your application cache is enabled, the template digest will not be recomputed until you restart your application and you will have to restart the app whenever you change template code.
67
+ For development environment you might want to cache your fragments AND have dependent partials changes detected (with the penalty of recomputing the digests for each request). For that you can add `CacheDigests::TemplateDigestor.cache = ActiveSupport::Cache::NullStore.new` to your development configuration
68
+
66
69
  You can use these handy rake tasks to see how deep the rabbit hole goes:
67
70
 
68
71
  ```
@@ -87,6 +90,7 @@ $ rake cache_digests:nested_dependencies TEMPLATE=projects/show
87
90
  }
88
91
  ]
89
92
  ```
93
+ Note: this rake task doesn't differentiate between dependencies that are cached and dependencies that aren't - it is meant to show you all the dependencies of your view that cache_digests is able to derive.
90
94
 
91
95
  Implicit dependencies
92
96
  ---------------------
@@ -136,4 +140,4 @@ You'll need to use a special comment format to call those out:
136
140
  <%= render_sortable_todolists @project.todolists %>
137
141
  ```
138
142
 
139
- The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so. You can only declare one template dependency per line.
143
+ The pattern used to match these is `/# Template Dependency: ([^ ]+)/`, so it's important that you type it out just so. You can only declare one template dependency per line.
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'cache_digests'
3
- s.version = '0.2.0'
3
+ s.version = '0.3.0'
4
4
  s.author = 'David Heinemeier Hansson'
5
5
  s.email = 'david@37signals.com'
6
6
  s.summary = 'Nested fragment caches with (even) less situps'
@@ -8,6 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.required_ruby_version = '>= 1.9'
9
9
 
10
10
  s.add_dependency 'actionpack', '>= 3.2'
11
+ s.add_dependency 'thread_safe'
11
12
 
12
13
  s.add_development_dependency 'rake'
13
14
  s.add_development_dependency 'minitest'
data/lib/cache_digests.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'digest/md5'
2
2
  require 'cache_digests/template_digestor'
3
3
  require 'cache_digests/fragment_helper'
4
+ require 'cache_digests/view_cache_dependency'
4
5
  require 'cache_digests/engine' if defined?(Rails)
@@ -0,0 +1,89 @@
1
+ require 'thread_safe'
2
+
3
+ module CacheDigests
4
+ class DependencyTracker
5
+ @trackers = ThreadSafe::Cache.new
6
+
7
+ def self.find_dependencies(name, template)
8
+ tracker = @trackers[template.handler]
9
+
10
+ if tracker.present?
11
+ tracker.call(name, template)
12
+ else
13
+ []
14
+ end
15
+ end
16
+
17
+ def self.register_tracker(extension, tracker)
18
+ handler = ActionView::Template.handler_for_extension(extension)
19
+ @trackers[handler] = tracker
20
+ end
21
+
22
+ def self.remove_tracker(handler)
23
+ @trackers.delete(handler)
24
+ end
25
+
26
+ class ERBTracker
27
+ EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
28
+
29
+ # Matches:
30
+ # render partial: "comments/comment", collection: commentable.comments
31
+ # render "comments/comments"
32
+ # render 'comments/comments'
33
+ # render('comments/comments')
34
+ #
35
+ # render(@topic) => render("topics/topic")
36
+ # render(topics) => render("topics/topic")
37
+ # render(message.topics) => render("topics/topic")
38
+ RENDER_DEPENDENCY = /
39
+ render\s* # render, followed by optional whitespace
40
+ \(? # start an optional parenthesis for the render call
41
+ (partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture
42
+ ([@a-z"'][@a-z_\/\."']+) # the template name itself -- 2nd capture
43
+ /x
44
+
45
+ def self.call(name, template)
46
+ new(name, template).dependencies
47
+ end
48
+
49
+ def initialize(name, template)
50
+ @name, @template = name, template
51
+ end
52
+
53
+ def dependencies
54
+ render_dependencies + explicit_dependencies
55
+ end
56
+
57
+ private
58
+ attr_reader :name, :template
59
+
60
+ def source
61
+ template.source
62
+ end
63
+
64
+ def directory
65
+ name.split("/")[0..-2].join("/")
66
+ end
67
+
68
+ def render_dependencies
69
+ source.scan(RENDER_DEPENDENCY).
70
+ collect(&:second).uniq.
71
+
72
+ # render(@topic) => render("topics/topic")
73
+ # render(topics) => render("topics/topic")
74
+ # render(message.topics) => render("topics/topic")
75
+ collect { |name| name.sub(/\A@?([a-z]+\.)*([a-z_]+)\z/) { "#{$2.pluralize}/#{$2.singularize}" } }.
76
+
77
+ # render("headline") => render("message/headline")
78
+ collect { |name| name.include?("/") ? name : "#{directory}/#{name}" }.
79
+
80
+ # replace quotes from string renders
81
+ collect { |name| name.gsub(/["']/, "") }
82
+ end
83
+
84
+ def explicit_dependencies
85
+ source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
86
+ end
87
+ end
88
+ end
89
+ end
@@ -6,9 +6,14 @@ module CacheDigests
6
6
  ActiveSupport.on_load :action_view do
7
7
  ActionView::Base.send :include, CacheDigests::FragmentHelper
8
8
  end
9
-
9
+
10
+ ActiveSupport.on_load :action_controller do
11
+ ActionController::Base.send :include, CacheDigests::ViewCacheDependency
12
+ end
13
+
10
14
  config.to_prepare do
11
15
  CacheDigests::TemplateDigestor.logger = Rails.logger
16
+ DependencyTracker.register_tracker :erb, DependencyTracker::ERBTracker
12
17
  end
13
18
  end
14
19
  end
@@ -1,14 +1,25 @@
1
1
  module CacheDigests
2
2
  module FragmentHelper
3
- def fragment_name_with_digest(name)
4
- [*name, TemplateDigestor.digest(@virtual_path, formats.last.to_sym, lookup_context)]
3
+ def fragment_name_with_digest(name, dependencies)
4
+ [*name, TemplateDigestor.digest(@virtual_path, formats.last.to_sym, lookup_context, dependencies: dependencies)]
5
5
  end
6
6
 
7
7
  private
8
- # Automatically include this template's digest -- and its childrens' -- in the cache key.
8
+ # Automatically include this template's digest -- and its childrens' --
9
+ # in the cache key. Cache digests can be skipped by either providing an
10
+ # explicitly versioned key (an Array-based key with the first key
11
+ # matching "v#" e.g. ['v3','my-key']) or by passing skip_digest: true to
12
+ # the options hash.
13
+ #
14
+ # "view_cache_dependencies" method is a helper defined in the
15
+ # ViewCacheDependency module, which is declared as a helper by the
16
+ # controller.
9
17
  def fragment_for(key, options = nil, &block)
10
- if !explicitly_versioned_cache_key?(key)
11
- super fragment_name_with_digest(key), options, &block
18
+ skip_digest = explicitly_versioned_cache_key?(key) ||
19
+ (options && options.delete(:skip_digest))
20
+
21
+ if !skip_digest
22
+ super fragment_name_with_digest(key, view_cache_dependencies), options, &block
12
23
  else
13
24
  super
14
25
  end
@@ -1,34 +1,19 @@
1
1
  require 'active_support/core_ext'
2
2
  require 'active_support/cache'
3
3
  require 'logger'
4
+ require 'cache_digests/dependency_tracker'
4
5
 
5
6
  module CacheDigests
6
7
  class TemplateDigestor
7
- EXPLICIT_DEPENDENCY = /# Template Dependency: ([^ ]+)/
8
-
9
- # Matches:
10
- # render partial: "comments/comment", collection: commentable.comments
11
- # render "comments/comments"
12
- # render 'comments/comments'
13
- # render('comments/comments')
14
- #
15
- # render(@topic) => render("topics/topic")
16
- # render(topics) => render("topics/topic")
17
- # render(message.topics) => render("topics/topic")
18
- RENDER_DEPENDENCY = /
19
- render\s* # render, followed by optional whitespace
20
- \(? # start an optional parenthesis for the render call
21
- (partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture
22
- ([@a-z"'][@a-z_\/\."']+) # the template name itself -- 2nd capture
23
- /x
24
-
25
8
  cattr_accessor(:cache) { ActiveSupport::Cache::MemoryStore.new }
26
9
  cattr_accessor(:cache_prefix)
27
10
 
28
11
  cattr_accessor(:logger, instance_reader: true)
29
12
 
30
13
  def self.digest(name, format, finder, options = {})
31
- cache.fetch([ "digestor", cache_prefix, name, format ].compact.join("/")) do
14
+ cache_key = [ "digestor", cache_prefix, name, format, *Array.wrap(options[:dependencies]) ].compact.join("/")
15
+ cache.fetch(cache_key) do
16
+ cache.write(cache_key, nil) # Prevent re-entry
32
17
  new(name, format, finder, options).digest
33
18
  end
34
19
  end
@@ -49,7 +34,7 @@ module CacheDigests
49
34
  end
50
35
 
51
36
  def dependencies
52
- render_dependencies + explicit_dependencies
37
+ DependencyTracker.find_dependencies(name, template)
53
38
  rescue ActionView::MissingTemplate
54
39
  [] # File doesn't exist, so no dependencies
55
40
  end
@@ -61,49 +46,33 @@ module CacheDigests
61
46
  end
62
47
  end
63
48
 
64
-
65
49
  private
66
50
  def logical_name
67
51
  name.gsub(%r|/_|, "/")
68
52
  end
69
-
70
- def directory
71
- name.split("/").first
72
- end
73
53
 
74
54
  def partial?
75
55
  options[:partial] || name.include?("/_")
76
56
  end
77
57
 
78
58
  def source
79
- @source ||= finder.find(logical_name, [], partial?, formats: [ format ]).source
59
+ template.source
80
60
  end
81
61
 
62
+ def template
63
+ @template ||= finder.find(logical_name, [], partial?, formats: [ format ])
64
+ end
82
65
 
83
66
  def dependency_digest
84
- dependencies.collect do |template_name|
67
+ template_digests = dependencies.collect do |template_name|
85
68
  TemplateDigestor.digest(template_name, format, finder, partial: true)
86
- end.join("-")
87
- end
88
-
89
- def render_dependencies
90
- source.scan(RENDER_DEPENDENCY).
91
- collect(&:second).uniq.
92
-
93
- # render(@topic) => render("topics/topic")
94
- # render(topics) => render("topics/topic")
95
- # render(message.topics) => render("topics/topic")
96
- collect { |name| name.sub(/\A@?([a-z]+\.)*([a-z_]+)\z/) { "#{$2.pluralize}/#{$2.singularize}" } }.
69
+ end
97
70
 
98
- # render("headline") => render("message/headline")
99
- collect { |name| name.include?("/") ? name : "#{directory}/#{name}" }.
100
-
101
- # replace quotes from string renders
102
- collect { |name| name.gsub(/["']/, "") }
71
+ (template_digests + injected_dependencies).join("-")
103
72
  end
104
73
 
105
- def explicit_dependencies
106
- source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
74
+ def injected_dependencies
75
+ Array.wrap(options[:dependencies])
107
76
  end
108
77
  end
109
- end
78
+ end
@@ -0,0 +1,22 @@
1
+ module CacheDigests
2
+ module ViewCacheDependency
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :_view_cache_dependencies
7
+ self._view_cache_dependencies = []
8
+
9
+ helper_method :view_cache_dependencies
10
+ end
11
+
12
+ module ClassMethods
13
+ def view_cache_dependency(&dependency)
14
+ self._view_cache_dependencies += [dependency]
15
+ end
16
+ end
17
+
18
+ def view_cache_dependencies
19
+ self.class._view_cache_dependencies.map { |dep| instance_exec &dep }.compact
20
+ end
21
+ end
22
+ end
@@ -1,6 +1,7 @@
1
1
  namespace :cache_digests do
2
2
  desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
3
3
  task :nested_dependencies => :environment do
4
+ raise 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
4
5
  template, format = ENV['TEMPLATE'].split(".")
5
6
  format ||= :html
6
7
  puts JSON.pretty_generate CacheDigests::TemplateDigestor.new(template, format, ApplicationController.new.lookup_context).nested_dependencies
@@ -8,8 +9,9 @@ namespace :cache_digests do
8
9
 
9
10
  desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
10
11
  task :dependencies => :environment do
12
+ raise 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
11
13
  template, format = ENV['TEMPLATE'].split(".")
12
14
  format ||= :html
13
15
  puts JSON.pretty_generate CacheDigests::TemplateDigestor.new(template, format, ApplicationController.new.lookup_context).dependencies
14
16
  end
15
- end
17
+ end
@@ -0,0 +1,49 @@
1
+ require 'cache_digests/test_helper'
2
+
3
+ class NeckbeardTracker
4
+ def self.call(name, template)
5
+ ["foo/#{name}"]
6
+ end
7
+ end
8
+
9
+ module ActionView
10
+ class Template
11
+ def self.handler_for_extension(extension)
12
+ extension
13
+ end
14
+ end
15
+ end
16
+
17
+ class DependencyTrackerTest < MiniTest::Unit::TestCase
18
+ class FakeTemplate
19
+ attr_reader :source, :handler
20
+
21
+ def initialize(source, handler)
22
+ @source, @handler = source, handler
23
+ end
24
+ end
25
+
26
+ def tracker
27
+ CacheDigests::DependencyTracker
28
+ end
29
+
30
+ def setup
31
+ tracker.register_tracker(:neckbeard, NeckbeardTracker)
32
+ end
33
+
34
+ def teardown
35
+ tracker.remove_tracker(:neckbeard)
36
+ end
37
+
38
+ def test_finds_tracker_by_template_handler
39
+ template = FakeTemplate.new("boo/hoo", :neckbeard)
40
+ dependencies = tracker.find_dependencies("boo/hoo", template)
41
+ assert_equal ["foo/boo/hoo"], dependencies
42
+ end
43
+
44
+ def test_returns_empty_array_if_no_tracker_registered_for_handler
45
+ template = FakeTemplate.new("boo/hoo", :hater)
46
+ dependencies = tracker.find_dependencies("boo/hoo", template)
47
+ assert_equal [], dependencies
48
+ end
49
+ end
@@ -0,0 +1 @@
1
+ <%= render "recursion" %>
File without changes
@@ -0,0 +1 @@
1
+ <%= render partial: "header" %>
@@ -0,0 +1 @@
1
+ <%= render "recursion" %>
File without changes
@@ -6,4 +6,8 @@
6
6
 
7
7
  <%= render @message.history.events %>
8
8
 
9
- <%# render "something_missing" %>
9
+ <%# render "something_missing" %>
10
+
11
+ <%
12
+ # Template Dependency: messages/form
13
+ %>
@@ -9,11 +9,31 @@ class Fragmenter
9
9
  end
10
10
  end
11
11
 
12
+ class BaseFragmenter
13
+ attr_accessor :virtual_path, :formats, :lookup_context, :view_cache_dependencies
14
+ def initialize
15
+ @virtual_path = ''
16
+ @formats = [:html]
17
+ @view_cache_dependencies = []
18
+ end
19
+
20
+ private
21
+ # Give a base implementation of fragment_for so super calls from
22
+ # ChildFragmenter have a method to delegate to.
23
+ def fragment_for(key,opts=nil,&blk)
24
+ key
25
+ end
26
+ end
27
+
28
+ class ChildFragmenter < BaseFragmenter
29
+ include CacheDigests::FragmentHelper
30
+ end
31
+
12
32
  class FragmentHelperTest < MiniTest::Unit::TestCase
13
33
  def setup
14
34
  # would love some mocha here
15
35
  @old_digest = CacheDigests::TemplateDigestor.method(:digest)
16
- CacheDigests::TemplateDigestor.send(:define_singleton_method, :digest) do |p,f,lc|
36
+ CacheDigests::TemplateDigestor.send(:define_singleton_method, :digest) do |p,f,lc,o={}|
17
37
  "digest"
18
38
  end
19
39
  end
@@ -23,31 +43,52 @@ class FragmentHelperTest < MiniTest::Unit::TestCase
23
43
  end
24
44
 
25
45
  def test_passes_correct_parameters_to_digestor
26
- CacheDigests::TemplateDigestor.send(:define_singleton_method, :digest) do |p,f,lc|
46
+ CacheDigests::TemplateDigestor.send(:define_singleton_method, :digest) do |p,f,lc,o={}|
27
47
  extend MiniTest::Assertions
28
48
  assert_equal 'path', p
29
49
  assert_equal :formats, f
30
50
  assert_equal 'lookup context', lc
51
+ assert_equal({dependencies:["foo"]}, o)
31
52
  end
32
53
  fragmenter.virtual_path = 'path'
33
54
  fragmenter.formats = ['formats']
34
55
  fragmenter.lookup_context = 'lookup context'
35
56
 
36
- fragmenter.fragment_name_with_digest("key")
57
+ fragmenter.fragment_name_with_digest("key", ["foo"])
37
58
  end
38
59
 
39
60
  def test_appends_the_key_with_digest
40
- key_with_digest = fragmenter.fragment_name_with_digest("key")
61
+ key_with_digest = fragmenter.fragment_name_with_digest("key", [])
41
62
  assert_equal ['key', 'digest'], key_with_digest
42
63
  end
43
64
 
44
65
  def test_appends_the_array_key_with_digest
45
- key_with_digest = fragmenter.fragment_name_with_digest(["key1", "key2"])
66
+ key_with_digest = fragmenter.fragment_name_with_digest(["key1", "key2"], [])
46
67
  assert_equal ['key1', 'key2', 'digest'], key_with_digest
47
68
  end
48
69
 
70
+ def test_digest_skipped_when_opted_out
71
+ key = child_fragmenter.send(:fragment_for, 'key1', {skip_digest: true})
72
+ # 'key1' key derived from super call to BaseFragmenter above
73
+ assert_equal 'key1', key
74
+ end
75
+
76
+ def test_digest_skipped_when_v_number_keyed
77
+ key = child_fragmenter.send(:fragment_for, ['v1','key1'])
78
+ assert_equal ['v1','key1'], key
79
+ end
80
+
81
+ def test_digest_not_skipped_otherwise
82
+ key_with_digest = child_fragmenter.send(:fragment_for, 'key1', {skip_digest: false})
83
+ assert_equal ['key1','digest'], key_with_digest
84
+ end
85
+
49
86
  private
50
87
  def fragmenter
51
88
  @fragmenter ||= Fragmenter.new
52
89
  end
90
+
91
+ def child_fragmenter
92
+ @child_fragmenter ||= ChildFragmenter.new
93
+ end
53
94
  end
@@ -7,10 +7,11 @@ module ActionView
7
7
  end
8
8
 
9
9
  class FixtureTemplate
10
- attr_reader :source
10
+ attr_reader :source, :handler
11
11
 
12
- def initialize(template_path)
12
+ def initialize(template_path, handler = :erb)
13
13
  @source = File.read(template_path)
14
+ @handler = handler
14
15
  rescue Errno::ENOENT
15
16
  raise ActionView::MissingTemplate
16
17
  end
@@ -28,11 +29,14 @@ end
28
29
  class TemplateDigestorTest < MiniTest::Unit::TestCase
29
30
  def setup
30
31
  FileUtils.cp_r FixtureFinder::FIXTURES_DIR, FixtureFinder::TMP_DIR
32
+ CacheDigests::DependencyTracker.register_tracker :erb, CacheDigests::DependencyTracker::ERBTracker
31
33
  end
32
34
 
33
35
  def teardown
34
36
  FileUtils.rm_r FixtureFinder::TMP_DIR
35
37
  CacheDigests::TemplateDigestor.cache.clear
38
+ CacheDigests::TemplateDigestor.cache_prefix = nil
39
+ CacheDigests::DependencyTracker.remove_tracker :erb
36
40
  end
37
41
 
38
42
  def test_top_level_change_reflected
@@ -47,6 +51,26 @@ class TemplateDigestorTest < MiniTest::Unit::TestCase
47
51
  end
48
52
  end
49
53
 
54
+ def test_explicit_dependency_in_multiline_erb_tag
55
+ assert_digest_difference("messages/show") do
56
+ change_template("messages/_form")
57
+ end
58
+ end
59
+
60
+ def test_explicit_dependency_via_options
61
+ plain = digest("messages/show")
62
+ fridge = digest("messages/show", dependencies: ["fridge"])
63
+ phone = digest("messages/show", dependencies: ["phone"])
64
+ fridge_phone = digest("messages/show", dependencies: ["fridge", "phone"])
65
+
66
+ assert plain != fridge
67
+ assert plain != phone
68
+ assert plain != fridge_phone
69
+ assert fridge != phone
70
+ assert fridge != fridge_phone
71
+ assert phone != fridge_phone
72
+ end
73
+
50
74
  def test_second_level_dependency
51
75
  assert_digest_difference("messages/show") do
52
76
  change_template("comments/_comments")
@@ -64,6 +88,12 @@ class TemplateDigestorTest < MiniTest::Unit::TestCase
64
88
  change_template("comments/_comment")
65
89
  end
66
90
  end
91
+
92
+ def test_directory_depth_dependency
93
+ assert_digest_difference("level/below/index") do
94
+ change_template("level/below/_header")
95
+ end
96
+ end
67
97
 
68
98
  def test_logging_of_missing_template
69
99
  assert_logged "Couldn't find template for digesting: messages/something_missing.html" do
@@ -77,6 +107,10 @@ class TemplateDigestorTest < MiniTest::Unit::TestCase
77
107
  end
78
108
  end
79
109
 
110
+ def test_recursion_in_renders
111
+ assert digest("level/recursion")
112
+ end
113
+
80
114
  def test_dont_generate_a_digest_for_missing_templates
81
115
  assert_equal '', digest("nothing/there")
82
116
  end
@@ -121,8 +155,8 @@ class TemplateDigestorTest < MiniTest::Unit::TestCase
121
155
  CacheDigests::TemplateDigestor.cache.clear
122
156
  end
123
157
 
124
- def digest(template_name)
125
- CacheDigests::TemplateDigestor.digest(template_name, :html, FixtureFinder.new)
158
+ def digest(template_name, options={})
159
+ CacheDigests::TemplateDigestor.digest(template_name, :html, FixtureFinder.new, options)
126
160
  end
127
161
 
128
162
  def change_template(template_name)
metadata CHANGED
@@ -1,62 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cache_digests
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
5
- prerelease:
4
+ version: 0.3.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - David Heinemeier Hansson
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-10-04 00:00:00.000000000 Z
11
+ date: 2013-04-16 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: actionpack
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - '>='
20
18
  - !ruby/object:Gem::Version
21
19
  version: '3.2'
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - '>='
28
25
  - !ruby/object:Gem::Version
29
26
  version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thread_safe
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
30
41
  - !ruby/object:Gem::Dependency
31
42
  name: rake
32
43
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
44
  requirements:
35
- - - ! '>='
45
+ - - '>='
36
46
  - !ruby/object:Gem::Version
37
47
  version: '0'
38
48
  type: :development
39
49
  prerelease: false
40
50
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
51
  requirements:
43
- - - ! '>='
52
+ - - '>='
44
53
  - !ruby/object:Gem::Version
45
54
  version: '0'
46
55
  - !ruby/object:Gem::Dependency
47
56
  name: minitest
48
57
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
58
  requirements:
51
- - - ! '>='
59
+ - - '>='
52
60
  - !ruby/object:Gem::Version
53
61
  version: '0'
54
62
  type: :development
55
63
  prerelease: false
56
64
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
65
  requirements:
59
- - - ! '>='
66
+ - - '>='
60
67
  - !ruby/object:Gem::Version
61
68
  version: '0'
62
69
  description:
@@ -68,17 +75,25 @@ files:
68
75
  - ./cache_digests.gemspec
69
76
  - ./Gemfile
70
77
  - ./Gemfile.lock
78
+ - ./lib/cache_digests/dependency_tracker.rb
71
79
  - ./lib/cache_digests/engine.rb
72
80
  - ./lib/cache_digests/fragment_helper.rb
73
81
  - ./lib/cache_digests/template_digestor.rb
82
+ - ./lib/cache_digests/view_cache_dependency.rb
74
83
  - ./lib/cache_digests.rb
75
84
  - ./lib/tasks/dependencies.rake
76
85
  - ./MIT-LICENSE
77
86
  - ./Rakefile
78
87
  - ./README.md
88
+ - ./test/dependency_tracker_test.rb
79
89
  - ./test/fixtures/comments/_comment.html.erb
80
90
  - ./test/fixtures/comments/_comments.html.erb
81
91
  - ./test/fixtures/events/_event.html.erb
92
+ - ./test/fixtures/level/_recursion.html.erb
93
+ - ./test/fixtures/level/below/_header.html.erb
94
+ - ./test/fixtures/level/below/index.html.erb
95
+ - ./test/fixtures/level/recursion.html.erb
96
+ - ./test/fixtures/messages/_form.html.erb
82
97
  - ./test/fixtures/messages/_header.html.erb
83
98
  - ./test/fixtures/messages/_message.html.erb
84
99
  - ./test/fixtures/messages/actions/_move.html.erb
@@ -89,26 +104,25 @@ files:
89
104
  - ./test/template_digestor_test.rb
90
105
  homepage:
91
106
  licenses: []
107
+ metadata: {}
92
108
  post_install_message:
93
109
  rdoc_options: []
94
110
  require_paths:
95
111
  - lib
96
112
  required_ruby_version: !ruby/object:Gem::Requirement
97
- none: false
98
113
  requirements:
99
- - - ! '>='
114
+ - - '>='
100
115
  - !ruby/object:Gem::Version
101
116
  version: '1.9'
102
117
  required_rubygems_version: !ruby/object:Gem::Requirement
103
- none: false
104
118
  requirements:
105
- - - ! '>='
119
+ - - '>='
106
120
  - !ruby/object:Gem::Version
107
121
  version: '0'
108
122
  requirements: []
109
123
  rubyforge_project:
110
- rubygems_version: 1.8.23
124
+ rubygems_version: 2.0.0
111
125
  signing_key:
112
- specification_version: 3
126
+ specification_version: 4
113
127
  summary: Nested fragment caches with (even) less situps
114
128
  test_files: []