使用Amoeba Gem克隆Rails模型时的PageElementDependency关联异常
Hey there, I’ve run into this exact issue with Amoeba when cloning models with bidirectional self-associations—default cloning logic doesn’t handle the cross-references between cloned records correctly. Let’s break down how to fix this.
The Root Problem
When you clone your Page, Amoeba creates new PageElement records, but it doesn’t automatically map both sides of the PageElementDependency association to their cloned counterparts. It only updates one ID (like the dependee_id) while leaving the other (dependent_id) pointing to the original record.
Solution: Use Amoeba’s Remapper to Map Cloned Records
Amoeba provides a remapper hash that tracks original objects to their cloned versions. We can use this in a custom lambda to manually update both sides of the dependency association after cloning.
Here’s the revised code for your models:
1. Page Model (No Changes Needed)
# models/page.rb has_many :page_elements, -> { order(position: :asc) }, inverse_of: :page, dependent: :destroy amoeba do enable clone [:page_elements] end
2. PageElement Model (Updated Amoeba Configuration)
This is where the magic happens—we add a custom customize block to fix the dependency IDs:
# models/page_element.rb has_many :dependent_page_element_dependencies, foreign_key: :dependent_id, inverse_of: :dependent, class_name: "PageElementDependency", dependent: :destroy has_many :dependees, through: :dependent_page_element_dependencies has_many :dependee_page_element_dependencies, foreign_key: :dependee_id, inverse_of: :dependee, class_name: "PageElementDependency", dependent: :destroy has_many :dependents, through: :dependee_page_element_dependencies amoeba do enable clone [:dependee_page_element_dependencies, :dependent_page_element_dependencies] # Custom logic to map both sides of the dependency to cloned records customize(lambda { |original_element, cloned_element| # Fix dependencies where this element is the dependent (it relies on another element) cloned_element.dependent_page_element_dependencies.each do |cloned_dep| original_dependee = original_element.dependees.find_by(id: cloned_dep.dependee_id) if original_dependee # Grab the cloned version of the original dependee from Amoeba's remapper cloned_dependee = amoeba.remapper[original_dependee] cloned_dep.update(dependee_id: cloned_dependee.id) if cloned_dependee end end # Fix dependencies where this element is the dependee (another element relies on it) cloned_element.dependee_page_element_dependencies.each do |cloned_dep| original_dependent = original_element.dependents.find_by(id: cloned_dep.dependent_id) if original_dependent cloned_dependent = amoeba.remapper[original_dependent] cloned_dep.update(dependent_id: cloned_dependent.id) if cloned_dependent end end }) end
3. PageElementDependency Model (No Changes Needed)
# models/page_element_dependency.rb belongs_to :dependent, class_name: "PageElement", inverse_of: :dependent_page_element_dependencies, foreign_key: :dependent_id belongs_to :dependee, class_name: "PageElement", inverse_of: :dependee_page_element_dependencies, foreign_key: :dependee_id
How This Works
- The
remapperstores every original object as a key, with its cloned counterpart as the value. This is populated as Amoeba clones each record in the association tree. - In the
customizeblock, we loop through both sets of dependencies on the clonedPageElement:- For each dependency, we find the original associated
PageElement. - We then look up its cloned version in the
remapperand update the dependency record’s ID to point to the cloned element.
- For each dependency, we find the original associated
Testing the Fix
When you clone Page#3 (with PageElement#4 and #5 linked by a dependency), you’ll now get:
- A new
PagewithPageElement#6and#7 - A
PageElementDependencywheredependent_id = 6anddependee_id = 7(both pointing to the cloned elements)
内容的提问来源于stack exchange,提问作者Waclock

