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.
- Memcached: Used for our key-based caching system. Our 1 TB cluster just started evicting keys last week!
- Redis: Used mostly for Resque, and for tracking live page updates with sorted sets.
- Elastic Search: Fantastic, open source searching.
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!
Alan Hogan
on 27 Apr 12Not 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 12Have 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!
Lakshan
on 28 Apr 12Have you seen the NavigationTiming API? It standardizes load and rendering times across browsers – http://test.w3.org/webperf/specs/NavigationTiming/
Currently Chrome, Firefox and IE 9 implements it – http://caniuse.com/#feat=nav-timing
jsuchal
on 28 Apr 12Larz 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 12Thanks 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 12jsuchal: Ah reaqlly? I haven’t tried it yet so I didn’t know what was better.
Luke
on 28 Apr 12Your link to launchpad is broken. Looks like someone forgot to prefix the URL with “http://”... Rookie mistake – hate to see it.
Nick
on 28 Apr 12Fixed, thanks Luke!
Thiet ke logo
on 29 Apr 12It is useful and i will apply it for my project
Chris
on 30 Apr 12@Nick: How are you excluding JS console logging code in production mode?
Zarel
on 30 Apr 12It looks like rails is very awesome framework, I think I will switch from php to ruby… Let’s learn ruby! cayoooo… :D
TVD
on 03 May 12@Nick: Wow! This technique of decoupling behavior from CSS classes and DOM IDs is excellent.
Not only does it increase separation of concerns, but it also adds value from a business perspective since the cost-of-change decreases.
I’ll definitely have to follow up on this. Excellent!
Gareth Rees
on 03 May 12How do you install/run Ruby in production? Our sysadmins are skeptical of using RVM as it requires compiling from source.
They basically want to build an RPM of everything (ruby, app, gems etc) for deployment.
Some info on that would be great.
This discussion is closed.