ActiveRecord, more quickly

Sunday, October 14

A while back I wrote about the ActiveRecord stub I use to easily hack around with AR. I’ve altered it to just create in-memory sqlite databases, and also to silence AR::Migration. Here’s the updated version:

require 'rubygems'
require 'active_record'

ActiveRecord::Base.logger = Logger.new(STDOUT) if 'irb' == $0

ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => ':memory:')
ActiveRecord::Migration.verbose = false

ActiveRecord::Base.silence do
  ActiveRecord::Schema.define(:version => 1) do
    with_options :force => true do |m|
      m.create_table 'examples' do |t|
        t.string :title
        t.timestamps
      end
    end
  end
end

class Example < ActiveRecord::Base
end

if __FILE__ == $0
  require 'test/unit'

  class TestExample < Test::Unit::TestCase
    def test_should_flunk
      flunk
    end
  end
end

My .irbrc

Thursday, September 13

Pursuant to this ancient post regarding irb history and completion, here is my current .irbrc, now sporting a custom “Rails” section to make my life on the Rails console easier.

require 'irb/completion'
require 'irb/ext/save-history'

IRB.conf[:SAVE_HISTORY] = 100
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-save-history" 
IRB.conf[:PROMPT_MODE]  = :SIMPLE

# Just for Rails...
if rails_env = ENV['RAILS_ENV']
  rails_root = File.basename(Dir.pwd)
  IRB.conf[:PROMPT] ||= {}
  IRB.conf[:PROMPT][:RAILS] = {
    :PROMPT_I => "#{rails_root}> ",
    :PROMPT_S => "#{rails_root}* ",
    :PROMPT_C => "#{rails_root}? ",
    :RETURN   => "=> %s\n" 
  }
  IRB.conf[:PROMPT_MODE] = :RAILS

  # Called after the irb session is initialized and Rails has
  # been loaded (props: Mike Clark).
  IRB.conf[:IRB_RC] = Proc.new do
    ActiveRecord::Base.logger = Logger.new(STDOUT)
    ActiveRecord::Base.instance_eval { alias :[] :find }
  end
end

I’m using the IRB.conf[:PROMPT] configuration option to create a custom Rails prompt that displays the current project name (which it assumes is the same as the PWD). Note that I’m also creating a convenience alias for AR::Base#find and enabling logging to STDOUT. Fun.

Array#extract_options!

Tuesday, September 11

Rails (really, ActiveSupport) now sports a nifty new addition to Array to encapsulate the pattern of getting an options hash out of a variable number of arguments: extract_options!

If the last item in an array is a Hash, extract_options! removes and returns it. Otherwise, it just returns an empty Hash. This is primarily useful when you have a method that accepts a variable number of arguments (*args), the last of which is to be a hash of options. This is a common idiom in the Rails source code.

def options(*args)
  args.extract_options!
end

options # => {}
options(:name => 'foo', :format => 'text') # => { :name => 'foo', :format => 'text' }

This is cleaner than the way this is normally done:

def options(*args)
  options = args.last.is_a?(Hash) ? args.last : {}
end

You gotta love ActiveSupport.

ActiveRecord, quickly

Thursday, September 06

I often want to experiment with a database model using ActiveRecord, from outside the context of a Rails application. Ideally, I want to do this all in one file. Where I can see my migrations and my models at the same time and tweak as needed.

This is the stub I’ve been using lately for that task.

require 'rubygems'
require 'active_record'

ActiveRecord::Base.logger = Logger.new(STDOUT) if 'irb' == $0

ActiveRecord::Base.establish_connection(
  :adapter  => 'mysql',
  :database => 'example',
  :username => 'root',
  :host     => 'localhost'
)

ActiveRecord::Base.silence do
  ActiveRecord::Schema.define(:version => 1) do
    with_options :force => true do |m|
      m.create_table 'examples' do |t|
        t.string :title
        t.timestamps
      end
    end
  end
end

class Example < ActiveRecord::Base
end

