require 'pathname' require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper' describe DataMapper::Validate do before :all do class Yacht include DataMapper::Resource property :id, Integer, :serial => true property :name, String, :auto_validation => false validates_present :name end end it 'should respond to save' do Yacht.new.should respond_to(:save) end describe '#save' do before do Yacht.auto_migrate! @yacht = Yacht.new :name => 'The Gertrude' end describe 'without context specified' do it 'should validate using the default context' do @yacht.should_receive(:valid?).with(:default) @yacht.save end it 'should save if the object is valid for the default context' do @yacht.should be_valid @yacht.save.should be_true @yacht.should_not be_new_record end it 'should not save if the object is not valid for the default context' do @yacht.name = 'a' @yacht.should be_valid @yacht.name = nil @yacht.should_not be_valid @yacht.save.should be_false @yacht.should be_new_record end end describe 'with context specified' do before :all do class Yacht validates_length :name, :min => 2, :context => [ :strict_name ] end end it 'should validate using the specified context' do @yacht.should_receive(:valid?).with(:strict_name) @yacht.save(:strict_name) end it 'should save if the object is valid for the specified context' do @yacht.should be_valid(:strict_name) @yacht.save(:strict_name).should be_true @yacht.should_not be_new_record end it 'should not save if the object is not valid for the specified context' do @yacht.name = 'aa' @yacht.should be_valid(:strict_name) @yacht.name = 'a' @yacht.should_not be_valid(:strict_name) @yacht.save(:strict_name).should be_false @yacht.should be_new_record end end end describe '#save!' do before do Yacht.auto_migrate! @yacht = Yacht.new end it "should save object without running validations" do @yacht.should_not_receive(:valid?) @yacht.save! @yacht.should_not be_new_record end end describe '#create!' do before do Yacht.auto_migrate! end it "should save object without running validations" do Yacht.create!.should be_a_kind_of(Yacht) end end describe "#create" do before do Yacht.auto_migrate! end it "should run validations" do Yacht.create.new_record?.should be_true end end it "should respond to validatable? (for recursing assocations)" do Yacht.new.should be_validatable Class.new.new.should_not be_validatable end it "should have a set of errors on the instance of the resource" do shamrock = Yacht.new shamrock.should respond_to(:errors) end it "should have a set of contextual validations on the class of the resource" do Yacht.should respond_to(:validators) end it "should execute all validators for a given context against the resource" do Yacht.validators.should respond_to(:execute) end it "should place a validator in the :default context if a named context is not provided" do Yacht.validators.context(:default).length.should == 2 end it "should allow multiple user defined contexts for a validator" do class Yacht property :port, String, :auto_validation => false validates_present :port, :context => [:at_sea, :in_harbor] end Yacht.validators.context(:at_sea).length.should == 1 Yacht.validators.context(:in_harbor).length.should == 1 Yacht.validators.context(:no_such_context).length.should == 0 end it "should alias :on and :when for :context" do class Yacht property :owner, String, :auto_validation => false property :bosun, String, :auto_validation => false validates_present :owner, :on => :owned_vessel validates_present :bosun, :when => [:under_way] end Yacht.validators.context(:owned_vessel).length.should == 1 Yacht.validators.context(:under_way).length.should == 1 end it "should alias :group for :context (backward compat with Validatable??)" do class Yacht property :captain, String, :auto_validation => false validates_present :captain, :group => [:captained_vessel] end Yacht.validators.context(:captained_vessel).length.should == 1 end it "should add a method valid_for_? for each context" do class Yacht property :engine_size, String, :auto_validation => false validates_present :engine_size, :when => :power_boat end cigaret = Yacht.new cigaret.valid_for_default?.should_not == true cigaret.should respond_to(:valid_for_power_boat?) cigaret.valid_for_power_boat?.should_not == true cigaret.engine_size = '4 liter V8' cigaret.valid_for_power_boat?.should == true end it "should add a method all_valid_for_? for each context" do class Yacht property :mast_height, String, :auto_validation => false validates_present :mast_height, :when => :sailing_vessel end swift = Yacht.new swift.should respond_to(:all_valid_for_sailing_vessel?) end it "should be able to translate the error message" # needs String::translations it "should be able to get the error message for a given field" do class Yacht property :wood_type, String, :auto_validation => false validates_present :wood_type, :on => :wooden_boats end fantasy = Yacht.new fantasy.valid_for_wooden_boats?.should == false fantasy.errors.on(:wood_type).first.should == 'Wood type must not be blank' fantasy.wood_type = 'birch' fantasy.valid_for_wooden_boats?.should == true end it "should be able to specify a custom error message" do class Yacht property :year_built, String, :auto_validation => false validates_present :year_built, :when => :built, :message => 'Year built is a must enter field' end sea = Yacht.new sea.valid_for_built?.should == false sea.errors.full_messages.first.should == 'Year built is a must enter field' end it "should execute a Proc when provided in an :if clause and run validation if the Proc returns true" do class Dingy include DataMapper::Resource property :id, Integer, :serial => true property :owner, String, :auto_validation => false validates_present :owner, :if => Proc.new{|resource| resource.owned?} def owned? false end end Dingy.new.valid?.should == true class Dingy def owned? true end end Dingy.new.valid?.should_not == true end it "should execute a symbol or method name provided in an :if clause and run validation if the method returns true" do class Dingy validators.clear! validates_present :owner, :if => :owned? def owned? false end end Dingy.new.valid?.should == true class Dingy def owned? true end end Dingy.new.valid?.should_not == true end it "should execute a Proc when provided in an :unless clause and not run validation if the Proc returns true" do class RowBoat include DataMapper::Resource property :id, Integer, :serial => true validates_present :salesman, :unless => Proc.new{|resource| resource.sold?} def sold? false end end RowBoat.new.valid?.should_not == true class RowBoat def sold? true end end RowBoat.new.valid?.should == true end it "should execute a symbol or method name provided in an :unless clause and not run validation if the method returns true" do class Dingy validators.clear! validates_present :salesman, :unless => :sold? def sold? false end end Dingy.new.valid?.should_not == true #not sold and no salesman class Dingy def sold? true end end Dingy.new.valid?.should == true # sold and no salesman end it "should perform automatic recursive validation #all_valid? checking all instance variables (and ivar.each items if valid)" do class Invoice include DataMapper::Resource property :id, Integer, :serial => true property :customer, String, :auto_validation => false validates_present :customer def line_items @line_items || @line_items = [] end def comment @comment || nil end def comment=(value) @comment = value end end class LineItem include DataMapper::Resource property :id, Integer, :serial => true property :price, String, :auto_validation => false validates_is_number :price def initialize(price) @price = price end end class Comment include DataMapper::Resource property :id, Integer, :serial => true property :note, String, :auto_validation => false validates_present :note end invoice = Invoice.new(:customer => 'Billy Bob') invoice.valid?.should == true for i in 1..6 do invoice.line_items << LineItem.new(i.to_s) end invoice.line_items[1].price = 'BAD VALUE' invoice.comment = Comment.new invoice.comment.valid?.should == false invoice.line_items[1].valid?.should == false invoice.all_valid?.should == false invoice.comment.note = 'This is a note' invoice.all_valid?.should == false invoice.line_items[1].price = '23.44' invoice.all_valid?.should == true end it "should retrieve private instance variables for validation" do class Raft include DataMapper::Resource property :length, Integer, :accessor => :private def initialize(length) @length = length end end Raft.new(10).validation_property_value("length").should == 10 end it "should duplicate validations to STI models" do class Company include DataMapper::Resource validates_present :title, :message => "Company name is a required field" property :id, Integer, :serial => true, :key => true property :title, String property :type, Discriminator end class ServiceCompany < Company end class ProductCompany < Company end company = ServiceCompany.new company.should_not be_valid end end