我怎样才能在保持原始 ID 和引用的同时用另一个 record/row 惯用地替换一个 record/row?

How can I idiomatically replace a record/row with another record/row while maintaining the original ids and references?

我有一个包含 3 个 "has_many" relations/sub 个 ActiveRecords 的 ActiveRecord 对象。

我的目标是让 "clones" 能够出于测试目的修改自己的 og_actions/og_objects/ob_stories 版本,而且还能够将数据从父级推送到所有克隆覆盖所做的任何更改。

我假设这样做的方法是更新那些与来自另一个 ActiveRecord 的数据的关系,但是,我不想在复制数据时更改 ID 或 Foreign_key 引用。

我该如何以惯用的方式做到这一点? 或者我应该只删除所有记录并使用旧 ID 创建新记录?如果是这样,最好的方法是什么?

这是我目前正在使用的代码,它不起作用:

class App < ActiveRecord::Base
...

belongs_to :user
  has_many :og_actions
  has_many :og_objects
  has_many :og_stories
  has_many :log_reports
  has_many :clones, class_name: "App", foreign_key: "parent_id"
...
def populate_clones
  self.clones.each do |c|
    p "updating ->"
    self.og_actions.each_with_index do | oa, ai |
    new_og_action = OgAction.create(oa.attributes.merge({app_id:c.id, id: c.og_actions[ai].id }))
      c.og_actions[ai] = new_og_action
    end
    self.og_objects.each_with_index do |oo, oi|
      new_og_object = OgObject.create(oo.attributes.merge({app_id:c.id, id: c.og_objects[oi].id }))
      c.og_objects[oi] = new_og_object
    end   
    self.og_stories.each_with_index do | s, si|
      new_og_story = OgStory.create(s.attributes.merge({app_id:c.id, id: c.og_stories[si].id }))
      s.story_variants.each do_with_index do |v, vi|
        new_variant = StoryVariant.create(v.attributes.merge({og_story_id:new_og_story.id, id:c.og_stories[si].story_variants[vi].id}))
        new_og_story.story_variants[vi] = new_variant
      end
      c.og_stories[si] = new_og_story
    end
    c.save
  end 
  p "end Update"
end

我也试过使用替换函数,以及 c.og_objects = self.og_objects 的简单赋值,似乎没有什么能正常工作。它要么创建一个新记录创建重复,替换所有引用,因此父 ActiveRecord 丢失其引用,要么得到一个 "duplicate id" 错误。

这很棘手。我一直在思考越来越多可能存在问题的案例。无论如何,这是一个开始:

def sync_clones
  clones.each do |clone|
    # Destroy any og_actions for clone that are no longer in the parent app
    clone.og_actions.where.not(parent_id: og_actions.ids).destroy_all

    # Create or update a og_action clone for app clone
    og_actions.each do |og_action|
      clone_attributes = og_action.dup.attributes.except("id").merge(parent_id: og_action.id)
      existing = clone.og_actions.find_by(parent_id: og_action.id)
      if existing
        existing.update(clone_attributes)
      else
        clone.og_actions.build(clone_attributes)
      end
    end
    # ...
  end
end

这将忠实地更新克隆并且不会创建不必要的记录。它确实需要您跟踪父 og_action 记录。这是因为您不能依赖 og_actions 索引来识别匹配的克隆记录(如果您销毁一个 og_action 或添加一个,或者如果顺序以其他方式更改,将会发生什么)。