if __FILE__ == $0
  require 'test/unit'

  class TestExample < Test::Unit::TestCase
    def test_should_flunk
      flunk
    end
  end
end

I simply define all my migrations and each model class here, and then write a few tests to ensure things are working the way I expect. Changing the schema and adding/removing fields is a snap, and the database tables are dropped and recreated each time the file is run.

The tests are run selectively, and AR::Logger is set to STDOUT when $0 is irb because I often want to play with my creation interactively on the console. This helps me get a sense of how things work, and how my objects feel from an API perspective.

Here’s what the stub above looks like from an irb session:

$ irb
>> require 'example_models'
-- create_table("examples", {:force=>true})
   -> 0.1467s
-- initialize_schema_information()
   -> 0.0006s
-- columns("schema_info")
   -> 0.0027s
=> true

>> Example.create!
  Example Columns (0.064651)   SHOW FIELDS FROM examples
  SQL (0.000134)   BEGIN
  SQL (0.000316)   INSERT INTO examples (`updated_at`, `title`, `created_at`) VALUES('2007-09-06 11:14:05', NULL, '2007-09-06 11:14:05')
  SQL (0.000682)   COMMIT
=> #<Example id: 1, title: nil, created_at: "2007-09-06 11:14:05", updated_at: "2007-09-06 11:14:05">
>> 

I find this to be a really fast way to prototype ideas.

Beginning Rails: Published

Tuesday, July 24

I’m happy to announce the release of the book I’ve been working on with Cloves Carneiro Jr. and Hampton Catlin: “Beginning Rails: From Novice to Professional.” [cue: sound of champagne cork popping]

Beginning Rails is the practical starting point for anyone wanting to learn how to build dynamic web applications using the Rails framework for Ruby. You’ll learn how all of the components of Rails fit together and how you can leverage them to create sophisticated web applications with less code and more joy.

Pete wrote up a nice article on the Unspace blog, and Colin from CommunityLend made me blush a little with what he wrote. And leave it to Pete to stir up some debate about the trademark of the Rails logo and its resulting absence from the front cover of the book.

Strangely enough, I ordered a copy of the book from Amazon on Sunday and it was just delivered to my door. It’s funny because I haven’t even received my shipment from Apress. As soon as I noticed that Amazon had it in stock, I knew I’d get my grubby hands on a copy faster by ordering it from them. So, I paid $50 for my own book (including costly expedited shipping). Sigh. Hopefully I’ll get my shipment from Apress soon.

Capistrano multi-stage support

Monday, July 23

This is cool. The newest version of Capistrano now supports multiple stages. Like when you want to have different configuration for, say, production, testing, and staging. This is something I’ve been doing manually, which while it works, is not without its quirks.

It’s part of the capistrano extensions plugin (capistrano-ext), which is as easy as gem install capistrano-ext to obtain. Once installed, you simply require the multistage support in your deploy recipe file.

require 'capistrano/ext/multistage'

The best part is how stages are defined. All you have to do is place your stage-specific code in config/deploy/staging.rb and/or config/deploy/production.rb and it will get loaded automatically.

And if your stages aren’t named “production” and “staging,” you can define your own easily. You can then deploy to the staging server with cap staging deploy and to the production server with cap production deploy. And if you want the stating server to be the default, you can save yourself some typing by setting the default stage:

set :default_stage, 'staging'
require 'capistrano/ext/multistage'

For the full announcement, see Jamis Buck’s blog.

Rake db:rollback

Friday, June 29

Whenever I create a new migration I want to make sure my down method works as well as my up. So, I always end up doing rake db:migrate, check the current version number, n, and then roll it back one using rake db:migrate VERSION=n-1. Sometimes I’m migrating up more than one version, so I want to roll back a few steps (rake db:migrate VERSION=n-3). This, of course, is cumbersome. So, I wrote a simple task to make it easier:

namespace :db do
  desc 'Rolls the schema back to the previous version. Specify the number of steps with STEP=n'
  task :rollback => :environment do
    step = ENV['STEP'] ? ENV['STEP'].to_i : 1
    version = ActiveRecord::Migrator.current_version - step
    ActiveRecord::Migrator.migrate('db/migrate/', version)
  end
