20

The Rails guides to active record migrations says that you can do

change_column_default :products, :approved, from: true, to: false

I've got a change method in Rails that's similar to the following:

change_column_default :people, :height, from: nil, to: 0

with the intention of going from not having any defaults, to having a default of zero.

However, when I try rolling it back, I get

ActiveRecord::IrreversibleMigration: ActiveRecord::IrreversibleMigration

Considering I give Rails a from and to, why isn't it accepting it?

I'm using Rails 4.2.0.

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
  • Could it be that Rails is trying to avoid a type coercion issue? – max Jul 22 '15 at 01:56
  • http://stackoverflow.com/questions/7098602/add-a-default-value-to-a-column-through-a-migration – max Jul 22 '15 at 01:58
  • I don't see any `from` or `to` parameters in the [method definition](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/PostgreSQL/SchemaStatements.html#method-i-change_column_default) – Jonathan Allard Jul 22 '15 at 03:53
  • @JonathanAllard assuming that `from` and `to` are hash keys, rather than optional arguments, they wouldn't appear in the method definition. – Andrew Grimm Jul 22 '15 at 04:11
  • @JonathanAllard how do you know that the method you link to is the one called by the `change_column_default` call within `ActiveRecord::Migration`? – Andrew Grimm Jul 22 '15 at 08:56
  • Is it possible that the column is not nullable? – Harish Shetty Jul 26 '15 at 23:32
  • Speculation: the edge guides and Rails 4 guides may be saying different things, and that's because things have changed between Rails 4 and Rails edge. – Andrew Grimm Jul 26 '15 at 23:34
  • **Using `from` and `to` was added in Rails 5+ in this commit: https://github.com/rails/rails/pull/20018/files** – Joshua Pinter Dec 29 '18 at 19:04

7 Answers7

21

Using from and to was added in Rails 5+

The guide linked to in the question is for Edge Rails, not for a released version of Rails.

Reversible syntax for change_column_default is the result of pull request 20018. The pull request also updated the Rails guides for Edge Rails.

From activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb:

-      def change_column_default(table_name, column_name, default)
+      # Passing a hash containing +:from+ and +:to+ will make this change
+      # reversible in migration:
+      #
+      #   change_column_default(:posts, :state, from: nil, to: "draft")
+      #
+      def change_column_default(table_name, column_name, default_or_changes)

The pull request was made June 27, 2015, which is more recent than any version of Rails released as of August 1, 2015.

The documentation for migration for Rails 4.2.3 reflects the fact that reversible syntax is not yet available:

change_column_default :products, :approved, false
Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
17

if you are using mysql as adapter, then according to this link http://apidock.com/rails/ActiveRecord/ConnectionAdapters/AbstractMysqlAdapter/change_column_default, your change_column_default migration runs like this

def change_column_default(table_name, column_name, default) #:nodoc:
 column = column_for(table_name, column_name)
 change_column table_name, column_name, column.sql_type, :default => default
end

so as you see it calls change_column within itself when you call change_column_default and according to this link http://edgeguides.rubyonrails.org/active_record_migrations.html change_column migration is irreversible.

This shows why you get ActiveRecord::IrreversibleMigration: ActiveRecord::IrreversibleMigration

So if you want to run migration using change_column_default you have to add def up and def down.

I would suggest to use change_column as it is already been called within change_column_default.

def up
 change_column :people, :height, :integer, default: 0
end

def down
 change_column :people, :height, :integer, default: nil
