Gem Version Build Status Coverage Status Code Climate

Rails rendering extension for server-side html caching

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.

Install

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

Use

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.

cache_replace

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:

Before

.lots.of.htmls
  = Time.now
.more.htmls
  = user.name

After

- 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

render_cached has options to deal with partials and collections. Use it as a replacement for Rails' render, with replace options. For example:

Before

file.html.haml:
= render 'outer'
_outer.html.haml:
.lots
  .of
    .htmls
      = Time.now

After

Replace render with render_cached in file, specify the content to replace, and cache outer:

file.html.haml:
= render_cached 'outer', replace: { inner: Time.now }
_outer.html.haml:
- cache 'outer' do
  .lots
    .of
      .htmls
        = cache_replace_key :inner

Here is the same example with an inner partial:

Before

file.html.haml:
= render 'outer'
_outer.html.haml:
.lots
  .of
    .htmls
      = render 'inner'
_inner.html.haml:
= Time.now

After

Replace render with render_cached in file, specify the partial to replace in outer, and cache outer:

file.html.haml:
= render_cached 'outer', replace: 'inner'
_outer.html.haml:
- cache 'outer' do
  .lots
    .of
      .htmls
        = cache_replace_key :inner
_inner.html.haml:
= Time.now

Options

render_cached supports several styles of arguments:

Single partial to replace

render_cached 'outer', replace: 'inner'

Array of partials to replace

render_cached 'outer', replace: ['inner', 'footer']

Hash of keys to replace with values

render_cached 'outer', replace: { key_name: a_method(object) }

Block containing a hash of keys to replace with values

render_cached 'outer' do
  { key_name: a_method(object) }
end

Render a collection with hash of keys, using a lambda for each collection item

render_cached 'outer', collection: objects,
  replace: { key_name: -> (object) { a_method(object) } }

Render a collection with block syntax

render_cached 'outer', collection: objects do
  { key_name: -> (object) { a_method(object) } }
end

Render a collection with block syntax with multiple keys

render_cached 'outer', collection: objects do
  {
    key_1: -> (object) { a_method(object) },
    key_2: -> (item) { item.name },
  }
end

YMMV

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.

Benefits

More server-side caching

See the example above.

More efficient memory usage

Typically, one would key the users/bio partial on the user object like so:

users/bio.html.haml:
- 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.

users/bio.html.haml:
- cache_replace('users/bio', replace: { bio: user.bio }) do
  .lots-of-htmls
    = cache_replace_key :bio

Simpler cache keys

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' }

Faster first page loads

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.

References