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 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. 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)