Matthew Bellantoni

Generating Complex Tables Using Ruby on Rails

rails, ruby

In couple of places (so far) in my current project I'm creating tables where the presentation varies widely between rows based on the nature of the data.  I ended up with a solution that I've never seen before, so I thought I'd share.  I'm using Rails 3 with Ruby 1.9.2.

Note, the data I'm presenting in my app is actually tabular in nature--I'm not just using the table as a presentation hack. (I think!)

Where I Started

The first solution involved a lot of presentation logic in the view.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<table>
  <tbody>
    <% person.list_of_things.each do |thing| %>
      <% case %>
      <% when thing.cond1?
        <%= render(:partial => "type_1"), :locals => {:person => person}) %>
      <% when thing.cond2? %>
        <%= render(:partial => "type_2"), :locals => {:person => person}) %>
      <% else %>
        <%= render(:partial => "type_3") %>
      <% end %>
    <% end %>
  </tbody>
</table>

This is pretty horrific, and the above example is greatly simplified from my actual case: the conditional expressions in reality are more complex and there's another nested level of control structures.

As you can see, there's a partial for each type of row. I won't show the code, but its pretty straight forward: each partial renders a single row, each of which has some very unique elements (most related to icons and other images contained in the cells.)

Where I Ended Up

A way to get the logic out of the view is to use a helper.  My helper iterates through the row data, and creates a lamba function that calls the appropriate render function.  Those lambdas are then executed in the view.

This is what the helper looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  def compute_table_rows(person)
    person.list_of_things.map do |thing|
      case
      when thing.cond1?
        lambda do
          render(:partial => "type_1"), :locals => {:person => person})
        end
      when thing.cond2?
        lambda do
          render(:partial => "type_2"), :locals => {:person => person})
        end
      else
        lambda do
          render(:partial => "type_3")
        end
      end
    end
  end

The code in the view now looks like this:

1
2
3
4
5
6
7
<table>
  <tbody>
    <% compute_table_rows(person).each do |row_proc| %>
      <%= row_proc.call %>
    <% end %>
  </tbody>
</table>

Commentary

There are other ways to do this.  I could have just iterated over the list with Enumerable#inject (a.k.a. Eumerable#reduce) to build up a single large string in the helper and then just ship that back to the view.  However, I didn't like the idea of building up a huge string in the helper and it feels less functional to me.  (Yeah, I know that's a persuasive argument.)

I don't love helpers.  While I'm not as zealous about this point as some others, helpers do seem out of place in an object-oriented, functional programming environment.  This one should probably be an object itself. (I'd love a pointer to a pattern anyone uses for these kinds of objects.)