Flushing content blocks with Rails 4

Besides the big and shiny features that Rails 4 holds, there’s a lot of small improvements on several other sections of the Rails framework – helpers, core extensions, app configurations and more – that might not even hit the Changelogs but will somehow make our lifes easier in the future. One of these hidden gems that I’ve found recently is an improvement on the content_for helper to flush and replace previous chunks of HTML with new ones.

The content_for that we are used to

The content_for method is an old friend of every Rails developer, and it’s a pretty simple and flexible helper. You can store a chunk of HTML from a String or a block, and grab it somewhere else in your views or yield it directly into your templates. It’s a pretty handy trick to move data from your views into your layouts, like page titles, custom meta tags or specific script tags that your page needs to include.

# On your 'application.html.erb' layout, inside the '' tag.
<% yield :metatags %>

# Then, into a specific view
<% content_for :metatags do %>
  
<% end %>

Multiple calls of the content_for helper using the same identifier will concatenate them and output them together when you read it back on your views, as:

<% content_for :example will be rendered %>
<% content_for :example do %>
  

This will be rendered too!

<% end %>

On some scenarios this behavior might not be desired, and with Rails 4 you can flush out the stored pieces of an identifier and replace it instead of adding more content to it: using the flush: true option. The first implementation used an extra true argument, but we changed to use a Hash instead, so the flush key can express better the behavior we’re expecting.

<% content_for :example will be rendered %>
<% content_for :example flush: true do %>
  

But this will override everything on the ':example' block.

<% end %>

The gallery situation

I’ve stumbled upon this on a recent project, where we had a somewhat classic scenario: a partial named _gallery, responsible for rendering the piece of HTML to display a gallery of images that also supplies a content_for block with a script tag to include the required libraries to put the gallery to work.


<% content_for :scripts javascript_include_tag %>

It works like a charm. But with an updated requirement we had the case where multiple galleries could be present on the same page, rendering the _gallery partial several times. The required HTML would be present, but the gallery.js script would be included multiple times into the rendered page. Instead of working this out using instance variables to check that the partial was rendered at least once, we could let Rails do all the hard work for us, using the flush option when including the gallery.js script.


<% we can render this partial several times and script will be included just once %>
<% content_for :scripts javascript_include_tag flush: true %>

Back to the present: Rails 3.2

Well, while this seems to be a perfect solution to my problem, this feature isn’t available on Rails 3.2 or on the 3-2-stable branch – it’s only available on the master branch that will be released with Rails 4. But, backporting this feature into a 3.x application is pretty simple, using a helper of your own.

def single_content_for(name, content = nil, &block)
  @view_flow.set(name, ActiveSupport::SafeBuffer.new)
  content_for(name, content, &block)
end

After some source diving into the ActionPack source code we’re done – it just needs to replace any present content with a brand new SafeBuffer instance before storing the piece of HTML.

What do you think about this little addition to Rails 4? Can you think of a similar problem that could be solved with this instead of a custom hack?

8 responses to “Flushing content blocks with Rails 4”

  1. Jakub Arnold says:

    Can’t wait to have this in my apps 🙂

  2. Kieran P says:

    One of your examples is wrong. It wouldn’t be:

    It should be:

  3. Lucas Mazza says:

    Fixed, thanks!

  4. josevalim says:

    It is fixed, thanks!

  5. very useful, thanks

  6. Aaron Breckenridge says:

    Neat trick, and great usable code for 3.2. Thanks.

    The example left me with two questions: Why isn’t your Javascript in your assets directory and included once? And wouldn’t a Boolean check be faster than repeatedly including and then flushing content?I’ll have to check out that :flush option, could be someone already thought this scenario out in Rails already.

  7. Our javascript is inside the assets directory but we want to add it only when these are galleries on the page. We are using this because our application is not a normal Rails application and in our scenario this was the best solution so far.

    The Rails 4 code is doing the boolean check. We used the flushing+set path so we don’t have to copy the content_for code inside our application.

  8. Lucas Mazza says:

    Thanks for the feedback Aaron!

    My 2 cents: besides whatever component is being used to handle assets, I rather cherry pick one or two scripts to be included instead of having huge “application.*” file – it’s easier to avoid clashes and conflict between styles and/or JavaScript components. On using a boolean check, I’ve done things like that but it feels cleaner to use options already baked on Rails instead of (re)building things like this on my own every now and then.