Inline ERB templating — for free

February 28th, 2007

I needed to support an email templating system in the project I'm working on, but creating my own seemed like a pain, especially with so many templating systems out there. I looked at Liquid (the templating system used by Mephisto) and eRuby, but ultimately, the answer was right under my nose the entire time…

Read the rest of this entry »

Extending render with a collection

February 22nd, 2007

Partials offer a lot in the way of cutting out common code, especially when you couple them with a collection of objects. Take this code, for example:

<% for item in @items -%>
  <%= render :partial => "item", :locals => { :item => item } %>
<% end -%>

While pretty nice, Rails has an easier way to do it, with the :collection option:

<!– items/list.rhtml –>
<%= render :partial => "item", :collection => @items %>

<!– items/_item.rhtml –>
<div class="item">
  <%= h item.name %>
  <%= h item.description %>
</div>

Now that looks pretty sweet. Rails automatically takes the name of your partial and adds a local variable by the same name, setting its value to the current iteration through our collection. But… we've got a visual problem with our page. We have the margin-bottom CSS attribute on all div objects in the class item set to 1em. For the last item in the collection, we'd like to make the margin 0, to get rid of extra space. Rails gives you one local variable for free when you render a collection of partials, called {partial_name}_counter. In this case, the name of the counter would be item_counter and it would give you the index in the array for the current item being rendered. Using this variable, you could, in theory, do something like this in your partial:

<!– items/_item.rhtml –>
<div class="item<%= " last" if item_counter + 1 == @items.size %>">
  <%= h item.name %>
  <%= h item.description %>
</div>

That's nice, but it's also pretty ugly. Also, what if you're rendering that partial in another view and you have not declared the @items variable? It's time to add our own auto-generated local variable to the render method. We're basically going to copy and paste Rails' default ActionView::Base.render method, but with one addition. You can put this code in your environment.rb file, if you'd like:

module ActionView
  class Base
    def render(options = {}, old_local_assigns = {}, &block)
      if options.is_a?(String)
        render_file(options, true, old_local_assigns)
      elsif options == :update
        update_page(&block)
      elsif options.is_a?(Hash)
        options[:locals] ||= {}
        options[:use_full_path] = options[:use_full_path].nil? ? true : options[:use_full_path]

        if options[:file]
          render_file(options[:file], options[:use_full_path], options[:locals])
        elsif options[:partial] && options[:collection]
          # This line is what we're concerned with. Note the addition of the 'last_item' local variable.
          render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals].merge(:last_item => options[:collection].last))
        elsif options[:partial]
          render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals])
        elsif options[:inline]
          render_template(options[:type] || :rhtml, options[:inline], nil, options[:locals] || {})
        end
      end
    end
  end
end

Now we get a local variable, for free, called last_item that we ca n use in our view to test for the last item in the collection:

<!– items/_item.rhtml –>
<div class="item<%= " last" if item == last_item %>">
  <%= h item.name %>
  <%= h item.description %>
</div>

Obviously we can name the variable whatever we'd like, in case you happen to have a conflicting local variable called last_item. You could prepend the name of the partial to the variable name just to be safe, but I don't mind it, so I'll leave it as it is.

Implicit exception handling

February 15th, 2007

Ruby does a fantastic job of making code look good, and today's tip is no exception, pun intended. Take this code, for example:

def delete_items(ids)
  begin
    # This could fail if any of the IDs were not found
    items = find(ids)
    items.each do |i|
      i.parent.log("Deleted #{item.name}")
      i.destroy
    end
  rescue
    return false
  end

  return true
end

While a simple example, it gets the point across. But the begin/rescue control loop has a few tricks up its sleeve, namely else and ensure. Adding an else into exception handling code will do just what you would expect: get run in case no exception was thrown. The other term, ensure denotes a block of code that will be run regardless of whether an exception was thrown or not. What we want to focus on here is the else clause, because Ruby has an implicit begin at the beginning of a method, which cleans up our code a tiny bit–but enough to make it worth doing, in my opinion:

def delete_items(ids)
  # This could fail if any of the IDs were not found
  items = find(ids)
  items.each do |i|
    i.parent.log("Deleted #{item.name}")
    i.destroy
  end
rescue
  return false
else
  return true
end

Looking good, Billy Ray.

Make Array#collect return a hash

February 11th, 2007

The Array#collect method is great for taking a hash or an array of objects and returning a new array based on the block you provide it, but sometimes you'd like to return a hash instead of an array. My primary use case for this is in testing Rails form posts with checkboxes. For instance, here is some HTML source of an imaginary page:

<form action="/people/update_item_status/1" method="post">
  <input type="checkbox" name="items[1]" /> Car
  <input type="checkbox" name="items[3]" /> Laptop
  <input type="checkbox" name="items[7]" /> Television
  <input type="submit" value="Update Status" />
</form>

When submitted to Rails with "Car" and "Television" checked, the above form should come over like this:

Processing PeopleController#update_item_status 
  (for 127.0.0.1 at 2007-02-11 17:00:41) [POST]
  Session ID: asdf
  Parameters: {"controller"=>"people", "action"=>"update_item_status", 
    "id"=>"1",  "items" => {"1" => "1", "7" => "1"}}

