From 977680ba948cbf186a84adce05cac127feb3d2ec Mon Sep 17 00:00:00 2001 From: jwkoelewijn Date: Wed, 27 Apr 2011 10:54:55 +0200 Subject: [PATCH] Fix preventing validation hooks when save! is used Added a spec causing the valid? call to be run when save! is used, to work around it, I've added an extra check to the save_self chainable in dm-validations.rb that checks for the execute_hooks parameter, circumventing a call to valid? Note: I don't think this is an actual fix, because I think it has to do with the way the validation_context_stack is used, especially the check for emptyness in the save_self chainable. Apparently it is possible to get in a state in which the resource is dirty and the stack is not empty, resulting in a call to valid? --- lib/dm-validations.rb | 4 +-- spec/fixtures/bill.rb | 58 ++++++++++++++++++++++++++++++++++++ spec/public/resource_spec.rb | 14 +++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/bill.rb diff --git a/lib/dm-validations.rb b/lib/dm-validations.rb index 7a6bb687..9d0e06ff 100644 --- a/lib/dm-validations.rb +++ b/lib/dm-validations.rb @@ -77,8 +77,8 @@ def update(attributes = {}, context = default_validation_context) end chainable do - def save_self(*) - return false unless !dirty_self? || validation_context_stack.empty? || valid?(current_validation_context) + def save_self(execute_hooks = true, *args) + return false unless !dirty_self? || !execute_hooks || validation_context_stack.empty? || valid?(current_validation_context) super end end diff --git a/spec/fixtures/bill.rb b/spec/fixtures/bill.rb new file mode 100644 index 00000000..cfebac39 --- /dev/null +++ b/spec/fixtures/bill.rb @@ -0,0 +1,58 @@ +module DataMapper + module Validations + module Fixtures + + # Simple class that represents a bill. This example was + # chosen because bills sometimes need corrections while + # keeping the originals + class Bill + include DataMapper::Resource + + property :id, DataMapper::Property::Serial + + # property to make sure the resource is marked as dirty + property :has_correction, DataMapper::Property::Boolean + + # convenience association to be able to get to the original + # bill if this is the correction + has 1, :original, :model => 'BillCorrection' + + # Keep track of the amount of time the valid hook is called + attr_accessor :valid_hook_call_count + + def valid?(context = :default) + @valid_hook_call_count ||= 0 + @valid_hook_call_count += 1 + super + end + end + + # correction of a bill creates a new bill which keeps an + # association to the original bill + class BillCorrection + include DataMapper::Resource + + property :id, DataMapper::Property::Serial + + belongs_to :original_bill, :model => 'Bill' + belongs_to :correction_bill, :model => 'Bill' + + def self.build_from(original, intermediary = nil) + correction = Bill.new + correction.original = self.new(:original_bill => original, :correction_bill => correction ) + correction + end + + def save + if save_result = super + original_bill.has_correction = true + # suppose we want to bypass validation for some reason + original_result = original_bill.save! + end + save_result && original_result + end + end + end + end +end + diff --git a/spec/public/resource_spec.rb b/spec/public/resource_spec.rb index 13f4e41d..53b675a9 100644 --- a/spec/public/resource_spec.rb +++ b/spec/public/resource_spec.rb @@ -103,3 +103,17 @@ end end end + +describe '#save!' do + it "should not call valid when save! is used" do + bill = DataMapper::Validations::Fixtures::Bill.new() + bill.save.should be_true + + bill_correction = DataMapper::Validations::Fixtures::BillCorrection.build_from(bill) + + # bill_correction.save will call save! on @bill + bill_correction.save.should be_true + + bill.valid_hook_call_count.should == 1 + end +end