I’m working on some improvements to Basecamp, specifically the screens where you manage which people have access to a project. There’s an area on our new template with checkboxes beside peoples’ names so you can check which people should be added to your project. I want to apply a class name to the label tag around the checkbox for each person. So I pulled up the template and searched for “label” to find where I might add the class name. There were no matches. So I dug deeper, and saw that the HTML for the label, checkbox, and person name is being generated by a Rails helper method.
This is the template code that calls the helper method.
<% people_without_access_from(company).each do |person| %>
<%= add_person_to_project_check_box(person, company) %>
<% end %>
Next I pulled up the helper method to see if it really is responsible for producing a chunk of HTML with the label and checkbox.
def add_person_to_project_check_box(person, company)
content_tag(:label,
check_box_tag("people_ids[]", person.id, false, { :class => "company_#{company.id}_person" }) +
" " + person.name
) + tag(:br)
end
Yup, it’s generating the HTML. Right away this smells bad to me. The helper is first generating a label tag. Inside that label, there is a checkbox followed by a space character and the person’s name. Finally a break is appended after the label. This smells bad for two reasons. First, it’s just not so nice for helpers to cook out HTML when they don’t have a good reason to. Second, it’s harder to locate and change HTML when it’s hidden inside a helper.
Returning to my original goal, I wanted to add a class name to the label around this checkbox. If I add the class name to the existing helper, it’s going to get even more messy and complicated because I have to give content_tag an attribute with the class name I want. It would look like this:
def add_person_to_project_check_box(person, company)
content_tag(:label,
(check_box_tag("people_ids[]", person.id, false, { :class => "company_#{company.id}_person" }) +
" " + person.name
), :class => 'checkbox') + tag(:br)
end
To find a better solution, we should rethink what the helper should be responsible for. Helpers are useful when they hide complexity that isn’t relevant to the template. Looking at this helper method, I see that it’s useful to hide away check_box_tag with all those params. But the label, the break, and even the person’s name could all be in the HTML and the template would be clearer. Let’s do that.
Here’s the new helper. Now it only produces the checkbox.
def add_person_to_project_check_box(person, company)
check_box_tag("people_ids[]", person.id, false, { :class => "company_#{company.id}_person" })
end
And here’s the updated template code, with the label, person name, and break moved over.
<% people_without_access_from(company).each do |person| %>
<label class="checkbox">
<%= add_person_to_project_check_box(person, company) %> <%= person.name %>
</label><br />
<% end %>
Now that’s a lot easier to read. Generally speaking, it’s a good idea to keep your HTML in your templates and out of your helpers. Helpers are useful when they hide implementation details that are irrelevant to your template, or when they allow you to abstract common template code to avoid repetition. If you find yourself generating a lot of HTML in a helper, think twice and try to keep as much HTML in your template as possible.
GeeIWonder
on 24 Jun 08Not a Rails guy, but the question always occurs to me: Any Benchmarks on that change?
Gnome
on 24 Jun 08It’s really cool to see a ‘design’ standpoint on this. This is actually one thing that turned me off from Rails in general to be honest. It just seemed like the community in general would generate helpers like this and they never seemed particularly ‘helpful’ to me from a separation-of-concerns standpoint. Your refactoring makes great sense though.
Jack Jennings
on 24 Jun 08I’m wondering what to do when the HTML gets more complicated. Does it make sense to render a partial 50+ times? I was always under the assumption that pulling partials was not the most efficient way to render things over and over.
Noah Stokes
on 24 Jun 08You could set your label.checkbox to be a block element and do away with the br tag altogether…
Kyle
on 24 Jun 08Personally, I think this would be better off in a partial. Generating tags in a helper seems like overkill to me, especially since the logic isn’t very complex.
It would turn the line in your view to:
<%= render :partial => ‘person_checkbox’, :collection => people_without_access_from(company) %>
or something similar.
Eric
on 24 Jun 08Thanks Ryan,
This was really helpful. I was bummed I didn’t get to see your talk at railsconf (is there a video?) and would love to see more of this type of post.
Raphael Campardou
on 24 Jun 08Hi Ryan. As David said : All code will eventually go stale ... I guess you have to find the right balance between refactoring and leaving it the way it is. Anyway, it’s cool to have a few technical posts once in a while. I second Eric: I wish there were a more of them.
Pete Forde
on 24 Jun 08I’m surprised that you’re using tag builder helpers anyhow, but my bias likely stems from using Haml for two years.
Have you tried Haml, Ryan? If you haven’t, I challenge you to try it out on a small project, just to see how you like it.
It’s not for everyone, but our experience is that designers seem to LOVE it.
RS
on 24 Jun 08I tried Haml but found it too limiting and also unnecessary. I really enjoy HTML/ERB.
leethal
on 24 Jun 08Oh hey, code talk on svn. And you have syntax highlighted code, too. Rawr!
leethal
on 24 Jun 08Oh, and regarding the article. I really prefer to make a form builder. And then, add methods to the form builder, than is DRY-ing up all the form code. Try it, it’s fun and rewarding for all sides!
http://pastie.org/221428
GeeIWonder
on 24 Jun 08Code looks much better on dark and colored like this, btw.
Nate
on 24 Jun 08I absolutely love Ryan or anyone there doing Rails tech talk on this blog. This is awesome. Please do more!
Pablo
on 24 Jun 08What about the other CSS class? wouldn’t it be nicer outside the helper?
Pablo
on 24 Jun 08ah! I almost forgot! Keep this kind of posts coming!!
Scott
on 24 Jun 08Pablo,
I think having a class in the helper is largely dependent on what that class is used for. In this case is appears to be for a programmatic purpose rather than stylistic. If in another case it was being added for a purely stylistic reason I’d first see if using decendent selectors within the css couldn’t accomplish the same thing.
Micheal Morgan
on 24 Jun 08Regarding form generators, it really depends on context. For simple, straight forward views with fairly static variables, a form generator keeps things DRY and allows for quick global changes. However, for more dynamic forms I find that maintenance can simplified if you can dive in and pinpoint changes quickly. Finding the right balance is a continuous challenge, as illustrated in this post.
Of course when CSS3 is fully supported, you could just select the checkbox attribute and not have added a class to the label. :-D
Nikc
on 24 Jun 08So form controls go inside labels?
Steven Walker
on 24 Jun 08A look inside successful design is always refreshing and informative. Nice.
Arik Jones
on 24 Jun 08More and more I’m noticing that Ryan Singer rides closely to how programmers think and work. Definitely a no-frills kind of designer, something I aspire to be.
Brad
on 24 Jun 08Ryan, Great example for your previous post: Designers who also develop have more power
Josh Goebel
on 25 Jun 08Ryan, awesome post. But watch out… learn too much and you’re going turn into a full-fledged developer. :-)
Jeff Dean
on 25 Jun 08That’s a great example of using a helper, but I’m not sure the generated HTML is valid. For a label to be clickable, and conform to accessibility standards it needs a “for” attribute that corresponds with the checkbox id.
Another minor point is that your code generates a separate class for each checkbox – which defeats the purpose of having a class there. Typically I see (and write) checkboxes outside of the label as well – I’m not sure if it’s valid to put inputs inside labels.
You might want to do something like this (forgive the formatting):
def add_person_to_project_check_box(person, company) check_box_tag(“people_ids[]”, person.id, false, { :id => “company_#{company.id}_person”, :class => “company_person” })
end
<% people_without_access_from(company).each do |person| %> <%= add_person_to_project_check_box(person, company) %> <= “company_#{company.id}_person” %>><= person.name %>
<% end %>
That should make the HTML valid (refactoring the label to it’s helper is an exercise for the reader…) and when you click on the label, the checkbox gets checked.
RS
on 25 Jun 08Thanks for your thoughts Jeff. The class on the checkbox isn’t unique. That class groups the checkboxes by company so we can select or deselect all people within a company if we wish. Since it isn’t unique, it has to be a class instead of an id.
Re: Validity, I prefer wrapping the checkbox and text in a label tag instead of using a “for” attribute. The code is cleaner and simpler that way. Code style is more important to me than validity, when the effect is the same.
GeeIWonder
on 25 Jun 08Code style is more important to me than [HTML] validity, when the effect is the same.
A contentious statement, I’m sure.
Christophe Porteneuve
on 25 Jun 08AAMOF, labels wrapping the relevant control, text and whatever are a perfectly valid way of structuring your markup (called “implicit association in the W3C TR). It just so happens that most people (myself included) prefer the for= attribute (styling forms the way I want them with “wrapper labels” would require extra spans or divs around the text proper in most cases for me).
Cameron
on 25 Jun 08Cool Post. We like rails code posts.
Dmitry
on 25 Jun 08A contentious statement, I’m sure.
How is it contentious? Code is your tool, and your are the master—not the other way around. Standards aren’t static, they will keep evolving as people figure out better practices. There is a big difference between an inexperienced programmer blindly breaking standards and churning out bad code, and a good programmer who takes educated decisions about how to structure their code.
daniel lopes
on 25 Jun 08Cool, nice to see code here.
Michael
on 25 Jun 08These kinds of posts are great. I’m curious though, how do you make sure that nothing else is calling this helper? You have made significant changes to it’s function which could break other code that calls this same helper.
RS
on 25 Jun 08The majority of our helpers are scoped by resource. So in this case, the purpose of the template is to control which people have access to a project. The resource we are creating/reading/updating/destroying is called an “involvement.” As in “which people are involved in a project?” All the templates for involvements are in their own directory, and the helpers are in their own involvements_helper.rb. Keeping the code scoped like this gives me confidence that all the methods in the helper only pertain to the screens for involvements.
john
on 25 Jun 08the effect may not be the same when viewed in a text-only browser… that said i’ve used checkboxes and radios inside labels for years. i believe that is legal - go the extra step and add the for”” attribute though - it’s a Good Thing.
William T
on 25 Jun 08I often have issues wondering which type of refactoring (if any) is best to use in Rails apps. I am looking forward to a Rails Best Practices type book, like the one Damian Conway wrote for Perl 5.
Helpful post
This discussion is closed.