end

This lets me migrate up, and roll back to the previous version easily.

$ rake db:migrate
$ rake db:rollback

And I can go back a certain number of steps just as easily.

$ rake db:rollback STEP=3

Hopefully somebody will find this as useful as I do.

Obligatory RailsConf Wrap-up

Saturday, June 09

Okay, so it turns out that while I thought I’d posted this article successfully several weeks ago, in fact, I hadn’t. Apparently in Mephisto, you have to publish your articles, or they won’t show up on the homepage. By publish, I mean assign it to a “section”. That’s what I get for never checking the front-page of my blog.

So, just when you thought the deluge of post-RailsConf posts was finally over, here’s another one: my obligatory RailsConf wrap-up.

+++

Along with about 1,600 fellow Rails developers from around the world, I’m at RailsConf in Portland Oregon. Traveling here was rather hellish: in a vain effort to save money, we had a flight out of Buffalo on Wednesday instead of YYZ (Toronto). The flight was cancelled due to the fact that bad weather had grounded our connecting flight from JFK, so we ended up staying the night in Buffalo, the armpit of western New York and possibly, the entire USA.

A Holiday Inn Express contained us for the night, and after drinking four of the hotel-restaurant’s finest bottles of mediocre wine, we made use of their luke-warm, heavily chlorinated hotub before calling it a night.

Our flight was rescheduled for 2:45PM the next day (almost 24 hours after the original departure time), and luckily, it was on time. After dining at a nearby Denny’s, we checked our bags and got on board. It was a short, hour-long trip to JFK where we were to wait two hours for our connecting flight. After four hours or so, we boarded the plane, got seated, and watched as the flight attendants went through the motions of the safety instructions before being told that due to a technical malfunction, the plane was being grounded.

We were herded out of our seats and back into the terminal where we waited another hour and a half for a fully functional plane. After boarding, we waited on the tarmac for another hour before takeoff. As a small penance, JetBlue awarded us each a $25 discount on our next JetBlue flight.

Finally, after leaving Unspace at 11AM Wednesday, we got to Portland at 4AM Friday.

So, here’s my write-up of the first day at RailsConf 2007. Most of the talks I attended, with the exception of the keynotes, were centered around testing. As most of my friends and readers know, I’m pretty big into testing. It makes sense, then, that I was drawn to this topic. First, though, came the keynotes.

Keynote: DHH

DHH had the first keynote of the conference. He started by talking about the accomplishments we’ve already made and how far Rails has come, especially in the last year. The code, the community, the plugins, and the books. He made special mention of the books, pointing out the plethora of Rails-specific texts that now exist (there were a mere three last year). His slide featured a collage of Rails book covers, including mine (yay!), even though it’s not published (quite) yet.

He did a quick demo of a resource scaffold (which now replaces the standard scaffold), and a console-based demo of ActiveResource, the latest addition to the Rails stack.

If you’re already using edge Rails and are paying attention to the commit logs, there wasn’t much that was new. Most (all?) of the features he spoke of are already implemented in edge, and if you’re like me, you’re already using them today. I was actually a bit surprised at the reaction of the audience for whom these features where brand spanking new. Apparently most people are using stable versions of Rails.

DHH finished off his presentation with a rundown of nine of his favorite things about Rails 2.0 (i.e., edge), summarized below.

9 Great Things in Rails 2.0

  1. Breakpoints make a comeback courtesy of ruby-debug
  2. HTTP performance improvements: stylesheet_include_tag :all, :cache => true
  3. QueryCache: cache SQL queries that have have already been seen
  4. Template extensions: html.erb and html.builder
  5. Initializers: files in config/initializers to clean up environment.rb
  6. Sexy migrations
  7. HTTP authentication plugin now in core
  8. The MIT Assumption: plugin generator creates the MIT license by default.
  9. Spring cleaning: currently deprecated methods are slated for permanent removal

