Adam @ Heroku
a tornado of razorblades

Railsconf

May 27, 2008 at 02:06 PM

Places you'll find me at Railsconf this year:

  • Giving my talk about custom Nginx modules on Saturday afternoon. The talk has evolved quite a bit since I wrote the description, so expect some broader topics, like why I think HTTP is the critical enabling protocol in the era of Rails and cloud computing.
  • Attending the Heroku product talk, in which we propose why you may never need to think about servers or hosting again. This is inconveniently scheduled immediately before my session talk, so I'll have to duck out partway through.
  • Signing books along with the other recipe contributors to Mike Clark's Advanced Rails Recipes at the Powell's Books booth at the lunch break on Friday.
  • Hanging around our booth, where I intend to hack on Heroku and my open source projects, listen in on Geoffrey Grosenbach's podcast interviews, and meet everyone that stops by. So... stop by! :)

Advanced Rails Recipes

May 19, 2008 at 07:42 PM

Advanced Rails Recipes by Mike Clark is now shipping. I'm pleased to have contributed to the chapter on nested resources.

Most of the staple Rails books - like Agile Web Development and the original Rails Recipes - are a bit out of date now, so it's nice to see a new one. Advanced Rails Recipes is full of useful goodies, some of which are what I consider basics (authentication, restful resources, foreign keys) but quite a lot of which live up to the "advanced" label (dtrace, custom rspec matchers, process monitoring). Good stuff.

More Git Techniques

May 15, 2008 at 05:28 PM

In the spirit of Graeme Mathieson's git techniques, here are a few of my favorites, with their svn equivalents for reference.

Restore a file to repository version
  svn: rm file; svn up file
  git: rm file; git checkout file

See what commits would be pulled on an update
  svn: svn stat -u
  git: git fetch; git log HEAD..origin/master

See what code changes would be pulled on an update
  svn: svn diff -rHEAD
  git: git fetch; git diff HEAD..origin/master

Grab one commit without any of the commits around it:
  svn: svn diff -rN1:N2 > my.patch; scp my.patch other.server; ssh other.server "patch < my.patch"
  git: git fetch; git cherry-pick [commit-hash]

Revert a commit
  svn: svn merge -rN2:N1; svn commit -m "reverted commit N2"
  git: git revert [commit-hash]

Set some changes aside to work on something else
   svn: cd ..; mv myproj myproj_stash; svn co svn+ssh://server/myproj
   git: git stash

Discard local changes
  svn: svn revert -R .
  git: git reset —hard HEAD

Edit recent commits
  svn: echo "Oops."
  git: git rebase -i HEAD~5
Comments: 3 (view/add your own) Tags: git

Firefox REST Plugin

May 12, 2008 at 03:02 PM

While we wait for web broswers to become fully REST-capable, Poster is a handy Firefox plugin for sending any type of HTTP request, including all four verbs and different content types. I usually use rest-client at a rush shell for one-offs; but if you need your browser's cookies for a call that can't be authenticated with http basic auth, or you just want a dialog that shows all the options visually, Poster is quite handy.

Comments: 0 (view/add your own) Tags: rest

Don't Build the Super Nifty Node System

May 08, 2008 at 03:26 PM

Programmers tend to overdesign. Rather than building a quick solution that works right now for the specific case, we want to build one that will solve all problems of that type, both now and for all time. Dreaming In Code shows a particularly egregious case of this: the developers spend years building a framework for the app, rather than the app itself. This type of story is quite common. So frequent is this pitfall that the agile methodology mantra for avoiding it if often referred to by its abbreviation: YAGNI (You Aren't Going To Need It).

On the opposite extreme, there's the quick-and-dirty hack. But this has its own problems: it's fragile and inflexible. (It's telling that Microsoft's first pass at an operating system was named QDOS, where the QD stood for Quick-and-Dirty.) Quick hacks don't lend themselves to being built upon. Contrived example: a method called sumtwoand_two that returns the constant four would not be nearly as useful as a method called sum that takes two integer arguments and returns their sum. Generalized solutions are important - hell, they're what software is about.

So where is the right balance? My parter Orion puts it well: "You want to build an architecture that will be somewhat flexible, but not infinitely so."

Here's an anti-example: at our first venture together, we needed some customer relationship management software which the whole company could access. (This was eight years ago, and there weren't any suitable web-based commercial or open source choices that we knew of.) We ended up writing something called the Super Nifty Node System. It didn't track companies, people, and leads, like you might expect. Instead it had a single object type - a Node - and the users could define node types and relationships between them. The idea was that we were building a system which was so flexible that we wouldn't need to do any programming when someone wanted to track something new. We thought we were solving both the original problem and many related problems, and would never need to touch the software again.

