So maybe this has been done before, but this is something I wrote that I
found useful.
Many of the ‘fragments’ I wanted to cache differed only by some small
value like the :id of the :url, but because this changed every request,
the options were to either store a fragment for every :id, or to not
cache.
So I wrote a helper I call ‘dynamic_cache’. It works by using escaped
Erb INSIDE of an ERb fragment, and calling render :inline on the cached
fragment before returning.
So for instance:
<% cache do %>
<%= link_to_remote ‘My Link’, :url => { :id => @my_id } %>
<% end %>
This doesn’t cache, since the :id is variable.
So with dynamic_cache it would look like the following:
<% dynamic_cache do %>
<%= link_to_remote ‘My Link’, :url => { :id => ‘<%= @my_id %>’ } %>
<% end %>
@my_id doesn’t get evaluated and the escaped ERb delimiters get cleaned
up to just ‘<%= @my_id %>’. The cache then calls render :inline on the
cached fragment which evaluates any of the newly un-escaped ERb with the
current values at the time of the render.
Add the following to your application.rb for this to work…
module ActionController #:nodoc:
module Caching
module Fragments
def dynamic_cache_erb_fragment(block, name = {}, options = nil)
# this grabs the _erbout variable from the blocks scope
buffer = eval(’_erbout’, block.binding)
if ! cache = read_fragment(name, options)
# by default the block.call will write everything to the
_erbout
# this is an ugly way to get around that, and is the same
method used in capture_erb_with_buffer
# but it is only called the first time, so not a big deal
pos = buffer.length
block.call
cache = buffer[pos…-1]
buffer[pos…-1] = ‘’
# save it to the cache if we are caching
write_fragment(name, cache, options) if perform_caching
end
buffer.concat(render :inline => cache)
end
end
end
end
module ActionView
module Helpers
module CacheHelper
def dynamic_cache(name = {}, &block)
@controller.dynamic_cache_erb_fragment(block, name)
end
end
end
end