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 +7 -0
- data/Gemfile.lock +22 -17
- data/README.md +5 -1
- data/cache_digests.gemspec +2 -1
- data/lib/cache_digests.rb +1 -0
- data/lib/cache_digests/dependency_tracker.rb +89 -0
- data/lib/cache_digests/engine.rb +6 -1
- data/lib/cache_digests/fragment_helper.rb +16 -5
- data/lib/cache_digests/template_digestor.rb +15 -46
- data/lib/cache_digests/view_cache_dependency.rb +22 -0
- data/lib/tasks/dependencies.rake +3 -1
- data/test/dependency_tracker_test.rb +49 -0
- data/test/fixtures/level/_recursion.html.erb +1 -0
- data/test/fixtures/level/below/_header.html.erb +0 -0
- data/test/fixtures/level/below/index.html.erb +1 -0
- data/test/fixtures/level/recursion.html.erb +1 -0
- data/test/fixtures/messages/_form.html.erb +0 -0
- data/test/fixtures/messages/show.html.erb +5 -1
- data/test/fragment_helper_test.rb +46 -5
- data/test/template_digestor_test.rb +38 -4
- metadata +35 -21
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.
|
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.
|
11
|
-
activemodel (= 3.2.
|
12
|
-
activesupport (= 3.2.
|
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.
|
16
|
-
rack (~> 1.4.
|
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
|
20
|
-
activemodel (3.2.
|
21
|
-
activesupport (= 3.2.
|
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.
|
24
|
+
activesupport (3.2.12)
|
24
25
|
i18n (~> 0.6)
|
25
26
|
multi_json (~> 1.0)
|
26
|
-
|
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.
|
30
|
-
journey (1.0.
|
31
|
+
i18n (0.6.1)
|
32
|
+
journey (1.0.4)
|
31
33
|
minitest (2.12.1)
|
32
|
-
multi_json (1.
|
33
|
-
rack (1.4.
|
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.
|
38
|
+
rack-test (0.6.2)
|
37
39
|
rack (>= 1.0)
|
38
40
|
rake (0.9.2.2)
|
39
|
-
sprockets (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
|
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.
|
data/cache_digests.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'cache_digests'
|
3
|
-
s.version = '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
@@ -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
|
data/lib/cache_digests/engine.rb
CHANGED
@@ -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' --
|
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
|
-
|
11
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
106
|
-
|
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
|
data/lib/tasks/dependencies.rake
CHANGED
@@ -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
|
@@ -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.
|
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:
|
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:
|
124
|
+
rubygems_version: 2.0.0
|
111
125
|
signing_key:
|
112
|
-
specification_version:
|
126
|
+
specification_version: 4
|
113
127
|
summary: Nested fragment caches with (even) less situps
|
114
128
|
test_files: []
|