merb_piece_cache 0.1.0
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.
- data/MIT-LICENSE +20 -0
- data/README +142 -0
- data/Rakefile +87 -0
- data/TODO +2 -0
- data/lib/merb_piece_cache.rb +47 -0
- data/lib/merb_piece_cache/file_cache_store.rb +77 -0
- data/lib/merb_piece_cache/helpers.rb +45 -0
- metadata +72 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright(c) 2009 kuwata-lab.com all rights reserverd.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
======
|
2
|
+
README
|
3
|
+
======
|
4
|
+
|
5
|
+
$Release: 0.1.0 $
|
6
|
+
|
7
|
+
|
8
|
+
About
|
9
|
+
-----
|
10
|
+
|
11
|
+
merb_piece_cache is a Merb plug-in to cache html fragments.
|
12
|
+
You can eliminate database access with this plugin.
|
13
|
+
|
14
|
+
Currently only file-base cache store is provided.
|
15
|
+
(Memory-base cache store may be provided in future.)
|
16
|
+
|
17
|
+
|
18
|
+
Install
|
19
|
+
-------
|
20
|
+
|
21
|
+
### install gem
|
22
|
+
$ sudo gem install merb_helpers_monkey
|
23
|
+
## add 'dependency "merb_piece_cache"'
|
24
|
+
$ vi config/dependencies.rb
|
25
|
+
|
26
|
+
This plugin requires 'called_from' library.
|
27
|
+
If you install this plugin manually, don't forget to install 'called_from'.
|
28
|
+
|
29
|
+
|
30
|
+
Example
|
31
|
+
-------
|
32
|
+
|
33
|
+
* [controller] delay database access by if_not_cached() helper.
|
34
|
+
* [template] specify cache area by cache_with() helper.
|
35
|
+
|
36
|
+
|
37
|
+
controller:
|
38
|
+
|
39
|
+
def index
|
40
|
+
## @user is necessary every time
|
41
|
+
@user = User.current()
|
42
|
+
## @members is necessary only when cache is refreshed
|
43
|
+
## (block parameter is called only when cache is expired or not found)
|
44
|
+
if_not_cached {|cache_key|
|
45
|
+
@members = Members.all(:order=>[:created_at])
|
46
|
+
}
|
47
|
+
render
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
template:
|
52
|
+
|
53
|
+
<!-- rendered every time (because never cached) -->
|
54
|
+
<% if @user %>
|
55
|
+
Hello <%=h @user %>!
|
56
|
+
<% else %>
|
57
|
+
<a href="/login">Login</a>
|
58
|
+
<% end %>
|
59
|
+
|
60
|
+
<!-- rendered only when cache is expired or not found -->
|
61
|
+
<% lifetime = 5*60 # sec, default is nil(=infinity) %>
|
62
|
+
<% cache_with("members/index", lifetime) do %>
|
63
|
+
<ul>
|
64
|
+
<% for member in @members %>
|
65
|
+
<li><%=h @member.name %></li>
|
66
|
+
<% end %>
|
67
|
+
<ul>
|
68
|
+
<% end %>
|
69
|
+
|
70
|
+
|
71
|
+
Reference
|
72
|
+
---------
|
73
|
+
|
74
|
+
Merb_piece_cache plugin provides the following helpers.
|
75
|
+
|
76
|
+
* if_not_cached() {|cache_key| ... }
|
77
|
+
Register block as callback object which is called when fragment
|
78
|
+
cache is re-generated (In other words, block parameter is called
|
79
|
+
only when cache is re-generated).
|
80
|
+
Block paramter can receive cache_key as 1st argument so that
|
81
|
+
you can switch data processing according to cache_key.
|
82
|
+
|
83
|
+
* piece_cache(cache_key, lifetime=nil) { ... }
|
84
|
+
When cache is found and not expired, then add cache data into view.
|
85
|
+
Otherwise, call block to re-generate cache data and cache it.
|
86
|
+
(Notice that block parameter is called only when cache is re-generated.)
|
87
|
+
It is able to specify cache lifetime. For example, if you specify
|
88
|
+
lifetime=5*60, cache data will be expired after 5 minutes.
|
89
|
+
|
90
|
+
* cache_with(cache_key, lifetime=nil) { ... }
|
91
|
+
Alias of piece_cache().
|
92
|
+
You can re-define this helper to extend cache behaviour.
|
93
|
+
|
94
|
+
|
95
|
+
Configuration
|
96
|
+
-------------
|
97
|
+
|
98
|
+
You can configure merb_piece_cache plugin in your environment file.
|
99
|
+
(config/environments/{production,development,test}.rb).
|
100
|
+
For example, add the followings into your 'development.rb':
|
101
|
+
|
102
|
+
Merb::Plugins.config[:merb_piece_cache] = {
|
103
|
+
## show cached area in template (convenient for debug!)
|
104
|
+
:show_area => true,
|
105
|
+
## directory to place cache files (default: "MERB_ROOT/tmp/cache")
|
106
|
+
:root_dir => "/var/tmp",
|
107
|
+
}
|
108
|
+
|
109
|
+
|
110
|
+
Tips
|
111
|
+
----
|
112
|
+
|
113
|
+
* You can prepare different data in if_not_exist() according to cache_key.
|
114
|
+
|
115
|
+
def index
|
116
|
+
if_not_cached {|cache_key|
|
117
|
+
case cache_key
|
118
|
+
when /^post\/(\d+)$/
|
119
|
+
@post = BlogPost.get($1)
|
120
|
+
when /^post\/(\d+)/comments$/
|
121
|
+
@comments = BlogComment.all(:post_id=>$1)
|
122
|
+
end
|
123
|
+
}
|
124
|
+
render
|
125
|
+
end
|
126
|
+
|
127
|
+
* piece_cache() (and also cache_with()) checks template timestamp.
|
128
|
+
If cache data is older than template timestamp, then cache data will be
|
129
|
+
re-generated.
|
130
|
+
|
131
|
+
|
132
|
+
Author
|
133
|
+
------
|
134
|
+
|
135
|
+
makoto kuwata <kwa(at)kuwata-lab.com>
|
136
|
+
copyright(c) 2009 kuwata-lab.com all rights reserved.
|
137
|
+
|
138
|
+
|
139
|
+
License
|
140
|
+
-------
|
141
|
+
|
142
|
+
MIT License
|
data/Rakefile
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
|
4
|
+
require 'merb-core'
|
5
|
+
require 'merb-core/tasks/merb'
|
6
|
+
|
7
|
+
GEM_NAME = "merb_piece_cache"
|
8
|
+
GEM_VERSION = "0.1.0"
|
9
|
+
AUTHOR = "makoto kuwata"
|
10
|
+
EMAIL = "kwa.at.kuwata-lab.com"
|
11
|
+
HOMEPAGE = "http://github.com/kwatch/merb_piece_cache/"
|
12
|
+
SUMMARY = "Merb plugin to cache html fragment"
|
13
|
+
|
14
|
+
@copyright = 'copyright(c) 2009 kuwata-lab.com all rights reserved.'
|
15
|
+
@license = 'MIT License'
|
16
|
+
|
17
|
+
spec = Gem::Specification.new do |s|
|
18
|
+
s.rubyforge_project = 'merb_piece_cache'
|
19
|
+
s.name = GEM_NAME
|
20
|
+
s.version = GEM_VERSION
|
21
|
+
s.platform = Gem::Platform::RUBY
|
22
|
+
s.has_rdoc = false
|
23
|
+
s.extra_rdoc_files = ["README", "MIT-LICENSE", 'TODO']
|
24
|
+
s.summary = SUMMARY
|
25
|
+
s.description = s.summary
|
26
|
+
s.author = AUTHOR
|
27
|
+
s.email = EMAIL
|
28
|
+
s.homepage = HOMEPAGE
|
29
|
+
#s.add_dependency('merb', '>= 1.0.12')
|
30
|
+
s.add_dependency('called_from', ['>= 0.1.0'])
|
31
|
+
s.require_path = 'lib'
|
32
|
+
#s.files = %w(MIT-LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
|
33
|
+
s.files = %w(MIT-LICENSE README Rakefile TODO) + Dir.glob("{lib}/**/*")
|
34
|
+
end
|
35
|
+
|
36
|
+
#Rake::GemPackageTask.new(spec) do |pkg|
|
37
|
+
# pkg.gem_spec = spec
|
38
|
+
#end
|
39
|
+
|
40
|
+
desc "install the plugin as a gem"
|
41
|
+
task :install do
|
42
|
+
Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "Uninstall the gem"
|
46
|
+
task :uninstall do
|
47
|
+
Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "Create a gemspec file"
|
51
|
+
task :gemspec do
|
52
|
+
File.open("#{GEM_NAME}.gemspec", "w") do |file|
|
53
|
+
file.puts spec.to_ruby
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "create gem package"
|
58
|
+
task :package => [:gemspec] do
|
59
|
+
dir = "build"
|
60
|
+
rm_rf dir
|
61
|
+
mkdir dir
|
62
|
+
#cp_r spec.files, dir
|
63
|
+
sh "tar cf - #{spec.files.join(' ')} | (cd #{dir}; tar xf -)"
|
64
|
+
Dir.glob("#{dir}/**/*").each do |fpath|
|
65
|
+
next unless File.file?(fpath)
|
66
|
+
next if File.basename(fpath) == 'Rakefile'
|
67
|
+
s = File.open(fpath, 'rb') {|f| f.read }
|
68
|
+
s.gsub! /\$Release\$/, GEM_VERSION
|
69
|
+
s.gsub! /\$Release:.*?\$/, "$Release: #{GEM_VERSION} $"
|
70
|
+
s.gsub! /\$Copyright\$/, @copyright
|
71
|
+
s.gsub! /\$License\$/, @license
|
72
|
+
File.open(fpath, 'wb') {|f| f.write s }
|
73
|
+
end
|
74
|
+
cp "#{GEM_NAME}.gemspec", dir
|
75
|
+
chdir dir do
|
76
|
+
sh "gem build #{GEM_NAME}.gemspec"
|
77
|
+
mv "#{GEM_NAME}-#{GEM_VERSION}.gem", ".."
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "install gem package"
|
82
|
+
task :inst => [:package] do
|
83
|
+
sh "gem install #{GEM_NAME}-#{GEM_VERSION}.gem"
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
spec
|
@@ -0,0 +1,47 @@
|
|
1
|
+
###
|
2
|
+
### $Release: 0.1.0 $
|
3
|
+
### copyright(c) 2009 kuwata-lab.com all rights reserved.
|
4
|
+
### MIT License
|
5
|
+
###
|
6
|
+
|
7
|
+
|
8
|
+
# make sure we're running inside Merb
|
9
|
+
if defined?(Merb::Plugins)
|
10
|
+
|
11
|
+
# Merb gives you a Merb::Plugins.config hash...feel free to put your stuff in your piece of it
|
12
|
+
default_conf = {
|
13
|
+
:show_area => false,
|
14
|
+
:root_dir => nil,
|
15
|
+
:cache_store => nil,
|
16
|
+
}
|
17
|
+
curr_conf = Merb::Plugins.config[:merb_piece_cache]
|
18
|
+
Merb::Plugins.config[:merb_piece_cache] = default_conf.merge(curr_conf || {})
|
19
|
+
|
20
|
+
Merb::BootLoader.before_app_loads do
|
21
|
+
# require code that must be loaded before the application
|
22
|
+
require 'merb-cache/cache'
|
23
|
+
require 'merb_piece_cache/file_cache_store'
|
24
|
+
require 'merb_piece_cache/helpers'
|
25
|
+
begin
|
26
|
+
require 'called_from.so'
|
27
|
+
rescue LoadError
|
28
|
+
require 'called_from.rb'
|
29
|
+
Merb.logger.info "[piece_cache] it is strongly recommended to install 'called_from.so' instead of 'called_from.rb' for good performance"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Merb::BootLoader.after_app_loads do
|
34
|
+
# code that can be required after the application loads
|
35
|
+
cache_store = Merb::Plugins.config[:merb_piece_cache][:cache_store]
|
36
|
+
if cache_store
|
37
|
+
Merb.logger.debug "[piece_cache] using #{cache_store.class}"
|
38
|
+
else
|
39
|
+
root_dir = Merb::Plugins.config[:merb_piece_cache][:root_dir]
|
40
|
+
cache_store = PieceCache::FileCacheStore.new(:dir=>root_dir)
|
41
|
+
Merb::Plugins.config[:merb_piece_cache][:cache_store] = cache_store
|
42
|
+
Merb.logger.debug "[piece_cache] using #{cache_store.class} (root_dir=#{cache_store.dir})"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#Merb::Plugins.add_rakefiles "merb_piece_cache/merbtasks"
|
47
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
###
|
2
|
+
### $Release: 0.1.0 $
|
3
|
+
### copyright(c) 2009 kuwata-lab.com all rights reserved.
|
4
|
+
### MIT License
|
5
|
+
###
|
6
|
+
|
7
|
+
|
8
|
+
module PieceCache
|
9
|
+
|
10
|
+
|
11
|
+
class FileCacheStore < Merb::Cache::FileStore
|
12
|
+
|
13
|
+
def pathify(key, parameters = {})
|
14
|
+
filename = super
|
15
|
+
ctype = parameters[:content_type]
|
16
|
+
ctype ? "#{filename}.#{ctype}" : filename
|
17
|
+
end
|
18
|
+
|
19
|
+
def read(key, parameters = {})
|
20
|
+
## if cache file is not found, return nil
|
21
|
+
fpath = pathify(key) # or pathify(key, parameters)
|
22
|
+
return nil unless File.exist?(fpath)
|
23
|
+
## if cache file is created before timestamp, return nil
|
24
|
+
timestamp = parameters[:timestamp]
|
25
|
+
return nil if timestamp && File.ctime(fpath) < timestamp
|
26
|
+
## if cache file is expired, return nil
|
27
|
+
return nil if File.mtime(fpath) <= Time.now
|
28
|
+
## return cache file content
|
29
|
+
return read_file(fpath)
|
30
|
+
end
|
31
|
+
|
32
|
+
MAX_TIMESTAMP = Time.at(0x7fffffff) # Tue Jan 19 03:14:07 UTC 2038
|
33
|
+
|
34
|
+
def write(key, data = nil, parameters = {}, conditions = {})
|
35
|
+
## create directory for cache
|
36
|
+
fpath = pathify(key) # or pathify(key, parameters)
|
37
|
+
dir = File.dirname(fpath)
|
38
|
+
FileUtils.mkdir_p(dir) unless File.exist?(dir)
|
39
|
+
## write data into file
|
40
|
+
File.unlink(fpath) if File.exist?(fpath)
|
41
|
+
write_file(fpath, data)
|
42
|
+
## set mtime (which is regarded as cache expired timestamp)
|
43
|
+
lifetime = parameters[:lifetime]
|
44
|
+
timestamp = lifetime && lifetime > 0 ? Time.now + lifetime : MAX_TIMESTAMP
|
45
|
+
File.utime(timestamp, timestamp, fpath)
|
46
|
+
## return data
|
47
|
+
return data
|
48
|
+
end
|
49
|
+
|
50
|
+
if RUBY_PLATFORM =~ /mswin(?!ce)|mingw|cygwin|bccwin/i
|
51
|
+
def read_file(path)
|
52
|
+
File.open(path, 'rb') {|f| f.read() }
|
53
|
+
end
|
54
|
+
else
|
55
|
+
def read_file(path)
|
56
|
+
File.read(path)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_file(path, data)
|
61
|
+
## write into tempoary file and rename it to path (in order not to call flock)
|
62
|
+
tmppath = "#{path}#{rand().to_s[1,6]}"
|
63
|
+
begin
|
64
|
+
File.open(tmppath, 'wb') {|f| f.write(data) }
|
65
|
+
File.rename(tmppath, path)
|
66
|
+
rescue => ex
|
67
|
+
File.unlink tmppath if File.exist?(tmppath)
|
68
|
+
File.unlink path if File.exist?(path)
|
69
|
+
raise ex
|
70
|
+
end
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
###
|
2
|
+
### $Release: 0.1.0 $
|
3
|
+
### copyright(c) 2009 kuwata-lab.com all rights reserved.
|
4
|
+
### MIT License
|
5
|
+
###
|
6
|
+
|
7
|
+
|
8
|
+
module Merb
|
9
|
+
module GlobalHelpers
|
10
|
+
|
11
|
+
def if_not_cached(&block)
|
12
|
+
@_callback = block # or @_deffered?
|
13
|
+
end
|
14
|
+
|
15
|
+
def piece_cache(cache_key, lifetime=nil)
|
16
|
+
#caller_str = caller(1)[0] # caller() is too high cost to use in production mode
|
17
|
+
#template_path = caller_str =~ /:\d+:in `/ && $` #`
|
18
|
+
template_path, linenum, funcname = called_from()
|
19
|
+
ts = File.exist?(template_path) && File.mtime(template_path)
|
20
|
+
cache_store = Merb::Plugins.config[:merb_piece_cache][:cache_store]
|
21
|
+
piece = cache_store.read(cache_key, :timestamp=>ts, :content_type=>self.content_type)
|
22
|
+
show_area = Merb::Plugins.config[:merb_piece_cache][:show_area]
|
23
|
+
if piece
|
24
|
+
Merb.logger.debug "[piece_cache] #{cache_key.inspect}: found."
|
25
|
+
@_erb_buf << "<div style=\"font-weight:bold\">----- cached key=#{cache_key.inspect} -----</div>\n" if show_area
|
26
|
+
@_erb_buf << piece
|
27
|
+
@_erb_buf << "<div style=\"font-weight:bold\">----- /cached -----</div>\n" if show_area
|
28
|
+
else
|
29
|
+
Merb.logger.debug "[piece_cache] #{cache_key.inspect}: expired or not found."
|
30
|
+
values = @_callback && @_callback.call(cache_key) || {}
|
31
|
+
#values.each_pair {|k, v| self.instance_variable_set("@#{k}", v) } if values.is_a?(Hash)
|
32
|
+
@_erb_buf << "<div style=\"font-weight:bold;color:red\">----- rendered key=#{cache_key.inspect} -----</div>\n" if show_area
|
33
|
+
pos = @_erb_buf.length
|
34
|
+
yield(values)
|
35
|
+
piece = @_erb_buf[pos..-1]
|
36
|
+
@_erb_buf << "<div style=\"font-weight:bold;color:red\">----- /rendered -----</div>\n" if show_area
|
37
|
+
cache_store.write(cache_key, piece, :lifetime=>lifetime, :content_type=>self.content_type)
|
38
|
+
end
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
alias cache_with piece_cache
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: merb_piece_cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- makoto kuwata
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-30 00:00:00 +09:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: called_from
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.1.0
|
24
|
+
version:
|
25
|
+
description: Merb plugin to cache html fragment
|
26
|
+
email: kwa.at.kuwata-lab.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README
|
33
|
+
- MIT-LICENSE
|
34
|
+
- TODO
|
35
|
+
files:
|
36
|
+
- MIT-LICENSE
|
37
|
+
- README
|
38
|
+
- Rakefile
|
39
|
+
- TODO
|
40
|
+
- lib/merb_piece_cache/file_cache_store.rb
|
41
|
+
- lib/merb_piece_cache/helpers.rb
|
42
|
+
- lib/merb_piece_cache.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://github.com/kwatch/merb_piece_cache/
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project: merb_piece_cache
|
67
|
+
rubygems_version: 1.3.5
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Merb plugin to cache html fragment
|
71
|
+
test_files: []
|
72
|
+
|