cache_debugging 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.travis.yml +3 -0
- data/Gemfile +29 -0
- data/LICENSE +20 -0
- data/README.md +104 -0
- data/Rakefile +32 -0
- data/cache_debugging.gemspec +20 -0
- data/lib/cache_debugging.rb +18 -0
- data/lib/cache_debugging/cache_blocks.rb +44 -0
- data/lib/cache_debugging/digestor.rb +3 -0
- data/lib/cache_debugging/railtie.rb +19 -0
- data/lib/cache_debugging/strict_dependencies.rb +47 -0
- data/lib/cache_debugging/utils.rb +40 -0
- data/lib/cache_debugging/version.rb +3 -0
- data/lib/cache_debugging/view_sampling.rb +80 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +10 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/controllers/workers_controller.rb +7 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.keep +0 -0
- data/test/dummy/app/models/.keep +0 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/models/contract.rb +5 -0
- data/test/dummy/app/models/electrician.rb +3 -0
- data/test/dummy/app/models/gardener.rb +3 -0
- data/test/dummy/app/models/plumber.rb +3 -0
- data/test/dummy/app/models/tweet.rb +6 -0
- data/test/dummy/app/models/worker.rb +7 -0
- data/test/dummy/app/views/electricians/_electrician.html.erb +1 -0
- data/test/dummy/app/views/gardeners/_gardener.html.erb +1 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/plumbers/_plumber.html.erb +1 -0
- data/test/dummy/app/views/workers/_tweets.html.erb +5 -0
- data/test/dummy/app/views/workers/_worker.html.erb +12 -0
- data/test/dummy/app/views/workers/index.html.erb +17 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +27 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +29 -0
- data/test/dummy/config/environments/production.rb +80 -0
- data/test/dummy/config/environments/test.rb +36 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/caching.rb +32 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +16 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +3 -0
- data/test/dummy/db/migrate/20130910001826_create_workers.rb +36 -0
- data/test/dummy/db/schema.rb +55 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/public/404.html +58 -0
- data/test/dummy/public/422.html +58 -0
- data/test/dummy/public/500.html +57 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/test/fixtures/electricians.yml +2 -0
- data/test/dummy/test/fixtures/gardeners.yml +2 -0
- data/test/dummy/test/fixtures/plumbers.yml +2 -0
- data/test/dummy/test/fixtures/workers.yml +17 -0
- data/test/integration/cache_debugging_test.rb +81 -0
- data/test/test_helper.rb +17 -0
- metadata +146 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 966d977f9f2e3f02a1659ef160c7386742b92a16
|
4
|
+
data.tar.gz: 069c388fe175aaaae2491748a4e81a387ef7ec5c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ea249f02ce4f52639732bc59d1b53a4017c1cbba3c1c1c4c2e9fd788727d0bf7e1a7bd2635617390b2648ebb9d73e6eeeae255eef10e320ff3a8ef09dc0d8816
|
7
|
+
data.tar.gz: 01ef735d0ae31649da1f70aef82ca5de7c8d60d31d8f1df6d05d892da35b6cbace54fb02ebc03087d61a33e9bac99789874cb0f51bc4426728294a40be2ffaa7
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile ~/.gitignore_global
|
6
|
+
|
7
|
+
# Ignore bundler config
|
8
|
+
/.bundle
|
9
|
+
|
10
|
+
# Ignore the default SQLite database.
|
11
|
+
*.sqlite3
|
12
|
+
|
13
|
+
# Ignore all logfiles and tempfiles.
|
14
|
+
*.log
|
15
|
+
/tmp
|
16
|
+
|
17
|
+
*.swp
|
18
|
+
*.mwb.bak
|
19
|
+
*.mwb.beforefix
|
20
|
+
|
21
|
+
*.DS_Store
|
22
|
+
|
23
|
+
Gemfile.lock
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
# Declare your gem's dependencies in cache_debugging.gemspec.
|
4
|
+
# Bundler will treat runtime dependencies like base dependencies, and
|
5
|
+
# development dependencies will be added by default to the :development group.
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
rails_version = ENV["RAILS_VERSION"] || "default"
|
9
|
+
|
10
|
+
rails = case rails_version
|
11
|
+
when "default"
|
12
|
+
">= 3.2.0"
|
13
|
+
else
|
14
|
+
"~> #{rails_version}"
|
15
|
+
end
|
16
|
+
|
17
|
+
gem "rails", rails
|
18
|
+
|
19
|
+
if rails.match /3\.2\.*/
|
20
|
+
gem 'cache_digests', '~> 0.3.0'
|
21
|
+
end
|
22
|
+
|
23
|
+
# Declare any dependencies that are still in development here instead of in
|
24
|
+
# your gemspec. These might include edge Rails or gems from your path or
|
25
|
+
# Git. Remember to move these dependencies to your gemspec before releasing
|
26
|
+
# your gem to rubygems.org.
|
27
|
+
|
28
|
+
# To use debugger
|
29
|
+
# gem 'debugger'
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) {{year}} {{fullname}}
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# CacheDebugging [](https://travis-ci.org/chingor13/cache_debugging)
|
2
|
+
|
3
|
+
`cache_debugging` aims to detangle `cache_digests`'s template dependencies. It currently contains 2 different hooks to check for cache integrity.
|
4
|
+
|
5
|
+
## StrictDependencies
|
6
|
+
|
7
|
+
This module ensures that every `render` call within a cache block is called on a partial that is in the callers template dependencies.
|
8
|
+
|
9
|
+
### Why?
|
10
|
+
|
11
|
+
We may not have declared all of our explicit template dependencies.
|
12
|
+
|
13
|
+
Consider:
|
14
|
+
|
15
|
+
```
|
16
|
+
cache @workers do
|
17
|
+
render @workers
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
Seems reasonable, as it will assume `workers/worker` is the partial to be depended upon.
|
22
|
+
|
23
|
+
We could also declaring the template explicitly:
|
24
|
+
|
25
|
+
```
|
26
|
+
cache @workers do
|
27
|
+
render partial: 'workers/worker', collection: @workers
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
What if, however, `@workers` is a collection of duck-typed objects that behave like workers? We have to explicitly declare the dependencies:
|
32
|
+
|
33
|
+
```
|
34
|
+
<%# Template Dependency: plumbers/plumber %>
|
35
|
+
<%# Template Dependency: gardeners/gardener %>
|
36
|
+
…
|
37
|
+
<%# Template Dependency: electricians/electrician %>
|
38
|
+
cache @workers do
|
39
|
+
render @workers
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
How do we ensure that all the possible worker types are declared?
|
44
|
+
|
45
|
+
### Usage
|
46
|
+
|
47
|
+
In application.rb (or environment config):
|
48
|
+
|
49
|
+
```
|
50
|
+
config.cache_debugging.strict_dependencies = true
|
51
|
+
```
|
52
|
+
|
53
|
+
We keep track of each cache block with it's dependencies and trigger an ActiveSupport notification (`cache_debugging.cache_dependency_missing`) if we're rendering a partial not included any parent's template dependencies. You can handle this notification any way you want.
|
54
|
+
|
55
|
+
## View Sampling
|
56
|
+
|
57
|
+
This module ensures that you have declared all the variable dependencies for your cache block.
|
58
|
+
|
59
|
+
### Why?
|
60
|
+
|
61
|
+
We might be missing a cache variable and not know it.
|
62
|
+
|
63
|
+
Consider an view of a bug report ticket that can be assigned:
|
64
|
+
|
65
|
+
```
|
66
|
+
# tickets/index.html.erb
|
67
|
+
<% cache @tickets do %>
|
68
|
+
<%= render @tickets %>
|
69
|
+
<% end %>
|
70
|
+
|
71
|
+
# tickets/_ticket.html.erb
|
72
|
+
<% cache ticket do %>
|
73
|
+
<tr>
|
74
|
+
<td><%= ticket.id %></td>
|
75
|
+
<td><%= ticket.title %></td>
|
76
|
+
<td><%= ticket.assigned_to.name %></td>
|
77
|
+
</tr>
|
78
|
+
<% end %>
|
79
|
+
```
|
80
|
+
|
81
|
+
Let's say we decided to replace `assigned_to.name` with "me" if the ticket is assigned to me. So we update the ticket partial:
|
82
|
+
|
83
|
+
```
|
84
|
+
# tickets/_ticket.html.erb
|
85
|
+
<% cache(ticket, current_user) do %>
|
86
|
+
<tr>
|
87
|
+
<td><%= ticket.id %></td>
|
88
|
+
<td><%= ticket.title %></td>
|
89
|
+
<td><%= ticket.assigned_to == current_user ? "me" : ticket.assigned_to.name %></td>
|
90
|
+
</tr>
|
91
|
+
<% end %>
|
92
|
+
```
|
93
|
+
|
94
|
+
We've updated the cache block for the singular ticket, but forgotten to update the collection cache block. We won't be notified that we're rendering a different view than we expect.
|
95
|
+
|
96
|
+
### Usage
|
97
|
+
|
98
|
+
In application.rb (or environment config):
|
99
|
+
|
100
|
+
```
|
101
|
+
config.cache_debugging.view_sampling = 0.1
|
102
|
+
```
|
103
|
+
|
104
|
+
Every X% of cache hits (10% for the above example), we will re-render the cache block anyways and compare the results. If they don't match, we trigger and ActiveSupport notification (`cache_debugging.cache_mismatch`). You can handle this notification any way you want.
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'CacheDebugging'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
Bundler::GemHelper.install_tasks
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
|
24
|
+
Rake::TestTask.new(:test) do |t|
|
25
|
+
t.libs << 'lib'
|
26
|
+
t.libs << 'test'
|
27
|
+
t.pattern = 'test/**/*_test.rb'
|
28
|
+
t.verbose = false
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
task default: :test
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Provide a simple gemspec so you can easily use your enginex
|
2
|
+
# project in your rails apps through git.
|
3
|
+
require File.expand_path('../lib/cache_debugging/version', __FILE__)
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "cache_debugging"
|
6
|
+
s.version = CacheDebugging::VERSION
|
7
|
+
s.description = 'Verify cache key dependencies'
|
8
|
+
s.summary = 'Verify cache key dependencies via random sampling'
|
9
|
+
s.add_dependency "rails", ">= 3.2.0"
|
10
|
+
|
11
|
+
s.author = "Jeff Ching"
|
12
|
+
s.email = "ching.jeff@gmail.com"
|
13
|
+
s.homepage = "http://github.com/chingor13/cache_debugging"
|
14
|
+
s.license = "MIT"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = Dir.glob('test/*_test.rb')
|
18
|
+
|
19
|
+
s.add_development_dependency "sqlite3"
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rails'
|
2
|
+
if Rails::VERSION::MAJOR != 4
|
3
|
+
begin
|
4
|
+
require 'cache_digests'
|
5
|
+
rescue LoadError
|
6
|
+
raise "You must use the 'cache_digests' gem if you are running Rails 3"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module CacheDebugging
|
11
|
+
autoload :CacheBlocks, 'cache_debugging/cache_blocks'
|
12
|
+
autoload :StrictDependencies, 'cache_debugging/strict_dependencies'
|
13
|
+
autoload :ViewSampling, 'cache_debugging/view_sampling'
|
14
|
+
autoload :Digestor, 'cache_debugging/digestor'
|
15
|
+
autoload :Utils, 'cache_debugging/utils'
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'cache_debugging/railtie'
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module CacheDebugging
|
2
|
+
module CacheBlocks
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
alias_method_chain :cache, :blocks
|
7
|
+
end
|
8
|
+
|
9
|
+
# every time we start a cache block, we want to store the template and the block's dependencies
|
10
|
+
def cache_with_blocks(name = {}, options = nil, &block)
|
11
|
+
if current_template
|
12
|
+
dependencies = Digestor.new(
|
13
|
+
current_template,
|
14
|
+
lookup_context.rendered_format || :html, ApplicationController.new.lookup_context
|
15
|
+
).nested_dependencies
|
16
|
+
|
17
|
+
cache_blocks.push({
|
18
|
+
template: current_template,
|
19
|
+
dependencies: Utils.deep_flatten(dependencies)
|
20
|
+
})
|
21
|
+
ret = cache_without_blocks(name, options, &block)
|
22
|
+
cache_blocks.pop
|
23
|
+
ret
|
24
|
+
else
|
25
|
+
cache_without_blocks(name, options, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def cache_blocks
|
32
|
+
@cache_blocks ||= []
|
33
|
+
end
|
34
|
+
|
35
|
+
def current_template
|
36
|
+
@virtual_path
|
37
|
+
end
|
38
|
+
|
39
|
+
def cache_depth
|
40
|
+
cache_blocks.length
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CacheDebugging
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
config.cache_debugging = ActiveSupport::OrderedOptions.new(
|
4
|
+
# raise exceptions if templates aren't in the dependency tree
|
5
|
+
strict_dependencies: false,
|
6
|
+
|
7
|
+
# [0,1] - decimal (percent) of cache hits to check
|
8
|
+
view_sampling: 0
|
9
|
+
)
|
10
|
+
|
11
|
+
initializer "cache_debugging.setup", :before => 'cache_digests' do |app|
|
12
|
+
ActiveSupport.on_load(:action_view) do
|
13
|
+
include CacheDebugging::CacheBlocks
|
14
|
+
include CacheDebugging::StrictDependencies
|
15
|
+
include CacheDebugging::ViewSampling
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module CacheDebugging
|
2
|
+
module StrictDependencies
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def self.strict_dependencies_enabled?
|
6
|
+
!!Rails.application.config.cache_debugging.strict_dependencies
|
7
|
+
end
|
8
|
+
|
9
|
+
included do
|
10
|
+
alias_method_chain :render, :template_dependencies
|
11
|
+
end
|
12
|
+
|
13
|
+
# every time we render, we want to check if the partial is in the dependency list
|
14
|
+
def render_with_template_dependencies(*args, &block)
|
15
|
+
if CacheDebugging::StrictDependencies.strict_dependencies_enabled? && cache_blocks.length > 0
|
16
|
+
options = args.first
|
17
|
+
if options.is_a?(Hash)
|
18
|
+
if partial = options[:partial]
|
19
|
+
validate_partial!(partial)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
(options.respond_to?(:to_ary) ? options.to_ary : Array(options)).each do |object|
|
23
|
+
validate_partial!(Utils.object_partial_path(object))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
render_without_template_dependencies(*args, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def validate_partial!(partial)
|
33
|
+
unless valid_partial?(partial)
|
34
|
+
Utils.publish_notification("cache_debugging.cache_dependency_missing", {
|
35
|
+
partial: partial,
|
36
|
+
template: cache_blocks.last[:template],
|
37
|
+
dependencies: cache_blocks.last[:dependencies]
|
38
|
+
})
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid_partial?(partial)
|
43
|
+
cache_blocks.last[:dependencies].include?(partial)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Use this module to keep helper methods out of ActionView::Base scope
|
2
|
+
module CacheDebugging
|
3
|
+
module Utils
|
4
|
+
# recursively flatten complex array/hash objects
|
5
|
+
def self.deep_flatten(array_or_hash)
|
6
|
+
case array_or_hash
|
7
|
+
when Array
|
8
|
+
array_or_hash.map do |value|
|
9
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
10
|
+
deep_flatten(value)
|
11
|
+
else
|
12
|
+
value
|
13
|
+
end
|
14
|
+
end.flatten
|
15
|
+
when Hash
|
16
|
+
deep_flatten(array_or_hash.keys + array_or_hash.values)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# wrapper for ActiveSupport::Notification publish
|
21
|
+
def self.publish_notification(name, extra = {})
|
22
|
+
ActiveSupport::Notifications.publish(
|
23
|
+
name,
|
24
|
+
Time.now, # not a block, so fake the start time
|
25
|
+
Time.now, # not a block, so fake the finish time
|
26
|
+
SecureRandom.hex(10), # generate a unique id
|
27
|
+
extra
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.object_partial_path(object)
|
32
|
+
partial = begin
|
33
|
+
object.to_partial_path
|
34
|
+
rescue
|
35
|
+
object.class.model_name.partial_path
|
36
|
+
end
|
37
|
+
partial.split("/").tap{|parts| parts.last.gsub!(/^_?/, "_")}.join("/")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module CacheDebugging
|
2
|
+
module ViewSampling
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
mattr_accessor :force_sampling
|
6
|
+
|
7
|
+
def self.force_sampling?
|
8
|
+
!!self.force_sampling
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.view_sampling_rate
|
12
|
+
Rails.application.config.cache_debugging.view_sampling
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.view_sampling_enabled?
|
16
|
+
!!view_sampling_rate
|
17
|
+
end
|
18
|
+
|
19
|
+
# code taken from fragment_for
|
20
|
+
def self.render_block(view, &block)
|
21
|
+
# VIEW TODO: Make #capture usable outside of ERB
|
22
|
+
# This dance is needed because Builder can't use capture
|
23
|
+
output_buffer = view.output_buffer
|
24
|
+
pos = output_buffer.length
|
25
|
+
yield
|
26
|
+
output_safe = output_buffer.html_safe?
|
27
|
+
fragment = output_buffer.slice!(pos..-1)
|
28
|
+
if output_safe
|
29
|
+
view.output_buffer = output_buffer.class.new(output_buffer)
|
30
|
+
end
|
31
|
+
fragment
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.should_sample?(options)
|
35
|
+
return false unless view_sampling_enabled?
|
36
|
+
return true if force_sampling?
|
37
|
+
|
38
|
+
sample = (options || {}).fetch(:sample) { view_sampling_rate }.to_f
|
39
|
+
rand <= sample
|
40
|
+
end
|
41
|
+
|
42
|
+
included do
|
43
|
+
alias_method_chain :cache, :view_sampling
|
44
|
+
end
|
45
|
+
|
46
|
+
# clear forcing of view sampling if it's been initiated
|
47
|
+
def cache_with_view_sampling(name = {}, options = nil, &block)
|
48
|
+
cache_without_view_sampling(name, options, &block)
|
49
|
+
CacheDebugging::ViewSampling.force_sampling = false if cache_depth == 0
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# since there are no hooks on a cache hit, that also has access to the render block, we
|
55
|
+
# must override fragement_for here
|
56
|
+
def fragment_for(name = {}, options = nil, &block)
|
57
|
+
if fragment = controller.read_fragment(name, options)
|
58
|
+
return fragment unless CacheDebugging::ViewSampling.should_sample?(options)
|
59
|
+
CacheDebugging::ViewSampling.force_sampling = true
|
60
|
+
|
61
|
+
CacheDebugging::ViewSampling.render_block(self, &block).tap do |uncached|
|
62
|
+
Utils.publish_notification("cache_debugging.cache_mismatch", {
|
63
|
+
cached: fragment,
|
64
|
+
uncached: uncached,
|
65
|
+
template: current_template,
|
66
|
+
cache_key: name
|
67
|
+
}) unless uncached == fragment
|
68
|
+
end
|
69
|
+
else
|
70
|
+
fragment = CacheDebugging::ViewSampling.render_block(self, &block)
|
71
|
+
controller.write_fragment(name, fragment, options)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def current_template
|
76
|
+
@virtual_path
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|