Code Spelunking in the all new Basecamp

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:

MySQL: Basecamp’s best friend. Stores pretty much all application data.

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:

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

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!

Alan Hogan

Not sure why a technical blog post would ever have a datestamp that does not include the year.

Anonymous Coward

on 27 Apr 12

@Alan come back next year and you will see 2012 on it.

Larz Conwell

on 28 Apr 12

Have you guys tried using PostgreSQL? It has built in full text search capabilities. So using it you could cut out the whole Elastic Search piece, and probably make it a little faster, who knows though!

jsuchal

on 28 Apr 12

Larz Conwell: As much as I like PostgreSQL, ElasticSearch’s features and results quality are far superior to build-in PostgreSQL fulltext search.

verticonaut

on 28 Apr 12

Thanks for sharing this. Some small – but really helpful bits.
NB: Alan is right. Wheras ‘5 days ago’ switching to a full date is an understandable date with a little ‘this is smart’ effect. Omitting the year is simply confusing.

Larz Conwell

on 28 Apr 12

jsuchal: Ah reaqlly? I haven’t tried it yet so I didn’t know what was better.

Luke

on 28 Apr 12

Your link to launchpad is broken. Looks like someone forgot to prefix the URL with “http://”...
Rookie mistake – hate to see it.