Welcome to Giant Robots Smashing Into Other Giant Robots — a weblog about development, business, design and technology — written by thoughtbot.

It's the little things...

Sometimes it’s the little things that really make coding fun.

We used to use a common pattern that helped out with our view code. By adding the link_to_xxx helpers, we could make our applications more consistent and maintainable:

1
2
3
4
5
6
7
8
9
10
11
12
13
def link_to_candidate(candidate, msg = nil)
  link_to(msg || h(candidate.name), candidate_path(candidate))
end

def link_to_issue(issue, msg = nil)
  link_to(msg || h(issue.title), issue_path(issue))
end

def link_to_intern(intern, msg = nil)
  link_to(msg || h(intern.name), intern_path(intern.candidate, intern))
end

#...and on...

But this grows out of hand pretty quickly—In one application we have over 30 of those puppies. Well, we figured out that by being a little clever, we could really clean this up…

1
2
3
4
5
6
7
8
9
def link(item, msg = nil)
  case item
  when Candidate: link_to(msg || h(item.name),  candidate_path(item))
  when Issue:     link_to(msg || h(item.title), issue_path(item))
  when Intern:    link_to(msg || h(item.name),  intern_path(item.candidate, item))
  #...
  else raise ArgumentError, "Unrecognized item given to link: #{item}"
  end
end

Well, that’s much better. It only grows one line for each model instead of four, and it’s easier to call in the views.

1
2
3
4
Candidates!
<% @candidates.each do |candidate| %>
  <%= link candidate %>
<% end %>

But it still smells a little fishy. I don’t think anyone here at Thoughtbot likes seeing a case statement. Let’s get just a little more clever…

Abandon hope, all ye who enter here

1
2
3
4
5
def link(item, msg = nil)
  msg ||= item.send([:name, :title, :id].detect {|n| item.respond_to? n})
  method = "#{item.class.name.underscore}_path"
  link_to(msg, self.send(method, item))
end

If you’re still with me—this version of link() figures out what attribute to call on the give model and generates the xxx_path method. It’s very concise, and won’t grow with the size of your code base, but hot-damn is it a doozy to decipher. But a larger issue is that we lost our ability to handle nested resources (like intern_path(intern.candidate, intern)).

Now, we definitely went with the case-statement version up there, but just as an exercise…

Let’s just say we required all nested models to provide a parents attribute, which returned the list of parent models. We could then clean up our link() method like so:

1
2
3
4
5
6
def link(item, msg = nil)
  msg ||= item.send([:name, :title, :id].detect {|n| item.respond_to? n})
  method = "#{item.class.name.underscore}_path"
  parents = item.parents rescue []
  link_to(msg, self.send(method, *parents, item))
end

I wonder what else could be simplified if the models could tell you what other models proceed them in the resource chain.


About this entry

 

thoughtbot is hiring

We are hiring web developers and web designers in both Boston and New York, NY.

What are we up to?

We built Shoulda, an eclectic set of additions to Test::Unit; Paperclip to manage uploaded files without hassle; Jester, a REST/ActiveResource client library written in Javascript, and Squirrel, an enhancement for ActiveRecord's find syntax; — amongst some other projects.


Chad (President) and Jon (CTO) co-authored a technical book titled Pro Active Record: Databases with Ruby and Rails, which explores the ins and outs of the ActiveRecord ruby library. You can buy it today at Amazon.com.

About thoughtbot, inc.

We are a small web application development consulting business, with offices in Boston, MA and New York, NY. If you're looking to find a team for your next web development project or your new web application — get in touch.