From c308aaf4a9fa75e633453bd4d77cbc3e5d799565 Mon Sep 17 00:00:00 2001 From: Adam Meehan Date: Sun, 28 Dec 2008 17:22:24 +1100 Subject: [PATCH 1/4] refactored attribute name handling in spec --- .../matchers/validate_timeliness_spec.rb | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/spec/spec/rails/matchers/validate_timeliness_spec.rb b/spec/spec/rails/matchers/validate_timeliness_spec.rb index c4f8f06..d5ba5f4 100644 --- a/spec/spec/rails/matchers/validate_timeliness_spec.rb +++ b/spec/spec/rails/matchers/validate_timeliness_spec.rb @@ -27,20 +27,21 @@ end describe "ValidateTimeliness matcher" do attr_accessor :no_validation, :with_validation + @@attribute_for_type = { :date => :birth_date, :time => :birth_time, :datetime => :birth_date_and_time } + before do @no_validation = NoValidation.new @with_validation = WithValidation.new end [:date, :time, :datetime].each do |type| - attribute = type == :datetime ? :date_and_time : type it "should report that #{type} is validated" do - with_validation.should self.send("validate_#{type}", "birth_#{attribute}".to_sym) + with_validation.should self.send("validate_#{type}", attribute_for_type(type)) end it "should report that #{type} is not validated" do - no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}".to_sym) + no_validation.should_not self.send("validate_#{type}", attribute_for_type(type)) end end @@ -52,18 +53,17 @@ describe "ValidateTimeliness matcher" do } [:date, :time, :datetime].each do |type| - attribute = type == :datetime ? :date_and_time : type it "should report that #{type} is validated" do - with_validation.should self.send("validate_#{type}", "birth_#{attribute}", :before => test_values[type][0]) + with_validation.should self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][0]) end it "should report that #{type} is not validated when option value is incorrect" do - with_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :before => test_values[type][1]) + with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][1]) end it "should report that #{type} is not validated with option" do - no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :before => test_values[type][0]) + no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][0]) end end end @@ -76,18 +76,17 @@ describe "ValidateTimeliness matcher" do } [:date, :time, :datetime].each do |type| - attribute = type == :datetime ? :date_and_time : type it "should report that #{type} is validated" do - with_validation.should self.send("validate_#{type}", "birth_#{attribute}", :after => test_values[type][0]) + with_validation.should self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][0]) end it "should report that #{type} is not validated when option value is incorrect" do - with_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :after => test_values[type][1]) + with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][1]) end it "should report that #{type} is not validated with option" do - no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :after => test_values[type][0]) + no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][0]) end end end @@ -100,18 +99,17 @@ describe "ValidateTimeliness matcher" do } [:date, :time, :datetime].each do |type| - attribute = type == :datetime ? :date_and_time : type it "should report that #{type} is validated" do - with_validation.should self.send("validate_#{type}", "birth_#{attribute}", :on_or_before => test_values[type][0]) + with_validation.should self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][0]) end it "should report that #{type} is not validated when option value is incorrect" do - with_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :on_or_before => test_values[type][1]) + with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][1]) end it "should report that #{type} is not validated with option" do - no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :on_or_before => test_values[type][0]) + no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][0]) end end end @@ -124,18 +122,17 @@ describe "ValidateTimeliness matcher" do } [:date, :time, :datetime].each do |type| - attribute = type == :datetime ? :date_and_time : type it "should report that #{type} is validated" do - with_validation.should self.send("validate_#{type}", "birth_#{attribute}", :on_or_after => test_values[type][0]) + with_validation.should self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][0]) end it "should report that #{type} is not validated when option value is incorrect" do - with_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :on_or_after => test_values[type][1]) + with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][1]) end it "should report that #{type} is not validated with option" do - no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :on_or_after => test_values[type][0]) + no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][0]) end end end @@ -175,4 +172,8 @@ describe "ValidateTimeliness matcher" do end end + + def attribute_for_type(type) + @@attribute_for_type[type.to_sym] + end end From 45ab815039971c651d89b605078ecb0317a2a997 Mon Sep 17 00:00:00 2001 From: Adam Meehan Date: Thu, 1 Jan 2009 20:11:30 +1100 Subject: [PATCH 2/4] added between option and some refactoring --- lib/validates_timeliness/locale/en.yml | 1 + lib/validates_timeliness/validator.rb | 75 +++++++++++---- spec/validator_spec.rb | 123 ++++++++++++++++--------- 3 files changed, 140 insertions(+), 59 deletions(-) diff --git a/lib/validates_timeliness/locale/en.yml b/lib/validates_timeliness/locale/en.yml index 1c5e63c..83e544b 100644 --- a/lib/validates_timeliness/locale/en.yml +++ b/lib/validates_timeliness/locale/en.yml @@ -9,3 +9,4 @@ en: on_or_before: "must be on or before {{restriction}}" after: "must be after {{restriction}}" on_or_after: "must be on or after {{restriction}}" + between: "must be between {{earliest}} and {{latest}}" diff --git a/lib/validates_timeliness/validator.rb b/lib/validates_timeliness/validator.rb index 1c2fcf1..0ae431d 100644 --- a/lib/validates_timeliness/validator.rb +++ b/lib/validates_timeliness/validator.rb @@ -11,6 +11,14 @@ module ValidatesTimeliness :datetime => '%Y-%m-%d %H:%M:%S' } + RESTRICTION_METHODS = { + :before => :<, + :after => :>, + :on_or_before => :<=, + :on_or_after => :>=, + :between => lambda {|v, r| (r.first..r.last).include?(v) } + } + attr_reader :configuration, :type def initialize(configuration) @@ -40,21 +48,17 @@ module ValidatesTimeliness end def validate_restrictions(record, attr_name, value) - restriction_methods = {:before => '<', :after => '>', :on_or_before => '<=', :on_or_after => '>='} - - display = self.class.error_value_formats[type] - value = type_cast_value(value) - restriction_methods.each do |option, method| + RESTRICTION_METHODS.each do |option, method| next unless restriction = configuration[option] begin - compare = restriction_value(restriction, record) - next if compare.nil? - compare = type_cast_value(compare) + restriction = restriction_value(restriction, record) + next if restriction.nil? + restriction = type_cast_value(restriction) - unless value.send(method, compare) - add_error(record, attr_name, option, :restriction => compare.strftime(display)) + unless evaluate_restriction(restriction, value, method) + add_error(record, attr_name, option, interpolation_values(option, restriction)) end rescue unless self.class.ignore_restriction_errors @@ -63,15 +67,41 @@ module ValidatesTimeliness end end end + + def interpolation_values(option, restriction) + format = self.class.error_value_formats[type] + restriction = [restriction] unless restriction.is_a?(Array) + + if defined?(I18n) + message = custom_error_messages[option] || I18n.translate('activerecord.errors.messages')[option] + subs = message.scan(/\{\{([^\}]*)\}\}/) + interpolations = {} + subs.each_with_index {|s, i| interpolations[s[0].to_sym] = restriction[i].strftime(format) } + interpolations + else + restriction.map {|r| r.strftime(format) } + end + end + + def evaluate_restriction(restriction, value, comparator) + return true if restriction.nil? + + case comparator + when Symbol + value.send(comparator, restriction) + when Proc + comparator.call(value, restriction) + end + end - def add_error(record, attr_name, message, interpolate={}) + def add_error(record, attr_name, message, interpolate=nil) if defined?(I18n) # use i18n support in AR for message or use custom message passed to validation method custom = custom_error_messages[message] - record.errors.add(attr_name, custom || message, interpolate) + record.errors.add(attr_name, custom || message, interpolate || {}) else message = error_messages[message] if message.is_a?(Symbol) - message = message % interpolate.values unless interpolate.empty? + message = message % interpolate record.errors.add(attr_name, message) end end @@ -83,7 +113,12 @@ module ValidatesTimeliness def custom_error_messages return @custom_error_messages if defined?(@custom_error_messages) - @custom_error_messages = configuration.inject({}) {|h, (k, v)| h[$1.to_sym] = v if k.to_s =~ /(.*)_message$/;h } + @custom_error_messages = configuration.inject({}) {|msgs, (k, v)| + if md = /(.*)_message$/.match(k.to_s) + msgs[md[0].to_sym] = v + end + msgs + } end def restriction_value(restriction, record) @@ -94,13 +129,20 @@ module ValidatesTimeliness restriction_value(record.send(restriction), record) when Proc restriction_value(restriction.call(record), record) + when Array + restriction.map {|r| restriction_value(r, record) }.sort + when Range + restriction_value([restriction.first, restriction.last], record) else record.class.parse_date_time(restriction, type, false) end end def type_cast_value(value) - case type + if value.is_a?(Array) + value.map {|v| type_cast_value(v) } + else + case type when :time value.to_dummy_time when :date @@ -109,10 +151,11 @@ module ValidatesTimeliness if value.is_a?(DateTime) || value.is_a?(Time) value.to_time else - value.to_time(ValidatesTimelines.default_timezone) + value.to_time(ValidatesTimeliness.default_timezone) end else nil + end end end diff --git a/spec/validator_spec.rb b/spec/validator_spec.rb index 620f1dd..d4750c9 100644 --- a/spec/validator_spec.rb +++ b/spec/validator_spec.rb @@ -43,6 +43,25 @@ describe ValidatesTimeliness::Validator do restriction_value(lambda {"2007-01-01 12:00"}, :datetime).should be_kind_of(Time) end + it "should return array of Time objects when restriction is array of Time objects" do + time1, time2 = Time.now, 1.day.ago + restriction_value([time1, time2], :datetime).should == [time2, time1] + end + + it "should return array of Time objects when restriction is array of strings" do + time1, time2 = "2000-01-02", "2000-01-01" + restriction_value([time1, time2], :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)] + end + + it "should return array of Time objects when restriction is Range of Time objects" do + time1, time2 = Time.now, 1.day.ago + restriction_value(time1..time2, :datetime).should == [time2, time1] + end + + it "should return array of Time objects when restriction is Range of time strings" do + time1, time2 = "2000-01-02", "2000-01-01" + restriction_value(time1..time2, :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)] + end def restriction_value(restriction, type) configure_validator(:type => type) validator.send(:restriction_value, restriction, person) @@ -212,83 +231,101 @@ describe ValidatesTimeliness::Validator do end end - describe "instance with on_or_before and on_or_after restrictions" do + describe "instance with between restriction" do describe "for datetime type" do before do - configure_validator(:on_or_before => Time.now.at_midnight, :on_or_after => 1.day.ago) + configure_validator(:between => [1.day.ago.at_midnight, 1.day.from_now.at_midnight]) end - it "should have error when value is past :on_or_before restriction" do - validate_with(:birth_date_and_time, Time.now.at_midnight + 1) - should_have_error(:birth_date_and_time, :on_of_before) + it "should have error when value is before earlist :between restriction" do + validate_with(:birth_date_and_time, 2.days.ago) + should_have_error(:birth_date_and_time, :between) end - it "should be valid when value is equal to :on_or_before restriction" do - validate_with(:birth_date_and_time, Time.now.at_midnight) - should_have_no_error(:birth_date_and_time, :on_of_before) + it "should have error when value is after latest :between restriction" do + validate_with(:birth_date_and_time, 2.days.from_now) + should_have_error(:birth_date_and_time, :between) end - it "should have error when value is before :on_or_after restriction" do - validate_with(:birth_date_and_time, 1.days.ago - 1) - should_have_error(:birth_date_and_time, :on_of_after) + it "should be valid when value is equal to earliest :between restriction" do + validate_with(:birth_date_and_time, 1.day.ago.at_midnight) + should_have_no_error(:birth_date_and_time, :between) end - it "should be valid when value is value equal to :on_or_after restriction" do - validate_with(:birth_date_and_time, 1.day.ago) - should_have_no_error(:birth_date_and_time, :on_of_after) + it "should be valid when value is equal to latest :between restriction" do + validate_with(:birth_date_and_time, 1.day.from_now.at_midnight) + should_have_no_error(:birth_date_and_time, :between) + end + + it "should allow a range for between restriction" do + configure_validator(:type => :datetime, :between => (1.day.ago.at_midnight)..(1.day.from_now.at_midnight)) + validate_with(:birth_date_and_time, 1.day.from_now.at_midnight) + should_have_no_error(:birth_date_and_time, :between) end end describe "for date type" do - before :each do - configure_validator(:on_or_before => 1.day.from_now, :on_or_after => 1.day.ago, :type => :date) + before do + configure_validator(:type => :date, :between => [1.day.ago.to_date, 1.day.from_now.to_date]) end - it "should have error when value is past :on_or_before restriction" do - validate_with(:birth_date, 2.days.from_now) - should_have_error(:birth_date, :on_or_before) + it "should have error when value is before earlist :between restriction" do + validate_with(:birth_date, 2.days.ago.to_date) + should_have_error(:birth_date, :between) end - it "should have error when value is before :on_or_after restriction" do - validate_with(:birth_date, 2.days.ago) - should_have_error(:birth_date, :on_or_after) + it "should have error when value is after latest :between restriction" do + validate_with(:birth_date, 2.days.from_now.to_date) + should_have_error(:birth_date, :between) end - it "should be valid when value is equal to :on_or_before restriction" do - validate_with(:birth_date, 1.day.from_now) - should_have_no_error(:birth_date, :on_or_before) + it "should be valid when value is equal to earliest :between restriction" do + validate_with(:birth_date, 1.day.ago.to_date) + should_have_no_error(:birth_date, :between) end - it "should be valid when value value is equal to :on_or_after restriction" do - validate_with(:birth_date, 1.day.ago) - should_have_no_error(:birth_date, :on_or_before) + it "should be valid when value is equal to latest :between restriction" do + validate_with(:birth_date, 1.day.from_now.to_date) + should_have_no_error(:birth_date, :between) + end + + it "should allow a range for between restriction" do + configure_validator(:type => :date, :between => (1.day.ago.to_date)..(1.day.from_now.to_date)) + validate_with(:birth_date, 1.day.from_now.to_date) + should_have_no_error(:birth_date, :between) end end describe "for time type" do - before :each do - configure_validator(:on_or_before => "23:00", :on_or_after => "06:00", :type => :time) + before do + configure_validator(:type => :time, :between => ["09:00", "17:00"]) end - it "should have error when value is past :on_or_before restriction" do - validate_with(:birth_time, "23:01") - should_have_error(:birth_time, :on_or_before) + it "should have error when value is before earlist :between restriction" do + validate_with(:birth_time, "08:59") + should_have_error(:birth_time, :between) end - it "should have error when value is before :on_or_after restriction" do - validate_with(:birth_time, "05:59") - should_have_error(:birth_time, :on_or_after) + it "should have error when value is after latest :between restriction" do + validate_with(:birth_time, "17:01") + should_have_error(:birth_time, :between) end - it "should be valid when value is on boundary of :on_or_before restriction" do - validate_with(:birth_time, "23:00") - should_have_no_error(:birth_time, :on_or_before) + it "should be valid when value is equal to earliest :between restriction" do + validate_with(:birth_time, "09:00") + should_have_no_error(:birth_time, :between) end - it "should be valid when value is on boundary of :on_or_after restriction" do - validate_with(:birth_time, "06:00") - should_have_no_error(:birth_time, :on_or_after) + it "should be valid when value is equal to latest :between restriction" do + validate_with(:birth_time, "17:00") + should_have_no_error(:birth_time, :between) + end + + it "should allow a range for between restriction" do + configure_validator(:type => :time, :between => "09:00".."17:00") + validate_with(:birth_time, "17:00") + should_have_no_error(:birth_time, :between) end end end @@ -433,6 +470,6 @@ describe ValidatesTimeliness::Validator do def error_messages return @error_messages if defined?(@error_messages) messages = validator.send(:error_messages) - @error_messages = messages.inject({}) {|h, (k, v)| h[k] = v.gsub(/ (\%s|\{\{\w*\}\})/, ''); h } + @error_messages = messages.inject({}) {|h, (k, v)| h[k] = v.sub(/ (\%s|\{\{\w*\}\}).*/, ''); h } end end From a71d6f79458abb62f5c028068d7ca4a0ceb3897f Mon Sep 17 00:00:00 2001 From: Adam Meehan Date: Thu, 1 Jan 2009 20:13:44 +1100 Subject: [PATCH 3/4] added between option testing to matcher and refactored --- .../rails/matchers/validate_timeliness.rb | 138 +++++++++++------- .../matchers/validate_timeliness_spec.rb | 33 ++++- 2 files changed, 117 insertions(+), 54 deletions(-) diff --git a/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb b/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb index 782aecb..73c97e5 100644 --- a/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb +++ b/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb @@ -2,120 +2,156 @@ module Spec module Rails module Matchers class ValidateTimeliness - cattr_accessor :test_values - @@test_values = { + VALIDITY_TEST_VALUES = { :date => {:pass => '2000-01-01', :fail => '2000-01-32'}, :time => {:pass => '12:00', :fail => '25:00'}, :datetime => {:pass => '2000-01-01 00:00:00', :fail => '2000-01-32 00:00:00'} } + OPTION_TEST_SETTINGS = { + :before => { :method => :-, :modify_on => :valid }, + :after => { :method => :+, :modify_on => :valid }, + :on_or_before => { :method => :+, :modify_on => :invalid }, + :on_or_after => { :method => :-, :modify_on => :invalid } + } + def initialize(attribute, options) @expected, @options = attribute, options @validator = ValidatesTimeliness::Validator.new(options) - compile_error_messages - end - - def compile_error_messages - messages = validator.send(:error_messages) - @messages = messages.inject({}) {|h, (k, v)| h[k] = v.gsub(/ (\%s|\{\{\w*\}\})/, ''); h } end def matches?(record) @record = record - type = options[:type] + @type = options[:type] - invalid_value = @@test_values[type][:fail] - valid_value = parse_and_cast(@@test_values[type][:pass]) - valid = error_matching(invalid_value, /#{messages["invalid_#{type}".to_sym]}/) && - no_error_matching(valid_value, /#{messages["invalid_#{type}".to_sym]}/) + valid = test_validity - valid = test_option(:before, :-) if options[:before] && valid - valid = test_option(:after, :+) if options[:after] && valid + valid = test_option(:before) if @options[:before] && valid + valid = test_option(:after) if @options[:after] && valid - valid = test_option(:on_or_before, :+, :modify_on => :invalid) if options[:on_or_before] && valid - valid = test_option(:on_or_after, :-, :modify_on => :invalid) if options[:on_or_after] && valid + valid = test_option(:on_or_before) if @options[:on_or_before] && valid + valid = test_option(:on_or_after) if @options[:on_or_after] && valid + + valid = test_between if @options[:between] && valid return valid end def failure_message - "expected model to validate #{options[:type]} attribute #{expected.inspect} with #{last_failure}" + "expected model to validate #{@type} attribute #{@expected.inspect} with #{@last_failure}" end def negative_failure_message - "expected not to validate #{options[:type]} attribute #{expected.inspect}" + "expected not to validate #{@type} attribute #{@expected.inspect}" end def description - "have validated #{options[:type]} attribute #{expected.inspect}" + "have validated #{@type} attribute #{@expected.inspect}" end private - attr_reader :actual, :expected, :record, :options, :messages, :last_failure, :validator - - def test_option(option, modifier, settings={}) - settings.reverse_merge!(:modify_on => :valid) - boundary = parse_and_cast(options[option]) + + def test_validity + invalid_value = VALIDITY_TEST_VALUES[@type][:fail] + valid_value = parse_and_cast(VALIDITY_TEST_VALUES[@type][:pass]) + error_matching(invalid_value, "invalid_#{@type}".to_sym) && + no_error_matching(valid_value, "invalid_#{@type}".to_sym) + end + + def test_option(option) + settings = OPTION_TEST_SETTINGS[option] + boundary = parse_and_cast(@options[option]) + method = settings[:method] + valid_value, invalid_value = if settings[:modify_on] == :valid - [ boundary.send(modifier, 1), boundary ] + [ boundary.send(method, 1), boundary ] else - [ boundary, boundary.send(modifier, 1) ] + [ boundary, boundary.send(method, 1) ] end - message = messages[option] - error_matching(invalid_value, /#{message}/) && - no_error_matching(valid_value, /#{message}/) + error_matching(invalid_value, option) && + no_error_matching(valid_value, option) + end + + def test_before + before = parse_and_cast(@options[:before]) + + error_matching(before - 1, :before) && + no_error_matching(before, :before) + end + + def test_between + between = parse_and_cast(@options[:between]) + + error_matching(between.first - 1, :between) && + error_matching(between.last + 1, :between) && + no_error_matching(between.first, :between) && + no_error_matching(between.last, :between) end def parse_and_cast(value) - value = validator.send(:restriction_value, value, record) - validator.send(:type_cast_value, value) + value = @validator.send(:restriction_value, value, @record) + @validator.send(:type_cast_value, value) end - def error_matching(value, match) - record.send("#{expected}=", value) - record.valid? - errors = record.errors.on(expected) - pass = [ errors ].flatten.any? {|error| match === error } - @last_failure = "error matching #{match.inspect} when value is #{format_value(value)}" unless pass + def error_matching(value, option) + match = error_message_for(option) + @record.send("#{@expected}=", value) + @record.valid? + errors = @record.errors.on(@expected) + pass = [ errors ].flatten.any? {|error| /#{match}/ === error } + @last_failure = "error matching '#{match}' when value is #{format_value(value)}" unless pass pass end - def no_error_matching(value, match) - pass = !error_matching(value, match) - @last_failure = "no error matching #{match.inspect} when value is #{format_value(value)}" unless pass + def no_error_matching(value, option) + pass = !error_matching(value, option) + unless pass + error = error_message_for(option) + @last_failure = "no error matching '#{error}' when value is #{format_value(value)}" + end pass end + + def error_message_for(option) + msg = @validator.send(:error_messages)[option] + restriction = @validator.send(:restriction_value, @validator.configuration[option], @record) + + if restriction + restriction = [restriction] unless restriction.is_a?(Array) + restriction.map! {|r| @validator.send(:type_cast_value, r) } + interpolate = @validator.send(:interpolation_values, option, restriction ) + if defined?(I18n) + msg = @record.errors.generate_message(@expected, option, interpolate) + else + msg = msg % interpolate + end + end + msg + end def format_value(value) return value if value.is_a?(String) - value.strftime(ValidatesTimeliness::Validator.error_value_formats[options[:type]]) + value.strftime(ValidatesTimeliness::Validator.error_value_formats[@type]) end end def validate_date(attribute, options={}) options[:type] = :date - validate_timeliness_of(attribute, options) + ValidateTimeliness.new(attribute, options) end def validate_time(attribute, options={}) options[:type] = :time - validate_timeliness_of(attribute, options) + ValidateTimeliness.new(attribute, options) end def validate_datetime(attribute, options={}) options[:type] = :datetime - validate_timeliness_of(attribute, options) - end - - private - - def validate_timeliness_of(attribute, options={}) ValidateTimeliness.new(attribute, options) end - end end end diff --git a/spec/spec/rails/matchers/validate_timeliness_spec.rb b/spec/spec/rails/matchers/validate_timeliness_spec.rb index d5ba5f4..7cdf7b1 100644 --- a/spec/spec/rails/matchers/validate_timeliness_spec.rb +++ b/spec/spec/rails/matchers/validate_timeliness_spec.rb @@ -6,13 +6,17 @@ end class WithValidation < Person validates_date :birth_date, :before => '2000-01-10', :after => '2000-01-01', - :on_or_before => '2000-01-09', :on_or_after => '2000-01-02' + :on_or_before => '2000-01-09', :on_or_after => '2000-01-02', + :between => ['2000-01-01', '2000-01-03'] + validates_time :birth_time, :before => '23:00', :after => '09:00', - :on_or_before => '22:00', :on_or_after => '10:00' + :on_or_before => '22:00', :on_or_after => '10:00', + :between => ['09:00', '17:00'] validates_datetime :birth_date_and_time, :before => '2000-01-10 23:00', :after => '2000-01-01 09:00', - :on_or_before => '2000-01-09 23:00', :on_or_after => '2000-01-02 09:00' + :on_or_before => '2000-01-09 23:00', :on_or_after => '2000-01-02 09:00', + :between => ['2000-01-01 09:00', '2000-01-01 17:00'] end @@ -137,6 +141,29 @@ describe "ValidateTimeliness matcher" do end end + describe "between option" do + test_values = { + :date => [ ['2000-01-01', '2000-01-03'], ['2000-01-01', '2000-01-04'] ], + :time => [ ['09:00', '17:00'], ['09:00', '17:01'] ], + :datetime => [ ['2000-01-01 09:00', '2000-01-01 17:00'], ['2000-01-01 09:00', '2000-01-01 17:01'] ] + } + + [:date, :time, :datetime].each do |type| + + it "should report that #{type} is validated" do + with_validation.should self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][0]) + end + + it "should report that #{type} is not validated when option value is incorrect" do + with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][1]) + end + + it "should report that #{type} is not validated with option" do + no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][0]) + end + end + end + describe "custom messages" do before do From a14bc306b373409c84771380213a478c17713cdb Mon Sep 17 00:00:00 2001 From: Adam Meehan Date: Thu, 1 Jan 2009 20:28:02 +1100 Subject: [PATCH 4/4] added between option details to README --- README.rdoc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.rdoc b/README.rdoc index 0f56654..7835778 100644 --- a/README.rdoc +++ b/README.rdoc @@ -64,6 +64,7 @@ the valid range of dates or times allowed :on_or_before - Attribute must be equal to or before this value to be valid :after - Attribute must be after this value to be valid :on_or_after - Attribute must be equal to or after this value to be valid + :between - Attribute must be between the values to be valid Regular validation options: :allow_nil - Allow a nil value to be valid @@ -79,6 +80,7 @@ the valid range of dates or times allowed :on_or_before_message :after_message :on_or_after_message + :between_message The temporal restrictions can take 4 different value types: @@ -86,6 +88,7 @@ The temporal restrictions can take 4 different value types: * Date, Time, or DateTime object value * Proc or lambda object * A symbol matching the method name in the model + * Between option takes an array of two values or a range When an attribute value is compared to temporal restrictions, they are compared as the same type as the validation method type. So using validates_date means all @@ -266,7 +269,8 @@ For Rails 2.0/2.1: :before => "must be before %s", :on_or_before => "must be on or before %s", :after => "must be after %s", - :on_or_after => "must be on or after %s" + :on_or_after => "must be on or after %s", + :between => "must be between %s and %s" ) Where %s is the interpolation value for the restriction. @@ -279,6 +283,7 @@ Rails 2.2+ using the I18n system to define new defaults: messages: on_or_before: "must be equal to or before {{restriction}}" on_or_after: "must be equal to or after {{restriction}}" + between: "must be between {{earliest}} and {{latest}}" The {{restriction}} signifies where the interpolation value for the restriction will be inserted.