Clean Code, by Robert Martin

The first talk I went to was Clean Code by Robert Martin. Seriously, this was a great talk. Robert’s a good speaker, and was full of enthusiasm. He talked about how keeping your code clean is a matter of professional ethics; that being a good programmer means being a craftsman. It reminded me a lot of Dave Thomas and Andy Hunt’s The Pragmatic Programmer.

He walked the audience through a refactoring example, talking about the importance of using sound design principals and the need for test-driven feedback. There was a big emphasis on testing, something that I feel too many programmers don’t pay enough attention to. This is especially true of a lot of Rails developers who’ve come to Rails from paradigms where there isn’t a lot of emphasis on testing (PHP, I’m looking at you). He even went so far as to say that if you’re not testing your code, you can’t rightly call yourself a professional programmer. I tend to agree.

It was a great start to the morning and really got me pumped. In keeping withe the testing theme, the next talk I went to was Jay Fields’ on maturing your test suite.

Taking Rails Tests to the Next Level, by Jay Fields

Jay Fields is one of my favorite Ruby bloggers. I’ve been reading him for several months, and I really like his ideas. So, it goes without saying that I was excited for his talk.

He’s a ThoughtWorker, and his experience is on large teams, with up to 16 programmers(!), working 10 hours a day, all pairing, all the time. He admits that this kind of work is indeed stressful, but he feels it produces better software and is therefore worth it. Commendable.

His talk was mostly about testing tips, strategies, and advice. One recurring theme was the speed and ease with which you can execute your test suite. When you’re making frequent commits on largish teams like he is, it can be very time consuming to run large test suites before every checkin. The faster the tests run, and the fewer dependencies they have, the better.

He had a number of noteworthy tips and tricks, here are the ones I managed to jot down.

General Testing Tips

  • Testing one thing per test
  • One assertion per test
  • If you have a lot of setup code for your test, you have some other problems, perhaps some Law of Demeter violations, or other setup issues
  • Don’t cross boundaries with your tests
  • Don’t couple your tests to your implementation
  • Oftentimes you don’t need to hit the database to test something
  • Unit tests should not hit the database
  • Functional tests should hit the database
  • Classes should be tested in isolation
  • Testing in isolation often implies the need for mocks and stubs
  • Tests aren’t OO, so don’t bother trying to make them OO
  • If your tests depend on each other, its a Bad Thing
  • Tests should be able to run independently, on their own
  • Tests should be explicit; they should convey their intent clearly
  • Avoid creating abstract classes or helper methods that encapsulate magic
  • Readability, readability, readability

Decoupling Controller Tests

Decoupling models from the database is fairly straightforward. But what about decoupling controller tests?

  • You can call methods on your controllers directly, just like regular classes
  • Try to isolate controller test: decouple the rendering of the view

Private and Protected: YAGNI?

  • how do you test private or protected methods?
  • is protected and private even necessary?
  • in Rails controllers, public methods are accessible via the URL, but outside this context, marking methods as non-public has questionable value
  • zentest addresses the idea of testing private methods

Interesting Tidbit

There’s talk of “unit tests” becoming “model tests” and “functional tests” becoming “controller tests” in Rails. I like this. It would help to clear up some of the confusion that exists as a result of the terminology.

After lunch, I went to Rabble’s talk on adding tests to legacy (yeah, legacy) Rails apps.

Testing Legacy Apps, by Rabble

Rabble (Even Henshaw-Plath) talked about adding tests to ‘legacy’ Rails apps. How many times has this happened to you: you inherit a Rails project and discover much to your horror that it lacks tests. Sadly, a lot of Rails projects lack tests. So, should you sit down and write a whack of tests right away? Not really. According to Rabble, there are two basic strategies when it comes to testing apps that lack tests.

  1. write tests when you find bugs
  2. write tests when you refactor

