Over the past few months I’ve learned an immense amount from the all new Basecamp codebase. This year for RailsConf, I was able to show off some new (and old!) patterns in the codebase along with some neat libraries that we’ve put to serious use for our customers. The slides are over at SpeakerDeck if you’d like to see them all, and hopefully a video of the presentation will be up soon!

Here’s a few choice sections from the talk, and plenty of great links to check out in the meantime:

Production setup

We’ve talked in bits and pieces about the infrastructure behind our apps, and I wanted to give a clearer picture of what Basecamp looks like both on our production machines and on each developer’s box.

For databases, we hook up to several:

There’s a mix of internal services as well, which are hooked up to through a series of gems and Rails engines:

  • Depot: Where your files are stored. I’m sure this is an old rails/train pun.
  • Portfolio: In charge of displaying and resizing avatars across all of the services.
  • Launchpad: Our single-sign-on gateway. Handles all authentication concerns.
  • Queenbee: Where the honey’s made! This is our internal billing system that manages plans, accounts, trials, and more.

Development setup

Thankfully, we don’t need to set up each individual service on our own machine to get hacking on Basecamp. Two are required though, along with the main Rails app: Portfolio, and Launchpad. The app would look pretty silly without avatars, and even locally we need to sign in to get access.

Running these services side by side is usually a challenge, but Pow handles it like a champ. If you haven’t checked it out yet, it’s a wonderful Ruby web server that hooks into OSX internals to make serving apps locally painless.

We also have a convention across all of our apps to use a single bash script that makes getting started with development so much easier. This script lives at script/setup in each repo. Typically, getting an app working on your machine feels like blowing on an NES cartridge. Having one script to be a “reset button” for your app that sets up the database, bounces your local web server, and does whatever custom wrangling required to get cranking away can save an immense amount of time and frustration for your team.

Be Concerned

ActiveSupport’s Concerns are used extensively in our models and controllers. Using them in your Rails app enables two big wins: it’s the simplest refactor out there, an Extract Method, and it allows you to group alike methods together instead of each class being a grab bag of unrelated functions. Here’s an example of our Ajax concern, used across several controllers:

# app/controllers/concerns/ajax.rb
module Ajax
  extend ActiveSupport::Concern
  included do
    before_filter :set_ajax_cookies
  end

  private
  def set_ajax_cookies
    # set some cookies!
  end
end

It’s a standard Ruby module, but the included block that allows class-level code to be be easily added in. Of course, even if you extract everything into concerns, your class can still be a ball of mud. Please don’t think this is an excuse to gloss over proper design for your code.

JavaScript Behaviors

The Ruby to CoffeeScript ratio is around 1:1 as well. Since Rails 3.0, using HTML5 data-* attributes for decoupling behavior between the client and server side has been immensely helpful. We’ve taken that to the next level with our usage of data-behavior. Here’s an example of what hooking up the date picker for todos looks like:

<label>
  <b>Set the due date:</b>
  <div data-behavior="date_picker"></div>
</label>
 $('[data-behavior~=date_picker]').live 'focus', ->
   $(this).datepicker()

This technique keeps complicated JavaScript logic out of the views, stops tying behavior to CSS classes and/or DOM IDs, and it’s still pretty easy to query the elements. Check out the usage of ~= as well, which allows us to chain multiple behaviors on the same element. If you’d like to get a start on using these in your app, rails-behaviors has some great examples.

Lots of Logging

Anything we can do to make debugging customer problems easier and faster is worth it. Noah blogged earlier this week about how we use marginalia to log the controller and action that each SQL query came from.

We also tag each log line in production with a unique ID for each request and the account ID of the user. This has two big benefits: we can easily grep out one entire request with the unique ID, and we split logs based on the account ID to narrow down the search.

Accomplishing this in Rails 3.2 is easy, thanks to ActiveSupport::TaggedLogging. Here’s a simple example of getting it running, crack open config/application.rb:

YourApp::Application.configure do
  config.log_tags = [ :uuid ]
end

After bouncing your server, you’ll start to see a unique ID for each request in the log:

[71ba53fd717c67a6677a058f4a5acdf4] Processing by ProjectsController#index as HTML

The config.log_tags array can also take a static string, or a proc/lambda to allow for more customization. I’ve pulled out some more examples to check out as well.

Console Mastery

There’s a lot more to the WebKit console than console.log. I remember when Firebug utterly changed how I worked with JavaScript. After learning some tricks out of this codebase and some great blog posts lately, I’m starting to feel that way again.

Two examples are console.warn(), and console.group() / console.groupEnd(). Warn adds the yellow icon, and grouping allows you to collapse/expand similar log messages:

These are only enabled when in development mode, but it’s really simplified how we dealing with log messages and nailing down performance issues.

Another great example is console.profile() and console.profileEnd(). There’s an entire tab for performance tuning and timing on the WebKit console. How did I miss this!?

This tool has been really useful for simply visualizing what’s slow. It’s a bit obtuse at times, and the nesting of how functions are called/displayed is a little wacky, but definitely check it out.

Finally, Chrome has a little piece of code that can show you how many milliseconds it’s been since the page load:

chrome.csi().pageT
// 2933006.221

This feels like it should be standardized somehow! In the meantime there’s a small snippet you can toss in to get it working in any browser.

Overload!

Hopefully you’ve picked up something new so far! There’s plenty more on the slides, along with some notes that conference-goers took. One huge take-away I’ve gotten from the Basecamp code base is that we’re still evolving and learning, and there’s no reason to stop!