Ruby 迁移期间的工作流问题

Ruby Workflow Issue During Migration

我在使用 Gem 的 ActiveRecords 中使用 Ruby 工作流:Workflow

现有运行代码包含:

新变化:

现在,当我 运行 rake db:migrate 更改后,(Ref2) 中断 cos Migration 查找 :status 字段,如中所述工作流部分中的 ActiveRecord 模型,但尚未添加 :status 字段,因为迁移 (Ref4) 尚未执行。

因此,当所有迁移按顺序 运行 时,所有构建都会失败,对此有什么解决方案吗? 我不想重新排序任何迁移或编辑任何旧的现有迁移。

我的模型看起来像:

  class BaseModel < ActiveRecord::Base
      #
      # Workflow to define states of Role
      #
      # Initial State => Active
      #
      # # State Diagram::
      #   Active  --soft_delete--> Deleted
      #   Deleted
      #
      # * activate, soft_delete are the event which triggers the state transition
      #
      include Workflow
      workflow_column :status
      workflow do
        state :active, X_MODEL_STATES::ACTIVE do
          event :soft_delete, transitions_to: :deleted
        end
        state :deleted, X_MODEL_STATES::DELETED

        on_transition do |from, to, event, *event_args|
          self.update_attribute(:status, to)
        end
      end

      def trigger_event(event)
        begin
          case event.to_i
            when X_MODEL_EVENTS::ACTIVATE
              self.activate!
            when X_MODEL_EVENTS::SOFT_DELETE
              self.soft_delete!
          end
        rescue ....
      end
  end

  class X_MODEL_STATES
    ACTIVE      = 1
    DELETED     = 2
  end

  class X_MODEL_EVENTS
    ACTIVATE      = 1
    SOFT_DELETE   = 2
  end

# Migrations(posting Up functions only - in correct sequence)
#--------------------------------------------------

#1st: Migration - This is already existing migration
CreateX < ActiveRecord::Migration
  def up
    create_table :xs do |t|
      t.string :name
      t.timestamps null: false
    end
  end
end

#2nd: Migration - This is already existing migration
CreateInitialX < ActiveRecord::Migration
  def up
    X.create({:name => 'Kartik'})
  end
end

#3rd: Migration - This is a new migration
AddStatusToX < ActiveRecord::Migration
  def up
    add_column :xs, :status, :integer
    x.all.each do |x_instance|
      x.status = X_MODEL_STATES::ACTIVE
      x.save!
    end
  end
end

因此,当 Migration#2 运行s 时,它会尝试查找要写入的 :status 字段,其初始值为 X_MODEL_STATES::ACTIVE在 Active Record 模型文件工作流程中提到:workflow_column :status 并且没有找到该字段,因为 Migration#3 尚未执行。

这很痛苦,因为您的模型需要在迁移时保持一致。我不知道有任何自动解决方案。

理论上最好的方法是将模型代码版本与迁移绑定。但我不知道有任何系统允许这样做。

每次重构大型模型时,我都会遇到这个问题。这种情况的可能解决方案。

  1. 运行 手动迁移生产以确保迁移和模型之间的一致状态

  2. 暂时注释掉模型中的工作流代码以运行阻止迁移,然后部署,然后取消注释工作流代码并继续部署和下一次迁移

  3. 分支上的版本迁移和模型更改,因此它们是一致的。部署到生产环境并 运行 按块迁移

  4. 在模型代码中包含临时解决方法,并在部署生产迁移后将其从源代码中删除。

  5. 为了向后兼容,迁移代码中的猴子补丁模型。在您的情况下,这将是从模型代码中动态删除 'workflow',这可能很难,然后 运行 migration

所有的解决方案都是一些肮脏的黑客,但要对迁移和模型代码进行版本控制并不容易。最好的方法是分块部署,或者如果需要一次全部部署,请在模型中使用一些临时代码并在生产部署后将其删除。

您可以通过检查 column_name.

来结束您的工作流代码
if self.attribute_names.include?('status')
  include Workflow
  workflow_column :status
  workflow do
    ...
  end
end

只有在 'AddStatusToTable' 迁移 运行 成功后,才会产生 运行 工作流代码。

谢谢大家 我找到了解决这个问题的方法,我把它贴在这里。这里要发布的问题是:

  • :status 字段添加到 ActiveRecord Model X 而不注释掉工作流代码,并且不让工作流在迁移期间禁止在 Table X 中创建实例。
  • 其次,按照@007sumit 的指定为其添加一个 if 条件。
  • 第三,能够使用 Model X
  • 的更新 column_information 在迁移中重新加载模型

在此处编写完整的代码解决方案:

  class BaseModel < ActiveRecord::Base
      #
      # Workflow to define states of Role
      #
      # Initial State => Active
      #
      # # State Diagram::
      #   Active  --soft_delete--> Deleted
      #   Deleted
      #
      # * activate, soft_delete are the event which triggers the state transition
      #

      # if condition to add workflow only if :status field is added
      if self.attribute_names.include?('status')
        include Workflow
        workflow_column :status
        workflow do
          state :active, X_MODEL_STATES::ACTIVE do
            event :soft_delete, transitions_to: :deleted
          end
          state :deleted, X_MODEL_STATES::DELETED

        end
      end

      def trigger_event(event)
        ...
      end
  end

  class X_MODEL_STATES
    ...
  end

  class X_MODEL_EVENTS
    ...
  end

# Migrations(posting Up functions only - in correct sequence)
#--------------------------------------------------

#1st: Migration - This is already existing migration
CreateX < ActiveRecord::Migration
  def up
    create_table :xs do |t|
      t.string :name
      t.timestamps null: false
    end
  end
end

#2nd: Migration - This is already existing migration
CreateInitialX < ActiveRecord::Migration
  def up
    X.create({:name => 'Kartik'})
  end
end

#3rd: Migration - This is a new migration to add status field and 
# modify status value in existing entries in X Model
AddStatusToX < ActiveRecord::Migration
  def up
    add_column :xs, :status, :integer

    # This resets Model Class before executing this migration and 
    # Workflow is identified from the if condition specified which was 
    # being skipped previously without this line as Model Class is  
    # loaded only once before all migrations run.
    # Thanks to post: 

    x.reset_column_information
    x.all.each do |x_instance|
      x.status = X_MODEL_STATES::ACTIVE
      x.save!
    end
  end
end

@stan-brajewski 现在,这段代码可以一次部署。 感谢大家的投入:)