# Dynamic Fragment Caching with Escaped ERb

**URL:** <https://www.ruby-forum.com/t/dynamic-fragment-caching-with-escaped-erb/77991>\
**Category:** Rails\
**Created:** [December 2, 2006, 9:06am UTC](https://www.ruby-forum.com/t/dynamic-fragment-caching-with-escaped-erb/77991 "2006-12-02T09:06:02Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![slain](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/2acd7d/32.png) [@slain](https://www.ruby-forum.com/u/slain)\
**Post date:** [December 2, 2006, 9:06am UTC](https://www.ruby-forum.com/t/dynamic-fragment-caching-with-escaped-erb/77991/1 "2006-12-02T09:06:02Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![slain](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/2acd7d/32.png) [@slain](https://www.ruby-forum.com/u/slain)\
**Post date:** [December 2, 2006, 9:26am UTC](https://www.ruby-forum.com/t/dynamic-fragment-caching-with-escaped-erb/77991/2 "2006-12-02T09:26:55Z")

</div>

You can also do some more complicated caching than would otherwise be  
allowed (ie, caching larger blocks of code…it gets a little ugly, but  
may be useful.

In the following example, condtional is executed at the time of  
rendering, while the links are cached.

\<% dynamic\_cache do %\>  
\<%= ‘\<% if @my\_test\_value == “HURRAY” %\>’ %\>  
\<%= link\_to “My Test2”, “HURRAY” %\>  
\<%= ‘\<% else %\>’ %\>  
\<%= link\_to “My Test3”, “HIDEYHO” %\>  
\<%= ‘\<% end %\>’ %\>  
\<% end %\>

PLEASE NOTE: THERE IS A BUG HERE CURRENTLY I NEED TO FIX. If you use  
more than one dynamic\_cache request you get the a double render error.  
I need to figure out how to get the effect of render :inline with  
actually using render.

---

<div class="post-metadata">

**Author:** ![slain](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/2acd7d/32.png) [@slain](https://www.ruby-forum.com/u/slain)\
**Post date:** [December 2, 2006, 1:06pm UTC](https://www.ruby-forum.com/t/dynamic-fragment-caching-with-escaped-erb/77991/3 "2006-12-02T13:06:22Z")

</div>

OK, here is a fixed version (just added ‘@template.’ to the render :

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  
logger.error cache  
buffer.concat(@template.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

Here is a fixed example of conditionals. Notice the use of double  
quotes to wrap the escaped ERb.

\<% dynamic\_cache do %\>  
\<%= “\<% if @my\_test\_value == ‘BOOOO’ %\>” %\>  
\<%= link\_to “My Test”, “/\<%= @my\_test\_value %\>” %\>  
\<%= “\<% else %\>” %\>  
\<%= link\_to “My Test”, “/\<%= @my\_test\_value %\>2” %\>  
\<%= “\<% end %\>” %\>  
\<% end %\>

Obviously, another option would be to use a separate delimiter and  
replace it with ‘\<%’ and ‘%\>’ in the dynamic\_cache method when it  
initially creates the cache record.

Like,

\<% dynamic\_cache do %\>  
\<%= “#% if @my\_test\_value == ‘BOOOO’ %#” %\>  
\<%= link\_to “My Test”, “/#%= @my\_test\_value %#” %\>  
\<%= “#% else %\>” %#\>  
\<%= link\_to “My Test”, “/#%= @my\_test\_value %#\>2” %\>  
\<%= “#% end %#\>” %\>  
\<% end %\>

You would just need to cache.gsub for ‘%#’ and ‘#%’ before  
write\_fragment.

---

<div class="post-metadata">

**Author:** ![slain](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/2acd7d/32.png) [@slain](https://www.ruby-forum.com/u/slain)\
**Post date:** [December 2, 2006, 1:43pm UTC](https://www.ruby-forum.com/t/dynamic-fragment-caching-with-escaped-erb/77991/4 "2006-12-02T13:43:10Z")

</div>

Sorry, left a logging statement in there, which shouldnt be:

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(@template.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