When testing, you might want to build the items hash based on any number of items you specify. Using Array#collect, you'll get back an array, which won't work when building the params hash. What you need is something like the following:

module Enumerable
  # Usage: items.build_hash { |item| [ item.id, 1 ] } => { 1 => 1, 7 => 1, … }
  def build_hash
    returning(result = {}) do
      self.each do |pair|
        key, value = yield(pair)
        result[key] = value
      end
    end
  end
end

It's probably worth noting that the returning method is something included in Rails, so if you're not using this method inside a Rails project, you can either change the code to declare result = {} and then return result at the end of the method, or add the following code somewhere in your project:

class Object
  def returning(value)
    yield(value)
    value
  end
end

Keep eager loaded associations in association extensions

February 8th, 2007

Association extensions are amazingly powerful. If you're not familiar with them, here's a small example of their power:

class Person < ActiveRecord::Base
  has_many :items do
    def expensive(expensive_price = 50.00)
      @expensive ||= find(:all, :conditions => [ "price >= ?", expensive_price ])
    end
  end
end

Then, you might do something like this from your view:

<h1>Adam's Expensive Items</h1>
<% for item in @person.items.expensive %>
  <span class="item-name"><%= h item.name %></span>
  <span class="item-price"><%= number_to_currency item.price %></span>
<% end %>

What Rails is actually doing when the find method is called in the above example is running the find within the scope of its parent association, Person. It effectively gives you the following SQL call:

SELECT *
  FROM people, items
 WHERE people.id        = items.person_id
   AND items.person_id  = 1
   AND items.price     >= 50.00

Now, let's assume you've got the following code in your controller that makes use of eager loading:

class PersonController < ApplicationController
  def show
    @person = Person.find(params[:id], :include => :items)
  end
end

You've included the items association, but in your view, you're still calling the find method in your assocation extension, which makes your eager loaded association less effective. Fortunately, Rails handles association extensions in a smart way that makes sure it plays nicely with eager loading, if you want it to. Here's how:

class Person < ActiveRecord::Base
  has_many :items do
    def expensive(expensive_price = 50.00)
      @expensive ||= self.select { |item| item.price >= expensive_price }
    end
  end
end

The above code makes use of the eager loaded association in self, if available, with the select method invoked, which is basically the same as a find(:all …) call. If the association has not been eager loaded, Rails will load issue the SQL to load it, then will run the select method against the returned collection.

One thing to note about this approach, however, is that the call to self.select will still fetch all the results of the association (that is, every item for the particular person), whereas going the route of using the call to find will only pull those records which match the conditions. If you have hundreds or thousands of items for each person, the latter method might not scale to your needs and sacrificing one more SQL call, as demonstrated in the former, would probably be the better option.

Create code dynamically with eval and here documents

February 8th, 2007

Here documents, sometimes called "heredocs," can be a powerful way to spruce up a call to eval without declaring a method on a single line. Take, for instance, this code, which might reside in your Rails project's routes.rb file:

map.with_options(:controller => "items") do |obj|
  obj.items("/items", :action => "list")
  obj.item("/items/:id", :action => "show", :id => /\d+/)
end

map.with_options(:controller => "people") do |obj|
  obj.people("/people", :action => "list")
  obj.person("/people/:id", :action => "show", :id => /\d+/)
end

The above code uses the Object#with_options method, which makes it easy to extract out common parameters in method calls. Without the call to with_options, it would have been necessary to provide the ":controller => "items"" parameter in each of the route calls.

Anyway, back to the point. After creating the above code on one controller, you might want to do it for a second, and third, etc. That quickly becomes unmanageable. Enter eval and here documents:

named_routes = [:item, :person]

named_routes.each do |controller|
  eval <<-STR
    map.with_options(:controller => "#{controller.pluralize}") do |obj|
      obj.#{controller.pluralize}("/#{controller.pluralize}", :action => "list")
      obj.#{controller}("/#{controller.pluralize}/:id", :action => "show", :id => #{'/\d+/'})
    end
  STR
end

The here document is specified by providing "<<-STR." You can name your heredoc whatever you'd like, as long as you close it using the same name. You can even nest heredocs, but we're not focused on that right now. Combining eval and here documents makes your code more reusable and more maintainable, not to mention easier to read.

Encapsulate common functionality with blocks and ERB

February 5th, 2007

You might find yourself adopting certain layout standards throughout your application. In an application I'm working on, I display a lot of tabular data in styled boxex; usually at least two per page. I found myself repeating this common pattern:

<table class="list">
  <tr>
    <th colspan="3">
      <%= image_tag "item", :size => "16×16" %>
      Items: <%= @items.size %>
    </th>
  </tr>
  <%= render :partial => "item", :collection => @items %>
</table>

Using a helper method, you can encapsulate most of this functionality into something more manageable:

# In your ApplicationHelper
def show_table(title, options = {}, &block)
  image = options[:image] ? image_tag(options[:image], :size => "16×16") || ""

  content = capture(&block)
  concat(tag(:table, { :class => "list" }, true), block.binding)
  concat(content_tag(:tr, content_tag