The Hobo Migration Generator

Posted by Tom on 2007-07-06

Right back at the start of the Hobo project we made writing migrations a little easier. We made it so you didn’t have to write the word “column” quite so many times.

create_table "users" do |t|
  t.column :name, :string
end

Became:

create_table "users" do |t|
  t.string :name
end

A pretty small contribution in the scheme of things, but kinda handy, and it made it into core Rails (by a circuitous route) which was nice.

Somehow though, it wasn’t good enough. It was quicker, but it wasn’t Hobo Quick. Don’t you always get the feeling that writing migrations is kinda mechanical? Especially those tedious down migrations. Don’t you wish you never had to write another migration again? I know I do. Or did, I should say.

Announcing the Hobo Migration Generator.

Creating your database tables in the next version of Hobo will go something like this:

Step 1. Code your models (being sure not to generate any migrations)

Step 2. ruby script/generate hobo_migration create_initial_tables

(Step 2.5. Observe db/migrate/001_create_initial_tables.rb)

Step 3. rake db:migrate

And you’re done.

Hang on one darn minute I hear you say! Where are the columns declared? One of the much loved features of Active Record is that you don’t have to enumerate the columns in your models - AR interrogates the database and finds them for you. If Hobo generated the migration, where were the columns declared?

In the model class.

class User < ActiveRecord::Base
  fields do
    name :string, :null => false
    email :string
    about :text, :default => "No description available"
  end
end

Sacrilege! Not at all - it’s actually much better this way. Work with me for a minute here.

What is it that you really love about not having to list the columns in your AR classes? It’s the fact that it’s DRY. You don’t have to list them once in the migration and then again in the class. Well that’s still true, all we’ve done is moved those declarations to where they should be - in the same place that defines all the rest of the behaviour of your model. Yes those column declarations will be in the migration too, but that is generated for you.

There’s no more trawling through old migrations or messing with MySQL in order to remind yourself what columns you have - it’s right there in your code.

The generator looks at the database, looks at your models, figures out the difference and creates a migration such that the database gets with the program.

It generates the down migration too of course.

Moving forward things get even more interesting. Say you wanted to get rid of one of those fields. Just delete it from the fields declaration. Run the migration generator again and you’ll get a remove_column migration. Change the type of a column or add a default? No problem, you’ll get a change_column.

What if you delete a model class outright? Well we’re guessing your production data is kind of important to you, so the generator is interactive. It will require you to physically type “drop users” (or whatever) before it will generate a drop_table migration. The same goes for removing columns in fact.

What about renaming a column or table? Those are kinda tricky. Say we rename the field name to username. All the generator sees is that an existing field name has gone away, and a new field username has appeared on the scene. The generator will alert you to the ambiguity and invite you to enter either “drop name” or the new name “username”.

That’s the long and short of it, but there’s a couple more niceties.

Inside the fields block you can say simply timestamps to get created_at and updated_at.

You can declare fields using Hobo’s rich types like :html and :markdown. These end up as :text columns unless you override that by giving the :sql_type option. Your DRYML tags will know what it really is and render appropriately.

As for foreign keys – don’t even bother. Just declare belongs_to as you normally would, and the migration generator will spot the need to add a foreign key. Either with the conventional name or a custom one if you gave the :foreign_key option. Delete the belongs_to later, and the migration generator will remove the foreign key.

If, like most Rails programmers, you’ve written a lot of migrations, I think you’ll find using this puppy to be a fairly pleasing experience :-) I know I do.

It’s working now in the tom_sandbox branch, and will be in the next release, which, if the coding gods be willing, will be out by the end of the month.



(edit)