content_for is the new GOTO

November 06, 2008 · 1 min read

I have a confession: I really don't like content_for. When you use it, your view code starts jumping around between files in a way that's genuinely hard to follow. It smells a lot like GOTO. And when was the last time anyone recommended you use a GOTO?

content_for :javascript and content_for :css

The good news is that content_for can be avoided entirely, at least when it comes to including CSS and JavaScript. The trick is simple: include the controller name and action name in your layout's <body> tag, then scope your CSS declarations accordingly.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title><%= page_title %></title>
  <meta http-equiv="Content-Language" content="English" />
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <link rel="stylesheet" type="text/css" href="/stylesheets/simple.css" media="screen" />
</head>
<body id="<%= "#{controller.controller_name.tableize.singularize}_#{controller.action_name}" %>" class="<%= "#{controller.controller_name.tableize.singularize} #{controller.action_name}" %>">
  <%= yield %>
</body>
</html>

Now, say you're looking at the Posts views in your app. You can style each action independently, like this:

.post.index .article .title {
  font-size: 1.25em;
}

.post.show .article .title {
  font-size: 0.9em;
}

If you need to support browsers that don't handle two classes as a selector on a single element, use the ID-based version instead:

#post_index .article .title {
  font-size: 1.25em;
}

#post_show .article .title {
  font-size: 0.9em;
}

Since all your JavaScript is unobtrusive anyway (right?), you can scope it with the same CSS selectors shown above.

As a bonus, this approach lets you bundle all your JavaScript and CSS into single files for production, saving a bunch of HTTP requests. No content_for required.