Rails: An Adventure With Don’t Repeat Yourself (DRY) Template
So you heard of Ruby On Rails (RoR), watched a 15 minutes screencast. And you kind of liked it. You tried coding ‘Hello world’, and you said, “Hey, this is easy”. You thought you should push yourself and try out a more complicated stuff, stuff real developers do.
You wanted to create a few standard panels on your home page, because almost every site you visited lately have panels on their homepage. You wanted each panel to have its own header and footer, probably something like this:
<div class="panel">
<div class="header">Panel Name</div>
<div class="body">Some content here....</div>
<div class="footer">Panel Footer</div>
</div>
And you did it real fast, you were done with 6 panels, 2 on each column for a 3-column layout in 2 minutes. But… hm… you did not like your codes, because you see a lot of repeated <div> elements.
You remembered Ruby programming language advocates Don’t repeat yourself. You are a smart guy, you found a solution in no time, “Why not create a helper ‘panel’ method?”“. Then you can do something like this in your RHTML template file.
<% panel (:header => 'Panel header', :footer => 'Panel footer') do %>
Some content here...
<% end %>
That surely looks more beautiful than the codes above. You started to love RoR even more, but… when you tried to create the helper method, you bumped into some problems.
class ControllerHelper
def panel(options={}, &proc)
# How to add panel header <div>s?
# Generate content
yield
# How to add panel footer <div>s?
end
end
You were a Java web developer, your first instinct is to find the response outputstream. You found the closest match, 'response.out' but it did not return the template output, instead it passed you a standard output.
You read about render :text or render :inline, you tried them too, but they throwed an error saying that you should not call render statement more than once.
You asked uncle Google for ‘rails response outputstream’ and you tried other questions too, but you could not find an answer.
You were getting frustrated, but you thanked God that people still use Internet Relay Chat (IRC) these days, and you found #rubyonrails channel flooded with people. You asked your question, but no answer, you asked again, and again, no answer. Frustrated, tired, and dehydrated, you started to hallucinate that you probably was a ghost, you saw your own message, but others did not.
It got nasty this time, you started screaming words you did not want your mum to hear, ‘WTF’, @#%@$%^*!!!.
And your did not love RoR anymore.
…
By luck, fate, chance or whatever it was, you probably found this page, coz there was another guy like you, faced a similar issue, he found a solution, and he shared his solution. He said you probably want to do it this way:
class ControllerHelper
def panel(options={}, &proc)
# add panel header to current template IO stream
concat("<div class='panel'><div class='header'>#{options[:header]}</div><div class='body'>", proc.binding)
# Generate content
yield
# add panel footer to current template IO stream
concat("</div><div class='footer'>#{options[:footer]}</div></div>", proc.binding)
end
end
And it worked like magic, you started to read more about ActionView::Helpers::TextHelper#concat, more about Binding, and you started to understand that RoR behaves differently in this aspect, coz the RHTML template file is compiled by ERB library, and concat method helps you to retrieve the RHTML output sting buffer through the ‘Binding’ object and the variable _erbout.
Shortly after, you understood more about Ruby and RoR, and your love for Ruby and RoR started to grow again. You called it a day, a happy man because you found a solution to your problem.
You promised yourself that you would want to share solutions to problems you are facing in the future, just like what that guy did. And you hoped for a better world, a world where less of us have to say words our mum would not like to hear.

Add Your Comment