From 0760f11bf73aba81e5459cc94945379b69d368ea Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Fri, 29 Sep 2023 19:41:06 +0900 Subject: [PATCH 1/3] Skip validation on deserializing pure references If a deserialization operation is only pointing to something, and making no assertions about it, then we don't need to demand that the referenced model be a valid ActiveRecord. --- lib/view_model/active_record/update_data.rb | 4 ++++ lib/view_model/active_record/update_operation.rb | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/view_model/active_record/update_data.rb b/lib/view_model/active_record/update_data.rb index a4de9179..af8951e7 100644 --- a/lib/view_model/active_record/update_data.rb +++ b/lib/view_model/active_record/update_data.rb @@ -522,6 +522,10 @@ def has_key?(name) delegate :new?, :child_update?, :auto_child_update?, to: :metadata + def reference_only? + attributes.empty? && associations.empty? && referenced_associations.empty? + end + def self.parse_hashes(root_subtree_hashes, referenced_subtree_hashes = {}) valid_reference_keys = referenced_subtree_hashes.keys.to_set diff --git a/lib/view_model/active_record/update_operation.rb b/lib/view_model/active_record/update_operation.rb index 71b4b3aa..400d1e3e 100644 --- a/lib/view_model/active_record/update_operation.rb +++ b/lib/view_model/active_record/update_operation.rb @@ -44,6 +44,10 @@ def built? @built end + def reference_only? + update_data.reference_only? && reparent_to.nil? && reposition_to.nil? + end + # Evaluate a built update tree, applying and saving changes to the models. def run!(deserialize_context:) raise ViewModel::DeserializationError::Internal.new('Internal error: UpdateOperation run before build') unless built? @@ -123,9 +127,14 @@ def run!(deserialize_context:) end end - # validate - deserialize_context.run_callback(ViewModel::Callbacks::Hook::BeforeValidate, viewmodel) - viewmodel.validate! + # If a request makes no assertions about the model, we don't demand + # that the current state of the model is valid. This permits making + # edits to other models that refer to this model when this model is + # invalid. + unless reference_only? && !viewmodel.new_model? + deserialize_context.run_callback(ViewModel::Callbacks::Hook::BeforeValidate, viewmodel) + viewmodel.validate! + end # Save if the model has been altered. Covers not only models with # view changes but also lock version assertions. From aedac8a4d28efca320e33f7ea26a3609924e574f Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Fri, 29 Sep 2023 19:42:11 +0900 Subject: [PATCH 2/3] Allow trivial up-migration of reference-only views --- lib/view_model/migration.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/view_model/migration.rb b/lib/view_model/migration.rb index 2c03d20f..c7b3360f 100644 --- a/lib/view_model/migration.rb +++ b/lib/view_model/migration.rb @@ -5,8 +5,18 @@ class ViewModel::Migration require 'view_model/migration/one_way_error' require 'view_model/migration/unspecified_version_error' + REFERENCE_ONLY_KEYS = [ + ViewModel::TYPE_ATTRIBUTE, + ViewModel::ID_ATTRIBUTE, + ViewModel::VERSION_ATTRIBUTE, + ].freeze + def up(view, _references) - raise ViewModel::Migration::OneWayError.new(view[ViewModel::TYPE_ATTRIBUTE], :up) + # Only a reference-only view may be (trivially) migrated up without an + # explicit migration. + if (view.keys - REFERENCE_ONLY_KEYS).present? + raise ViewModel::Migration::OneWayError.new(view[ViewModel::TYPE_ATTRIBUTE], :up) + end end def down(view, _references) From 2eea809a55fc08f2d867850513870528bd344645 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Wed, 29 May 2024 16:03:11 +0900 Subject: [PATCH 3/3] Bump minor gem version --- lib/iknow_view_models/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iknow_view_models/version.rb b/lib/iknow_view_models/version.rb index 3c738c80..6b8aa7fa 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.11.0' + VERSION = '3.12.0' end