module ViewModel::ActiveRecord::AssociationManipulation
def delete_associated(association_name, associated_id, type: nil, deserialize_context: self.class.new_deserialize_context)
garbage-collected if the assocation is specified with `dependent: :destroy`
the provided associated viewmodel. The associated model will be
Removes the association between the models represented by this viewmodel and
def delete_associated(association_name, associated_id, type: nil, deserialize_context: self.class.new_deserialize_context) if self.changes.changed? raise ArgumentError.new('Invalid call to delete_associated on viewmodel with pending changes') end association_data = self.class._association_data(association_name) direct_reflection = association_data.direct_reflection unless association_data.collection? raise ArgumentError.new("Cannot remove element from single association '#{association_name}'") end check_association_type!(association_data, type) target_ref = ViewModel::Reference.new(type || association_data.viewmodel_class, associated_id) model_class.transaction do ViewModel::Callbacks.wrap_deserialize(self, deserialize_context: deserialize_context) do |hook_control| association_changed!(association_name) deserialize_context.run_callback(ViewModel::Callbacks::Hook::BeforeValidate, self) association = self.model.association(direct_reflection.name) association_scope = association.scope if association_data.through? raise ArgumentError.new('Polymorphic through relationships not supported yet') if association_data.polymorphic? direct_viewmodel = association_data.direct_viewmodel association_scope = association_scope.where(association_data.indirect_reflection.foreign_key => associated_id) else # viewmodel type for current association: nil in case of empty polymorphic association direct_viewmodel = association.klass.try { |k| association_data.viewmodel_class_for_model!(k) } if association_data.pointer_location == :local # If we hold the pointer, we can immediately check if the type and id match. if target_ref != ViewModel::Reference.new(direct_viewmodel, model.read_attribute(direct_reflection.foreign_key)) raise ViewModel::DeserializationError::AssociatedNotFound.new(association_name.to_s, target_ref, blame_reference) end else # otherwise add the target constraint to the association scope association_scope = association_scope.where(id: associated_id) end end models = association_scope.to_a if models.blank? raise ViewModel::DeserializationError::AssociatedNotFound.new(association_name.to_s, target_ref, blame_reference) elsif models.size > 1 raise ViewModel::DeserializationError::Internal.new( "Internal error: encountered multiple records for #{target_ref} in association #{association_name}", blame_reference) end child_context = self.context_for_child(association_name, context: deserialize_context) child_vm = direct_viewmodel.new(models.first) ViewModel::Callbacks.wrap_deserialize(child_vm, deserialize_context: child_context) do |child_hook_control| changes = ViewModel::Changes.new(deleted: true) child_context.run_callback(ViewModel::Callbacks::Hook::OnChange, child_vm, changes: changes) child_hook_control.record_changes(changes) association.delete(child_vm.model) end self.children_changed! final_changes = self.clear_changes! unless final_changes.contained_to?(associations: [association_name.to_s]) raise ViewModel::DeserializationError::InvalidParentEdit.new(final_changes, blame_reference) end deserialize_context.run_callback(ViewModel::Callbacks::Hook::OnChange, self, changes: final_changes) hook_control.record_changes(final_changes) child_vm end end end