CacheRocket improves server-side fragment caching efficiency in Rails. CacheRocket allows caching more generic html fragments and allows the contents of the cached fragments to be replaced with dynamic content.
Add the gem to your Gemfile:
gem 'cache_rocket'
Include the CacheRocket module so your views can use the cache_replace
and render_cached
methods.
Most likely you would put this in your ApplicationHelper
:
include CacheRocket
CacheRocket allows you to cache a fragment of html and replace inner html. You inject dynamic content into a cached fragment that contains a placeholder.
The simplest usage is inline with the cache_replace
method. It works
like Rails' cache
method, plus it replaces content that you do not want
to cache because it is impractical or inefficient.
For example:
.lots.of.htmls
= Time.now
.more.htmls
= user.name
- cache_replace("some-key", replace: { time: Time.now, name: user.name }) do
.lots.of.htmls
= cache_replace_key :time
.more.htmls
= cache_replace_key :name
Now the fragment is cached once with placeholders, and the time and user name are replaced when rendered.
Obviously, the above example is contrived and would not result in any performance benefit. When the block of html you are rendering is large, caching can help.
render_cached
has options to deal with partials and collections.
Use it as a replacement for Rails' render
, with replace
options.
For example:
= render 'outer'
.lots
.of
.htmls
= Time.now
Replace render
with render_cached
in file
, specify the content to replace, and cache outer
:
= render_cached 'outer', replace: { inner: Time.now }
- cache 'outer' do
.lots
.of
.htmls
= cache_replace_key :inner
Here is the same example with an inner partial:
= render 'outer'
.lots
.of
.htmls
= render 'inner'
= Time.now
Replace render
with render_cached
in file
,
specify the partial to replace in outer
, and cache outer
:
= render_cached 'outer', replace: 'inner'
- cache 'outer' do
.lots
.of
.htmls
= cache_replace_key :inner
= Time.now
render_cached
supports several styles of arguments:
render_cached 'outer', replace: 'inner'
render_cached 'outer', replace: ['inner', 'footer']
render_cached 'outer', replace: { key_name: a_method(object) }
render_cached 'outer' do
{ key_name: a_method(object) }
end
render_cached 'outer', collection: objects,
replace: { key_name: -> (object) { a_method(object) } }
render_cached 'outer', collection: objects do
{ key_name: -> (object) { a_method(object) } }
end
render_cached 'outer', collection: objects do
{
key_1: -> (object) { a_method(object) },
key_2: -> (item) { item.name },
}
end
cache_rocket
is not magic. It should not be used in all situations.
Benchmark your page rendering times before and after to see if it helps.
See the example above.
Typically, one would key the users/bio
partial on the user
object like so:
- cache [user, 'bio'] do
.lots-of-htmls
= user.bio
With 1000 users, there are 1000 cached items. This can use a lot of memory.
Instead we can cache the users/bio
partial once and replace the content we need using
cache_replace
. With 1000 users, we use 1/1000th the memory.
- cache_replace('users/bio', replace: { bio: user.bio }) do
.lots-of-htmls
= cache_replace_key :bio
If you have a cache key containing multiple models, it will generally be very inefficient:
- cache(user, other_user) do
= render 'common_interests'
If the cached content is rarely retrieved, render_cached
can help:
- cache('common_interests') do
.htmls
= cache_replace_key :something
= render_cached 'common_interests', replace: { something: 'common_interests/inner' }
By caching common html, you ensure that you will render cached content the first time a model-dependent
fragment is rendered. See the Use far less memory
section above for an example.
- Slides from Boulder Ruby presentation: http://www.slideshare.net/teeparham/rails-html-fragment-caching-with-cache-rocket
- Rails cache benchmark test app: https://github.com/teeparham/cache_benchmark