Dealing with Collections in Rails Views

Wednesday, February 14, 2007 by Nate Murray.  

Often, we want to render collections of things in Rails views. In this example the end goal is to output a list of Category names with urls. This is often referred to as "breadcrumbs"

Example 1

For our breadcrumbs the first thing one might think to do is create a helper to perform this function.

For those of you who are new to Rails, helpers are often designed to return a string and this string is added to the output.

Below is our first attempt at a helper for this problem. This code returns a string with our anchor tags separated by an image.

== helpers/sites_helper.rb

  def make_breadcrumbs(breadcrumbs)
    crumbs = []  
    spacer = "<img src='/images/separator.gif' width=5 height=5
        border=0>"
    breadcrumbs.each do |crumb|
      crumbs <<  "<a href='#{url_for_category(crumb)}'>
       #{crumb.name}</a>"
    end
    return crumbs.join(spacer)
  end

We can call this helper easily from our view:

== views/sites/_breadcrumbs.rhtml

 <%= make_breadcrumbs(@breadcrumbs) %>

This works just fine, but there are a couple consequences:

  • its very specific to our problem.
  • All the HTML is in the code instead of in our views.

Example 2

"But wait a minute," you ask, "Doesn't Rails come with built-in collection-rendering methods?" You're right! In fact we can use Rails render method to achieve the same effect.

== views/sites/_breadcrumbs.rhtml

 <%= render :partial => "sites/breadcrumb", 
               :collection => @breadcrumbs,
               :spacer_template => 'sites/breadcrumb_spacer' %>

In our view we call the render method and we pace the options :collection and :spacer_template. This renders the partial sites/breadcrumb and creates the local variable breadcrumb with each element of @breadcrumbs.

The two templates are below:

== views/sites/_breadcrumb.rhtml
<a href='<%= url_for_category(breadcrumb)%>'><%=breadcrumb.name%></a>
== views/sites/_breadcrumb_spacer.rhtml
<img src='/images/separator.gif' width=5 height=5 border=0>

A benefit of this is that all of our HTML is in our views.However, a consequence that we have to create two new partials that contain only one line each.

What we would really want is a way to keep all of our html in our view while not having to create any extra templates.

Example 3

Here is an example of the syntax we want:

== views/sites/_breadcrumbs.rhtml

 <% spacer = capture do %>
   <img src="/images/separator.gif" width=5 height=5 border=0>
 <% end %>
 
 <% with_collection @breadcrumbs, :spacer_template => spacer do |crumb| %> 
   <a href='<%= url_for_category(crumb) %>'><%= crumb.name %></a>
 <% end %>


The Rails helper capture takes the block and puts it in the variable spacer.

Then we put this in helpers/sites_helper.rb

== helpers/sites_helper.rb

  def with_collection(collection, opts={}, &proc)
    collection.each_with_index do |element, i|
      yield element
      if spacer = opts[:spacer_template]
        concat(spacer, proc.binding) unless i == collection.size - 1
      end
    end
  end

This helper takes the collection and yields each element. As each element is yielded the block in the view is added to the output. The trick is that we add the spacer to the output by using the concat method. The concat method outputs the string to the view from the helper. This is how we can output something to the template without using <%= (This is also what the helpers such as form_for use).

Labels: , , ,

Finding a Better State Pattern (in Ruby)

Wednesday, February 07, 2007 by Nate Murray.  

Introduction

Gang of Four outlines a pattern for modifying an object depending on its state. This is called the State pattern. I'm not going to go into all the details here, so if you are unfamiliar with the State pattern I'd recommend looking here and here.

This post addresses how to implement the typical State pattern in Ruby and explores its consequences and alternatives.

Problem: An object's behavior needs to be modified depending on what state it is in.
Solution: Create a State object and delegate the functionality to that State object.

Traditional State Pattern

Gang of Four outlines a pattern for modifying an object depending on its state. This is typically done be creating an Abstract State and then subclassing it to create a Concrete State. The originating object, referred to as the Context then delegates the specific method to an instance of the Concrete State along with any information needed.

For example, say we have a Product and we want the inventory levels to vary depending on where we are selling the Product. While the actual inventory we have doesn't change, we may want to tell, say, eBay or Amazon, that the inventory is lower than what we actually have to prevent overselling.

Figure 1. is a traditional implementation of this.

A simple implementation would delegate the method to the state 100% of the time. However, in our system, assume that we only want to use the State if it exists and if it contains the method we are interested in. This allows us to have default behaviors in our Product object. This may not always be the case, but in our example it is.

In Ruby, this looks something like the following:

class Product
  attr_accessor :state

  def inventory
    if state && state.respond_to?(:inventory)
      return state.send(:inventory, self)
    else
      return @inventory
    end
  end

end # end Product

class AmazonProductState < ProductState
  
  # take the inventory from the Context object (the Product) and divide it by 2
  def inventory(context)
    context.inventory / 2
  end

end

Consequences

  • A consequence of this approach is that you have to write the lines if state && state.respond_to?(:inventory) return state.send(:inventory, self) every time. Ideally we wouldn't have to write this over and over for every method we want to delegate.

A slight improvement

What we could do is write a method that writes the delegation code for the method for us.


def define_state_method(name, &block)
  # ... 
end

# Then just call that method fo r
define_state_method :quantity_on_hand do
  return @inventory
end # writes #1 in effect

Consequences

  • Eliminates the repetition of #1
  • However, you still have to plan ahead to make this method be delegated to the State object.

Ideally, what we want to do is have any method overwritten for a particular instance when the state is set. We want to keep the same class, and we don't want to change the methods of other instances of that class.

Just extend it

We can achive the affect we are looking for by simply extending the class on our Product object. See below:

class AmazonProductState < ProductState
  def inventory
    @inventory / 2
  end
  
  def new_method
    "7 llamas"
  end
end

class Product
  ...
  def set_state(klass)
    self.instance_eval do
      extend klass
    end
  end
  ...
end
>> p = Product.find(1) # => #<Product:@id=1...>
   q = Product.find(1) # => #<Product:@id=1...>
   
   q.inventory # => 10

   p.set_state(AmazonProductState) # => nil
   p.inventory # => 5

   p.respond_to(:new_method) # => true
   q.respond_to(:new_method) # => false

Consequences

  • Allows us to override any method without having to plan ahead when designing the Product object
  • Allows us to add new methods that only exist in the state
  • The new state does not change the class of the object
  • The new state only affects particular instances of the object, not the whole class

Labels: , , , ,

Who we are:
The Pasadena Ruby Brigade is a group of programmers who are interested in Ruby. We enjoy sharing our ideas and code with anyone who is interested in this wonderful programming language.

Who can join:
Anyone! Just head over to our mailing list and drop us an email.

What we do:
We recently started a project over at RubyForge. This project is a group of programs written and maintained by our members that we thought could be beneficial to the whole community.

Projects

Downloads

Recent Posts

Archives