Schema loading for tests in rails

February 04, 2011

I’m reading The Rails 3 Way by Obie Fernandez, and it says

Every time you run tests, Rails dumps the schema of your development database and copies it to the test database using an autogenerated schema.rb script.

In my experience this isn’t quite what happens. From what I’ve observed:

  • schema.rb gets generated when you call rake db:migrate
  • the schema gets loaded from schema.rb to your test database when you call rake db:test:prepare
  • when you run tests, the calls to the database inside each of the tests are run within a transaction that gets rolled back at the end of the test

In practice, you could therefore load seed data into your test database after calling rake db:test:prepare and you wouldn’t lose this every time you run your tests.

namespace :db do
  namespace :test do
    desc "Init bare bones test data"
    task :seed_db do
      # your seed data here
    end
  end
end
Rake::Task["db:test:prepare"].enhance do
  Rake::Task["db:test:seed_db"].invoke
end

Is this a good idea? Well, It depends. I’m working on one project where it is extremely helpful. Perhaps when we’ve gotten things refactored a bit it won’t be necessary anymore.

Rails 3 Scopes: With great power comes great responsibility.

January 29, 2011

I wrote one of the worst bugs of my career (so far) the other day.

The long and short of it is this:
ActiveRecord::Relation may behave like an array in many ways, but it’s not an array. If you are doing something which is completely harmless to an array, but could be potentially destructive to an active record relation, make sure what you have really is an array.

It goes like this

Create a fresh rails3 project, and set it up to use rspec.

$ rails new todo -T
# Gemfile
source 'http://rubygems.org'
gem 'rails', '~> 3.0'
gem 'sqlite3-ruby', :require => 'sqlite3'
gem 'rspec-rails', '~> 2.4'
rails g rspec:install

Create a simple model.

rails g migration create_tasks
class CreateTasks < ActiveRecord::Migration
  def self.up
    create_table :tasks do |t|
      t.column :description, :text
      t.column :done, :boolean, :default => false
      t.timestamps
    end
  end
 def self.down
    drop_table :tasks
  end
end
rake db:migrate && rake db:test:prepare

Add a scope

class Task < ActiveRecord::Base
  scope :done, where('done = ?', true)
end

This is where it gets interesting.

require 'spec_helper'
describe Task do
  it "cleanly extracts from array" do
    trucs = {:des => :trucs}
    machin = {:un => :machin}
    bidule = {:une => :bidule}
    stuff = [trucs, machin, bidule]
    extracted = stuff.delete(machin)
    extracted.should == machin
    stuff.should_not include(machin)
  end
  context "active record relation" do
    it "extracts cleanly" do
      jump = Task.create(:description => 'jump')
      run = Task.create(:description => 'run')
      walk = Task.create(:description => 'walk')
      activities = Task.all # activities.class == Array
      extracted = activities.delete(run)
      extracted.should == run
      activities.should_not include(run)
      Task.all.should include(run)
    end
    it "doesn't. Sneaky bastard" do
      jump = Task.create(:description => 'jump', :done => true)
      run = Task.create(:description => 'run', :done => true)
      walk = Task.create(:description => 'walk', :done => true)
      activities = Task.done # activities.class == ActiveRecord::Relation
      extracted = activities.delete(run)
      Task.all.should include(run)
      # fails
      # incidentally, also fails on these:
      # popped.should == run
      # activities.should_not include(run)
     end
  end
end

To quote the log:

AREL (0.2ms) DELETE FROM “tasks” WHERE (done = ‘t’) AND (“tasks”.“id” = 2)

Yeah. Potentially really not good.

PostgreSQL template tables and rake db:test:prepare

January 13, 2011

I recently overcame the final hurdle to getting rspec hooked up within this legacy project that I’m working on (legacy in the sense that it has no tests, and is in production, and works).

There were three levels of fail with respect to running rake db:test:prepare

Fail #1: Configuration

The database configuration file had the test environment looking at the same database as development. The first time I ran rake db:test:prepare it dropped my development database. Usually this isn’t such a huge disaster, but in this case we’re using a slightly sanitized version of the production database. It takes 45 minutes to scp it down from the backup server, and 4+ hours to load.

The solution, of course, was to change this:

test:
  adapter: postgresql
  database: legacyproject_development

to this:

test:
  adapter: postgresql
  database: legacyproject_test

Fail #2: Bug in Schema Dumper

This project happens to be using a homegrown plugin for PostGIS (because at the time we needed one there were no other available options). Since we’ve never needed to actually load the database from the schema, nobody had noticed that the schema definition of geometry tables were lacking the parameter for the SRID.

wrong number of arguments (4 for 5)

Fixing the custom schema dumper for the geometry tables fixed this issue.

