208: ERB Blocks in Rails 3
The second beta version of Rails 3.0 has just been released. One of the more significant changes in this release is to the way blocks are handled in the view layers. In this episode we’ll take a look at what has changed.
Upgrading Ruby and Rails
Before we upgrade to the new beta version of Rails 3 we’re going to update our version of Ruby to 1.9.2. In episode 200 [watch, read] we used rvm to install Ruby 1.9.1, but Ruby 1.9.1 is a little buggy while 1.9.2 feels more solid, even though it is still under development.
To install Ruby 1.9.2 with rvm we need to run this command:
rvm install ruby-head
As Ruby 1.9.2 isn’t out yet this will get us the latest version of it. We’re living on the edge here, but Ruby 1.9.2 is close to release so we’re using a pretty solid version. Once rvm has installed the new version we can switch to it with:
rvm ruby-head --default
Note that we’ve used the
--default switch to make 1.9.2 the default version of Ruby. This means that any new terminal windows we open will still have Ruby 1.9.2 as their current version.
Rails 3.0 beta 2 requires RubyGems version 1.3.6 so before we install it we’ll run
to see which version we have. If we have a version earlier than 1.3.6 we can run
gem update --system
to update it. Now we can install Rails 3.0 beta 2 with:
gem install rails --pre
Any applications developed with the first beta version of Rails 3.0 can be upgraded by simply changing the version number in the Gemfile for that application from
gem "rails", "3.0.0.beta2"
Then we can run
bundle install to make sure that all of the dependencies are resolved.
What’s Changed in erb
Now that we have Rails 3.0 beta 2 installed let’s begin to look at what’s changed. There are two types of tags used in erb files. If code is placed between
<%= %> tags then whatever is returned by the Ruby code between the tags will be output to the view. If the equals sign is omitted then the code is interpreted but the output is discarded. This is a common concept in erb and in Rails 3 it has been extended to work this way inside blocks.
The most common place you’ll see this is in forms. Below is an erb template for a form for a
<% form_for @product do |f| %> <%= f.error_messages %> <p> <%= f.label :category_id %><br /> <%= f.collection_select :category_id, Category.all, :id, :name %> </p> <p> <%= f.label :name %><br /> <%= f.text_field :name %> </p> <p> <%= f.label :price %><br /> <%= f.text_field :price %> </p> <p> <%= f.label :description %><br /> <%= f.text_area :description %> </p> <p><%= f.submit "Submit" %></p> <% end %>
In the first line of this code we use
form_for. This will insert form tags into the view around the content in the block, but there is no equals sign in the erb tags. This breaks the rule that erb blocks that output code to the view should use
<%= %> and has made this difficult to work with the internals of
form_for in previous versions of Rails. From the new beta version onwards, however, we use an equals signs here as we would with any other erb code that generates output.
<%= form_for @product do |f| %> <!-- rest of form --> <% end %>
Even though we’re passing in a block we now use the equals sign with
form_for, although the closing
end tag stays as it is. This will insert the
form tag and the form’s contents correctly. This also cleans up the internals quite a bit as we’ll show you shortly.
You might be wondering when the equals sign is needed and when it isn’t. Let’s take a look at a couple of examples. First, div_for:
<%= div_for @product do %> <% end %>
In earlier versions of Rails we wouldn’t use the equals sign here but now it’s required as we’re outputting content around the block, in this case a
div tag. In fact most helpers in Rails now require the equals sign as they output something around the block they take.
There are still some cases that don’t take an equals sign, however. One example is anything that uses the
<% @comments.each do |c|%> <% end %>
each method doesn’t return anything that we want to output to the view so this still doesn’t use an equals sign as what’s being returned by the
each method isn’t output.
Another example is the
<% content_for :side do %> <% end %>
The equals sign isn’t used here as the
content_for call stores the content of the block in a variable to use later on in the layout. Nothing is being output to the view here so the equals sign isn’t required.
Unfortunately there is one exception to this rule: the
cache method. Ideally
cache would use an equals sign as it might return some cached content into the view, but because of how it works internally it doesn’t use an equals sign.
<% cache do %> <% end %>
Using Blocks in Helper Methods
At first this might all seem a little overwhelming but once you’ve got used to it the changes make a lot of sense. The main reason this change has been made is that it cleans up the internals of the code. Episode 40 showed how to use blocks in views in earlier versions of Rails. It was necessary then to use the
concat method to place text before or after the block which was a little difficult to work with.
def admin_area(&block) concat('<div class="admin">', block.binding) block.call concat("</div>", block.binding) end
To demonstrate how much easier it is to use blocks in helper methods now we’ll use a simple store application that has a number of links that should only be visible to system administrators.
We want the “Edit”, “Destroy” and “View All” links to be visible only if the user is an admin and we also want to wrap the links in a
div. The links are created in the view with the following code:
<p> <%= link_to "Edit", edit_product_path(@product) %> | <%= link_to "Destroy", @product, :confirm => "Are you sure?", :method => :delete %> | <%= link_to "View All", products_path %> </p>
We’re going to replace the paragraph tags that the links are wrapped in with a new helper method called
admin_area like this:
<%= admin_area do %> <%= link_to "Edit", edit_product_path(@product) %> | <%= link_to "Destroy", @product, :confirm => "Are you sure?", :method => :delete %> | <%= link_to "View All", products_path %> <% end %>
admin_area method will add the
div tag and show or hide the links depending on whether the current user is an admin. Note that because our helper method outputs to the view we’ve used an equals sign in its opening tag.
We’ll define the method inside the application helper. What’s nice about this new way of working with blocks in helper methods is that it behaves like you would expect it to so if the method just returns a string then that string will be output in the view.
module ApplicationHelper def admin_area(&block) "OH HAI!" end end
If we reload the page now the content of the block will be replaced by the string that the
admin_area method returns. The links aren’t shown as we’re not executing the block anywhere in the method.
In order to execute the block and return the content we need to call a new method called
with_output_buffer and pass it the block. This will call the block in a separate output buffer so that the content isn’t rendered out directly to the view. We can assign that output to a variable and then do whatever we like with it. We want to wrap the content in a
div with a
admin so we’ll change our
admin_area method to look like this:
def admin_area(&block) content = with_output_buffer(&block) content_tag(:div, content, :class => 'admin') end
When we reload the page now we’ll see the admin links wrapped in a div. As we’ve already created a style for the admin class in the application’s stylesheet the div will have that styling applied it it.
If we look in the source code for the page we can see the div around the links.
<div class="admin"> <a href="/products/1/edit">Edit</a> | <a href="/products/1" data-confirm="Are you sure?" data-method="delete" rel="nofollow">Destroy</a> | <a href="/products">View All</a> </div>
So, if we ever need to fetch the contents of a block in a view we can use the
with_output_buffer method and pass the block to it and that will return the content of the block. That said, the code we’ve written in our helper method was mainly for demonstration purposes and there is a more efficient way to to do the same thing.
content_tag method can take a block as an argument and that will handle the output buffer switching automatically. That means that our helper method can be rewritten like this:
def admin_area(&block) content_tag(:div, :class => 'admin', &block) end
Of course to make the helper method fully functional we’d want to output the tag only if the user is an admin which we can do by making use of a method that returns a boolean value based on whether the current user is an admin.
def admin_area(&block) content_tag(:div, :class => 'admin', &block) if admin? end
That’s pretty much it for this episode. Using an equals sign with blocks in views might take a little getting used to but in the long run it does make more sense as these blocks do output content. With the cleaner implementation behind the scenes as well then this is an improvement the implementation of Rails’ view code.
As a bonus here’s an extra tip. In earlier versions of Rails you can add a minus sign to the beginning or end of an erb tag to help strip out whitespace so that your markup is a little cleaner. In Rails 3 this is no longer necessary. If an erb tag has no output it will automatically be stripped away so there’s no unnecessary whitespace where the erb tags were.