【问题标题】:Ruby Workflow Issue During Migration迁移期间的 Ruby 工作流问题
【发布时间】:2016-06-29 13:48:56
【问题描述】:

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

现有运行代码包含:

  • 我有一个 ActiveRecord: X
  • 我已经进行了两次迁移:
    • (Ref1) CreateX 迁移(创建表 X)
    • (Ref2) CreateInitialEntryInX 迁移(在表 X 中创建一个条目)

新变化:

  • 现在我想在 ActiveRecord X 中添加工作流,因此我做了:
    • (Ref3) 我在 ActiveRecord Model X 中添加了工作流代码(提到 :status 作为我的工作流字段)
    • (Ref4) AddStatusFieldToX 迁移(在表 X 中添加 :status 字段)

现在,当我在更改后运行 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 运行时,它会尝试查找要写入的 :status 字段的初始值为 X_MODEL_STATES::ACTIVE,正如 Active Record 模型文件工作流中提到的那样:workflow_column :status并没有找到该字段,因为 Migration#3 尚未执行。

【问题讨论】:

  • 请发布一些代码和实际的异常,另外,没有按正确顺序进行迁移的原因是什么?
  • @onionfeelings ,因为早期的迁移,即 Ref1 和 Ref2 已经投入生产并运行了好几个月。这是一个迁移,由于增加了要求,现在正在进行。这个问题不会出现在生产中,因为只会运行新的迁移。但是,如果有人分叉代码并在开发环境中运行,迁移会中断(我想避免)
  • 你能发布你的代码吗?理论上如果你这样做: add_column :table_x, :status_id, :integer 你不应该有任何问题,而且由于你遵循约定,rails 仍然会正确处理关联
  • @onionfeelings,我已经发布了这里使用的代码供您参考。
  • @onionfeelings 我已经发布了解决方案。谢谢

标签: ruby-on-rails ruby activerecord workflow rails-migrations


【解决方案1】:

您可以通过检查 column_name 来结束您的工作流代码。

if self.attribute_names.include?('status')
  include Workflow
  workflow_column :status
  workflow do
    ...
  end
end

这将导致仅在“AddStatusToTable”迁移成功运行后运行工作流代码。

【讨论】:

  • 所以它的作用是: - 在迁移运行之前,初始化 Rails 应用程序,没有工作流,因为它在 if condition(which you mentioned) 中被绕过 - 然后,Migration#2迁移工作正常并且成功,但是当 Migration#3 运行时,它不会对代码做任何事情:X_MODEL_STATES::ACTIVE 因为加载的模型不是新列的模型,而且工作流仍然是以某种方式控制模型(尽管if{condition})。
  • 在所有执行的迁移之后,观察结果是:所有X Model 条目都有:status = nil,尽管您建议进行此更改并且尽管 Migration#3 包含@987654327 的初始化@ 值 X_MODEL_STATES::ACTIVE1
【解决方案2】:

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

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

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

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

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

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

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

  5. 迁移代码中的猴子补丁模型以实现向后兼容性。在您的情况下,这将是从模型代码中动态删除“工作流程”,这可能很难,然后运行迁移

所有解决方案都是某种肮脏的 hack,但迁移和模型代码版本化并不容易。最好的方法是分块部署,或者如果需要一次性部署,请在模型中使用一些临时代码,并在部署到生产环境后将其删除。

【讨论】:

  • 感谢您的解决方案,确实它们是黑客,但即使我执行上述任何解决方案或如前所述,分块部署,我可能会远离生产或其他现有的开发部署,但仍然会遇到的问题是:> 如果有人 fork 我的 repo 来构建项目,出于同样的原因,他系统上的迁移仍然会中断。
  • 我已经发布了解决方案。感谢@StanBrajewski 的宝贵意见
  • @kattybilly 当用户克隆新的 repo 并启动项目时,他不应该从迁移开始,而是从 db/schema.rb 创建一个新的数据库 - 在 Rails 文档中说 edgeguides.rubyonrails.org/…“有无需(而且容易出错)通过重放整个迁移历史来部署应用程序的新实例。只需将当前模式的描述加载到数据库中就更简单、更快捷了。”
【解决方案3】:

谢谢大家 我找到了解决方案,并将其发布在这里。这里要发布的问题是:

  • :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: http://stackoverflow.com/questions/200813/how-do-i-force-activerecord-to-reload-a-class

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

@stan-brajewski 现在,此代码可以在一个部署中使用。 感谢大家的投入:)

【讨论】:

  • 你做得很好。这仍然是一个临时代码破解,因为您不希望您的模型在所有这些属性上永远拥有所有这些 if :-)
  • 您应该接受某人的回答,我投票给@007sumit,因为您刚刚将他的回答复制到您的代码并做出了新的回答。您使用 007sumit 代码作为解决方案,所以请接受 007sumit 答案。这就是 SO 的工作原理。
  • 所以这不是一个完整的答案。它并没有完全解决它。答案是@007sumit 的答案和帖子中提供的答案的组合:stackoverflow.com/questions/200813/…。因此我添加了正确的答案,这是两者的结合。 M 乐于接受任何人的回答,但他们不完整,因此不正确接受。虽然赞成可用的。
  • 对,我错过了第 3 点和 reset_column_information 的使用。问候
  • 感谢您的意见 :) ,您可以为最后一个答案投票。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-23
  • 2015-07-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多