rc3.org

Strong opinions, weakly held

Ruby on Rails Migrations

I just started using the migrations feature of Ruby on Rails. Migrations are a way to capture changes to a database schema so that you can apply them without destroying data, and so that you can roll them back if you made a mistake. Before I understood how they worked, I thought they were basically magical and was hesitant to try them out because I don’t believe in magic. Some Rails developers I’m working were extolling them, so I investigated further and discovered that they are not magic, but that they are pretty darn useful.

One thing you learn about Rails is that just about every file you touch is a full blown Ruby script, whether it’s a unit test, a build file (or Rakefile as they’re known in Rails world), or a database migration script. The only thing that differentiates them is how they’re used and which libraries they import. So a migration script is just a Ruby script that has access to a bunch of methods that perform database operations. It also has access to all of the model classes in your application, so that you can modify the data in your database from within a migration as well. Fundamentally, a migration script isn’t all that different than a file full of SQL statements that modify a database schema, except for perhaps a small usability improvement.

Each migration script has two methods that it has to override, one to apply the changes in the migration and another to roll them back. So if you want to add a new table called “widgets” to your database, in your migration script you add the code to create the table to the “up” method, and the code to drop the table in the “down” method.

As I said, there’s nothing magical about the migration files themselves. They’re just collections of database operations. What’s more interesting is how Ruby on Rails applies them. Migration files are versioned, and the version number is part of the file name. The first migration has a name like 001_initial_schema.rb. You apply it using the command rake migrate. So if another developer checks in versions two through five of the schema, the next time you run rake migrate it will apply each of the migrations in turn to bring your schema up to the most recent revision. Of course you can also use it when deploying to production, eliminating a lot of risk that comes from hacking on a live schema manually to apply changes that were made since the last version was deployed.

All in all, migrations are an elegant solution to a problem that’s common to all Web applications, regardless of the problem that they’re written in. They could easily be ported to Ant for Java applications, or to Perl or PHP scripts for those platforms as well. Maybe I’m missing the fact that such tools are already common, but I haven’t seen them.

The only question for me with migrations is how to best include them in my workflow. For example, let’s say your adding a new feature to your application that requires you to add a column called “color” to a table called “widgets”. Do you generate a new migration script (Rails comes with a generator that will automatically build a skeleton of a migration script for you with the proper version number), add the command to add the column to that migration script, and then run rake migrate, or do you modify the schema yourself and just create the migration before you check your code in?

If you do your initial work in the migration script, the problem is that if you add more stuff later, you have to roll back the migration and then run it again to apply all of the changes to your development database. On the other hand, if you make the schema changes by hand, you increase the risk of checking in a migration that has not been well tested. After some testing, it seems like the best approach is to create one migration for each change you come up with? Let’s say you’re adding a comments feature to a blog, so you write a migration that creates the table for comments and run the migration on your local system. You realize later that you need to store the URL of the commenter. The best approach, in terms of using migrations most efficiently, is to create a new migration to add that field rather than editing the previous migration. At least that’s how it seems to me.

7 Comments

  1. I usually have a migration per check in, but I don’t know if I can justify that approach vs. what you’ve described. I guess I personally don’t find going down a version and then up again much more onerous than creating a new migration and then running it. The nice thing is, both ways of working are compatible 🙂

  2. We make a Migration when we need to make a change. This way we can update the test/dev databases very easily. Most db changes take place during development, so once we hit our first “release” we “reset” the db version down to zero and dump the schema. From there on out, we know that all the Migration files are for a post-release database.

    It’s worked out pretty well.

  3. I love migration, but it’s a pain to create foreign keys, so I wrote a module that makes it each to create foreign keys. The code is availble at http://dppruby.com/dppsrubyplayground/show/Migrate+Plus

    Thanks,

    David

  4. What happens if version 1 of my schema has column1 and version 2 drops column1 but version 3 brings back column1 and, furthermore, there is a requirement to upgrade from version 1 to version 3. Does the data in column1 migrate in this scenario given that fact that it is two versions ahead?

    Thanks,

    Jason

  5. The migration scripts are just Ruby scripts, so it depends on how you write them. If you drop the column in version 2 and don’t archive the data anywhere, it’s gone.

  6. hey i have tried various methods for migrations but the point is is there any way we can populate the db with data using migrations ..like not only the structure but also data

  7. Data can be (de)populated using migrations. The “best practices” I’ve seen involve using ‘anonymous’ models so the existing models do not have an effect on the database.

    For example:

    class InitialSchema < ActiveRecord::Migration
    # The 'anonymous' model
    class Category < ActiveRecord::Base
    end
    
    def self.up
    create_table :categories do |t|
    t.column "category", :string
    end
    Category.create :category => "Play"
    Category.create :category => "Work"
    end
    
    def self.down
    drop_table :categories
    end
    

    You can get more elaborate with multiple models, etc. The one sticking point I’m having at the moment is linking foreign keys from one model to another: finding a specific record in one table and linking it to a new record in another. I know I’ve seen the answer somewhere, but thus far it eludes me. This is probably the biggest beef I have with RoR – the documentation is horrible.

Leave a Reply

Your email address will not be published.

*

© 2016 rc3.org

Theme by Anders NorenUp ↑