Home > Development > How to make rails migrations pretty

How to make rails migrations pretty

February 26th, 2010

I have seen a lot of migrations, most of them was just doing what it had to do. But I found a nice way to make my migrations more pretty.

So I had an Article model, which had field :published of type :boolean , as comparing to other solutions, I found that it should be rather :publish_date of type date.
First I have created migration using “script/generate migration article_update” and got empty migration:

class UpdateArticles < ActiveRecord::Migration
  def self.up
  end
  def self.down
  end
end
&#91;/sourcecode&#93;
To be able to freely play with Fields we add our private implementation of article model, we change the fields and move data for them:
&#91;sourcecode language='ruby'&#93;
class UpdateArticles < ActiveRecord::Migration
  class Article < ActiveRecord::Base; end
  def self.up
    add_column :articles, :publish_date, :datetime, :null => true
    Article.reset_column_information
    Article.all.each { |article| article.update_attribute(:publish_date, :created_at) if article.publish }
    remove_column  :articles, :publish
  end
  def self.down
    add_column  :articles, :publish, :boolean
    Article.reset_column_information
    Article.all.each { |article| article.update_attribute(:publish, true) unless article.publish_date.nil? }
    remove_column :articles, :publish_date
  end
end

But this is not really what we wanted, if we have already a lot of data in Article tables then we will not know what is going on during update attribute action.
To make it display pretty we have to add say_with_time function:

class UpdateArticles < ActiveRecord::Migration
  class Article < ActiveRecord::Base; end

  def self.up
    add_column :articles, :publish_date, :datetime, :null => true
    say_with_time "Updating publish_date column" do
      Article.reset_column_information
      Article.all.each { |article| article.update_attribute(:publish_date, :created_at) if article.publish }
    end
    remove_column  :articles, :publish
  end

  def self.down
    add_column  :articles, :publish, :boolean
    say_with_time "Updating publish column" do
      Article.reset_column_information
      Article.all.each { |article| article.update_attribute(:publish, true) unless article.publish_date.nil? }
    end
    remove_column :articles, :publish_date
  end
end

And after running migration we will get he following result:

==  UpdateArticles: migrating =================================================
-- add_column(:articles, :publish_date, :datetime, {:null=>true})
-> 0.0007s
-- Updating publish_date column
-> 0.0034s
-- remove_column(:articles, :publish)
-> 0.0224s
==  UpdateArticles: migrated (0.0269s) ========================================

For me it was very small amount of data and the migration went fast, but if i would run it on production with thousands of records then it may take a bit more.

I know this exact code could be rewritten using SQL – and it would be almost always done in seconds, but even then It is nice to show progress, because person responsible for deployment might be confused seeing only blinking cursor.

Did You liked this post or maybe not, vote on it at dzone.

Categories: Development Tags: ,
  1. February 27th, 2010 at 01:56 | #1

    @Michal Papis
    I didn’t understand that was the intent. Yes, publish_at might make more sense in that case.

  2. February 27th, 2010 at 00:42 | #2

    @Jeremy Weiskotten
    Nice hints, what about naming the field as it is purposed, so it will not only show the date when it was published.

    The purpose of this field is to allow write Article today and publish it tomorrow, wouldn’t be better to have it named publish_at instead of published_at then ?

  3. February 27th, 2010 at 00:31 | #3

    Following Rails conventions, the column should be named published_at because it’s a timestamp.

    If you have 10s of thousands of articles in your database, I’d replace the loops (in up and down) with update_all statements. That way you only have to run one SQL UPDATE instead of one per article. That will be MUCH faster, especially if you have indexes on the columns you use in your WHERE clauses.

    Here’s my take: http://gist.github.com/316311

  4. February 26th, 2010 at 20:39 | #4

    @kc00l
    If you ask for Article.reset_column_information – this is for re reading columns from database, in production configuration (on server) this is done only once so If it was changed we have to re read it,

    If you ask for class Article < ActiveRecord::Base; end – then this is for having clean implementation of the Model it prevents Your model code from being run during migration, so functions like after_save which sends email will be not called, it is just the lightest implementation of your model.

  5. kc00l
    February 26th, 2010 at 17:42 | #5

    Very nice, It could indeed be useful for migration taking a long time to complete.
    I wonder why you invoked model class before the up and down method for the migration. Is that needed to do (and undo) Article.do_stuff within the .up and .down methods?

Comments are closed.