How do you make remove_column reversible? - ruby-on-rails

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.

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.

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

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

Related

Rails rollback change_column migration

I just migrated my create_supplier migration, then I realized that one of my data type was wrong, so I added another migration which looks like this:-
class ChangePhoneToStringInSuppliers < ActiveRecord::Migration[5.1]
def change
change_column :suppliers, :phone_no_1, :string
change_column :suppliers, :phone_no_2, :string
end
end
After migrating this, I realized that I haven`t pushed my code, so ideally I should rollback till create_suppliers migration and add the changes there itself. When I rollback ChangePhoneToStringInSuppliers, I get following error:-
This migration uses change_column, which is not automatically reversible.
To make the migration reversible you can either:
1. Define #up and #down methods in place of the #change method.
2. Use the #reversible method to define reversible behavior.
I think the method suggested in above error message(and other posts on internet) is a prevention to this problem, rather cure(correct me if I am wrong). How can I rollback this migration now?
you need to update migration file code. remove def change method and instead add both method up and down because change_column migration not supported rollback.
I don't know which column data type you used earlier, so please change it as per your need
class ChangePhoneToStringInSuppliers < ActiveRecord::Migration[5.1]
def up
change_column :suppliers, :phone_no_1, :string
change_column :suppliers, :phone_no_2, :string
end
def down
change_column :suppliers, :phone_no_1, :text
change_column :suppliers, :phone_no_2, :text
end
end
In up method write what you want to do, like change column data type to text,
In down method write if you rollback migration what should it do, like currently your column data type is string and you want back it to string when you rollback than write appropriate code.

Adding column to database table in rails 1.2.6(legacy version)

I am using ruby 1.8.7 and rails 1.2.6. (Its old I know. But I have to use it.) I need to add column to users table. I cant use rails generate migration with rails 1.2.6. I need to add a versioned db migrate file. How can I do that?
I want to add product column to users table. I created a file in the db/migrate folder with following contents.
class AddProductToUser < ActiveRecord::Migration
def self.up
add_column :users, :product, :string
end
def self.down
remove_column :users, :product
end
end
I used script/generate migration AddProductToUser. It gives an error as
undefined method 'cache' for Gem:Module.
Any pointers on how to run migration in rails 1.2.6(<2.x) will also be useful.
Your migrate file looks (almost) fine, does the filename match the class name, and does it have a sequence number at the beginning that follows on from the previous migrate?
Slight change:
# db/migrate/123_add_product_to_user.rb
class AddProductToUser < ActiveRecord::Migration
def self.up
add_column :users, :product, :string
end
def self.down
remove_column :users, :product # note sure where radius came from?
end
end
Then should just be run with rake migrate
More info at apidock

Why is this migration irreversible? (change_table, rename, text)

I have what I think is a pretty simple migration. For some reason I get an IrreversibleMigration error when I try to db:rollback or db:migrate:redo.
The migration runs smoothly, but I'd rather keep it reversible. I can't figure out why it's not as written. Any ideas?
Here's the migration:
class AddWhyHypAndWhyHypeToStatements < ActiveRecord::Migration
def change
change_table :statements do |t|
t.rename :description, :why_hypocritical
t.text :why_hypothetical
end
end
end
If it matters, "description" column is a text column. I'm using Rails 3.1/Ruby 1.9.2/PostgreSQL. Thanks for any help.
Looks like Rails has troubles reverting change_table method. Try doing it that way instead:
class AddWhyHypAndWhyHypeToStatements < ActiveRecord::Migration
def change
rename_column :statements, :description, :why_hypocritical
add_column :statements, :why_hypothetical, :text
end
end
You can see the list of commands that can be inverted in the docs or in Rails Guides.

Undefined add_column in Rails 3

I am trying to run a migration in Rails 3, I wish to add a column to a table, the code looks like this:
class AddConstAdr < ActiveRecord::Migration
def change
change_table: constants do |t|
t.add_column :home_address, :string
end
end
end
When I do rake db:migrate I get an error saying undefined method 'add_column'. I am confused as to why this is happening, can anyone help?
You seem to be mixing two different ways of doing a migration. You probably want this:
def change
change_table :constants do |t|
t.string :home_address
end
end
or this:
def change
add_column :constants, :home_address, :string
end
Both forms should do the same thing: add a home_address string column to the constants table.
I'm also assuming that your change_table: constants is just a typo that should have been change_table :constants.
Further information may be found in the Migrations Guide.
You should do as below:
def change
add_column :constants, :home_address, :string
end

Adding a Model Reference to existing Rails model

I'd like to know the "proper" way to approach adding a relation between two existing classes in Rails 3.
Given existing models: Clown & Rabbit
I'd like to add a reference (belongs_to) from Rabbit to Clown. I start by trying to generate a migration:
rails g migration AddClownToRabbits clown:reference
which gives me a migration that looks like:
class AddClownToRabbits < ActiveRecord::Migration
def self.up
add_column :rabbits, :clown, :reference
end
def self.down
remove_column :rabbits, :clown
end
end
After rake db:migrate on this migration I examine SQLite3's development.db and see a new column: "clown" reference
I guess I was expecting a "clown_id" integer column and a migration that looked like:
class AddClownToRabbits < ActiveRecord::Migration
def self.up
add_column :rabbits, :clown_id
end
def self.down
remove_column :rabbits, :clown_id
end
end
I'm sure :reference is supposed to be equivalent to "t.references :clown" but I can't find the documentation (big surprise). API says add_column: Instantiates a new column for the table. The type parameter is normally one of the migrations native types, which is one of the following: :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean.
...with no reference to :reference.
If you are using edge rails (4.0) you can use:
rails generate migration AddAddressRefToContacts address:references
As you can see by the docs.
After you set belongs_to in Rabbit, and has_many in Clown, you can do a migration with:
add_column :rabbit, :clown_id, :integer
EDIT: See Paulo's answer below for a more updated answer (Rails 4+)
I'm not sure where you got this idea, but there is no (and never has been) such syntax to do what you want with add_column. To get the behavior you want, you'd have to do t.refences :clown, as you stated. In the background this will call: #base.add_column(#table_name, "#{col}_id", :integer, options).
See here.
EDIT:
I think I can see the source of your confusion. You saw the method call t.reference and assumed it was a datatype because calls such as t.integer and t.string exist, and those are datatypes. That's wrong. Reference isn't a datatype, it's just simply the name of a method, similar to t.rename is.

Resources