What's New in Edge Rails: UTC-based Migration Versioning

Posted by ryan
at 9:11 AM on Wednesday, April 02, 2008

Rails migrations are a somewhat contentious bunch. On one hand they provide a consistent way of provisioning your database, and on the other hand they like to conflict with each other if there’s a heavy stream of development due to a simplistic naming convention. Just in is a change that will name your migrations based on a more unique UTC-based timestamp instead of just a sequential ordering.

When you go to create your next migration you’ll see something like this:

1
2
> script/generate migration one
      create  db/migrate/20080402122512_one.rb

Instead of the migration file being named 001_one.rb it now has a more unique prefix that will less likely conflict with another migration somebody else happened to check-in around the same time.

Update: When you do an svn update and get new migrations, even if they have a timestamp previous to a migration you’ve already added and run in your own environment, rake db:migrate will intelligently apply all migrations that have not yet run. This eliminates the interleaved migrations issue that existed on the first rev of this feature (see comments).

Another change that accompanies this functionality is the addition of rake db:migrate:up and rake db:migrate:down tasks, which will let you run the up and down operations of an individual migration (that may have been added on an svn merge or update):

1
2
3
4
5
> rake db:migrate:up VERSION=20080402122523
== 20080402122523 Two: migrating ==============================================
-- create_table(:two)
   -> 0.0122s
== 20080402122523 Two: migrated (0.0124s) =====================================

This functionality was previously available as the enhanced migrations plugin, so if you’re on an older version of Rails you can still make your migrations less whiny.

And if you’re just not lovin’ this (as some of the comments seem to mildly indicate) you can always revert back to the sequential prefix naming style of migrations with this guy in the config block of your environment.rb file:


config.active_record.timestamped_migrations = false

tags: ruby, rubyonrails

Comments