Tips

  • Don’t do it all at once
  • Do BDT: Bug Driven Testing
  • Test before refactoring
  • Don’t write tests for code you’re not editing
  • Don’t write tests for tests sake
  • Baby steps: test one thing at a time
  • Build tests from logs
  • Use rcov
  • Use Autotest
  • Use Zentest
  • Use Heckle

What to test? (when you’ve not done TDD)

  • assert that calling methods doesn’t raise exceptions (assert_nothing_raised { })
  • assert that the page action returns :ok
  • assert responses, renders, assigns

Heckle

Heckle, by Kevin Clark and others, is a mutation tester (fuzzer). It screws with your code.

A lot of people use rcov to measure code coverage. This is absolutely a good thing. However, rcov isn’t enough: just because the line was run, doesn’t mean the line was useful.

If testing is your safety belt; rcov is your airbag; Heckle is your helmet, neck brace and fireproof suit.

Heckle is…

  • a measurement tool
  • an authoritative measurement of test quality
  • to be used alongside rcov, not in place of it
  • great for inherited code with no tests

Heckle can…

  • tell you which permutations survived the tests
  • let you know which things “don’t matter” to your tests

Interesting tidbit: S-Expressions

I’d never heard of S-Expressions before. It turns out, an S-Expression is an in-code expression of what the parse-tree looks like. Heckle uses the ParseTree library and ruby2ruby to rewrite the mutated code on the fly.

Fixtures: Friend or Foe by Tom Preston-Werner

In most cases, Rails makes testing a veritable cake walk. That is, with the exception of fixtures. As projects increase in size, fixtures tend to become brittle and interdependent. When your fixtures become a web of complex relationships, seemingly benign changes to one fixture can cause many test cases to fail. This is a Bad Thing.

There are a few solutions to this problem, none of which are very good.

Solutions

  • add lots and lots of documentation to your fixtures (ugh…)
  • use helpers that leverage AR to create fixture data (lots of code…)
  • use an in-memory database
  • use mocks and stubs

Tom proposes a solution I like very much: fixture scenarios. Here’s the gist:

All of the fixtures placed into your scenario/ directory will be loaded when you invoke the scenario method with your scenario name. In addition, any Ruby files you place in the scenario directory will be run after the fixtures. You can use a Ruby file to create non-database model instances, set up relationships between fixtures, or replace fixtures entirely by creating your database items with Ruby code.

[RAILS_ROOT]
+-test/
  +-fixtures/
    +-someting.yml # gets loaded for all scenarios
    +-brand_new_user/
      +-users.yml
      +-books.rb
      +-experienced_user/
        +-articles.yml

In your tests, you simply replace fixture with scenario:

class UserTest < Test::Unit::TestCase
  scenario: brand_new_user
end
class UserTest < Test::Unit::TestCase
  scenario: brand_new_user, :root => false
end

rake test:scenarios # run just the scenario tests

Fixture scenarios sovlessolves the following test grievances:

  • namespacing
  • brittleness
  • validation
  • contamination
  • performance

Creating scenarios with pure Ruby

You can create a rb file called scenarios.rb to define your scenario yaml files in pure Ruby. Observe:

+-fixtures
  +-scenarios.rb
build_scenario :banned_users do
  %w(Tom Chris Kevin).each_with_index do |user, index|
    User.create(:name => user, :banned => index.odd?)
  end
end

Creates the scenario directory, banned_users, with a users.yml fixture inside it

+-test/
  +-fixtures/
    +-banned_users/
      +-users.yml

Awesome. I’ll definitely be using this.

Zen and the Art of Rails Deployment

Ezra is easily the foremost authority on Rails deployment. It makes sense, then, that he’s a founder of EngineYard. He started his talk by pointing out the fact that the Rails deployment scene is in constant flux. Although different options have come and gone, the basics remain unchanged.

The new recipe? A gateway server (Apache, Nginx, Lighty, etc.) that fronts Mongrel. As if you don’t know this already, Mongrel is Zed Shaw’s HTTP server. It’s fast and flexible, and has removed the need for any of the more clunky solutions we used to use: CGI, mod_fastcgi, and mod_fcgid.

