From fb4f8899bba05d205379af8723449e5ab43f735e Mon Sep 17 00:00:00 2001 From: "Michael S. Klishin" Date: Fri, 27 Mar 2009 00:51:55 +0300 Subject: [PATCH] [dm-validations] Make it possible to use callable objects for messages, requires Edge Extlib This makes it possible to construct complex custom error messages and not add more and more and more ad hoc error messages to default set * ValidationErrors objects are now aware of models they are attached to (#model accessor) * For pure Ruby objects, if :message is a callable, model (object) is yielded to it * For DM resources, if :message is a callable, model AND property are yielded to it [#769 state:resolved] --- dm-validations/lib/dm-validations.rb | 2 +- .../lib/dm-validations/validation_errors.rb | 22 +++++++++++++++++-- .../confirmation_validator_spec.rb | 3 ++- .../confirmation_validator/spec_helper.rb | 4 +++- .../plain_old_ruby_object_validation_spec.rb | 16 ++++++++++---- .../unit/validation_errors/adding_spec.rb | 2 +- .../unit/validation_errors/emptiness_spec.rb | 2 +- .../unit/validation_errors/enumerable_spec.rb | 2 +- 8 files changed, 41 insertions(+), 12 deletions(-) diff --git a/dm-validations/lib/dm-validations.rb b/dm-validations/lib/dm-validations.rb index f94e8f6f..d96cd5cf 100644 --- a/dm-validations/lib/dm-validations.rb +++ b/dm-validations/lib/dm-validations.rb @@ -73,7 +73,7 @@ def save! # Return the ValidationErrors # def errors - @errors ||= ValidationErrors.new + @errors ||= ValidationErrors.new(self) end # Mark this resource as validatable. When we validate associations of a diff --git a/dm-validations/lib/dm-validations/validation_errors.rb b/dm-validations/lib/dm-validations/validation_errors.rb index 56d7d8f5..5463e45a 100644 --- a/dm-validations/lib/dm-validations/validation_errors.rb +++ b/dm-validations/lib/dm-validations/validation_errors.rb @@ -41,6 +41,13 @@ def self.default_error_message(key, field, *values) @@default_error_messages[key] % [field, *values].flatten end + attr_reader :model + + def initialize(model) + @model = model + @errors = {} + end + # Clear existing validation errors. def clear! errors.clear @@ -52,6 +59,17 @@ def clear! # @param [Symbol] field_name the name of the field that caused the error # @param [String] message the message to add def add(field_name, message) + # see 6abe8fff in extlib, but don't enforce + # it unless Edge version is installed + if message.respond_to?(:try_call) + # DM model + message = if model.class.respond_to?(:properties) + message.try_call(model, model.class.properties[field_name]) + else + # pure Ruby object + message.try_call(model) + end + end (errors[field_name] ||= []) << message end @@ -80,7 +98,7 @@ def each end def empty? - entries.empty? + @errors.empty? end def method_missing(meth, *args, &block) @@ -89,7 +107,7 @@ def method_missing(meth, *args, &block) private def errors - @errors ||= {} + @errors end end # class ValidationErrors diff --git a/dm-validations/spec/integration/confirmation_validator/confirmation_validator_spec.rb b/dm-validations/spec/integration/confirmation_validator/confirmation_validator_spec.rb index f336b0a0..261d9f65 100644 --- a/dm-validations/spec/integration/confirmation_validator/confirmation_validator_spec.rb +++ b/dm-validations/spec/integration/confirmation_validator/confirmation_validator_spec.rb @@ -12,7 +12,8 @@ describe "reservation with mismatched seats number", :shared => true do it "has meaningful error message" do - @model.errors.on(:number_of_seats).should include("Number of seats does not match the confirmation") + # Proc gets expanded here + @model.errors.on(:number_of_seats).should include("Reservation requires confirmation for number_of_seats") end end diff --git a/dm-validations/spec/integration/confirmation_validator/spec_helper.rb b/dm-validations/spec/integration/confirmation_validator/spec_helper.rb index 526468ef..f7fae51a 100644 --- a/dm-validations/spec/integration/confirmation_validator/spec_helper.rb +++ b/dm-validations/spec/integration/confirmation_validator/spec_helper.rb @@ -29,7 +29,9 @@ class Reservation # validates_is_confirmed :person_name, :allow_nil => false - validates_is_confirmed :number_of_seats, :confirm => :seats_confirmation + validates_is_confirmed :number_of_seats, :confirm => :seats_confirmation, :message => Proc.new { |model, property| + "%s requires confirmation for %s" % [model.class.name.split("::").last, property.name] + } end # Reservation end # Fixtures end # Validate diff --git a/dm-validations/spec/integration/pure_ruby_objects/plain_old_ruby_object_validation_spec.rb b/dm-validations/spec/integration/pure_ruby_objects/plain_old_ruby_object_validation_spec.rb index fb1d118c..7dce8ee9 100644 --- a/dm-validations/spec/integration/pure_ruby_objects/plain_old_ruby_object_validation_spec.rb +++ b/dm-validations/spec/integration/pure_ruby_objects/plain_old_ruby_object_validation_spec.rb @@ -15,7 +15,9 @@ class Country # validates_present :name, :when => [:default, :adding_to_encyclopedia] - validates_present :population, :when => :adding_to_encyclopedia + validates_present :population, :when => :adding_to_encyclopedia, :message => Proc.new { |record| + "population really needs to be specified when adding %s to encyclopedia" % [record.class.name] + } validates_length :name, :in => (4..50) @@ -64,6 +66,12 @@ def initialize(name, population = nil) @model.should_not be_valid(:adding_to_encyclopedia) @model.should_not be_valid_for_adding_to_encyclopedia end + + it "has a meaningful error message" do + # trigger validation => have errors on the object + @model.valid_for_adding_to_encyclopedia? + @model.errors.on(:population).should == ["population really needs to be specified when adding PureRubyObjects::Country to encyclopedia"] + end end @@ -96,16 +104,16 @@ def initialize(name, population = nil) @model.name = "It" @model.valid? end - + it_should_behave_like "object invalid in default context" it "has errors on name" do @model.errors.on(:name).should_not be_blank end - + it "is valid in encyclopedia context" do @model.should be_valid(:adding_to_encyclopedia) @model.should be_valid_for_adding_to_encyclopedia end - end + end end diff --git a/dm-validations/spec/unit/validation_errors/adding_spec.rb b/dm-validations/spec/unit/validation_errors/adding_spec.rb index 5ad311c3..09fe1c75 100644 --- a/dm-validations/spec/unit/validation_errors/adding_spec.rb +++ b/dm-validations/spec/unit/validation_errors/adding_spec.rb @@ -4,7 +4,7 @@ describe DataMapper::Validate::ValidationErrors do before :all do - @model = DataMapper::Validate::ValidationErrors.new + @model = DataMapper::Validate::ValidationErrors.new(Object.new) end describe "after first error being added" do diff --git a/dm-validations/spec/unit/validation_errors/emptiness_spec.rb b/dm-validations/spec/unit/validation_errors/emptiness_spec.rb index 83eeadaa..e6de7ee7 100644 --- a/dm-validations/spec/unit/validation_errors/emptiness_spec.rb +++ b/dm-validations/spec/unit/validation_errors/emptiness_spec.rb @@ -4,7 +4,7 @@ describe DataMapper::Validate::ValidationErrors do before :all do - @model = DataMapper::Validate::ValidationErrors.new + @model = DataMapper::Validate::ValidationErrors.new(Object.new) end describe "initially" do diff --git a/dm-validations/spec/unit/validation_errors/enumerable_spec.rb b/dm-validations/spec/unit/validation_errors/enumerable_spec.rb index cfb77197..aff58030 100644 --- a/dm-validations/spec/unit/validation_errors/enumerable_spec.rb +++ b/dm-validations/spec/unit/validation_errors/enumerable_spec.rb @@ -4,7 +4,7 @@ describe DataMapper::Validate::ValidationErrors do before :all do - @model = DataMapper::Validate::ValidationErrors.new + @model = DataMapper::Validate::ValidationErrors.new(Object.new) @model.add(:ip_address, "must have valid format") @model.add(:full_name, "can't be blank") end