Cast Rails migrations data types

If you are a Rails developer, you probably have used migrations to update your database schema, and sometimes you make some mistakes and need to rollback some of these changes.

I've been working with Rails and Postgres for a while now and everything works really smooth. But I recently faced a problem I'd solved years ago.

The problem

On a project I'm recently working on, I had to change a data type column from an integer to a string which is really simple to achieve on Rails:

$ rails g migration updates_field_data_type_from_table

And the migration file should look something like:

class UpdatesFieldDataTypeFromTable < ActiveRecord::Migration  
  def change
    change_column :table_name, :column, :string
  end
end  

What I like to do when working with these kind of migrations is to use the self.up and self.down class methods in order to have more control when rollbacking. The migration file now should look something like this:

class UpdatesFieldDataTypeFromTable < ActiveRecord::Migration  
  def self.up
    change_column :table_name, :column, :string
  end

  def self.down
    change_column :table_name, :column, :integer
  end
end  

Seems right and safe, right? Well although the rake db:migrate command runs perfectly I recommend you rollback too just in case you need it in the future:

$ rake db:rollback
== 20150410192929 UpdatesFieldDataTypeFromTable: reverting ==
-- change_column(:table_name, :column, :integer)
rake aborted!  
StandardError: An error has occurred, this and all later migrations canceled:

PG::DatatypeMismatch: ERROR:  column "column" cannot be cast automatically to type integer  
...

This makes so much sense once you step back and think for a while. Go ahead.

The solution

The solution is right there ERROR: column "column" cannot be cast automatically to type integer. We just now need to figure out how to achieve this on Postgres and then apply it on the migration.

The final migration file should look like:

class UpdatesFieldDataTypeFromTable < ActiveRecord::Migration  
  def self.up
    change_column :table_name, :column, :string
  end

  def self.down
    change_column :table_name, :column, 'integer USING CAST(column AS integer)'
  end
end  

Hope you find this useful!