From e83af52814d0b2f262e39e28bf3bc825142d0507 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Mon, 8 Jul 2024 12:32:29 +0900 Subject: [PATCH] Add support for batch deletion The VM::AR controller destroy action now optionally parses multiple ids for deletion, and the action is aliased as a collection route as well as a member route. Deletion is performed one at a time within the same transaction. --- lib/iknow_view_models/version.rb | 2 +- lib/view_model/active_record/controller.rb | 31 +++++++++++++++++-- .../active_record/controller_base.rb | 2 ++ .../active_record/controller_test.rb | 16 ++++++++++ 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/lib/iknow_view_models/version.rb b/lib/iknow_view_models/version.rb index 2186409c..3c738c80 100644 --- a/lib/iknow_view_models/version.rb +++ b/lib/iknow_view_models/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module IknowViewModels - VERSION = '3.10.1' + VERSION = '3.11.0' end diff --git a/lib/view_model/active_record/controller.rb b/lib/view_model/active_record/controller.rb index f2c5a1c7..bfd62caa 100644 --- a/lib/view_model/active_record/controller.rb +++ b/lib/view_model/active_record/controller.rb @@ -56,9 +56,14 @@ def create(serialize_context: new_serialize_context, deserialize_context: new_de end def destroy(serialize_context: new_serialize_context, deserialize_context: new_deserialize_context) + viewmodel_ids = parse_param( + :id, with: IknowParams::Serializer::ArrayOf.new(ViewmodelIdSerializer, allow_singleton: true)) + viewmodel_class.transaction do - view = viewmodel_class.find(viewmodel_id, eager_include: false) - view.destroy!(deserialize_context: deserialize_context) + views = viewmodel_class.find(viewmodel_ids, eager_include: false) + views.each do |view| + view.destroy!(deserialize_context: deserialize_context) + end end render_viewmodel(nil) end @@ -91,8 +96,28 @@ def prerender_viewmodel(...) private + # Viewmodel ids are permitted to be either integers or strings + class ViewmodelIdSerializer < IknowParams::Serializer + def initialize + super(::Object) + end + + def load(val) + case val + when ::Integer, ::String + val + else + raise IknowParams::Serializer::LoadError.new( + "Incorrect type for #{self.class.name}: #{val.inspect}:#{val.class.name}") + end + end + + set_singleton! + json_value! + end + def viewmodel_id - parse_param(:id) + parse_param(:id, with: ViewmodelIdSerializer) end def migrated_deep_schema_version diff --git a/lib/view_model/active_record/controller_base.rb b/lib/view_model/active_record/controller_base.rb index 9ff788ee..3c36d26e 100644 --- a/lib/view_model/active_record/controller_base.rb +++ b/lib/view_model/active_record/controller_base.rb @@ -156,11 +156,13 @@ def arvm_resources(resource_name, options = {}, &block) name_route = { as: '' } # Only one route may take the name post('', action: :create, **name_route.extract!(:as)) unless except.include?(:create) || !add_shallow_routes get('', action: :index, **name_route.extract!(:as)) unless except.include?(:index) || !add_shallow_routes + delete('', action: :destroy, as: :bulk_delete) unless except.include?(:destroy) || !add_shallow_routes end end else collection do get('', action: :index, as: '') unless except.include?(:index) + delete('', action: :destroy, as: :bulk_delete) unless except.include?(:destroy) end end end diff --git a/test/unit/view_model/active_record/controller_test.rb b/test/unit/view_model/active_record/controller_test.rb index 7d00ddc9..9a4ef88d 100644 --- a/test/unit/view_model/active_record/controller_test.rb +++ b/test/unit/view_model/active_record/controller_test.rb @@ -167,6 +167,22 @@ def test_update end def test_destroy + other_parent = make_parent + parentcontroller = ParentController.new(params: { id: [@parent.id, other_parent.id] }) + parentcontroller.invoke(:destroy) + + assert_equal(200, parentcontroller.status) + + assert(Parent.where(id: @parent.id).blank?, "record doesn't exist after delete") + assert(Parent.where(id: other_parent.id).blank?, "record doesn't exist after delete") + + assert_equal({ 'data' => nil }, + parentcontroller.hash_response) + + assert_all_hooks_nested_inside_parent_hook(parentcontroller.hook_trace) + end + + def test_batch_destroy parentcontroller = ParentController.new(params: { id: @parent.id }) parentcontroller.invoke(:destroy)