homeASCIIcasts

208: ERB Blocks in Rails 3 

(view original Railscast)

Other translations: Es It

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

gem -v

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

Upgrading Applications

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 3.0.0.beta to 3.0.0.beta2.

/Gemfile

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 Product model.

/app/views/products/_form.html.erb

<% 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.

/app/views/products/_form.html.erb

<%= 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 each method:

<% @comments.each do |c|%>
<% end %>

The 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 method.

<% 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.

The show page for a product showing the admin links.

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:

/app/views/products/show.html.erb

<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:

/app/views/products/show.html.erb

<%= 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 %>

The 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.

/app/helpers/application_helper.rb

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.

The contents of the block are replaced by the string.

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 class of admin so we’ll change our admin_area method to look like this:

/app/helpers/application_helper.rb

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.

The block in now wrapped in a div.

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.

The 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:

/app/helpers/application_helper.rb

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.

/app/helpers/application_helper.rb

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.