Sunday, May 10, 2009

Erector - ruby CRUD views

In my last post I created a simple Erector view with a layout and talked about the pro's and con's of using a ruby class based view. Before we go any further I want to re-establish that I'm not a fan of markup. So if you are a markup jockey ... then you might want to move on. This Object Oriented view business is probably not your cup of tea. After all who would want clean, testable code in their views? Besides me that is.

OK you decided that you care enough about your views to read on. Stick with it, its worth the ride.

Let's get started by using the default scaffolding to generate a customer model. Run the following:


script/generate scaffold customer first_name:string, last_name:string, company:string, phone:string, email:string


Then run the migration and test the pages.

rake db:migrate


Now lets turn these views into erector views. Erector comes with a tool that will turn your erb's into erector classes. Run the following from your application root directory:

erector app/views/customers/**

If you look in your views/customers directory you will now see .rb files. Let's compare the code. Here is our show.html.erb:


<p>
<b>First name:</b>
<%=h @customer.first_name %>
</p>

<p>
<b>Last name:</b>
<%=h @customer.last_name %>
</p>

<p>
<b>Company:</b>
<%=h @customer.company %>
</p>

<p>
<b>Phone:</b>
<%=h @customer.phone %>
</p>

<p>
<b>Email:</b>
<%=h @customer.email %>
</p>


<%= link_to 'Edit', edit_customer_path(@customer) %> |
<%= link_to 'Back', customers_path %>


Now here is the erector equivalent:


class Views::Customers::Show < Erector::Widget
def content
p do
b do
text 'First name:'
end
text @customer.first_name
end
p do
b do
text 'Last name:'
end
text @customer.last_name
end
p do
b do
text 'Company:'
end
text @customer.company
end
p do
b do
text 'Phone:'
end
text @customer.phone
end
p do
b do
text 'Email:'
end
text @customer.email
end
rawtext link_to('Edit', edit_customer_path(@customer))
text '|'
rawtext link_to('Back', customers_path)
end
end


It is pretty straight forward. One gotcha is that the base class is the
Erector::Widget class. The problem is that we are using rails helper and these are exposed using the Erector::RailsWidget base class. You can either change the base class to Erector::RailsWidget or you can introduce an erector layout that derives from Erector::RailsWidget and then you would derive from your layout class. (See my last post for more info on this.) The other thing you need to do is to move or destroy the erb files. If you don't move or delete them then your new erector view classes will not be found. Now you have erector views for your scaffolded customer. Take it for a spin.

I personally think the syntax is cleaner if we use the {} block syntax instead of do end. Here is the same view re-written to use {}:


class Views::Customers::Show < Erector::Widget
def content
p {
b {text 'First name:'}
text @customer.first_name
}
p {
b {text 'Last name:'}
text @customer.last_name
}
p {
b {text 'Company:'}
text @customer.company
}
p {
b {text 'Phone:'}
text @customer.phone
}
p {
b {text 'Email:'}
text @customer.email
}
rawtext link_to('Edit', edit_customer_path(@customer))
text '|'
rawtext link_to('Back', customers_path)
end
end


Now you have a feel for what it looks like. How about a quick refactor of this code. I personally don't like the hard coded labels, so let's remove that:


class Views::Customers::Show < Erector::Widget

def show_column(col)
p {
b {text col.to_s.titleize }
text @customer[col]
}
end

def content
show_column :first_name
show_column :last_name
show_column :company
show_column :phone
show_column :email

rawtext link_to('Edit', edit_customer_path(@customer))
text '|'
rawtext link_to('Back', customers_path)
end
end


Now that's what I'm talking about. Now gee, if i do that in all my show views, I can easily refactor that method and make it available in either a base class or a module mix-in. What I like is how easy the refactoring is. It feels as it should easy.

Rawtext and text


You'll notice that we output using either text or rawtext. The difference is text is escaped and rawtext is not.

2 comments: