How to combine "change", "up", and "down" in a Rails migration

Updated . Posted . Visible to the public. Repeats.

Rails migrations allow you to use a change method whose calls are automatically inverted for the down path. However, if you need to some path-specific logic (like SQL UPDATE statements) you can not define up and down methods at the same time.

If you were to define define all 3 of them, Rails would only run change and ignore up and down. However, Rails 4+ features a helper method called reversible Show archive.org snapshot :

class MyMigration < ActiveRecord::Migration

  def change
    rename_table :old_table, :new_table
    add_column :new_table, :foo
    # ...
   
    reversible do |change|
      change.up do
        update "UPDATE new_table SET foo = something_really_complex"
      end
      
      change.down do
        update "UPDATE new_table SET foo = what_it_was_before"
      end
    end
  end
  
end

Note that the "down" path is like running the change block from bottom upwards. This is relevant when writing SQL statements: When migrating down, the rename_table statement will be run after the reversible block. While this makes sense, it may feel odd that the SQL statement says "UPDATE new_table".

If your migration becomes too complex, you could put your logic into multiple reversible blocks (so they contain only one of up/down).

When you only need to do something when migrating "up", you can use up_only (available since Rails 5.2):

class MyMigration < ActiveRecord::Migration

  def change
    rename_table :old_table, :new_table
    add_column :new_table, :foo
    # ...

    up_only { "UPDATE new_table SET foo = something_really_complex" }
  end
  
end

While there is no down_only, you can use reverting? and avoid the reversible boilerplate. It might be less intuitive since if reverting? puts the "down" path first.

class MyMigration < ActiveRecord::Migration

  def change
    rename_table :old_table, :new_table
    add_column :new_table, :foo
    # ...
   
    if reverting?
      update "UPDATE new_table SET foo = what_it_was_before"
    else
      update "UPDATE new_table SET foo = something_really_complex"
    end
  end
  
end

A word of advice: Depending on your migration, you might be better off just defining 2 separate up and down methods instead of using change.

Arne Hartherz
Last edit
Michael Leimst├Ądtner
Keywords
migrate
License
Source code in this card is licensed under the MIT License.
Posted by Arne Hartherz to makandra dev (2014-11-19 10:11)