-
Get a monthly update on best practices for delivering successful software.

Last week's post about ERb and other HTML + Markup view languages got five actual comments. By the standards of my recent posts, that's practically a mob.
Most of the commenters wanted me to look at their favorite view tool, which I dutifully did. I then went out and did something completely different.
I covered HAML and Markaby a bit in Professional Ruby on Rails. Markaby I like conceptually, but the concerns about it's structure and speed have kept me from using it more (although the solution I did pick is probably not a speed demon either). I find HAML really opaque, but if it makes sense to you, more power to you. Erector is in some sense an enhanced Markaby.
Commenters also wanted me to look at Kwartz and Wicket, which are both tools where the template is valid HTML, but a special attribute namespace is used to tell the templating system to insert content and logic. I like the idea in theory, but anything that uses XML namespaces makes me break out in hives (to be fair, the Wicket folk say they aren't using XML, just the same namespace syntax). It's a little more high-ceremony then I needed.
Looking at all these solutions was quite helpful in making me think about what I actually wanted. One issue I had with Erector, and to some extent Markaby, is that the entire output page is an object -- to use a layout you subclass it. Using inheritance in this case doesn't work for me, I'm having a hard time seeing the layout/page relationship as IS-A, it really feels like a HAS-A relationship to me. Besides, I like the ability to use HTML for static parts and where appropriate -- I still haven't seen a markup language that handles a mixed mode case like this very well:
<div>This is <b>the</b> <i>best</> tool</div>
What I really want is the ability to treat chunks of output as objects and drop them into the output with a single line of code, and be able to cleanly specify the HTML related to the object in Ruby code. This is the Presenter pattern, and there have been a couple of different attempts to integrate it into Rails.
I don't quite have a full presenter pattern, but I do have a way of generating the HTML that I like. It's basically a wrapper around the Rails content_tag helper, but fixing two problems I had with content_tag.
The content_tag method takes an optional block which is evaluated as the contents of the tag. However, since it's an ordinary Ruby block, only the last value is returned. This is a problem if you are composing multiple subtags inside the block. You need to concatinate or join the subtags together, otherwise only the last one is used, making the code inside the block ugly. Also, the block version is dependent on ERb being around in certain cases, which makes content_tag hard to unit test.
My current solution is a Ruby evolution of the tag-generation library I mentioned in the last post. This being Ruby, it's about 25 lines of code.
Usage looks something like this:
def object_to_vertical_rows(obj, *rows)
rows.map do |method, caption|
Html.tr do |tr|
caption = method.to_s.humanize unless caption
tr < "caption")
tr << Html.td(obj.send(method))
end
end
end
Html is a class with a tag name, a content array, and an options hash. There's class level method_missing function that just creates a new instance with method name, so Html.tr prepares a <tr> tag pair. The content of the tag can be specified as the second argument, or built up in the block. The block takes the tag being built as it's argument -- giving that block variable the same name as the tag is a trick to make the block more readable. Any options passed to the constructor are treated as attributes.
Inside the block, content can be added to the tag using the << operator, which just contatinates the new content to the existing content, eliminating the need for ugly join code in the block. You can also assign content to the content attribute directly. Inside the block, you can also assign attributes -- it's another method_missing deal, where any unknown method is treated as an attribute of the resulting HTML output.
Here's what I like about this setup:
content_tag.content_tag with block.Here's what I don't like about it:
content_tag, although.instance_eval kind of magic needed to reduce it completely. On the plus side, it does have a nice explicitness to it.Anyway, here's the current implementation of the class -- if there's interest, I'll throw it on GitHub.
class Html include ActionView::Helpers::TagHelper attr_accessor :tag, :content,ptions def self.method_missing(name, *args, &block) Html.new(name, *args, &block) end def initialize(tag, content = [], options = {}, &block) @tag = tag if content.is_a? Hash @options = content @content = [] else @content = [content] @options = options end block.call(self) if block end def method_missing(name, args) options[name] = args end def <<(text) content << text content.compact.flatten end def to_s content_tag(tag, content.join("\n"), options) end def to_str to_s end end
Related posts:
Topics: Ruby on Rails
Looks good to me. I kinda like the syntax and how it flows. I would like it even more if it didn’t depended on ActionView. I might need to fork it someday!
AEM
Comment by Adrian Madrid, Friday, May 23, 2008 @ 5:37 pm
I do hope to break the dependency on content_tag, but it’s a convenient way to get the markup right.
Comment by Noel Rappin, Friday, May 23, 2008 @ 6:21 pm
[...] experience at a Birds of a Feather hosted by Pivotal Labs, where I got discussing view layers and my issues with Erector with the actual developers of Erector. That was a fun [...]
Pingback by Pathfinder Development » RailsConf 2008: A Belated Look Back, Friday, June 13, 2008 @ 4:27 pm
[...] wrote the view code for this using the HTML code generating helper I wrote about a few weeks ago. I still like the resulting code, although it has been called, and I [...]
Pingback by Pathfinder Development » Project Website, Part Two: Simple jQuery With Rails, Monday, July 14, 2008 @ 11:33 pm
[...] I’m also interested to see how this works with my Html generator mini-library [...]
Pingback by Pathfinder Development » Rails 2.2 For Me And For You, Monday, November 3, 2008 @ 1:50 pm