class ViewModel::ActiveRecord::UpdateOperation
Constants
- ParentData
inverse association and record to update a change in parent from a child
Attributes
pointed_to[RW]
points_to[RW]
released_children[RW]
reparent_to[RW]
reposition_to[RW]
update_data[RW]
viewmodel[RW]
Public Class Methods
new(viewmodel, update_data, reparent_to: nil, reposition_to: nil)
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 25 def initialize(viewmodel, update_data, reparent_to: nil, reposition_to: nil) self.viewmodel = viewmodel self.update_data = update_data self.points_to = {} self.pointed_to = {} self.reparent_to = reparent_to self.reposition_to = reposition_to self.released_children = [] @run_state = RunState::Pending @changed_associations = [] @built = false end
Public Instance Methods
add_update(association_data, update)
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 256 def add_update(association_data, update) target = case association_data.pointer_location when :remote then pointed_to when :local then points_to end target[association_data] = update end
build!(update_context)
click to toggle source
Recursively builds UpdateOperations for the associations in our UpdateData
# File lib/view_model/active_record/update_operation.rb, line 221 def build!(update_context) raise ViewModel::DeserializationError::Internal.new('Internal error: UpdateOperation cannot build a deferred update') if viewmodel.nil? return self if built? update_data.associations.each do |association_name, association_update_data| association_data = self.viewmodel.class._association_data(association_name) update = if association_data.collection? build_updates_for_collection_association(association_data, association_update_data, update_context) else build_update_for_single_association(association_data, association_update_data, update_context) end add_update(association_data, update) end update_data.referenced_associations.each do |association_name, reference_string| association_data = self.viewmodel.class._association_data(association_name) update = if association_data.through? build_updates_for_collection_referenced_association(association_data, reference_string, update_context) elsif association_data.collection? build_updates_for_collection_association(association_data, reference_string, update_context) else build_update_for_single_association(association_data, reference_string, update_context) end add_update(association_data, update) end @built = true self end
built?()
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 45 def built? @built end
propagate_tree_changes(association_data, child_changes)
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 211 def propagate_tree_changes(association_data, child_changes) if association_data.nested? viewmodel.nested_children_changed! if child_changes.changed_nested_tree? viewmodel.referenced_children_changed! if child_changes.changed_referenced_children? elsif association_data.owned? viewmodel.referenced_children_changed! if child_changes.changed_owned_tree? end end
run!(deserialize_context:)
click to toggle source
Evaluate a built update tree, applying and saving changes to the models.
# File lib/view_model/active_record/update_operation.rb, line 50 def run!(deserialize_context:) raise ViewModel::DeserializationError::Internal.new('Internal error: UpdateOperation run before build') unless built? case @run_state when RunState::Running raise ViewModel::DeserializationError::Internal.new('Internal error: Cycle found in running UpdateOperation') when RunState::Run return viewmodel end @run_state = RunState::Running model = viewmodel.model debug_name = "#{model.class.name}:#{model.id || '<new>'}" debug "-> #{debug_name}: Entering" model.class.transaction do # Run context and viewmodel hooks ViewModel::Callbacks.wrap_deserialize(viewmodel, deserialize_context: deserialize_context) do |hook_control| # update parent association if reparent_to.present? debug "-> #{debug_name}: Updating parent pointer to '#{reparent_to.viewmodel.class.view_name}:#{reparent_to.viewmodel.id}'" association = model.association(reparent_to.association_reflection.name) association.writer(reparent_to.viewmodel.model) debug "<- #{debug_name}: Updated parent pointer" end # update position if reposition_to.present? debug "-> #{debug_name}: Updating position to #{reposition_to}" viewmodel._list_attribute = reposition_to end # update user-specified attributes valid_members = viewmodel.class._members.keys.map(&:to_s).to_set bad_keys = attributes.keys.reject { |k| valid_members.include?(k) } if bad_keys.present? causes = bad_keys.map { |k| ViewModel::DeserializationError::UnknownAttribute.new(k, blame_reference) } raise ViewModel::DeserializationError::Collection.for_errors(causes) end attributes.each do |attr_name, serialized_value| # Note that the VM::AR deserialization tree asserts ownership over any # references it's provided, and so they're intentionally not passed on # to attribute deserialization for use by their `using:` viewmodels. A # (better?) alternative would be to provide them as reference-only # hashes, to indicate that no modification can be permitted. viewmodel.public_send("deserialize_#{attr_name}", serialized_value, references: {}, deserialize_context: deserialize_context) end # Update points-to associations before save points_to.each do |association_data, child_operation| reflection = association_data.direct_reflection debug "-> #{debug_name}: Updating points-to association '#{reflection.name}'" association = model.association(reflection.name) new_target = if child_operation child_ctx = viewmodel.context_for_child(association_data.association_name, context: deserialize_context) child_viewmodel = child_operation.run!(deserialize_context: child_ctx) propagate_tree_changes(association_data, child_viewmodel.previous_changes) child_viewmodel.model end association.writer(new_target) debug "<- #{debug_name}: Updated points-to association '#{reflection.name}'" end # validate deserialize_context.run_callback(ViewModel::Callbacks::Hook::BeforeValidate, viewmodel) viewmodel.validate! # Save if the model has been altered. Covers not only models with # view changes but also lock version assertions. if viewmodel.model.changed? || viewmodel.model.new_record? debug "-> #{debug_name}: Saving" begin model.save! rescue ::ActiveRecord::RecordInvalid => ex raise ViewModel::DeserializationError::Validation.from_active_model(ex.errors, blame_reference) rescue ::ActiveRecord::StaleObjectError => _ex raise ViewModel::DeserializationError::LockFailure.new(blame_reference) end debug "<- #{debug_name}: Saved" end # Update association cache of pointed-from associations after save: the # child update will have saved the pointer. pointed_to.each do |association_data, child_operation| reflection = association_data.direct_reflection debug "-> #{debug_name}: Updating pointed-to association '#{reflection.name}'" association = model.association(reflection.name) child_ctx = viewmodel.context_for_child(association_data.association_name, context: deserialize_context) new_target = if child_operation ViewModel::Utils.map_one_or_many(child_operation) do |op| child_viewmodel = op.run!(deserialize_context: child_ctx) propagate_tree_changes(association_data, child_viewmodel.previous_changes) child_viewmodel.model end end association.target = new_target debug "<- #{debug_name}: Updated pointed-to association '#{reflection.name}'" end if self.released_children.present? # Released children that were not reclaimed by other parents during the # build phase will be deleted: check access control. debug "-> #{debug_name}: Checking released children permissions" self.released_children.reject(&:claimed?).each do |released_child| debug "-> #{debug_name}: Checking #{released_child.viewmodel.to_reference}" child_vm = released_child.viewmodel child_association_data = released_child.association_data child_ctx = viewmodel.context_for_child(child_association_data.association_name, context: deserialize_context) ViewModel::Callbacks.wrap_deserialize(child_vm, deserialize_context: child_ctx) do |child_hook_control| changes = ViewModel::Changes.new(deleted: true) child_ctx.run_callback(ViewModel::Callbacks::Hook::OnChange, child_vm, changes: changes) child_hook_control.record_changes(changes) end if child_association_data.nested? viewmodel.nested_children_changed! elsif child_association_data.owned? viewmodel.referenced_children_changed! end end debug "<- #{debug_name}: Finished checking released children permissions" end final_changes = viewmodel.clear_changes! if final_changes.changed? # Now that the change has been fully attempted, call the OnChange # hook if local changes were made deserialize_context.run_callback(ViewModel::Callbacks::Hook::OnChange, viewmodel, changes: final_changes) end hook_control.record_changes(final_changes) end end debug "<- #{debug_name}: Leaving" @run_state = RunState::Run viewmodel rescue ::ActiveRecord::StatementInvalid, ::ActiveRecord::InvalidForeignKey, ::ActiveRecord::RecordNotSaved => ex raise ViewModel::DeserializationError::DatabaseConstraint.from_exception(ex, blame_reference) end
viewmodel_reference()
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 39 def viewmodel_reference unless viewmodel.model.new_record? viewmodel.to_reference end end
Private Instance Methods
blame_reference()
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 913 def blame_reference self.viewmodel.blame_reference end
build_update_for_single_association(association_data, association_update_data, update_context)
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 357 def build_update_for_single_association(association_data, association_update_data, update_context) model = self.viewmodel.model previous_child_viewmodel = model.public_send(association_data.direct_reflection.name).try do |previous_child_model| vm_class = association_data.viewmodel_class_for_model!(previous_child_model.class) vm_class.new(previous_child_model) end if association_data.pointer_location == :remote if previous_child_viewmodel.present? # Clear the cached association so that AR's save behaviour doesn't # conflict with our explicit parent updates. If we still have a child # after the update, we'll either call `Association#writer` or manually # fix the target cache after recursing in run!(). If we don't, we promise # that the child will no longer be attached in the database, so the new # cached data of nil will be correct. clear_association_cache(model, association_data.direct_reflection) end reparent_data = ParentData.new(association_data.direct_reflection.inverse_of, viewmodel) end if association_update_data.present? if association_data.referenced? # resolve reference string reference_string = association_update_data child_update, child_viewmodel = resolve_referenced_viewmodels(association_data, reference_string, previous_child_viewmodel, update_context) if reparent_data set_reference_update_parent(association_data, child_update, reparent_data) end if child_viewmodel.is_a?(ViewModel::Reference) update_context.defer_update(child_viewmodel, child_update) end else # Resolve direct children child_viewmodel = resolve_child_viewmodels(association_data, association_update_data, previous_child_viewmodel, update_context) child_update = if child_viewmodel.is_a?(ViewModel::Reference) update_context.new_deferred_update(child_viewmodel, association_update_data, reparent_to: reparent_data) else update_context.new_update(child_viewmodel, association_update_data, reparent_to: reparent_data) end end # Build the update if we've claimed it unless child_viewmodel.is_a?(ViewModel::Reference) child_update.build!(update_context) end else child_update = nil child_viewmodel = nil end # Handle changes if previous_child_viewmodel != child_viewmodel viewmodel.association_changed!(association_data.association_name) # free previous child if present and owned if previous_child_viewmodel.present? && association_data.owned? if association_data.pointer_location == :local # When we free a child that's pointed to from its old parent, we need to # clear the cached association to that old parent. If we don't do this, # then if the child gets claimed by a new parent and `save!`ed, AR will # re-establish the link from the old parent in the cache. # Ideally we want # model.association(...).inverse_reflection_for(previous_child_model), but # that's private. inverse_reflection = association_data.direct_reflection_inverse(previous_child_viewmodel.model.class) if inverse_reflection.present? clear_association_cache(previous_child_viewmodel.model, inverse_reflection) end end release_viewmodel(previous_child_viewmodel, association_data, update_context) end end child_update end
build_updates_for_collection_association(association_data, association_update, update_context)
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 446 def build_updates_for_collection_association(association_data, association_update, update_context) model = self.viewmodel.model # reference back to this model, so we can set the link while updating the children parent_data = ParentData.new(association_data.direct_reflection.inverse_of, viewmodel) # load children already attached to this model previous_child_viewmodels = model.public_send(association_data.direct_reflection.name).map do |child_model| child_viewmodel_class = association_data.viewmodel_class_for_model!(child_model.class) child_viewmodel_class.new(child_model) end if association_data.ordered? previous_child_viewmodels.sort_by!(&:_list_attribute) end if previous_child_viewmodels.present? # Clear the cached association so that AR's save behaviour doesn't # conflict with our explicit parent updates. If we still have children # after the update, we'll reset the target cache after recursing in # run(). If not, the empty array we cache here will be correct, because # previous children will be deleted or have had their parent pointers # updated. clear_association_cache(model, association_data.direct_reflection) end # Update contents are either UpdateData in the case of a nested # association, or reference strings in the case of a reference association. # The former are resolved with resolve_child_viewmodels, the latter with # resolve_referenced_viewmodels. resolve_child_data_reference = ->(child_data) do case child_data when UpdateData child_data.viewmodel_reference if child_data.id when String update_context.resolve_reference(child_data, nil).viewmodel_reference else raise ViewModel::DeserializationError::Internal.new( "Unexpected child data type in collection update: #{child_data.class.name}") end end case association_update when AbstractCollectionUpdate::Replace child_datas = association_update.contents when AbstractCollectionUpdate::Functional # A fupdate isn't permitted to edit the same model twice. association_update.check_for_duplicates!(update_context, blame_reference) # Construct empty updates for previous children child_datas = previous_child_viewmodels.map do |previous_child_viewmodel| UpdateData.empty_update_for(previous_child_viewmodel) end # Insert or replace with either real UpdateData or reference strings association_update.actions.each do |fupdate| case fupdate when FunctionalUpdate::Append # If we're referring to existing members, ensure that they're removed before we append/insert existing_refs = fupdate.contents .map(&resolve_child_data_reference) .to_set child_datas.reject! do |child_data| child_ref = resolve_child_data_reference.(child_data) child_ref && existing_refs.include?(child_ref) end if fupdate.before || fupdate.after rel_ref = fupdate.before || fupdate.after # Find the relative insert location. This might be an empty # UpdateData from a previous child or an already-fupdated # reference string. index = child_datas.find_index do |child_data| rel_ref == resolve_child_data_reference.(child_data) end unless index raise ViewModel::DeserializationError::AssociatedNotFound.new( association_data.association_name.to_s, rel_ref, blame_reference) end index += 1 if fupdate.after child_datas.insert(index, *fupdate.contents) else child_datas.concat(fupdate.contents) end when FunctionalUpdate::Remove removed_refs = fupdate.removed_vm_refs.to_set child_datas.reject! do |child_data| child_ref = resolve_child_data_reference.(child_data) removed_refs.include?(child_ref) end when FunctionalUpdate::Update # Already guaranteed that each ref has a single existing child attached new_child_datas = fupdate.contents.index_by(&resolve_child_data_reference) # Replace matched child_datas with the update contents. child_datas.map! do |child_data| child_ref = resolve_child_data_reference.(child_data) new_child_datas.delete(child_ref) { child_data } end # Assertion that all values in the update were found in child_datas unless new_child_datas.empty? raise ViewModel::DeserializationError::AssociatedNotFound.new( association_data.association_name.to_s, new_child_datas.keys, blame_reference) end else raise ViewModel::DeserializationError::InvalidSyntax.new( "Unknown functional update type: '#{fupdate.type}'", blame_reference) end end end if association_data.referenced? # child_datas are either pre-resolved UpdateData (for non-fupdated # existing members) or reference strings. Resolve into pairs of # [UpdateOperation, ViewModel] if we can create or claim the # UpdateOperation or [UpdateOperation, ViewModelReference] otherwise. resolved_children = resolve_referenced_viewmodels(association_data, child_datas, previous_child_viewmodels, update_context) resolved_children.each do |child_update, child_viewmodel| set_reference_update_parent(association_data, child_update, parent_data) if child_viewmodel.is_a?(ViewModel::Reference) update_context.defer_update(child_viewmodel, child_update) end end else # child datas are all UpdateData child_viewmodels = resolve_child_viewmodels(association_data, child_datas, previous_child_viewmodels, update_context) resolved_children = child_datas.zip(child_viewmodels).map do |child_data, child_viewmodel| child_update = if child_viewmodel.is_a?(ViewModel::Reference) update_context.new_deferred_update(child_viewmodel, child_data, reparent_to: parent_data) else update_context.new_update(child_viewmodel, child_data, reparent_to: parent_data) end [child_update, child_viewmodel] end end # Calculate new positions for children if in a list. Ignore previous # positions (i.e. return nil) for unresolved references: they'll always # need to be updated anyway since their parent pointer will change. new_positions = Array.new(resolved_children.length) if association_data.ordered? set_position = ->(index, pos) { new_positions[index] = pos } get_previous_position = ->(index) do vm = resolved_children[index][1] vm._list_attribute unless vm.is_a?(ViewModel::Reference) end ActsAsManualList.update_positions( (0...resolved_children.size).to_a, # indexes position_getter: get_previous_position, position_setter: set_position) end resolved_children.zip(new_positions).each do |(child_update, child_viewmodel), new_position| child_update.reposition_to = new_position # Recurse into building child updates that we've claimed unless child_viewmodel.is_a?(ViewModel::Reference) child_update.build!(update_context) end end child_updates, child_viewmodels = resolved_children.transpose.presence || [[], []] # if the new children differ, including in order, mark that this # association has changed and release any no-longer-attached children if child_viewmodels != previous_child_viewmodels viewmodel.association_changed!(association_data.association_name) released_child_viewmodels = previous_child_viewmodels - child_viewmodels released_child_viewmodels.each do |vm| release_viewmodel(vm, association_data, update_context) end end child_updates end
build_updates_for_collection_referenced_association(association_data, association_update, update_context)
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 778 def build_updates_for_collection_referenced_association(association_data, association_update, update_context) model = self.viewmodel.model # We have two relationships here. # - the relationship from us to the join table models: direct # - the relationship from the join table to the children: indirect direct_reflection = association_data.direct_reflection indirect_reflection = association_data.indirect_reflection direct_viewmodel_class = association_data.direct_viewmodel indirect_association_data = association_data.indirect_association_data indirect_ref_for_direct_viewmodel = ->(direct_viewmodel) do direct_model = direct_viewmodel.model model_class = direct_model.association(indirect_reflection.name).klass model_id = direct_model.public_send(indirect_reflection.foreign_key) viewmodel_class = indirect_association_data.viewmodel_class_for_model!(model_class) ViewModel::Reference.new(viewmodel_class, model_id) end previous_members = model.public_send(direct_reflection.name).map do |m| direct_vm = direct_viewmodel_class.new(m) indirect_viewmodel_ref = indirect_ref_for_direct_viewmodel.(direct_vm) ReferencedCollectionMember.new(indirect_viewmodel_ref, direct_vm) end if association_data.ordered? previous_members.sort_by!(&:position) end target_collection = MutableReferencedCollection.new( association_data, update_context, previous_members, blame_reference) # All updates to referenced collections produce a complete target list of # ReferencedCollectionMembers including a ViewModel::Reference to the # indirect child, and an existing (from previous) or new ViewModel for the # direct child. # # Members participating in the update (all members in the case of Replace, # specified append or update members in the case of Functional) will also # include a reference string for the update operation for the indirect # child, which will be subsequently added to the new UpdateOperation for # the direct child. case association_update when ReferencedCollectionUpdate::Replace target_collection.replace(association_update.references) when ReferencedCollectionUpdate::Functional # Collection updates are a list of actions modifying the list # of indirect children. # # The target collection starts out as a copy of the previous collection # members, and is then mutated based on the actions specified. All # members added or modified by actions will have their `ref` set. association_update.check_for_duplicates!(update_context, self.viewmodel.blame_reference) association_update.actions.each do |fupdate| case fupdate when FunctionalUpdate::Append # Append new members, possibly relative to another member case when fupdate.before target_collection.insert_before(fupdate.before, fupdate.contents) when fupdate.after target_collection.insert_after(fupdate.after, fupdate.contents) else target_collection.concat(fupdate.contents) end when FunctionalUpdate::Remove target_collection.remove(fupdate.removed_vm_refs) when FunctionalUpdate::Update # Update contents of members already in the collection target_collection.update(fupdate.contents) else raise ArgumentError.new("Unknown functional update: '#{fupdate.class}'") end end else raise ViewModel::DeserializationError::InvalidSyntax.new("Unknown association_update type '#{association_update.class.name}'", blame_reference) end # We should now have an updated list `target_collection.members`, # containing members for the desired new collection in the order that we # want them, each of which has a `direct_viewmodel` set, and additionally # a `ref_string` set for those that participated in the update. if target_collection.members != previous_members viewmodel.association_changed!(association_data.association_name) end if association_data.ordered? ActsAsManualList.update_positions(target_collection.members) end parent_data = ParentData.new(direct_reflection.inverse_of, self.viewmodel) new_direct_updates = target_collection.members.map do |member| update_data = UpdateData.empty_update_for(member.direct_viewmodel) if (ref = member.ref_string) update_data.referenced_associations[indirect_reflection.name] = ref end update_context.new_update(member.direct_viewmodel, update_data, reparent_to: parent_data, reposition_to: member.position) .build!(update_context) end # Members removed from the collection, either by `Remove` or by # not being included in the new Replace list can now be # released. target_collection.orphaned_members.each do |member| release_viewmodel(member.direct_viewmodel, association_data, update_context) end new_direct_updates end
clear_association_cache(model, reflection)
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 903 def clear_association_cache(model, reflection) association = model.association(reflection.name) association.target = if reflection.collection? [] else nil end end
debug(msg)
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 917 def debug(msg) return unless ViewModel::Config.debug_deserialization ::ActiveRecord::Base.logger.try do |logger| logger.debug(msg) end end
release_viewmodel(viewmodel, association_data, update_context)
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 899 def release_viewmodel(viewmodel, association_data, update_context) self.released_children << update_context.release_viewmodel(viewmodel, association_data) end
resolve_child_viewmodels(association_data, update_datas, previous_child_viewmodels, update_context)
click to toggle source
Resolve or construct viewmodels for incoming update data. Where a child hash references an existing model not currently attached to this parent, it must be found before recursing into that child. If the model is available in released models we can take it and recurse, otherwise we must return a ViewModel::Reference
to be added to the worklist for deferred resolution.
# File lib/view_model/active_record/update_operation.rb, line 274 def resolve_child_viewmodels(association_data, update_datas, previous_child_viewmodels, update_context) if self.viewmodel.respond_to?(:"resolve_#{association_data.direct_reflection.name}") return self.viewmodel.public_send(:"resolve_#{association_data.direct_reflection.name}", update_datas, previous_child_viewmodels) end previous_child_viewmodels = Array.wrap(previous_child_viewmodels) previous_by_key = previous_child_viewmodels.index_by do |vm| vm.to_reference end ViewModel::Utils.map_one_or_many(update_datas) do |update_data| child_viewmodel_class = update_data.viewmodel_class key = ViewModel::Reference.new(child_viewmodel_class, update_data.id) case when update_data.new? child_viewmodel_class.for_new_model(id: update_data.id) when existing_child = previous_by_key[key] existing_child when taken_child = update_context.try_take_released_viewmodel(key) taken_child else # Refers to child that hasn't yet been seen: create a deferred update. key end end end
resolve_referenced_viewmodels(association_data, update_datas, previous_child_viewmodels, update_context)
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 303 def resolve_referenced_viewmodels(association_data, update_datas, previous_child_viewmodels, update_context) previous_child_viewmodels = Array.wrap(previous_child_viewmodels).index_by(&:to_reference) ViewModel::Utils.map_one_or_many(update_datas) do |update_data| if update_data.is_a?(UpdateData) # Dummy child update data for an unmodified previous child of a # functional update; create it an empty update operation. viewmodel = previous_child_viewmodels.fetch(update_data.viewmodel_reference) update = update_context.new_update(viewmodel, update_data) next [update, viewmodel] end reference_string = update_data child_update = update_context.resolve_reference(reference_string, blame_reference) child_viewmodel = child_update.viewmodel unless association_data.accepts?(child_viewmodel.class) raise ViewModel::DeserializationError::InvalidAssociationType.new( association_data.association_name.to_s, child_viewmodel.class.view_name, blame_reference) end child_ref = child_viewmodel.to_reference # The case of two potential owners trying to claim a new referenced # child is covered by set_reference_update_parent. claimed = !association_data.owned? || child_update.update_data.new? || previous_child_viewmodels.has_key?(child_ref) || update_context.try_take_released_viewmodel(child_ref).present? if claimed [child_update, child_viewmodel] else # Return the reference to indicate a deferred update [child_update, child_ref] end end end
set_reference_update_parent(association_data, update, parent_data)
click to toggle source
# File lib/view_model/active_record/update_operation.rb, line 344 def set_reference_update_parent(association_data, update, parent_data) if update.reparent_to # Another parent has already tried to take this (probably new) # owned referenced view. It can only be claimed by one of them. other_parent = update.reparent_to.viewmodel.to_reference raise ViewModel::DeserializationError::DuplicateOwner.new( association_data.association_name, [blame_reference, other_parent]) end update.reparent_to = parent_data end