The Hobo Migration Generator
Posted by
Tom | July 6, 2007 34 Responses comments

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.

Reader Comments Add your comment »

That’s hot!

I hate migrations. The idea is good but implementation is miserable.

Any chance of turning this into a stand-alone plugin?

Vunderbar!

I’ll second the request for extracting this into a plugin. It sounds quite useful to all developers.

Very nice Tom :)

This could be a revolution for rails developers. In my opinion this is exactly how things should work. In Django (and not only) you declare fields inside models, but AFAIR they don’t have any migrations there, so it’s not possible to rollback changes.

I’m sure that this will show up in the rails core someday :)

Cheers!

Very cool. I can’t stand shuffling back and forth between migration scripts and code just to remember what fields my model has. I’ll definitely be using this going forward.

I can’t wait!

I think this is terrific and that is definitely where tables should be defined. I can’t tell you how many times I’ve had to go back and look at migrations or poke at the database to remember what the structure is. Hope this will make it into core.

How are indexes handled? How about sql for those times you can’t get exactly what you want?

Thank you, Tom. You’ve given me a valid retroactive reason not to have built that website yet. Soon I’ll have no excuse. :)

[...] Hobo migrations – I like! Getting rid of migration files all together and putting it where it belongs – in the model itself. [...]

You know Tom, you really keep raining on our proverbial parade with these spectacular new announcements. Please stop! ;) j/k

Truly though, I feel the same in terms of procrastinating my projects. How safe are we to check out your source and start using it? I’m going to do it, but I am just wondering how much trouble I’ll be in and how closely I’m going to need to watch your changes on an ongoing basis.

Thanks for the positive feedback people :-)

Just to clarify one thing. Migrations haven’t gone away at all – I realise the title of the post was a bit misleading (I’ve changed it).

The point is that migrations are completely unchanged by this feature. It’s just that you don’t have to write them yourself. If you want to do extra stuff like creating indexes or munging your data in some way, just edit the generated file. You never use the generator to regenerate the same migration later, so you won’t run into the classic problem of overwriting your edits.

I agree that this would be very nice as a separate plugin, but personally I’m totally focussed on making Hobo as good as it can be right now. If anyone wants to extract this feature, the MIT license permits it and you’re very welcome.

[...] Me encuentro, leyendo los blogs de algunos de los ponentes de la próxima RailsConf Europe, con esta propuesta que pretende hacer más fácil la vida del desarrollador de backend en Rails. [...]

Now where’s that time machine to beam us to the end of the month?

Where would one define columns for STI models? The common columns in the parent class and the specific in the child classes? Or all columns in the parent classes?

Niko – eek! Good question. I haven’t thought about STI yet. It’s probably buggy – I’ll go sort that :-)

Once fixed, the answer to your question will be… um… I think the common in the parent and the specific in the subclasses. It’s unlikely but conceivable that the subclass may re-type a field, e.g. changing plain text to HTML.

Another aspect of STI is assoziations defined for just one subclass. One can’t choose where to put those so Hobo has to look in the subclasses anyway to find the foreign key requirements.

+1 to make it a separate plugin, and to make it a rails patch

The last thing that was extracted into rails from hobo came via errtheblog and hobo got no recognition for it.

i really like the new feature. defining your data objects in the Model layer makes more sense than the migration layer to me.

i have a question:
where do i put the code sections that i previously had in my migration scripts if i use the new migration generator? for example, if i start a new hobo rails app i get users by default. the users migration script has the following code in it:

# create the admin user now because we can't do it
# through the web UI as it would fail validation
admin = User.new
admin.nick_name = 'admin'
admin.first_name = 'admin'
admin.last_name = 'admin'
admin.email = 'admin'
admin.password = 'password'
admin.save_without_validation

how do i preserve this functionality now that my migration script is generated?

thanks.

Robert – please see my previous comment (10)

RE: 18:
that makes sense, however, now you are having to create the fields in the Model and then go edit the generated migration script. have you given any thought to expanding the migration generator to include such functionality as indexing and including generic “code sections” (that basically just copies a chunk of code, such as the previously mentioned ‘add user’ code, to the migration script during the generate process)?

thanks again.

Definately make this a separate plugin. I want it now!

You know, you’re getting pretty close to what DRYSql
is about.

http://drysql.rubyforge.org/
http://rubyforge.org/projects/drysql/

I think what it/you are doing should/will eventually be apart of Rails core (with all the customization and legacy options available) but all you should have to do is profile your models/tables in one place and let the software work for you thereafter, instead of vice versa.

