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?