module ViewModel::ActiveRecord::NestedControllerBase
def association_data
def association_data @association_data ||= owner_viewmodel._association_data(association_name) end
def association_name
def association_name params.fetch(:association_name) { raise ArgumentError.new('No association name from routes') } end
def destroy_association(collection, serialize_context: new_serialize_context, deserialize_context: new_deserialize_context, lock_owner: nil)
def destroy_association(collection, serialize_context: new_serialize_context, deserialize_context: new_deserialize_context, lock_owner: nil) require_external_referenced_association! if lock_owner owner_viewmodel.find(owner_viewmodel_id, eager_include: false, lock: lock_owner) end empty_update = collection ? [] : nil owner_viewmodel.deserialize_from_view(owner_update_hash(empty_update), deserialize_context: deserialize_context) render_viewmodel(empty_update, serialize_context: serialize_context) end
def owner_update_hash(update)
def owner_update_hash(update) { ViewModel::ID_ATTRIBUTE => owner_viewmodel_id, ViewModel::TYPE_ATTRIBUTE => owner_viewmodel.view_name, association_name.to_s => update, } end
def owner_viewmodel
def owner_viewmodel name = params.fetch(:owner_viewmodel) { raise ArgumentError.new("No owner viewmodel present") } owner_viewmodel_class_for_name(name.to_s.camelize) end
def owner_viewmodel_class_for_name(name)
def owner_viewmodel_class_for_name(name) ViewModel::Registry.for_view_name(name) end
def owner_viewmodel_id(required: true)
def owner_viewmodel_id(required: true) id_param_name = owner_viewmodel.view_name.underscore + '_id' default = required ? {} : { default: nil } parse_param(id_param_name, **default) end
def require_external_referenced_association!
def require_external_referenced_association! unless association_data.referenced? && association_data.external? raise ArgumentError.new("Expected referenced external association: '#{association_name}'") end end
def show_association(scope: nil, serialize_context: new_serialize_context, lock_owner: nil)
def show_association(scope: nil, serialize_context: new_serialize_context, lock_owner: nil) require_external_referenced_association! associated_views = nil pre_rendered = owner_viewmodel.transaction do owner_view = owner_viewmodel.find(owner_viewmodel_id, eager_include: false, lock: lock_owner) ViewModel::Callbacks.wrap_serialize(owner_view, context: serialize_context) do # Association manipulation methods construct child contexts internally associated_views = owner_view.load_associated(association_name, scope: scope, serialize_context: serialize_context) associated_views = yield(associated_views) if block_given? child_context = owner_view.context_for_child(association_name, context: serialize_context) prerender_viewmodel(associated_views, serialize_context: child_context) end end render_json_string(pre_rendered) associated_views end
def write_association(serialize_context: new_serialize_context, deserialize_context: new_deserialize_context, lock_owner: nil)
single parent each child can only appear once. This means it's
There's no multi membership, so when viewing the children of a
viewmodels directly.
This method always takes direct update hashes, and returns
def write_association(serialize_context: new_serialize_context, deserialize_context: new_deserialize_context, lock_owner: nil) require_external_referenced_association! association_view = nil pre_rendered = owner_viewmodel.transaction do update_hash, refs = parse_viewmodel_updates update_hash = ViewModel::ActiveRecord.add_reference_indirection( update_hash, association_data: association_data, references: refs, key: 'write-association', ) owner_view = owner_viewmodel.find(owner_viewmodel_id, eager_include: false, lock: lock_owner) association_view = owner_view.replace_associated(association_name, update_hash, references: refs, deserialize_context: deserialize_context) ViewModel::Callbacks.wrap_serialize(owner_view, context: serialize_context) do child_context = owner_view.context_for_child(association_name, context: serialize_context) ViewModel.preload_for_serialization(association_view) association_view = yield(association_view) if block_given? prerender_viewmodel(association_view, serialize_context: child_context) end end render_json_string(pre_rendered) association_view end
def write_association_bulk(serialize_context: new_serialize_context, deserialize_context: new_deserialize_context, lock_owner: nil)
hash. This means it's only safe to use update hashes directly in
appear once so each is guaranteed to have a unique update
If an association is referenced and owned, each child may only
the input structure.
reference hashes for shared associations. The return value matches
This method takes direct update hashes for owned associations, and
def write_association_bulk(serialize_context: new_serialize_context, deserialize_context: new_deserialize_context, lock_owner: nil) require_external_referenced_association! updated_by_parent_viewmodel = nil pre_rendered = owner_viewmodel.transaction do updates_by_parent_id, references = parse_bulk_update if association_data.owned? updates_by_parent_id.transform_values!.with_index do |update_hash, index| ViewModel::ActiveRecord.add_reference_indirection( update_hash, association_data: association_data, references: references, key: "write-association-bulk-#{index}", ) end end updated_by_parent_viewmodel = owner_viewmodel.replace_associated_bulk( association_name, updates_by_parent_id, references: references, deserialize_context: deserialize_context, ) views = updated_by_parent_viewmodel.flat_map { |_parent_viewmodel, updated_views| Array.wrap(updated_views) } ViewModel.preload_for_serialization(views) updated_by_parent_viewmodel = yield(updated_by_parent_viewmodel) if block_given? return_updates = updated_by_parent_viewmodel.map do |owner_view, updated_views| ParentProxyModel.new(owner_view, association_data, updated_views) end return_structure = { ViewModel::TYPE_ATTRIBUTE => ViewModel::BULK_UPDATE_TYPE, ViewModel::BULK_UPDATES_ATTRIBUTE => return_updates, } prerender_viewmodel(return_structure, serialize_context: serialize_context) end render_json_string(pre_rendered) updated_by_parent_viewmodel end