Search code examples
ruby-on-railsrails-migrations

Using renamed columns in a migration


In Rails 6.1, I would like to rename a column and convert the underlying data in a single migration:

class RestructureExamples < ActiveRecord::Migration[6.1]
  
  def up
    rename_column :examples, :old_column_name, :new_column_name
    Example.reset_column_information
    Example.find_each do |example|
      unless example.new_column_name.nil?
        if example.new_column_name == 100
          example.new_column_name = 300
        else
          example.new_column_name = (example.new_column_name.to_f * 2.989).to_i
        end
      end
    end
  end

  def down
    # Do the reverse - left out for brevity
  end

end

Even after adding reset_column_information (from the docs: "Resets all the cached information about columns, which will cause them to be reloaded on the next request."), this throws NoMethodError: undefined method `new_column_name'.

The record example still has old_cloumn_name. I was expecting that the column information is updated in the database and the model after calling rename_column together with reset_column_information.

I can see in the sources that rename_column performs an alter table SQL command. Checking the column names via SQL after rename_column reveals that the column is renamed correctly. So, I assume that only the model holds outdated information.

Probably there are several workarounds (use SQL instead of model, do the rename after converting the data, use two separate migrations, ...), but I would prefer my approach for comprehensibility.


Solution

  • I think I found an anwser to my own question. As suggested in the migrations guide, it is possible to use a local model in combination with reset_column_information:

    class RestructureExamples < ActiveRecord::Migration[6.1]
      
      class Example < ActiveRecord::Base
      end
      
      def up
        rename_column :examples, :old_column_name, :new_column_name
        Example.reset_column_information
        Example.find_each do |example|
          unless example.new_column_name.nil?
            if example.new_column_name == 100
              example.new_column_name = 300
            else
              example.new_column_name = (example.new_column_name.to_f * 2.989).to_i
            end
          end
        end
      end
    
      def down
        # Do the reverse - left out for brevity
      end
    
    end
    

    This approach worked for me.