Tuesday, April 28, 2009

DRY views using builders - AKA markup sucks

In this post I'm taking a look at how to DRY the view code from the Getting Started with Rails

The first thing I noticed about the Getting Started Guide was that the mark-up was really not very dry, so the question became how can I dry up the mark-up? OK so it does use a partial for the shared edit and new form. It has been my experience that most forms and lists are pretty simple and should not need this much mark-up. Truth be told, I hate writing mark-up. So how can I describe the presentation with enough detail so that I don't have to write so much? Don't get me wrong I don't mind writing mark-up that is unique for a given page. Just don't make me write more form and table markup...please.

So we want to be dry, cool. But we also want to be DRO (don't repeat others). So who is solving this problem? Let's start with our forms. How to DRY our forms. A quick look around a common pattern appears: Use Builders. A builder allows an application to emit custom code for a given form field. Were going to examine the formtastic plugin:
* http://github.com/justinfrench/formtastic/tree/master

In our case we would like to write simple erb code that emits all the tags for our form. In keeping with our DRO model we are going to take the formtastic plugin for a spin and see what we get.

First thing is to install the formtastic plugin:


script/plugin install git://github.com/justinfrench/formtastic.git


Now let's see if we can update the _form.html.erb file using formtastic:


<% @post.tags.build if @post.tags.empty? %>
<% form_for(@post) do |post_form| %>
<%= post_form.error_messages %>
<p>
<%= post_form.label :name %><br/>
<%= post_form.text_field :name %>
</p>
<p>
<%= post_form.label :title, "title" %><br/>
<%= post_form.text_field :title %>
</p>
<p>
<%= post_form.label :content %><br/>
<%= post_form.text_area :content %>
</p>
<h2>Tags</h2>
<% post_form.fields_for :tags do |tag_form| %>
<p>
<%= tag_form.label :name, 'Tag:' %>
<%= tag_form.text_field :name %>
</p>
<% unless tag_form.object.nil? || tag_form.object.new_record? %>
<p>
<%= tag_form.label :_delete, 'Remove:' %>
<%= tag_form.check_box :_delete %>
</p>
<% end %>
<% end %>
<p>
<%= post_form.submit "Save" %>
</p>
<% end %>


With our new form builder we can dry this up a little bit:

<% @post.tags.build if @post.tags.empty? %>

<% semantic_form_for(@post) do |post_form| %>
<%= post_form.error_messages %>
<%= form.inputs %>

<h2>Tags</h2>
<% post_form.fields_for :tags do |tag_form| %>
<p>
<%= tag_form.label :name, 'Tag:' %>
<%= tag_form.text_field :name %>
</p>
<% unless tag_form.object.nil? || tag_form.object.new_record? %>
<p>
<%= tag_form.label :_delete, 'Remove:' %>
<%= tag_form.check_box :_delete %>
</p>
<% end %>
<% end %>
<%= form.buttons %>
<% end %>


A couple of things to notice when you run this. You now have some more view logic that was provided by formtastic. Required fields are marked on the form with an *. The form is now drawn with a fieldset. The form uses left justified labels. If you look at the browswer source you'll also notice that it uses a <ol>> elements. (This is why the fields are numbered.) The form fields tags now have classes and id's for all the tags.

OK that went pretty well..so let's go a step further and refactor the tags:

<% @post.tags.build if @post.tags.empty? %>
<% semantic_form_for(@post) do |post_form| %>
<%= post_form.inputs %>
<% post_form.semantic_fields_for :tags do |tag_form| %>
<% tag_form.inputs :name, :name => 'Tags' do %>
<%= tag_form.input :name %>
<% unless tag_form.object.nil? || tag_form.object.new_record? %>
<%= tag_form.input :_delete, :as=>:boolean, :label => 'Remove:' %>
<% end %>
<% end %>
<% end %>
<%= post_form.buttons %>
<% end %>


Now we are getting somewhere! No mark-up! I could get use to doing forms like this.

My next implementation step is to generate client side validations by reading the model validations and presenting them at input time. Check out the livevalidation plugin

No comments:

Post a Comment