I made a mistake when modeling my data and created a model with 4 booleans, only one of which are active at a time. I want to create a column of Postgres enum type, and then set the value based on which boolean flag was previously set.
I know I want to start with something like this:
class ChangePositionTypeToBeEnumInPosition < ActiveRecord::Migration[6.0] def change reversible do |change| change.up do execute <<-SQL CREATE TYPE position_type AS ENUM ('chair', 'jboard', 'eboard', 'aboard'); SQL # TODO: Execute code to create a new column and set values based off of existing values in the same row # TODO: Drop the 4 boolean columns end change.down do # TODO: Create the 4 boolean columns # TODO: Set one to true depending on the enum state execute <<-SQL DROP TYPE position_type; SQL end end end end
My question is: What can I do where my TODO comments are? Previously I wrote a migration to go from an integer to a Postgres enum in my User model, and my change_column
code looked like this:
change_column :users, :member_type, <<-SQL.strip member_status USING CASE member_type WHEN '0' THEN 'inactive'::member_status WHEN '1' THEN 'active'::member_status WHEN '2' THEN 'suspended'::member_status END SQL
I imagine that the code I’m trying to write is some variation of this with a flurry of if statements. Any input would be appreciated.
Advertisement
Answer
Although you have suggested the approach yourself in question, but here is the solution anyway.
Based on this article. You can try something like below:
class ChangePositionTypeToBeEnumInPosition < ActiveRecord::Migration[6.0] def change reversible do |change| change.up do execute <<-SQL CREATE TYPE position_type_enum AS ENUM ('chair', 'jboard', 'eboard', 'aboard'); SQL add_column :positions, :position_type, :position_type_enum # You may also want to add index on this column # If this table's size is huge(10m-20m+), this is not a good idea in that case. execute <<-SQL WITH cte AS ( SELECT id, CASE WHEN chair = true THEN 'chair'::position_type_enum WHEN jboard = true THEN 'jboard'::position_type_enum WHEN eboard = true THEN 'eboard'::position_type_enum ELSE 'aboard'::position_type_enum END AS position_type FROM positions ) UPDATE positions p SET position_type = c.position_type FROM cte c WHERE p.id = c.id; SQL remove_column :positions, :chair, :boolean remove_column :positions, :jboard, :boolean remove_column :positions, :eboard, :boolean remove_column :positions, :aboard, :boolean end change.down do add_column :positions, :chair, :boolean add_column :positions, :jboard, :boolean add_column :positions, :eboard, :boolean add_column :positions, :aboard, :boolean execute <<-SQL UPDATE positions SET chair = CASE WHEN position_type = 'chair' THEN true ELSE false END, jboard = CASE WHEN position_type = 'jboard' THEN true ELSE false END, eboard = CASE WHEN position_type = 'eboard' THEN true ELSE false END, aboard = CASE WHEN position_type = 'aboard' THEN true ELSE false END SQL remove_column :positions, :position_type execute <<-SQL DROP TYPE position_type_enum; SQL end end end end
I have not tried to run this code, you should correct any typos you may find(and comment here as well so i can update answer).
Please also read the article i have linked above for proper usage of postgres enums on models.