There are many options for http tools that front mongrel

  • pen, pound, balance, haproxy
  • lightspeed—can serve static files and proxy to mongrel
  • apache2.2.x/mod_proxy_balancer

There are many problems with the http tools that front mongrel

  • Pen—no ssl, no connection rate limiting
  • pound—falls down under high load
  • haproxy—supports connection rate limits, very high performance, no static files, many moving parts
  • lightspeed—free version is crippled
  • apache 2.2.x—works, but bloated

Enter: Nginx

  • seriously high performance
  • insanely fast
  • super small resource footprint
  • stands up under the heaviest loads without leaking memory
  • killer rewrite and proxy modules
  • approachable author and growing community
  • memcached module
  • ssl support baked in

Nginx + Mongrel

According to Ezra, Nginx + Mongrel is the stack to be on. The only reason to keep Apache around anymore is for mod_dav_svn (if you need it). Nginx’s configuration is incredibly flexible, allowing for the serving of static files and rails caches, addition to proxying dynamic request to mongrel. And Nginx is ridiculously fast.

Nginx Gotchas

There aren’t many gotchas when it comes to Nginx. Ezra pointed out a few.

  • niginx buffers file uploads
  • no connection rate limiting

Future of Nginx

As far as the future of Nginx goes, it’s looking bright.

  • mod_rewrite is going away
  • to be replaced with http_script_module
  • embed nekoVM directly in niginx

The Perfect Simple Stack

I love it when experts make recommendations. It takes out the guesswork. Here’s Ezra’s recommendation for the perfect (simple) stack.

  • Linux
  • Nginx
  • Mongrel (cluster)
  • Monit

Swiftiply

Swiftiply is a new entrant onto the Rails deployment scene.

It hot patches Mongrel, removing ruby’s thread and socket handling from mongrel core replacing it with EventMachine event loop. So, we get Evented Mongrel: single threaded and event driven. This provides a noticeable speed and io throughput increase that stands up much better under higher concurrent load without starting to slow down or leak memory.

This really makes a difference when Mongrel’s being hit with hundreds of concurrent users. In this scenario, Evented Mongrel can offer up to a 5x increase.

Swiftiply Proxy

Swiftiply Proxy is an event-driven proxy with a small memory footprint. According to Ezra, it’s even faster than Haproxy. It differs from a normal proxy in that, while a standard proxy must know about the ports of all backend, Swiftiply Proxy works by having the backends connect to it. So, all mongrels get started on the same port and then they open a persistent connection to the proxy. This alleviates the need to tell the server which backends are available, meaning you can start and stop as many mongrels as you want and they get auto configured in the proxy. This opens the door for scaling the number of mongrels automatically in response to the increased load on the fly. Sweet.

The Swiftiply Stack

  • Nginx
  • Swiftiply Proxy
  • Mongrels

+

Yep, so that’s my writeup. Hope you enjoyed it and all that.

IRB History and Completion

Saturday, June 09

Found this on the RubyGarden wiki and it totally made my day. To get persistent history rocking in IRB, add the following to your .irbrc.

require 'irb/completion'
require 'irb/ext/save-history'
ARGV.concat [ "--readline", "--prompt-mode", "simple" ]
IRB.conf[:SAVE_HISTORY] = 100
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-save-history" 

Note that this will also add tab completion.

The semi-colon separator in resource URIs: JNWI

Thursday, March 29

I’m quite thrilled by a recent commit to Rails: the dropping of the semi-colon separator in resource routes. Before now, the semi-colon was being used to distinguish non-CRUD actions on resources. From DHH’s commit message on the subject:

It (the semi-colon) was a neat idea, but lots of the non-crud actions turned out not to be RPC (as the ; was primarily intended to discourage), but legitimate sub-resources, like /parties/recent, which didn’t deserve the uglification of /parties;recent. Further more, the semicolon caused issues with caching and HTTP authentication in Safari. Just Not Worth It [DHH]

