Fut*_*cky 4 ruby mysql ruby-on-rails ruby-on-rails-4
我正在尝试将一个记录及其所有关系子项“合并”到另一记录中。
例如:
我有vendor1和vendor2其中都有许多包含其他 has_many 的关系。例如,供应商有许多purchase_orders,采购订单有许多ordered_items,ordered_item有许多received_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
Run Code Online (Sandbox Code Playgroud)
这样做会给我带来约束错误,因为 has_many 的 has_many 以及 has_many through: :ect...
有什么直接的解决方案吗?
您将需要在应用程序中定义合并逻辑。这可能是一个 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
Run Code Online (Sandbox Code Playgroud)
用法:
VendorMerger.new(Vendor.first, Vendor.last).perform!
Run Code Online (Sandbox Code Playgroud)
此 PORO 允许您将与合并相关的所有逻辑包含到一个文件中。它尊重 SRP(单一责任原则),并使测试和维护变得非常容易(例如:包括记录器、自定义错误对象等)。
想法 (1):您可以手动检索要合并的数据(如我的示例中所示),但这意味着如果有一天您向模型添加另一个关系Vendor,比如说Vendor has_many :customers但忘记将其添加到 VendorMerger,那么由于 VendorMerger 不知道新关系,它将“默默地失败” :customers。为了解决这个问题,您可以动态地获取所有引用的模型Vendor(其中列是vendor_idORclass_name选项等于'Vendor'OR 关系是多态的并且XX_type列包含一个'Vendor'值)并将所有这些外部 ID 从旧 ID 转换为新 ID。