33

I really like the Rails 3 style migrations, i.e. one change method being smart enough to recognize if the migrations is being installed or rolled back, so I don't have to write up and down methods mirroring each other. But I have situation that I need to skip some code when the migration is rolled back (updating counter_cache columns that I'm adding).

I looked at http://guides.rubyonrails.org/migrations.html but the examples at the end of section 5 suffer from the same problem:

class AddFuzzToProduct < ActiveRecord::Migration
  class Product < ActiveRecord::Base
  end
  def change
    add_column :products, :fuzz, :string
    Product.reset_column_information
    Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
  end
end

When this migration is rolled back, the update of fuzz field is unnecessary. Is there a way to prevent it?

I tried looking into Product.column_names but since Rails is smart enough to perform migration in reverse direction, the update is executed before the column is removed. Also, when change method is defined, any up or down methods seem to be ignored. Any other ideas?

szeryf
  • 3,197
  • 3
  • 27
  • 28
  • up and down methods are not deprecated for all versions of rails and they needed for things like this. All solutions that here provided is bad because they are not oblivious, and have bad smell, and best way to solve your problem is just write correct up and down methods – edikgat Jan 25 '16 at 12:59

4 Answers4

74

Just for future reference, for Rails 4 the best way to do this is to use reversible:

def change
  # ...change code...

  reversible do |dir|
    dir.up do
      # ...up-only code...
    end
  end
end

See http://guides.rubyonrails.org/migrations.html#using-reversible

Jordan Running
  • 102,619
  • 17
  • 182
  • 182
Tim Diggins
  • 4,364
  • 3
  • 30
  • 49
23

In rails 3.x you can also do this

class AddFuzzToProduct < ActiveRecord::Migration
  class Product < ActiveRecord::Base
  end
  def change
    add_column :products, :fuzz, :string
    unless reverting?
      # Do this only when direction is up
      Product.reset_column_information
      Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
    end
  end
end
Fabio
  • 18,856
  • 9
  • 82
  • 114
22

In this case I think you'll have to use up and down methods as usual. Don't worry, despite the addition of change in Rails 3 those methods aren't, as far as I know, bound for the chopping block. Continue using them where necessary.

Edit: Here's an option: Override migrate.

class AddFuzzToProduct < ActiveRecord::Migration
  class Product < ActiveRecord::Base
  end

  def change
    add_column :products, :fuzz, :string
  end

  def migrate(direction)
    super # Let Rails do its thing as usual...

    if direction == :up # ...but then do something extra if we're going 'up.'
      Product.reset_column_information
      Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
    end
  end
end

Thoughts?

Jordan Running
  • 102,619
  • 17
  • 182
  • 182
  • 1
    I know I can define `up` and `down` instead of `change`, but I'd prefer using `change` if possible, since it reduces duplication. But I guess I'll have to use them if there's no other option :( – szeryf Oct 06 '11 at 20:10
  • 2
    @szeryf Take a look at my updated answer. Hopefully this will be useful to you, and I think it should be pretty robust with regard to `ActiveRecord::Migration`'s API. – Jordan Running Oct 06 '11 at 20:24
  • this worked perfectly for me; thanks! In my case, I had to include some SQL magic to insert data into a table based on existing data. Would have been too time-consuming to do in code on an existing production data with huge number of records. – dsaronin May 24 '13 at 01:36
  • Possibly, one might want to just have 2 migrations? – justingordon Apr 01 '14 at 02:02
1

Here's a nasty thought: @connection is the CommandRecorder when going down.

def change
    add_column :products, :fuzz, :string
    unless @connection.kind_of?(ActiveRecord::Migration::CommandRecorder)
        Product.reset_column_information
        Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
    end
end

Haven't tried it. Obviously you're outside the Rails API so it could break at any time.

If only the change method had a legitimate way of determining the migration direction...

bronson
  • 5,612
  • 3
  • 31
  • 18