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

Bustin' out "joins"

Here at t-bot we scrunitize every line of code. Here’s one from the Rails’ world.

Ok, pretend we have a site where each registered user has their own blog. Naturally we’d have the ability for you to view someone’s blog. Following a design where each model has its own controller that handles all its CRUD, we put this feature in the #show action like so:

user.rb
1
2
3
4
5
class User < ActiveRecord::Base

  has_one :blog

end
blog.rb
1
2
3
4
5
class Blog < ActiveRecord::Base

  belongs_to :user

end
blogs_controller.rb
1
2
3
4
5
6
7
class BlogsController < ApplicationController

  def show
    @blog = Blog.find params[:id]
  end

end

The URL of that action would look something like ’/blogs/show/1’.

But wait. That URL is just not pretty enough. The client asks it to be changed to ’/blogs/show/chad’, ‘chad’ being the username of the user that the blog belongs to. When users register we also validates_uniqueness_of :username, to ensure its unique.

Alright thats not that hard. Our first try:

routes.rb
1
2
3
4
5
ActionController::Routing::Routes.draw do |map|

  map.show_blog 'blogs/show/:username', :controller => 'blogs', :action => 'show'

end
blogs_controller.rb
1
2
3
4
5
6
7
8
class BlogsController < ApplicationController

  def show
    @user = User.find :first, :conditions => ['username = ?', params[:username]]
    @blog = @user.blog
  end

end

Ugly. I don’t like setting instance variables to objects that I can navigate to from other instance variables I’ve already set. I already found the User object with the given username, I can then just ask it for its blog. I like keeping the actions short and the instance variables to the absolute minimum that I need. How about making user a local variable? Nah, I don’t like local variables in actions.

Let’s try again.

blogs_controller.rb
1
2
3
4
5
6
7
class BlogsController < ApplicationController

  def show
    @user = User.find :first, :conditions => ['username = ?', params[:username]]
  end

end

Nope, still don’t like it. Like I said earlier, I like to let each model have its own controller that handles all its CRUD e.g. Blog model, BlogsController. I expect the model’s controller to be only working with objects of that type e.g. the BlogsController should only be dealing with Blog objects but now the #show action find’s a User object. That doesn’t seem right to me.

Here we go again.

blogs_controller.rb
1
2
3
4
5
6
7
8
9
class BlogsController < ApplicationController

  def show
    @blog = Blog.find :first,
       :joins => 'inner join users on users.id = blogs.user_id',
       :conditions => ['users.username = ?', params[:username]]
  end

end

Oooo. No more User object, I like that. The BlogsController is now only working with Blog objects, another plus. But…the ‘joins’ keyword parameter.

From the Rails doc

:joins: An SQL fragment for additional joins like “LEFT JOIN comments ON comments.post_id = id”. (Rarely needed).

Rarely needed….hmmm. Some of you might think I’m doing this for performance. After all, if I loaded just the User object that would be 1 query, then when I navigated to the user’s blog that would be another query. 2 queries comparied to 1. I also could get the same performance as the query using ‘joins’ by using the ‘include’ keyword parameter in my #find to eagerly fetch the user object’s associated blog, but nah I don’t feel like doing that because I’d still be loading a User object in the BlogsController and that feels wrong. I’m not doing this for performance reasons, its purely for clarity.

The bottom line is: don’t trade pretty URLs for ugly code even if that means you have to bust out ‘joins’.


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.