编写 Rails 迁移以将布尔值折叠为枚举

Writing a Rails migration to collapse booleans into an enum

我在对数据建模时犯了一个错误,创建了一个包含 4 个布尔值的模型,一次只有一个处于活动状态。我想创建一个 Postgres 枚举类型的列,然后根据先前设置的布尔标志设置值。

我知道我想从这样的事情开始:

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

我的问题是:在我的 TODO 注释所在的位置我可以做什么?之前我在我的用户模型中编写了一个从整数到 Postgres 枚举的迁移,我的 change_column 代码如下所示:

        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

我想我正在尝试编写的代码是它的一些变体,带有一系列 if 语句。如有任何意见,我们将不胜感激。

虽然你自己提出了问题的方法,但无论如何这里是解决方案。

基于this article。您可以尝试以下操作:

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

我还没有尝试 运行 此代码,您应该更正您可能发现的任何拼写错误(并在此处发表评论,以便我更新答案)。

另请阅读我在上面链接的文章,了解如何在模型上正确使用 postgres 枚举。