In practice, this worked out poorly. Our non-technical users didn't understand how to create new node types, and didn't really want to anyway. Things like getting the fields to go in a certain order was really difficult, leading to a lot of user frustration on entering addresses. Worse was that simple programmatic tasks we wanted to perform - like pulling out a list of email addresses for all our partner companies, or a list of all our customers who had been with us for a year or more - required SQL statements with fifteen complex joins that wrapped around the screen six times. In retrospect, we should have just made tables for companies, people, and leads - even though there would have been a fair bit of duplicated code, this being in the pre-Rails (and generally pre-framework) era.

Many people have asked us why we didn't build Heroku to support multiple frameworks and languages. Why not Heroku for Python/Django, my second favorite language/framework combo after Ruby/Rails? Why not for PHP and some of the excellent MVC frameworks that exist for it? Why limit ourselves, and our potential audience, by building to the more specific case?

Were I building Heroku earlier in my career, I might have designed it with this in mind. I might have created an abstract base class App, and from that inherited RailsApp. Or perhaps App hasone :framework and hasone :language, and then Framework and Language are abstract classes that can be inherited by Framework::Rails, Framework::Django, Language::Ruby, and Language::Python. The Django and Python classes would have sat empty for months or years as we put our energy into developing for Ruby and Rails.

And there's a cost to leaving that infrastructure laying around. Just because you're not actively developing on a particular part of the codebase doesn't mean its maintenance-free: you've got more abstractions to keep in your head, more training time for new members on the team, more specs to keep running. Even just the extra files hanging around in the code tree adds a tiny bit of overhead for your brain each time you do a directory listing or otherwise manage the code.

So don't start generalizing until you have a strong need for it. Generalizing too early is the death of many a project - almost as often as generalizing too late.

A Better Daemonize

May 07, 2008 at 02:16 AM

Mongrel, Thin, and every other web application server I've ever used all suffer from a similar deficiency: they daemonize too early. That is, they daemonize prior to trying to boot your app, which means any error - even a really obvious, immediate boot problem - will silently feed into the log as the process dies, without so much as a peep on the command line.

Try this:

$ mkdir nothing
$ cd nothing
$ thin start -d && echo success

(Substitute "mongrel_rails" for "thin" here if you want, the result is identical.)

Wait, what? There's not even anything there. Why does it return true? Early daemonization, that's why.

A better approach would be to boot the app, and once it's online and listening and ready to serve requests, then return.

Comments: 0 (view/add your own) Tags: ruby, thin

Rocking the Mocking

May 02, 2008 at 02:12 PM

How do you write a spec for this method without touching the filesystem or the user's environment?

def authkey
  File.read("#{ENV['HOME']}/.ssh/id_rsa.pub")
end

Just repeat this mantra to yourself: It's Ruby. Everything Is An Object Or A Method. Objects And Methods Are Always Mutable.

Got your answer yet? Here's mine:

it "reads the ssh rsa key from the user's home directory" do
   ENV.should_receive(:[]).with('HOME').and_return('/home/joe')
   File.should_receive(:read).with('/home/joe/.ssh/id_rsa.pub').and_return('the key')
   @client.authkey.should == 'the key'
end
Comments: 1 (view/add your own) Tags: bdd