64

I have a migration that removes a column:

def change
  remove_column :foos, :bar, :boolean
end

When I try to rake db:rollback that migration, I get the following error:

remove_column is only reversible if given a type.

The ActiveRecord::Migration documentation says that the following is the signature for remove_column:

remove_column(table_name, column_name, type, options)

So my type in this case should be :boolean, and I expect that migration to be reversible. What am I missing?

I can certainly break this out into an up and down migration to avoid this problem, but I'd like to understand why the change syntax isn't working in this case.

  • I'm using Rails 4.1.2 in this case, and that matches the version listed in the documentation. –  Jul 01 '14 at 23:12
  • def up; remove_column :foos, :bar, :boolean; end; for the migrate and rollback will call down. def down; add_column :foos, :bar, :boolean; end; – Afonso Tsukamoto Jul 01 '14 at 23:12
  • I have this irreversible migration problem as well, and I have already ran rake:db migrate. I can't rollback and edit, so I'm thinking I'll just delete with rails destroy migration migration_name and redo the migration with the column_type. I'll let you know how it turns out – tomb Feb 06 '18 at 19:44
  • @tomb ah I am more than 2 years late and I think the steps you mentioned must have messed up your database even more ): I hope you managed to resolve it all. That's not how you do it. – ARK Jul 14 '20 at 15:34
  • @ARK better late than never? I did manage to resolve it, and posted my steps here: https://stackoverflow.com/a/51061650/1072655 – tomb Jul 22 '20 at 23:20

3 Answers3

103

Simply adding the 3rd argument (the column's :type) to the remove_column method makes that migration reversible. So the OP's original code actually did work, as in:

remove_column :foos, :bar, :boolean

The rest of this answer was an attempt to discover why this method would not have been working, but the OP ended up getting it to work.


I see somewhat contrary info in the documentation for ActiveRecord::Migration:

Some commands like remove_column cannot be reversed. If you care to define how to move up and down in these cases, you should define the up and down methods as before.

For a list of commands that are reversible, please see ActiveRecord::Migration::CommandRecorder.

And this from ActiveRecord::Migration::CommandRecorder:

ActiveRecord::Migration::CommandRecorder records commands done during a migration and knows how to reverse those commands. The CommandRecorder knows how to invert the following commands:

add_column

add_index

add_timestamps

create_table

create_join_table

remove_timestamps

rename_column

rename_index

rename_table

Anyway, it appears that this documentation is out of date... Digging into the source on github:

The method that's giving you grief is:

def invert_remove_column(args)
  raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2
  super
end

I gave this a shot... setup a migration on my Rails 4.1.2 app and the migration worked both ways -- up and down. Here was my migration:

class TestRemoveColumn < ActiveRecord::Migration
  def change
    remove_column :contacts, :test, :boolean
  end
end

I also tried with the :boolean argument missing and got the same error as you're talking about. Are you sure you're on the final version of Rails 4.1.2 -- not one of the release candidates? If you are, I'd suggest putting a binding.pry into the Rails source for the invert_remove_column method to inspect the arguments list and see what's going on. To do so, just run bundle open activerecord and then explore to: lib/active_record/migration/command_recorder.rb:128.

Community
  • 1
  • 1
pdobb
  • 17,688
  • 5
  • 59
  • 74
  • I had some wires crossed somewhere in my Rails code, and am no longer able to reproduce my problem. I think you might have been right that I may have been using a strange Rails version (despite having 4.1.2 in my gemfile). In any case, I think this is a great answer for helping future readers troubleshoot their own oddities if anyone else runs into similar problems. –  Jul 03 '14 at 17:42
  • 1
    Great research to try to discover what might be causing the error. Congrats! – Angelos Makrygiorgos Mar 17 '17 at 10:19
  • 3
    This indeed works, but there's a HUGE caveat here: if you are removing a column that contains a reference (like `user_id`), you probably have an index on that column, which will be automatically removed by Rails 4+ on `remove_column`. If you revert this migration, even if you specify the column type (like `:integer`), it will NOT re-add the index on the column, which can bring you a huge performance problem. So I would recommend spending a little bit more time and writing the up and down methods on the migration. – sandre89 Sep 10 '18 at 13:43
2

Instead of using change, you use up and down methods to your migration:

def up
  remove_column :foos, :bar
end

def down
  add_column :foos, :bar, :boolean
end
bratsche
  • 2,666
  • 20
  • 23
0

If you're doing a bulk remove of columns, you can make the migration reversible as follows (since rails 6.1)


def change
    change_table :foobar, bulk: true do |t|
      t.remove :foo, type: :float
      t.remove :bar, type: :int
    end
  end
Bruno Degomme
  • 883
  • 10
  • 11