10

I need to convert string fields into integer and use enum instead. What is the best way to do this without losing data?

This is current migration:

class CreateSystems < ActiveRecord::Migration
  def change
    create_table :systems do |t|
      t.string :operation
      t.string :status

      t.timestamps null: false
    end
  end
end

Then I change type of the fields like so:

class ChangeColumnsForSystems < ActiveRecord::Migration
  def change
    change_column :systems, :operation, :integer
    change_column :systems, :status, :integer
  end
end

And update model file.

/app/models/system.rb

...
enum operation { start: 0, stop: 1 }
enum status { init: 0, working: 1, complete: 2 }
...

How can I update old data?

2 Answers 2

11

After some research I found this to be a proper solution.

class ChangeColumnsForSystems < ActiveRecord::Migration
  def change
    change_column :systems, :operation, "integer USING (CASE operation WHEN 'start' THEN '0'::integer ELSE '1'::integer END)", null: false
    change_column :systems, :status, "integer USING (CASE status WHEN 'init' THEN '0'::integer WHEN 'working' THEN '1'::integer ELSE '2'::integer END)", null: false
  end
end

UPDATE: In some cases you will have to remove default value prior to changing type. Here is the version with rollback.

class ChangeColumnsForSystems < ActiveRecord::Migration
  def up
    change_column_default :systems, :status, nil
    change_column :systems, :operation, "integer USING (CASE operation WHEN 'start' THEN '0'::integer ELSE '1'::integer END)", null: false
    change_column :systems, :status, "integer USING (CASE status WHEN 'init' THEN '0'::integer WHEN 'working' THEN '1'::integer ELSE '2'::integer END)", null: false, default: 0
  end

  def down
    change_column_default :systems, :status, nil
    change_column :systems, :operation, "varchar USING (CASE operation WHEN '0' THEN 'start'::varchar ELSE 'stop'::varchar END)", null: false
    change_column :systems, :status, "varchar USING (CASE status WHEN '0' THEN 'init'::varchar WHEN '1' THEN 'working'::varchar ELSE 'complete'::varchar END)", null: false, default: 'init'
  end
end
Sign up to request clarification or add additional context in comments.

Comments

2

You can do it in 2 migration steps

1. Rename current operation column and add new with neccessary type

def up
    rename_column :systems, :operation, :operation_str
    add_column :systems, :operation, ... # your options
end

2. Move values from old column to new and drop old column

def up
    System.all.each do |sys|
        sys.operation = sys.operation_str.to_i # replace it with your converter
    end
    remove_column :systems, :operation
end

Don't forget write rollback code if it's neccessary

2 Comments

This migration will corrupt all data, because when you call .to_i on a string, you will always get 0. Next piece of code will work as expected. However it's not the best solution sys.operation = System.operations[sys.operation_str]
Won't this remove the new column you just created rather than dropping the old column?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.