Leave a response

  1. Tim HarperApril 02, 2008 @ 10:34 AM

    I don’t get it :) Is enhanced_migrations supposed to be broken in Rails 2.0 or something? We’re using it (enhanced_migrations) in rails 2.0, and it works awesome.

    Am I missing something? :)

  2. ncApril 02, 2008 @ 10:45 AM

    I’ll wait and see how this works out; it is a solution but I can’t say that I’m sure it is the best one. I don’t like the idea of timestamp based version numbers because there is still the possibility of collisions (very rarely), but mostly because moving up and down between versions requires an “ls” and copy and paste of the appropriate timestamps.

    I’ve always been of the opinion that there should have been a script tagged on to the end of “svn up” that would look in your development DB, decrement your schema version number as needed, move your unchecked in migrations out of the directory, run the new migrations, move your migrations back into the directory, and update their filenames. Simple, hacky, but no collisions, and you keep the version number goodness. I always meant to write this but with enhanced_migrations now a part of Rails I probably won’t bother.

  3. jcApril 02, 2008 @ 10:51 AM

    Ummm.. I hope this is an april fools joke

  4. TomApril 02, 2008 @ 11:06 AM

    I don’t see how the timestamps help in any way. Can anyone describe a case in which they do?

    The biggest problem I see is that migrations will still conflict when multiple people commit them, but now you can’t tell which ones those are without looking through logs. Not to mention that this seems to unnecessarily complicate finding the migration you just added in an Open dialog to edit it.

  5. jswannerApril 02, 2008 @ 11:33 AM

    Tom: From my understanding… This prevents you from creating 15_AddPersonTable, while I’m working on 15_AddArticleTable. Once we both check in our code there will be 2 migrations with the version number of 15. Instead you would create 20080402122523_AddPersonTable, and I would be working on 2008040202312311_AddArticleTable. Now the version numbers are different.

    That said, I can still see a problem when you create a migration file, then 10 minutes later (but before you check in) I create one. My database version will be timestamped to when I created mine, which will be greater than your migration. So after you check in, I would still need to know to downgrade my database prior to your migration, then run both migrations.

  6. CameronApril 02, 2008 @ 12:17 PM

    jswanner: From the linked article about the enhanced migrations plugin;

    “The biggest difference from the standard way is that migrations below the current schema version are still applied if they have not been applied yet (not in the tracking table).”

  7. JohnApril 02, 2008 @ 12:19 PM

    In response to those wary of the new system:

    http://en.wikipedia.org/wiki/Addition#Commutativity

    The fundamental model behind migrations is one where a linear “diffs” can can be applied to a database in chronological order to make the final product.

    The previous system of numbered versions was insufficient, because if commutative diffs were made, they had to be manually merged or modified to enable them to be applied correctly.

    Timestamp based migrations were built to solve that one particular problem: when 2+ developers make commutative diffs, where previously they would have to me merged or modified, now they Just Work™.

    It doesn’t solve the problem of non-commutative changes, though, but it doesn’t intend to. Those must still be merged or modified manually.

    It is possible to minimize the amount of manual intervention by declaring and building dependency graphs between migrations. You could even use rake, which has dependency calculation built-in, but that is “too much software” and sounds way too enterprisey. If you were an economist, you might say that the cost (lots more infrastructure code + more code required for each migration to describe dependencies) outweighs the benefits (a very small percentage of new diffs wouldn’t require manual merging or modification).

    So, welcome the new changes, but don’t be fooled into thinking it’s a panacea. As well, don’t criticize it because it doesn’t solve every single possible case of problem. Nothing is magic.

  8. Tim HarperApril 02, 2008 @ 01:25 PM

    Oh… I get it now :) This was previously available in the plugin enhanced migrations, and now it’s in edge rails! I’ll have to check have my eyeballs checked.

    Tom: UTC based migrations do look ugly, but in practice they haven’t been difficult to deal with. db:migrate:previous still works, and migrating to a specific UTC stamps works as expected as well. We branch like crazy at my company, and do a lot of parallel development. Incremental migration numbers were a nightmare for us, because whenever we merged two branches, or did a release and merged back into the task branches, keeping our migrations revisioned was just pure insanity (we were considering having a special migrations branch, where all migrations were created/modified from there… but that was too much overhead).

  9. Sean SoperApril 02, 2008 @ 01:41 PM

    As the current maintainer of the Enhanced Migrations plugin, I’m glad to see it get such exposure outside of our own setup. We’ve been using it in all our projects for over a year now to great success. It tackles very effectively the problem it was designed to solve, namely conflicting database migrations in a large development environment.

    FYI, we updated the plugin in November (ver 1.2.1) to address some minor issues. A new version should be forthcoming in the next few weeks as well. You can find this and other plugins developed at Revolution Health at our Rubyforge site. Feedback is welcome.

    http://rubyforge.org/projects/revhealth

  10. Jean-Francois CoutureApril 02, 2008 @ 02:02 PM

    I just took a quick look at the changeset. It does not seem to work exactly like enhanced_migrations, which keeps a table of the run migrations, instead of just the last version like the current schema_info. That means on branch merge, I will have to manually run migrations from the branch where the timestamp is smaller than the current version.

    Is that correct? If it is I’ll have to keep using the plugin.

  11. SebastianApril 02, 2008 @ 04:08 PM

    On which version didi you test it?

  12. JustinApril 02, 2008 @ 04:10 PM

    I am thrilled to see this change. Perhaps we should have been using the Enhanced Migrations plugin but we have a case where we need to merge in a set of migrations into each of our new projects. We keep running into problems were we have two migrations with 001_. This solves our problem.

  13. Jordi BunsterApril 02, 2008 @ 07:44 PM

    After you {git, svn} merge and new migrations appear in the middle, you have to ls to find which ones are new, and copy & paste to rake db:migrate:up VERSION=foo to migrate the new ones.

    Or you can help me test this patch (http://dev.rubyonrails.org/ticket/11493), +1 it, and then in Rails 2.1 you can just do rake db:migrate and it’ll pick up the new ones. Thanks!

  14. Alex GregianindApril 03, 2008 @ 09:14 AM

    I don’t see much improvement changing to UTC based migrations.Let’s say I created migration at 03:15 pm today and didn’t commit it to the repository yet because im not done with whatever Im working on,well…5 minutes lter my partner does the same but he ’s working on a different part of the project.Well,When I commit my changes to the repository and he updates it,the migrations won’t work because the schema_info now records the last migrations timestamp he used,so it will ignore my migration cause it was created before my partner’s migration file.

  15. Josh OwensApril 04, 2008 @ 12:35 PM

    When converting to edge, do we need to change our current migration naming, then?

  16. Josh OwensApril 04, 2008 @ 12:36 PM

    So when moving to edge, do we need to change the naming scheme of our current migrations setup?

  17. Peter BolingApril 10, 2008 @ 11:32 AM

    http://dev.rubyonrails.org/ticket/11493

    It appears the patch was accepted and is now in Edge Rails. That means that in Rails 2.1 it will track each migration that has been run, and when migrating up will run ones that haven’t yet been run. When migrating down it will only revert ones that have actually been run, skipping ones that were never run on that DB.

    Excellent!

    Ryan, perhaps this warrants an update to your article?

  18. RyanApril 11, 2008 @ 07:38 AM

    Great idea, Peter – done.

  19. BryanJune 09, 2008 @ 03:28 PM

    So, instead of rolling back by typing up to a 3 (or-unlikely-4) digit number, I now get to type a FOURTEEN DIGIT NUMBER?! Oh, yeah, that is time-saving!</sarcasm>

    rake db:migrate VERSION=54 rake db:migrate VERSION=20080609142333

    Yes, definitely easier.

    db:migrate:previous still works (Tim Harper)

    Which makes sense to use if the target version is not too far away. Want to run that command 10 or 20 times?

    Oh, and when all the migrations are listed out in my IDE (Eclipse), they are sorted by filename. With the recent “improvement,” my list can now be sorted out-of-order, making it just that much more confusing. Taking jswanner’s scenario above (April 2), the list of migrations is immediately out of order—and there’s no way to order them properly, short of renaming the file(s) manually.

    Is there a better solution? Not sure, but it would’ve been nice if there was a way to turn this one off and use the “classic” methods. I’ve easily created hundreds of migrations, branched, and merged without much difficulty on teams of one to many.

  20. comboyJune 16, 2008 @ 03:43 PM

    It’s still not too late to undo it. Please.

  21. PhilJune 17, 2008 @ 03:20 PM

    It is a crazy idea. I am now renaming migrations by hand! Surely we should be able to configure this somehow?

  22. Joshua WarcholJune 26, 2008 @ 01:24 PM

    I think the new system is an improvement, but we were using enhanced_migrations and now we’re having issues with it trying to re-run those migrations. Gotta figure that out.

  23. LudwigJuly 18, 2008 @ 03:16 PM

    Hi, i decided to rename my old migrations files with the new convention, is there any script to make this task easy?In case not, which is the best way to rename them by hand?

    Thank you very much!

  24. JabberJuly 21, 2008 @ 09:48 PM

    Oh, and when all the migrations are listed out in my IDE (Eclipse), they are sorted by >>filename. With the recent “improvement,” my list can now be sorted out-of-order, >>making it just that much more confusing.

    If the format of the name is yyyymmddhhmmss_name, then how can the files be listed out-of-order? Each subsquent number is a subset of the previous number.

    Year>Month>Day>Hours>Minutes>Seconds

    I’m also using Aptanta (Eclipse) and RadRails Plug-in, and the files are displayed in the correct order.

  25. RMAAugust 02, 2008 @ 12:11 AM

    hello thank you , useful post

    when i use migration for addin additional column i cant see the result on the app itself but i can see this column in my table on database

    can you help me please

  26. FredAugust 10, 2008 @ 04:34 PM

    WTF is a Migration?

  27. JeffMay 12, 2009 @ 12:48 PM

    I am using eclipse and it does not let me run the new migrations, I get “Invalid Migration Number”, running directly from rails works fine. Anyone know what the issue is? I am running version 3.4.1 which is not that old.

  28. ChrisMay 25, 2009 @ 03:05 PM

    Jeff, I had the same problem. I found that to get around it (while still using “Run Migration” from RadRails) I had to old-school-ize the migration name back to 001_whatever.