technicalpickles

Open Source ProjectsCode that Might be Useful to You

Talks I've GivenOn Technologies and Ideas

ThoughtsWhere I Sometimes Write Things

Resume If You Believe In Those

Follow Me On

GitHubIf coding is your thing

TwitterIf you tweet

Determine if content_for was used


One of those common things to do is have a sidebar with content, which is dynamic based on where you are in the app. The common solution uses content_for :sidebar, and yield :sidebar. In practice, this looks like:

<!-- In app/views/layouts/application.html.erb -->
<html>
  <body>
    <div id="sidebar">
      <%= yield :sidebar %>
    </div>
    
    <div id="content">
      <%= yield %>
    </div>
  </body>
</html>
<!-- In app/views/foo/index.html.erb -->
<% content_for :sidebar do %>
  im in ur sidebar showing of ur foo
<% end %>

<ul>
  <li>Foo</li>
  <li>Bar</li>
</ul>

This way, you get the custom content in sidebar when you specify. Here’s the kink: you yield the sidebar inside your sidebar div, so the containing sidebar div is always going to be rendered, whether or not there’s anything rendered inside of it.

Annoying.

Wouldn’t it be awesome to conditionally render the containing div? Yeah, but there’s not a builtin way as far as I can tell.

Why don’t we take a peek into content_for’s implementation, and try to understand what it’s doing:

def content_for(name, content = nil, &block)
  existing_content_for = instance_variable_get("@content_for_#{name}").to_s
  new_content_for      = existing_content_for + (block_given? ? capture(&block) : content)
  instance_variable_set("@content_for_#{name}", new_content_for)
end

So, if I understand it, for sidebar, it would be doing:

  • Getting existing content in @content_for_sidebar
  • Generate content from the block, and combine it with the existing content
  • Set @content_for_sidebar to the combined content

So, if @content_for_sidebar is nil, I think we can safely know that content_for :sidebar has not been used. Let’s try this out:

<!-- In app/views/layouts/application.html.erb %>
<html>
  <body>
    <% unless @content_for_sidebar.nil? %>
      <div id="sidebar">
        <%= yield :sidebar %>
      </div>
    <% end %>
    
    <div id="content">
      <%= yield %>
    </div>
  </body>
</html>

Does it work? Yes. Am I a naughty, naughty boy who should be punished? That’s kind of irrelevant, but it feels kludgy to be checking the Rails internals in the view. How about we do something-a-like-this:

# in app/helpers/application_helper.rb
module ApplicationHelper
  def content_given?(name)
    content = instance_variable_get("@content_for_#{name}")
    ! content.nil?
  end
end
<!-- In app/views/layouts/application.html.erb %>
<html>
  <body>
    <% if content_given? :sidebar %>
      <div id="sidebar">
        <%= yield :sidebar %>
      </div>
    <% end %>
    
    <div id="content">
      <%= yield %>
    </div>
  </body>
</html>

That definitely feels nicer. Maybe it should be included with Rails? As a plugin? As a gem? All of the above?

comments powered by Disqus