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)