How to make rails migrations pretty
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 [/sourcecode] 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: [sourcecode language='ruby'] 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.
@Michal Papis
I didn’t understand that was the intent. Yes, publish_at might make more sense in that case.
@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 ?
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
@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.
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?