Fail #3: Missing PostGIS Functions and Tables

The production and development databases had been set up manually to include the PostGIS goodies. rake db:test:prepare was now correctly creating the test database based on the schema, but was lacking everything PostGIS. Three options immediately came to mind:

  • write a task that manually loaded all the postGIS stuff and have it run prior to the rake db:test:prepare task (uhm, no thanks).
  • add the PostGIS stuff to the default postgresql template table, template1 (I’d rather not).
  • create a special PostGIS template table (yes, please).

So I did:

psql -d postgres -U postgres
CREATE DATABASE template_postgis WITH TEMPLATE=template1 ENCODING='UTF8';
\c template_postgis;
CREATE LANGUAGE plpgsql;
\i /opt/local/share/postgresql90/contrib/postgis-1.5/postgis.sql
\i /opt/local/share/postgresql90/contrib/postgis-1.5/spatial_ref_sys.sql
UPDATE pg_database SET datistemplate = TRUE WHERE datname = 'template_postgis';
GRANT ALL ON geometry_columns TO PUBLIC;
GRANT ALL ON spatial_ref_sys TO PUBLIC;  

And then all I needed to do define the database config so that it used this template instead of template1 when created the test database.

test:
  adapter: postgresql
  template: template_postgis
  database: legacyproject_test

Ah, right. Not so simple. It would seem that noone has needed this config options, so rails doesn’t recognize the template option.

So we submitted a patch to rails. Smallest patch in the history of rails probably, at less than 30 characters

#win

A (silly) PostgreSQL gotcha

January 07, 2011

As I was setting up my latest project in Rails 3 with PostgreSQL, Cucumber, RSpec and various other goodies, I got this error when running rake db:migrate:

PGError: ERROR: permission denied for relation schema_migrations
SELECT version FROM schema_migrations

I took a quick look in my config/database.yml

And saw the following ridiculousness:

development:
  adapter: postgresql
  encoding: unicode
  database: myproject_development
  host: localhost
  pool: 5
  username:
  password: productionpassword

Updating username to myproject and password to be blank did the trick.

Using PostgreSQL with Rails 3, Cucumber & RSpec

January 05, 2011

One thing that tripped me up when trying to set up a new project using PostgreSQL rather than sqlite3, is that when running rake db:test:prepare I got the error message:

/path/to/rake:19:in `<main>'
Couldn't create database for
{"adapter"=>"postgresql", "encoding"=>"unicode",
"database"=>"myproject_test", "host"=>"localhost",
"pool"=>5, "username"=>"moi", "password"=>nil, "min_messages"=>"WARNING"}
rake aborted!
FATAL:  database "test" does not exist

This despite having just run createdb -O moi test

I recreated the database, checked that it was, in fact, there (psql myproject_test, and ran the rake command again. Boom. Same error message.

Turns out the user “moi” was allowed to drop databases, but not create them.

psql myproject_development;
ALTER USER moi CREATEDB;
\q

Et voila.

Lazybones: Skeleton Rails3 App

December 28, 2010

I’ve set myself a 30 day challenge for the month of January. Nevermind that January has 31 days.

Every morning, I’m going to get up an hour early (yes, weekends included) and practice programming.

This is different from programming at work. For one, I don’t need to deliver anything. I basically want to be able to do some deliberate practice on TDD/BDD and refactoring, as well as explore some gems that I’m unfamiliar with.

I have a couple of projects that I can use for this. One is OverkillCMS:
git://github.com/kowen/overkill.git

This is a simple CMS for a friend of mine which I’m making simply to practice full TDD/BDD using rspec and cucumber. That, and because her current website requires her to edit html and css, and she’s not a programmer.

Another is Carbon Dating – An Open-Source Ruby on Rails Dating App
git://github.com/kowen/c14.git

This one is also inspired by some friends of mine. They’re running a tiny niche dating site that uses some of the ugliest PHP code you will ever see. It’s an embarrassment to cowboy coders everywhere.

In preparation for these two projects, I went ahead and found out how to set up a rails 3 project per my own preferences (rspec, cucumber, jasmine, haml, sass, compass, watchr, metric_fu) and documented it here, along with some convenience files:

git://github.com/kowen/lazybones.git

Order of Callbacks/Validations on Active Record

December 08, 2010

Every once in a while I find myself wondering about this. So here it is, for the record:

The order of callbacks and validations on a rails active record model is

1. before_validation
2. before_validation, :on => :create
3. after_validation
4. after_validation, :on => :create
5. before_save
6. before_create
7. after_create
8. after_save

ActiveRecord wraps this whole thing in a transaction, so if anything anywhere in this chain fails, the whole thing will be rolled back.

Good to know!