将所有记录关系移动到另一个记录

时间:2017-08-30 15:09:06

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

我正在努力"合并"与其所有关系孩子的一条记录到另一条记录中。

例如:

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

如果我将vendor2的名称更改为与vendor1的名称相同,那么我想要销毁vendor2但移动所有内容&# 39; s 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通过:: ect ...

对此有任何直接的解决方案吗?

1 个答案:

答案 0 :(得分:3)

您需要在应用中定义合并逻辑。这可能是一个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(单一责任原则),使测试变得非常简单,并且维护(例如:包括记录器,自定义错误对象等)。

思想(1):您可以手动检索要合并的数据(如我的示例所示),但这意味着如果有一天您将另一个关系添加到{{1} }模型,让我们说Vendor Vendor但是忘了将它添加到VendorMerger,然后它会“无声地失败”,因为VendorMerger不知道新关系has_many :customers。要解决此问题,您可以动态获取所有引用:customers的模型(其中列为Vendorvendor_id选项等于class_name或者关系是多态的'Vendor'列包含XX_type值,并将所有外国人从旧ID转换为新ID。