【问题标题】:Move all record relationships to another record将所有记录关系移动到另一条记录
【发布时间】:2017-08-30 15:09:05
【问题描述】:

我正在尝试将一条记录“合并”到另一条记录及其所有关系子项中。

例如:

我有vendor1vendor2,它们都有许多包含其他has_many 的关系。例如,一个供应商有很多 purchase_orders,一个采购订单有很多ordered_items,一个ordered_item 有很多receed_items。

如果我将vendor2 的名称更改为与vendor1 的名称相同,那么我想销毁vendor2,但将其所有has_many 移至vendor1

这是我一直在尝试做的:

  def vendor_merge(main_vendor, merge_vendor)
    relationships = [
      merge_vendor.returns, merge_vendor.receiving_and_bills,
      merge_vendor.bills, merge_vendor.purchase_orders, merge_vendor.taxes,
      Check.where(payee_id: merge_vendor.id, payee_type: "Vendor"),
      JournalEntryAccount.where(payee_id: merge_vendor.id)
    ]

    relationships.each do |relationship|
      class_name = relationship.class.name
      relationship.each do |r|
        if class_name === "Check"
          r.update(payee_id: main_vendor.id)
        else
          r.update(vendor_id: main_vendor.id)
        end
        r.save
      end

      relationship.delete_all
    end

    merge_vendor.destroy
  end

这样做会给我带来约束错误,因为 has_many 的 has_many 和 has_many through: :ect...

有什么直接的解决方案吗?

【问题讨论】:

  • 你遇到了什么约束问题?
  • 您说要将vendor2 的名称更改为与vendor1 的名称相同,但是您还要删除vendor2 对吗?因此,您不再需要更改名称,因为无论如何它都会被删除。此外,我认为这就是您所指的“约束问题”:您对“名称”列具有唯一性约束。
  • @Jay-ArPolidario 名称更改等于vendor1 是触发“合并”的条件
  • 您对供应商名称有唯一性约束吗?您能否向我们展示您的 Vendor 模型中的所有验证?
  • @moveson 约束与属于购买的购买项目有关。该名称与问题无关。

标签: mysql ruby-on-rails ruby ruby-on-rails-4


【解决方案1】:

您需要在应用中定义合并逻辑。这可能是一个 PORO(普通的旧 ruby​​ 对象),例如 VendorMerger,它包含所有逻辑,以便将 Vendor 记录合并到另一个记录中(这也可能在 Vendor 模型中,但它会污染您的模型)。

这是 PORO 的一个例子:

# lib/vendor_merger.rb
class VendorMerger
  def initialize(vendor_from, vendor_to)
    @vendor_from = vendor_from
    @vendor_to = vendor_to
  end

  def perform!
    validate_before_merge!
    ActiveRecord::Base.connection.transaction do # will rollback if an error is raised in this block
      migrate_related_records!
      destroy_after_merge!
    end
  end

  private

  def validate_before_merge!
    raise ArgumentError, 'Trying to merge the same record' if @vendor_from == @vendor_to
    raise ArgumentError, 'A vendor is not persisted' if @vendor_from.new_record? || @vendor_to.new_record?
    # ...
  end

  def migrate_related_records!
    # see my thought (1) below
    @vendor_from.purchases.each do |purchase|
      purchase.vendor = @vendor_to
      # ...
      purchase.save!
    end
  end

  def destroy_after_merge!
    @vendor_from.reload.destroy!
  end

用法:

VendorMerger.new(Vendor.first, Vendor.last).perform!

此 PORO 允许您将与合并相关的所有逻辑包含在一个文件中。它尊重 SRP(单一责任原则)并使测试和维护非常容易(例如:包括 Logger、自定义错误对象等)。

想法 (1):您可以手动检索要合并的数据(如我的示例),但这意味着如果有一天您向 Vendor 模型添加另一个关系,假设Vendor has_many :customers 但忘记将其添加到VendorMerger,那么它将“静默失败”,因为VendorMerger 不知道新关系:customers。为了解决这个问题,您可以动态获取所有引用Vendor 的模型(其中列是vendor_idclass_name 选项等于'Vendor' 或关系是多态的,XX_type 列包含一个@ 987654335@ 值)并将所有外来的从旧 ID 转换为新 ID。

【讨论】:

  • 我在概念上比我自己的解决方案更喜欢这个,因为它把这个任务的职责隔离在一个单一职责的交互器类中。但我不明白为什么migrate_related_records! 不只是一行:@vendor_to.purchases << @vendor_from.purchases。为什么不使用 Rails 的方式呢?
  • 我写的是我想要完成的一个例子。为了更清楚,我根据我的实际情况更新了问题。
  • @moveson 将记录从父项转移到另一个项的确切方式可能因偏好而异。我选择的方式也是一种“rails 方式”,在我的示例中只是详尽地展示了 OP 可以对每个 Purchase 记录执行一些额外的逻辑,例如验证或删除。
  • @MrYoshiji 去测试一下,会让你知道结果。谢谢。
  • 实际上,它不起作用,因为在删除供应商后,它仍然会破坏 has_many 关系,即使它们使用更新的 vendor_id 保存。
猜你喜欢
  • 1970-01-01
  • 2012-06-09
  • 1970-01-01
  • 2017-04-19
  • 1970-01-01
  • 1970-01-01
  • 2019-09-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多