Avoiding errors when migrating data that involves Enums

Ademar

Ademar / December 14, 2018

Scenario

We had a Rental model that had a column which is an enum type.

class Rental < ApplicationRecord
  enum delivery_and_return_type: %i[pickup courier post]

We needed to remove the "courier" option on the enum.

We created a migration to remove the "courier" type and change all existing delivery_and_return_type into a "post". The migration implementation is this:

class RentalCourierToRentalPost < ActiveRecord::Migration[5.2]
  def change
    Rental.where(delivery_and_return_type: 'courier').each do |rental|
      rental.update(delivery_and_return_type: 'post')
    end
  end
end

Everything went well in development. However there was an issue when pushed the code to production and ran the migration.


The Problem

Since delivery_and_return_type is implemented as an enum on Rental model, when the migration ran on production, it could no longer find the "courier" option because we already removed it on the Rental model.

class Rental < ApplicationRecord
  enum delivery_and_return_type: %i[pickup post]


The Fix

First step is to remove courier as an option on the Rental model.

class Rental < ApplicationRecord
  enum delivery_and_return_type: %i[pickup post]

Since delivery_and_return_type is saved as an integer in the database

  create_table "rentals", force: :cascade do |t|
    t.integer "delivery_and_return_type"

We need to use the integer value on the migration so the code changes on the enum array will not affect the migration.

courier is represented as 1 and post is represented as 2. This means that all the rentals that have courier as return_and_delivery_type is automatically turned in to post, which is what we need. However, all the rentals that have "courier" return_and_delivery_type will have an incorrect value on DB.

The solution is to change the migration to this

class RentalCourierToRentalPost < ActiveRecord::Migration[5.2]
  def change
    # `enum delivery_and_return_type: %i[pickup courier post]`
    # courier is represented as one on the enum array
    # post is represented as two on the enum array
    Rental.where(delivery_and_return_type: 2).each do |rental|
      rental.update(delivery_and_return_type: 1)
    end
  end
end

Now we have successfully migrated data and created a migration code that is not dependent on the enum array values.