The ‘edit’ action is a good example. Edit isn’t really a CRUD action. The CRUD action that ultimately works on edit is update, however, we need to edit to display the html forms we need to modify the resource in our browsers.

I’ll confess to never having liked the semi-colon. It was just too ugly. I understand that the ugliness was, of course, intentional, but it still made me throw up in my mouth a little.

I also enjoyed DHH’s coining of the acronym: JNWI (Just Not Worth It).

Generating a SPECDOC from tests in Rails

Thursday, March 29

So I’ve been using RSpec for a while, but sometimes its integration with the Rails framework frustrates me. On the last project I started, I decided not to make waves and just use Test::Unit, which is the Rails default. It’s just easier that way.

Still, there are some things I like about RSpec. One of these things is the ‘this should do that’ syntax, which feels more natural to me. So, ever since I’ve been back on the Test::Unit bandwagon, I’ve been writing by tests in a test_should_do_something_amazing style. (I’ve noticed that a lot of Rails developers are using this style as of late, and even the test stubs that the Rails generators spit out are using this style as well).

Another thing I like about RSpec is its ability to produce a specification document based on your tests, which it calls a SPECDOC. Inspired by the recently added rake task notes, (which enumerates all the commented fixmes, todos, etc. in your app), I figured a SPECDOC enumerator wouldn’t be too hard to add. Here’s a stab at a rake task that does the job. This will rip through your tests, look for specification-style tests, and generate a SPECDOC in the docs directory.

desc "Generate specdoc-style documentation from tests" 
namespace :doc do
  task :specs do
    puts 'Started'

    timer, count = Time.now, 0

    File.open(RAILS_ROOT + '/doc/SPECDOC', 'w') do |file|
      Dir.glob('test/**/*_test.rb').each do |test|
        test =~ /.*\/([^\/].*)_test.rb$/
        file.puts "#{$1.gsub('_', ' ').capitalize} should:" if $1
        File.read(test).map {|line| /test_should_(.*)$/.match line }.compact.each do |spec|
          file.puts " * #{spec[1].gsub('_', ' ')}" 
          sleep 0.001; print '.'; $stdout.flush; count += 1
        end
        file.puts
      end
    end

    puts "\nFinished in #{Time.now - timer} seconds.\n" 
    puts "#{count} specifications documented" 
  end
end

Place this snippet in a file called specdoc.rake and throw it in lib/tasks and you’ll be good to go. Run it using rake doc:specs as follows:

$ rake doc:specs
Started
..............................................................................
..............................................................................
......................................
Finished in 2.308674 seconds.

194 specifications documented

The dots and chatter are all just eye-candy, but I thought I’d add them in for good measure. I’m thinking of removing it, though, just to cut down on the code. The SPECDOC will be output to the doc directory.

$ cat doc/SPECDOC 
Memberships controller should:
 * require login for protected actions
 * not require login for public actions
 * create and send invitations

Users controller should:
 * create user
 * require unique login on create
 * require password on create
 * require password confirmation on create
 * require email on create
 * require valid email on create
 * require authentication
 * accept authentication
 * get new user
 * show user
 * update user
...

Note that this will only document tests that begin with the aforementioned test_should_ prefix. Test cases that don’t use this convention are ignored, though you could tweak the regex to include them.

My regex-fu may be lacking, so if anyone has any suggestions for how I could make this better, don’t hesitate to pipe up.

Installing gems with capistrano

Friday, March 16

Ever go to deploy a copy of your application and realize that you don’t have all the required gems installed? This is what I do to handle such a precarious situation via capistrano.


desc 'Installs required gems for this application'
task :install_gems, :roles => :app do
  # Add all gems required by your application here
  gems = %w(
    aws-s3
    libxml-ruby
    redcloth
    tzinfo
  )

  sudo "gem install -y --no-rdoc --no-ri #{gems.join(' ')}" do |channel, stream, data|
    print data if stream == :out
    channel.send_data($stdin.gets) if data =~ /^>\s/
  end
end