Only thing though, I keep holding off on using Hobo until it gets all these goodies in place so I don’t have to go back and redo my project. :>)

Jabari – I’d say what we’re doing here is exactly opposite to DRYSQL. DRYSQL says “make my app behave exactly as the database schema says it should”, while this migration generator says “make the database structure be exactly what my application source-code says it should be”.

Only thing though, I keep holding off on using Hobo until it gets all these goodies in place so I don’t have to go back and redo my project. :>)

Very sensible! :-)

Heh Tom,

What I was alluding to was the concept of DRYing up the process generating the details of the models relationship to the database.

Here’s what Bryan Adams says DRYSQL purpose is.
http://www.infoq.com/articles/DrySQL
http://allyourdatabase.blogspot.com/2006/11/introducing-drysql.html

Magic Models is also doing a similar thing.
http://magicmodels.rubyforge.org/drnicmagic_models/

Philosophically though, what you and the other projects have identified is the chance/ability, to DRYup the modeling/migration/database creation of the process more than what Rails natively does. You’re extending DRY philosophy to more components of the process, and taking the drudgery of physically having to do all of these repetitive standard things by hand. Hey, format it right and just let the framework do it for you!

Again, it seems so logical upon looking at the design now, but when you’re pioneering a whole new paradigm shift, its hard (nay impossible) to recognize all of the optimizations at once.

A year from now, when Ruby and Rails hit 2.0, Rails will be so much more DRYed out and efficient the days of the 1.xx series will seem like pre-history, like when people wrote assembly language (or at least C) code. :>)

Opps. Its Bryan Evans not Bryan Adams.

I mean this is nice and all, but who needs to rummage through old migrations to figure out what columns are in a table? Surely you didn’t do all this just because you never noticed the schema.rb/schema.sql file that gets generated after running a migration? :)

That. Is. Sweet. And I’m almost ready to seriously start playing with Hobo!

[...] So when writing migrations gets you down, know that just around the corner is a whole new sexy migration tool from Tom at Hobo Central. Tom is responsible for the current “sexy migrations” trend in Rails and has some amazing ideas. The new tool allows you to define all columns and relationships within the models themselves, and based upon the comparison between current models and previous schema, the migration generator migration files to do all the work of both up and down methods! That’s right, no more typing redundant  code which is just a mirror of the previous method def! I’d love to see this implemented in core Rails. Let us pray… [...]

[...] That’s a question I ask myself after reading an article about an alternative approach to realize migrations in Rails. With that approach, you define the columns of your table in the model and then you generate the migration files from the model. You no longer have to touch any SQL scripts or to write migration files — if there are changes in the table definition, you make them in the model. [...]

I’d love to see a switch to make the migration silent. For instance:

script/generate hobomigrations mymigration -g (or -c, -m)

It’s an easy add, but I’m not yet feeling comfortable submitting a possible diff…

Steven – We’ve also had that request from who wants to hook up the migration to a button in an IDE. The problem is that the generator is interactive. If it sees a field named “a” that is not in the database, and a DB column named “b” that is not in the model, it asks you if you are renaming “b” to “a’, or removing “b” and creating “a”.

The other issue is that I think it’s really important to show the migration code before generating the files, so folk get a chance to check it’s what they intended.

I had a default value on a :text and it screamed at me.

my model:

fields do
name :string, :null => false
email :string
about :text, :default => “No description available”
timestamps
end

the migration step:

What now: [g]enerate migrations, generate and [m]igrate now or [c]ancel? m
create db/migrate
create db/migrate/001createinitialtables.rb
(in /Users/home/Development/test)
== 1 CreateInitialTables: migrating ===========================================
– create
table(:foomodels)
rake aborted!
Mysql::Error: BLOB/TEXT column ‘about’ can’t have a default value: CREATE TABLE foomodels (id int(11) DEFAULT NULL auto_increment PRIMARY KEY, name varchar(255) NOT NULL, email varchar(255) DEFAULT NULL, about text DEFAULT ‘No description available’, created_at datetime DEFAULT NULL, updated_at datetime DEFAULT NULL) ENGINE=InnoDB

Taking out the default value (rming the db/migrate/001createinitial_tables.rb file) and retrying made it go through. Is there a reason why it died with a default value for :text ?

Thanks, Hobo rocks.

patrick – looks like MySQL doesn’t support that

This makes so much more sense rather than using the annotate_models plugin. Plus, the current migrations clash with the ActiveRecord namespace. The core team really needs to rethink migrations.


Write a Comment

Comments are formatted using markdown. To include code, either quote it in `backticks` or indent a code-block by 4 spaces.