end
Athar
  • 3,258
  • 1
  • 11
  • 16
  • 1
    This answer isn't perfect, but it's better than the other answers (obviously, I can't award the bounty to myself). – Andrew Grimm Aug 02 '15 at 05:57
  • yes @AndrewGrimm you are right regarding perfect answer. i added http://edgeguides.rubyonrails.org/ as reference which is yet to be realesed. Still for adding information, i checked this link http://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html to see the list of reversible migrations. and i see `change_column` is not available in the list. lastly thank you for the bounty and thanks for your answer as well. helpful..:) – Athar Aug 02 '15 at 07:51
4

If you have an issue with migration file, Change your migration to this format.

change_column_default :table_name, :column_name, from: nil, to: "something"
Jin Lim
  • 1,759
  • 20
  • 24
1

As of Oct 2016, This feature (using to: and from: for change_column_default to be reversible) is now available on the 5.x branch. Unfortunately it's not available 4.2.x or lower. :(

Verification: git tag --contains f9c841927ac3d1daea2a9cebf08b18e844e5eec5 in the rails project.

hajpoj
  • 13,299
  • 2
  • 46
  • 63
0

First of all as Jonathan Allard said the from and to are not in the method source which means that the change_column_default doesn't accepts it. It is simply like this:

def sum(a)
  return a
end

Now if you try and pass two variables to it like sum(a, b) or anything it will not accept that right. This what you are trying to do above using from and to.

Now the correct syntax of this is:

change_column_default(:people, :height, 0)

The method doesn't accepts from and to(as it is defined as such, even if they are hash keys, if the method doesn't uses that key value pair anywhere then it is of no use to it) and if it is a new column the obviously it will have default value nil(if not set before) and suppose if the column height if of type integer and you give it default value a it will store 0 as the default value (not 100% sure but have tried doing this from rails console). It doesn't matters to rails what the default value currently is, it just needs the new default value. So if the current default value be 0 and you set it to nil rails will not complaint. It is your database and you wish what to do with it. Just if the database interrupts it if you are doing something wrong like assigning string to boolean then it will obviously throw error.

Now once this migration has ran then it will set the default value to 0 now rails doesn't know what the previous default value was. As it is gone and it has not store that anywhere. So that is why change_column_default is a irreversible migration. And if you try to roll it back it gives you ActiveRecord::IrreversibleMigration: ActiveRecord::IrreversibleMigration in case of change method. Means when you have used:

def change
  change_column_default(:people, :height, 0)
end

So that is why for this kind of migrations we use the method up and down:

def up
  change_column_default(:people, :height, 0)
end

def down
  change_column_default(:people, :height, nil)
end

Hope this helps.

Community
  • 1
  • 1
Deepesh
  • 6,138
  • 1
  • 24
  • 41
  • `from: nil, to: 0` is the same as `{:from => nil, :to => 0}`, which is a single object. – Andrew Grimm Jul 22 '15 at 08:13
  • Actually I think you didn't get my point, the `change_column_default` accepts only a single value for setting the default value of column, now instead of that you are writing `from: nil` so it has no code written in the source which will process your `from: nil` and `to: 0`. – Deepesh Jul 22 '15 at 08:36
0

Just to add a few points. If you are stuck with the Rails version not supporting reversible change_column_default, one option to get past the problem is:

def change
    # If your dataset is small, just cache in memory, if large, consider file dump here:
    cache = Table.all
    # Full column def important for reversibility
    remove_column :table, :column, :type, { config_hash }
    # Re-add the column with new default:
    add_column  :table, :column, :type, { config_hash, default: 0 }
    # Now update the data with cached records (there might be more efficient ways, of course):
    cache.each do |rec|
        Table.find(rec.id).update(column: rec.column)
    end
end
Vlad
  • 3,866
  • 1
  • 24
  • 20
-3

When you try to downgrade the data type , from value (0) to nil in this case , rails will complain this error. Because it might lose data

Another example would be going from string -> integer.

this is a really good article explain the same

UPDATE

It seems like your reverse migration calls change_column_default in command_recorder (activerecord-4.2.0/lib/active_record/migration/command_recorder.rb#76) please check if you set the nil as the default value.

UPDATE2

it turns out that, if you use change in migrations, those have to be able to reversible

but change_column is not reversible through change method. So you will have to use the old rails migration method with up and down

read this article explain the scenario

Community
  • 1
  • 1
sameera207
  • 16,547
  • 19
  • 87
  • 152