From 3f7e154592f0f8fea72c1e53f021995ba1c9f052 Mon Sep 17 00:00:00 2001 From: Andrew Konchin Date: Fri, 1 Nov 2024 19:45:08 +0200 Subject: [PATCH] Support #save(object, validate: false) --- lib/dynamoid/transaction_write/save.rb | 14 +-- spec/dynamoid/transaction_write/save_spec.rb | 93 ++++++++++++++++++++ 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/lib/dynamoid/transaction_write/save.rb b/lib/dynamoid/transaction_write/save.rb index 2718e9c6..7357a55b 100644 --- a/lib/dynamoid/transaction_write/save.rb +++ b/lib/dynamoid/transaction_write/save.rb @@ -16,12 +16,14 @@ def initialize(model, **options) def on_registration validate_model! - unless @valid = @model.valid? - if @options[:raise_error] - raise Dynamoid::Errors::DocumentNotValid, @model - else - @aborted = true - return + unless @options[:validate] == false + unless @valid = @model.valid? + if @options[:raise_error] + raise Dynamoid::Errors::DocumentNotValid, @model + else + @aborted = true + return + end end end diff --git a/spec/dynamoid/transaction_write/save_spec.rb b/spec/dynamoid/transaction_write/save_spec.rb index 7b827f02..9c82241d 100644 --- a/spec/dynamoid/transaction_write/save_spec.rb +++ b/spec/dynamoid/transaction_write/save_spec.rb @@ -346,6 +346,25 @@ def around_save_callback expect(obj).to be_changed end + context 'validate: false option' do + it 'persists an invalid model' do + obj = klass_with_validation.new(name: 'one') + expect(obj.valid?).to eql false + + expect { + described_class.execute do |txn| + txn.save obj, validate: false + end + }.to change { klass_with_validation.count }.by(1) + + obj_loaded = klass_with_validation.find(obj.id) + expect(obj_loaded.name).to eql 'one' + + expect(obj).to be_persisted + expect(obj).not_to be_changed + end + end + it 'returns true when model valid' do obj = klass_with_validation.new(name: 'oneone') expect(obj.valid?).to eql true @@ -420,6 +439,24 @@ def around_save_callback expect(obj).to be_changed end + context 'validate: false option' do + it 'persists an invalid model' do + obj = klass_with_validation.create!(name: 'oneone') + obj.name = 'one' + expect(obj.valid?).to eql false + + described_class.execute do |txn| + txn.save obj, validate: false + end + + obj_loaded = klass_with_validation.find(obj.id) + expect(obj_loaded.name).to eql 'one' + + expect(obj).to be_persisted + expect(obj).not_to be_changed + end + end + it 'returns true when model valid' do obj = klass_with_validation.create!(name: 'oneone') obj.name = 'oneone [Updated]' @@ -810,6 +847,45 @@ def around_create_callback 'run after_save') expect(ScratchPad.recorded).to eql [] end + + it 'skips *_validation callbacks when validate: false option specified and valid model' do + klass_with_callbacks = new_class do + before_validation { ScratchPad << 'run before_validation' } + after_validation { ScratchPad << 'run after_validation' } + + field :name + validates :name, presence: true + end + + ScratchPad.record [] + klass_with_callbacks.create_table + obj = klass_with_callbacks.new(name: 'Alex') + + described_class.execute do |txn| + txn.save obj, validate: false + end + + expect(ScratchPad.recorded).to eql [] + end + + it 'skips *_validation callbacks when validate: false option specified and invalid model' do + klass_with_callbacks = new_class do + before_validation { ScratchPad << 'run before_validation' } + after_validation { ScratchPad << 'run after_validation' } + + field :name + validates :name, presence: true + end + ScratchPad.record [] + klass_with_callbacks.create_table + obj = klass_with_callbacks.new(name: '') + + described_class.execute do |txn| + txn.save obj, validate: false + end + + expect(ScratchPad.recorded).to eql [] + end end context 'persisted model' do @@ -1078,6 +1154,23 @@ def around_update_callback }.to raise_error(Dynamoid::Errors::DocumentNotValid) end + it 'persists an invalid model when validate: false option specified' do + obj = klass_with_validation.new(name: 'one') + expect(obj.valid?).to eql false + + expect { + described_class.execute do |txn| + txn.save! obj, validate: false + end + }.to change { klass_with_validation.count }.by(1) + + obj_loaded = klass_with_validation.find(obj.id) + expect(obj_loaded.name).to eql 'one' + + expect(obj).to be_persisted + expect(obj).not_to be_changed + end + it 'rolls back the whole transaction when a model to be created is invalid' do obj_invalid = klass_with_validation.new(name: 'one') obj_valid = klass_with_validation.new(name: 'twotwo')