Sunday, May 10, 2009

Erector - Object Oriented views

I just got back from railsconf. On the plane ride home I decided to take a look at Erector. Erector, besides having a cool name, is a dsl for writing markup. It is very similar to markaby, except erector views are not templates, they are plain old ruby objects. Let's look at an example:

class Views::Home::Index < Views::Layouts::Application
def main_content
h1 'Welcome to Voomify'
p do
text 'What is Voomify?'
end
p "Voomify is the verb for applying Voom to a problem."

p "Voom as defined by Dr Seuss in 'The Cat in the Hat Comes Back':"

blockquote do
p "'Voom is so hard to get,
You never saw anything
Like it, I bet.
Why, Voom cleans up anything
Clean as can be!'"

p "Then he yelled,
'Take your hat off now,
Little Cat Z!
Take the Voom off your head!
Make it clean up the snow!
Hurry! You Little Cat!
One! Two! Three! GO!'"

p "Then the Voom...
It went VOOM!
And, oh boy! What a VOOM!"


p "Now, don't ask me what Voom is.
I never will know.
But, boy! Let me tell you
It DOES clean up snow!"
end

end
end


This will render the following code:


<h1>Welcome to Voomify</h1><p>What is Voomify?</p><p>Voomify is the verb for applying Voom to a problem.</p><p>Voom as defined by Dr Seuss in 'The Cat in the Hat Comes Back':</p><blockquote><p>'Voom is so hard to get,
You never saw anything
Like it, I bet.
Why, Voom cleans up anything
Clean as can be!'</p><p>Then he yelled,
'Take your hat off now,
Little Cat Z!
Take the Voom off your head!
Make it clean up the snow!
Hurry! You Little Cat!
One! Two! Three! GO!'</p><p>Then the Voom...
It went VOOM!
And, oh boy! What a VOOM!</p><p>Now, don't ask me what Voom is.
I never will know.
But, boy! Let me tell you
It DOES clean up snow!</p></blockquote>



Admittedly this is not a very good example showing any benefit at all to using Erector. It is all markup and requires no real logic at all. Be patient we'll eventually get there.


Why would anyone want to do that? We'll for one I hate markup. (When I'm in full markup mode, i feel dirty.) If you love markup and erbs, then you should stop reading now. Before you leave, I'll leave you with this, you can easily mix erector and erbs (or any other template that works with rails). Some tasks are very well suited to markup and others are better suited to a markup dsl. If you are tag heavy then you should stick with your erb's. If you find you views are light with markup but heavy with ruby flow and control logic then erector or markaby may be the ticket.

Markaby or Erector



I dont' know much about Markaby. After taking Erector for a spin, I took a closer look at Markaby. I like it. The biggest difference to me is that Markaby allows you to create ruby template files the same way erb's do. The views are still templates with ruby syntax. They are not first class ruby objects. With erector they are first class plain old ruby objects. Why is this good? It gives you all the tools of inheritance and mixin's for your views. That is cool. Especially for an application with multiple views of the same underlying models. You can refactor your views into base classes that derive and render the same data in different ways. This is object oriented design for views. Nice.


Side Note



I've seen object oriented view code in other languages and it leads to some very powerful re-use that all OO programmers can understand. The most ambitious of these attemps was by an HR company named Seeker in the bay area. I was working for Concur at the time and we bought Seeker back in 1999. (It did not work out well, but that is a story for another time.) Seeker created their own markup language that was object oriented. The nature of HR data is that it has very complicated rules regarding who can see what data and when. The OO design of the language allowed that to be abstracted to the base classes and a functional programmer simply focused on the problem at hand. They took it further, as all commercial enterprise applications do, and they allowed the customer to define new models and views. Those views were very easy to write with this advanced data access logic abstracted out. Their customers loved it. They wrote very advanced business applications on top of this abstraction.


A Closer Look



So let's look a little deeper at using Erector with rails. Follow the erector installation and you'll be able to write an erector view. One gotcha I ran into was the fact that your views should derive from Erector::RailsWidget. If you don't do that you don't have access to the rails helpers.

To layout or not to layout



You have a couple of options for the layout. You can keep your erb layout and just drop in your Erector views. That is cool if you have an app already that you want to mix erector into quickly. It also allows the views to be rendered with different layouts.


The other option is to create an Erector layout. If you do that then you set your controler like so:


class HomeController < ApplicationController
layout nil
...


Here is a simple erector layout:


class Views::Layouts::Application < Erector::RailsWidget
def javascript_includes
javascript_include_tag 'application'
end

def stylesheet_includes
stylesheet_link_tag 'styles'
end

def header
h1 'Voomify'
end

# override this to render your view 'main' content
def main_content
end

def footer
a 'Home', :href=>"/"
end


def content

html do
head do
title "Voomify"
javascript_includes
stylesheet_includes
end
body do

div :id=>"maincontainer" do

div :id=>"contentwrapper" do
div :id=>"topsection" do
header
end

div :id=>"contentcolumn" do
main_content
end
div :id=>"footer" do
footer
end
end
end
end
end
end
end




Then you derive your view from the layout erector class. The view example above does just that. Notice that the view defined above implements main_content. This is the method defined by our layout, you can name it whatever you want. You'll also notice that it has methods to override for the header, footer, javascript_includes and stylesheet_includes. So if the page want's to modify any of those elements all it needs to do is override those methods. It can call super if it wants or just replace it.


This got me thinking about the decoupled relationship between templates and their view. The view does not know anything about where they are being rendered. Generally this is a very good thing. For example a view may be rendered on a page, or as an ajax call. Coupling the view to the layout creates a tight dependency between the two. This troubled me at first. (OK not that much, the world has bigger problems!) But then I started thinking about it. How often do I have single view that has a different layout? Not much. When I do with Erector I could use a decorator pattern from my base layout. OK that works. What about ajax forms? I have not tried it yet, but I'm pretty sure I can call my main_content to return to an ajax request.



OK so not very often do I have multiple layout variations with the same view. What does happen a lot is that I would like to make a modification to how the layout renders based on the view. Eventually the layout ends up with conditionals that render some of the erb if some variable is set by the controller. Yuck! How many times have you done that. (Be honest!) You say to yourself its ugly, but it's a template. You grit your teeth and move on. Or if it really bothers you, you introduce a helper, but it suffers from the same condition, branching logic, but now its written in ruby. An Object Oriented layout can easily eliminate that. Another case is when a view participates in another relationship. For example a given view may be related to other models in the system. The layout defines a standard layout for this relationship and then the view code implements main_content and related_content. The related_content method could return markup, or the layout may take care of the markup, and all the related content needs to do is return the model objects that are related to my current view.


In my next post I'll be building a simple contact model and then turning it into an Erector set of views. Until then ... keep it clean.

No comments:

Post a Comment

Post a Comment