Example 1. A Simple Helper

Fortitude expresses your view code as Ruby itself, using a simple DSL patterned after HTML. One big benefit of this approach is its consistency: views, helpers, and partials are all written in the exact same language, Ruby.

Ruby is a great deal nicer to work with than either an HTML templating language or the string interpolation of traditional helper methods. We’ll see the difference this can make in the following example.

What We’re Trying To Do

The following ERb code was extracted from a real-world application, and is a great, simple example of where Fortitude can help the most.

...
<a href='<%= conditional_refresh_url(:user => @user) %>'
   class="button icon refresh" onclick="javascript:handleRefreshClick();">
  <div class="button_text">
    <p>Refresh this page if:</p>
    <ul>
      <li>Content has changed</li>
      <li>Local data is <%= @out_of_date_condition %></li>
    </ul>
  </div>
</a>
...

Combined with appropriate CSS, this code creates an “icon button” — a small icon, with a tooltip available:

Unsurprisingly, this button gets used in many places throughout the application. Using ERb, let’s see what we can do to factor out this common pattern.

Using Standard Templating Engines

Using traditional templating engines, we have two choices:

  • We can create a helper, which is written using Ruby string interpolation and called like a traditional Ruby method;
  • Or we can create a partial, which is written using our templating language of choice and called using render :partial => ....

In either case, we’ll need to make sure we allow for several variations in our desired output:

  • The URL (the href parameter) is, of course, different in almost every case.
  • Sometimes we need to add additional attributes (e.g., onclick, data-*, etc.) to the a element, and sometimes not.
  • What’s inside the tooltip varies: it can be plain text or HTML, it can have variable substitutions — really, it can be anything at all.

Given these constraints, let’s see how each of these looks.

Using a Helper

Here’s what our new helper method looks like:

application_helper.rb
def icon_button(icon_name, target, tooltip_html, additional_attributes_string = "")
  "<a href="#{target}" class="button icon #{icon_name}" #{additional_attributes_string}><div class="button_text">#{tooltip_html}</div></a>"
end

And here’s how we’d call it for the example above:

...
<%= icon_button(:refresh, conditional_refresh_url(:user => @user), "<p>Refresh this page if:</p><ul><li>Content has changed</li><li>Local data is #{h(@out_of_date_condition)}</li></ul>", "onclick="javascript:handleRefreshClick();"")

What’s good and what’s bad about this?

  • Concise: Both the caller and helper are quite short; but:
  • Inconsistent: Suddenly, we’re forced to write both the helper method and the HTML for the interior of the button using Ruby string interpolation, rather than ERb — which leads to:
  • Messy: We have significant stretches of HTML sitting around in Ruby strings, which is quite hard to read;
  • Bad Formatting: Because of this, the generated HTML will be quite poorly formatted, with the HTML in those strings all run-together. But, most importantly:
  • Dangerous (XSS Potential): We’re now responsible for worrying about HTML escaping in a way we weren’t before: every single caller needs to make sure that any user data in the additional_attributes_string is escaped before being passed. Similarly, we need to remember to call h() on the variable being interpolated into the tooltip_html string before passing it, or else we’ll be vulnerable to XSS attacks again.

Yikes. We’ve factored out this helper method, which cleans up our calling code, but at a rather significant expense. Perhaps we can do better by creating an actual partial, instead?

Using a Partial

Here’s what our new partial looks like:

<a href="<%= target %>" class="button icon <%= icon_name %>"
   <%= (defined?(additional_attributes) ? additional_attributes || '') %>">
  <div class="button_text">
    <%= tooltip_html %>
  </div>
</a>

And here’s how we’d call it for the example above:

<%= render :partial => '/shared/buttons/icon_button', :locals => {
  :target => conditional_refresh_url(:user => @user),
  :icon_name => 'refresh',
  :additional_attributes => 'onclick="javascript:handleRefreshClick();"'.html_safe,
  :tooltip_html => %{<p>Refresh this page if:</p>
    <ul>
      <li>Content has changed</li>
      <li>Local data is #{h(@out_of_date_condition)}</li>
    </ul>}.html_safe
} %>

Again, what’s good and what’s bad about this?

  • Consistency/Inconsistency: on one hand, at least now the partial itself is expressed using ERb, not a Ruby string. On the other hand, we still have to use a Ruby string for the HTML we’re passing in to it.
  • Verbose and Messy: we’ve actually managed to create a caller that is 33% longer than the original code it replaced, and is actually quite a lot harder to read at first glance. (The helper method was a lot better here.)
  • Formatting: the resulting HTML will at least look a lot better than in our earlier example;
  • Method Signature: when reading the new _icon_button.html.erbpartial, how can you easily tell which variables you need to pass in, and which of those are optional? You can’t — except by reading through the entire text of the partial and thinking about each use carefully, which is really painful. (In this, the helper method was certainly a lot cleaner, too.)
  • Dangerous (XSS Potential): Once again, we’re now responsible for worrying about HTML escaping in a way we weren’t before: every single caller needs to make sure that any user data in the tooltip_html string is correctly escaped using h() before being passed in. (We also need to make sure we call #html_safe on the value we pass to additional_attributes, too; this is less dangerous, but also very easy to forget, and will cause corrupt HTML if we forget about it.)

Issues with Standard Templating Engines

Both cases above are far from perfect. Helper methods are more succinct to call, yet also a lot messier to both write and call when passing around HTML, and have serious XSS risks. Partials mitigate some of those problems, but introduce others, and are really verbose and messy to call.

Ironically, some of the issues above may feel a little unfamiliar, and it’s probably because of this: nobody does this — because both these approaches have such serious tradeoffs, most teams, developers, or designers just leave well enough alone for small(ish) examples like this, and repeat the original HTML everywhere, over and over. And when it comes time to change the HTML structure of this “icon button” element that’s been repeated all over the site, we either just bite the bullet and do a really painful global search and repeated manual modificaftion, or give up, hack on CSS to make it do sort of what we want it to do, and then leave that mess in place.

We imagine that this is just the way views are. In fact, these problems are all because we generally don’t have the right tools to do a better job. Views don’t need to be like this, any more than any code needs to be like this.

Using Fortitude

To see what Fortitude brings to this example, let’s start by looking at Fortitude code for the original example, before we try to refactor it:

...
a(:href => conditional_refresh_url(:user => @user),
  :class => 'button icon refresh') {
  div(:class => 'button_text') {
    p "Refresh this page if:"
    ul {
      li "Content has changed"
      li "Local data is #{@out_of_date_condition}"
    }
  }
}
...

We won’t delve more deeply into Fortitude’s syntax immediately, but it should be clear that Fortitude expresses HTML using a very simple Ruby DSL that’s easy to grasp.

Using this syntax, we can now factor out this shared code as a “helper method” that we can easily make available on any view that needs it.

(We put “helper method” in quotes because this isn’t a traditional helper that we’d put into something like app/helpers/application_helper.rb and write using Ruby string interpolation. Rather, it’s a method we define in a module, and mix into any view class that needs it — Fortitude views are actually just normal Ruby classes — or into our base view class to make it magically available everywhere.)

Using this strategy, we can create the following “helper method”:

def icon_button(icon_name, target, additional_attributes = { })
  a(additional_attributes.merge(
    :href => target, :class => "button icon #{icon_name}")) {
    div(:class => :button_text) {
      yield
    }
  }
end

And the calling code for the example above now looks like this:

...
icon_button('refresh', conditional_refresh_url(:user => @user),
  :onclick => 'javascript:handleRefreshClick();') {
  p "Refresh this page if:"
  ul {
    li "Content has changed"
    li "Local data is #{@out_of_date_condition}"
  }
}
...

The Benefits

Let’s take a look at how much cleaner this is. A few of the most obvious improvements:

  • Concise: The calling code is now actually both much shorter and more expressive than the code it replaced.
  • Consistent: both the new “helper method” and its caller are written in the same language — which is the same language you use for all Fortitude code and the entire rest of your application, Ruby.
  • Well-Formatted: Because Fortitude is not interpolating strings, but, rather, understands the structure of the HTML, all output is perfectly formatted. (In development, it’s indented correctly, no matter how the code is written; in production, it is automatically produced with minimal spacing, to reduce the size of the HTML transmitted across the network.)
  • Clean and Clear: Because it’s a Ruby method, any Ruby programmer will instantly understand what needs to be passed to it; because all of the code is Ruby, it all reads cleanly and clearly.
  • Safe: at no point does any caller, nor the method itself, need to worry about HTML escaping; all data will be escaped properly without worrying about it.

However, one of the biggest improvements we’ve made is slightly more subtle: by creating this new icon_button method, we’ve started slowly building up a customized view language that is specific to our needs and our application. As we proceed through further examples, we’ll see how powerful this can be in creating clean, concise, maintainable views that are a joy to use.