The channel.send bit is so you can respond to gems that have multiple versions available (i.e., mongrel). I’m also skipping rdoc and ri for this since I generally don’t care about having the documentation on the production server and it’s a bit faster when you skip them.

Oh, and if you wanted this to happen automatically after your cap setup, you could invoke it using (wait for it) the after_setup callback.

I’m sure there are improvements that could be made to this code, so suggestions are welcome.

TextMate tab-triggers for Ruby

Thursday, February 01

I’m always forgetting the handy Ruby tab triggers for TextMate. Today I went through them and highlighted a few that you just can’t afford to miss.

r     # => attr_reader :attr_names
w     # => attr_writer :attr_names
rw    # => attr_accessor :attr_names
req   # => require "" 
mm    # => def method_missing(meth, *args, &block); end
am    # => alias_method :new_name, :old_name
:     # => :key => "value", 
doo   # => do |object| end
ea    # => each { |e|  }
patfh # => File.join(File.dirname(__FILE__), *%w[rel path here])

In a related note, I recently purchased the TextMate beta book from Pragmatic. Full of useful tidbits and highly recommended.

Upgrading to Ruby 1.8.5

Thursday, December 21

Today I realized that I was running Ruby 1.8.4. Actually, I knew I was running 1.8.4, but I’d been ignoring the fact because I’m too lazy to update and I lacked a compelling reason. Well, tonight I decided to bite the bullet and upgrade. It was a painless affair. Unlike Obie’s experience.

Here’s a little script I wrote to automate the process before I started. I generally like to script things ahead of time because doing so is less error prone. You get to think about what you’re doing before you do it. Anyways, YMMV.

#!/bin/sh

PREFIX="/usr/local" 
READLINE_VERSION="5.2" 
RUBY_VERSION="1.8.5-p2" 

curl -O ftp://ftp.gnu.org/gnu/readline/readline-${READLINE_VERSION}.tar.gz
tar xzvf readline-${READLINE_VERSION}.tar.gz
pushd readline-${READLINE_VERSION}
./configure --prefix=${PREFIX}
make
make install
popd

curl -O ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-${RUBY_VERSION}.tar.gz
tar zxvf ruby-${RUBY_VERSION}.tar.gz
pushd ruby-${RUBY_VERSION}
./configure --prefix=${PREFIX} --enable-pthread --with-readline-dir=${PREFIX}
make
make install
popd

Save this to a file named install_ruby.sh and run it from the CLI as follows:

$ sudo sh install_ruby.sh

That should do it.

Balloon: svn configuration for Rails

Tuesday, October 17

I just created my first balloon! Balloons are web pages that double as Ruby programs.

This magical balloon will automatically configure your subversion repository for a new Rails project. It goes like this: make a new Rails project in a working copy directory, inflate the balloon, and presto! Your repository is configured.

http://balloon.hobix.com/svn_configure

Here’s what the balloon does:

  • Adds all files
  • Ignores files in tmp/ and log/
  • Creates a sample database.yml and ignores the original
  • Sets permissions for log/ and public/dispatch.*
  • Tells subversion which files should be executable

This won’t automatically check in changes; you must check them in yourself.

Here’s the Ruby that makes it all possible.


require 'fileutils'

def ignore(path, pattern='*')
  `svn rm #{path}/#{pattern} --force`
  `svn propset svn:ignore '#{pattern}' #{path}`
end

# Backup the database.yml file
FileUtils.cp 'config/database.yml', 'config/database.yml.tpl'

# Add all the files
`svn add . --force`

# Ignore stuff
ignore 'config', 'database.yml'
ignore 'tmp'
ignore 'log'

# Copy database.yml back
FileUtils.cp 'config/database.yml.tpl', 'config/database.yml'

# Set the correct permissions for logs
FileUtils.chmod 0775, 'log'

# Set permissions for dispatchers
%w{rb cgi fcgi}.each { |ext| FileUtils.chmod 0755, "public/dispatch.#{ext}" }

# Tell subversion which files are executables
`svn propset svn:executable ON public/dispatch.* script/process/*`