Merge branch 'with' for with_date and with_time options

This commit is contained in:
Adam Meehan 2009-03-10 15:38:04 +11:00
commit 903850bc23
5 changed files with 196 additions and 137 deletions

View File

@ -55,12 +55,9 @@ validation method
end end
The list of validation methods available are as follows: The list of validation methods available are as follows:
validates_date - validate value as date
* validates_date - validate value as date validates_time - validate value as time only i.e. '12:20pm'
validates_datetime - validate value as a full date and time
* validates_time - validate value as time only i.e. '12:20pm'
* validates_datetime - validate value as a full date and time
The validation methods take the usual options plus some specific ones to restrict The validation methods take the usual options plus some specific ones to restrict
the valid range of dates or times allowed the valid range of dates or times allowed
@ -70,7 +67,7 @@ the valid range of dates or times allowed
:on_or_before - Attribute must be equal to or before this value to be valid :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 :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 :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 :between - Attribute must be between the values to be valid. Takes an array of two values or a range
Regular validation options: Regular validation options:
:allow_nil - Allow a nil value to be valid :allow_nil - Allow a nil value to be valid
@ -78,6 +75,10 @@ the valid range of dates or times allowed
:if - Execute validation when :if evaluates true :if - Execute validation when :if evaluates true
:unless - Execute validation when :unless evaluates false :unless - Execute validation when :unless evaluates false
Special options:
:with_time - Validate a date attribute value combined with a time value against any temporal restrictions
:with_date - Validate a time attribute value combined with a date value against any temporal restrictions
Message options: - Use these to override the default error messages Message options: - Use these to override the default error messages
:invalid_date_message :invalid_date_message
:invalid_time_message :invalid_time_message
@ -88,17 +89,17 @@ the valid range of dates or times allowed
:on_or_after_message :on_or_after_message
:between_message :between_message
The temporal restrictions can take 4 different value types: The temporal restrictions, with_date and with_time can take 4 different value types:
* String value * String value
* Date, Time, or DateTime object value * Date, Time, or DateTime object value
* Proc or lambda object * Proc or lambda object which may take an optional parameter being the record object
* A symbol matching the method name in the model * 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 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 the same type as the validation method type. So using validates_date means all
values are compared as dates. values are compared as dates. This is except in the case of with_time and with_date
options which effectively force the value to validated as a datetime against the
temporal options.
== EXAMPLES: == EXAMPLES:
@ -112,6 +113,10 @@ values are compared as dates.
validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now } validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now }
validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now }
validates_date :entry_date, :with_time => '17:00', :on_or_before => :competition_closing
=== DATE/TIME FORMATS: === DATE/TIME FORMATS:
@ -137,8 +142,7 @@ plugin is needed.
h-nn_ampm h-nn_ampm
h_ampm h_ampm
NOTE: Any time format without a meridian token (the 'ampm' token) is considered NOTE: Any time format without a meridian token (the 'ampm' token) is considered in 24 hour time.
in 24 hour time.
Date formats: Date formats:
yyyy/mm/dd yyyy/mm/dd

1
TODO
View File

@ -1,4 +1,3 @@
- :format option - :format option
- :with_date and :with_time options
- valid formats could come from locale file - valid formats could come from locale file
- add replace_formats instead add_formats :before - add replace_formats instead add_formats :before

View File

@ -92,8 +92,8 @@ module Spec
end end
def parse_and_cast(value) def parse_and_cast(value)
value = @validator.send(:restriction_value, value, @record) value = @validator.class.send(:evaluate_option_value, value, @type, @record)
@validator.send(:type_cast_value, value) @validator.class.send(:type_cast_value, value, @type)
end end
def error_matching(value, option) def error_matching(value, option)
@ -117,11 +117,11 @@ module Spec
def error_message_for(option) def error_message_for(option)
msg = @validator.send(:error_messages)[option] msg = @validator.send(:error_messages)[option]
restriction = @validator.send(:restriction_value, @validator.configuration[option], @record) restriction = @validator.class.send(:evaluate_option_value, @validator.configuration[option], @type, @record)
if restriction if restriction
restriction = [restriction] unless restriction.is_a?(Array) restriction = [restriction] unless restriction.is_a?(Array)
restriction.map! {|r| @validator.send(:type_cast_value, r) } restriction.map! {|r| @validator.class.send(:type_cast_value, r, @type) }
interpolate = @validator.send(:interpolation_values, option, restriction ) interpolate = @validator.send(:interpolation_values, option, restriction )
# get I18n message if defined and has interpolation keys in msg # get I18n message if defined and has interpolation keys in msg
if defined?(I18n) && !@validator.send(:custom_error_messages).include?(option) if defined?(I18n) && !@validator.send(:custom_error_messages).include?(option)

View File

@ -20,7 +20,8 @@ module ValidatesTimeliness
} }
VALID_OPTIONS = [ VALID_OPTIONS = [
:on, :if, :unless, :allow_nil, :empty, :allow_blank, :blank, :invalid_time_message, :invalid_date_message, :invalid_datetime_message :on, :if, :unless, :allow_nil, :empty, :allow_blank, :blank, :with_time, :with_date,
:invalid_time_message, :invalid_date_message, :invalid_datetime_message
] + RESTRICTION_METHODS.keys.map {|option| [option, "#{option}_message".to_sym] }.flatten ] + RESTRICTION_METHODS.keys.map {|option| [option, "#{option}_message".to_sym] }.flatten
attr_reader :configuration, :type attr_reader :configuration, :type
@ -52,14 +53,21 @@ module ValidatesTimeliness
end end
def validate_restrictions(record, attr_name, value) def validate_restrictions(record, attr_name, value)
value = type_cast_value(value) value = if @configuration[:with_time] || @configuration[:with_date]
restriction_type = :datetime
combine_date_and_time(value, record)
else
restriction_type = type
self.class.type_cast_value(value, type)
end
return if value.nil?
RESTRICTION_METHODS.each do |option, method| RESTRICTION_METHODS.each do |option, method|
next unless restriction = configuration[option] next unless restriction = configuration[option]
begin begin
restriction = restriction_value(restriction, record) restriction = self.class.evaluate_option_value(restriction, restriction_type, record)
next if restriction.nil? next if restriction.nil?
restriction = type_cast_value(restriction) restriction = self.class.type_cast_value(restriction, restriction_type)
unless evaluate_restriction(restriction, value, method) unless evaluate_restriction(restriction, value, method)
add_error(record, attr_name, option, interpolation_values(option, restriction)) add_error(record, attr_name, option, interpolation_values(option, restriction))
@ -123,26 +131,49 @@ module ValidatesTimeliness
} }
end end
def restriction_value(restriction, record) def combine_date_and_time(value, record)
case restriction if type == :date
when Time, Date, DateTime date = value
restriction time = @configuration[:with_time]
when Symbol
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 else
record.class.parse_date_time(restriction, type, false) date = @configuration[:with_date]
time = value
end
date, time = self.class.evaluate_option_value(date, :date, record), self.class.evaluate_option_value(time, :time, record)
return if date.nil? || time.nil?
record.class.send(:make_time, [date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
end
def validate_options(options)
invalid_for_type = ([:time, :date, :datetime] - [@type]).map {|k| "invalid_#{k}_message".to_sym }
invalid_for_type << :with_date unless @type == :time
invalid_for_type << :with_time unless @type == :date
options.assert_valid_keys(VALID_OPTIONS - invalid_for_type)
end
# class methods
class << self
def evaluate_option_value(value, type, record)
case value
when Time, Date, DateTime
value
when Symbol
evaluate_option_value(record.send(value), type, record)
when Proc
evaluate_option_value(value.call(record), type, record)
when Array
value.map {|r| evaluate_option_value(r, type, record) }.sort
when Range
evaluate_option_value([value.first, value.last], type, record)
else
record.class.parse_date_time(value, type, false)
end end
end end
def type_cast_value(value) def type_cast_value(value, type)
if value.is_a?(Array) if value.is_a?(Array)
value.map {|v| type_cast_value(v) } value.map {|v| type_cast_value(v, type) }
else else
case type case type
when :time when :time
@ -161,11 +192,6 @@ module ValidatesTimeliness
end end
end end
def validate_options(options)
invalid_types = [:time, :date, :datetime]
invalid_types.delete(@type)
valid_options = VALID_OPTIONS.reject {|option| invalid_types.include?("#{option}_message".to_sym) }
options.assert_valid_keys(valid_options)
end end
end end

View File

@ -18,9 +18,8 @@ describe ValidatesTimeliness::Validator do
describe "option keys validation" do describe "option keys validation" do
before do before do
@valid_options = ValidatesTimeliness::Validator::VALID_OPTIONS.inject({}) {|hash, opt| hash[opt] = nil; hash } keys = ValidatesTimeliness::Validator::VALID_OPTIONS - [:invalid_date_message, :invalid_time_message, :with_date, :with_time]
@valid_options.delete(:invalid_date_message) @valid_options = keys.inject({}) {|hash, opt| hash[opt] = nil; hash }
@valid_options.delete(:invalid_time_message)
end end
it "should raise error if invalid option key passed" do it "should raise error if invalid option key passed" do
@ -33,55 +32,55 @@ describe ValidatesTimeliness::Validator do
end end
end end
describe "restriction_value" do describe "evaluate_option_value" do
it "should return Time object when restriction is Time object" do it "should return Time object when restriction is Time object" do
restriction_value(Time.now, :datetime).should be_kind_of(Time) evaluate_option_value(Time.now, :datetime).should be_kind_of(Time)
end end
it "should return Time object when restriction is string" do it "should return Time object when restriction is string" do
restriction_value("2007-01-01 12:00", :datetime).should be_kind_of(Time) evaluate_option_value("2007-01-01 12:00", :datetime).should be_kind_of(Time)
end end
it "should return Time object when restriction is method and method returns Time object" do it "should return Time object when restriction is method and method returns Time object" do
person.stub!(:datetime_attr).and_return(Time.now) person.stub!(:datetime_attr).and_return(Time.now)
restriction_value(:datetime_attr, :datetime).should be_kind_of(Time) evaluate_option_value(:datetime_attr, :datetime).should be_kind_of(Time)
end end
it "should return Time object when restriction is method and method returns string" do it "should return Time object when restriction is method and method returns string" do
person.stub!(:datetime_attr).and_return("2007-01-01 12:00") person.stub!(:datetime_attr).and_return("2007-01-01 12:00")
restriction_value(:datetime_attr, :datetime).should be_kind_of(Time) evaluate_option_value(:datetime_attr, :datetime).should be_kind_of(Time)
end end
it "should return Time object when restriction is proc which returns Time object" do it "should return Time object when restriction is proc which returns Time object" do
restriction_value(lambda { Time.now }, :datetime).should be_kind_of(Time) evaluate_option_value(lambda { Time.now }, :datetime).should be_kind_of(Time)
end end
it "should return Time object when restriction is proc which returns string" do it "should return Time object when restriction is proc which returns string" do
restriction_value(lambda {"2007-01-01 12:00"}, :datetime).should be_kind_of(Time) evaluate_option_value(lambda {"2007-01-01 12:00"}, :datetime).should be_kind_of(Time)
end end
it "should return array of Time objects when restriction is array of Time objects" do it "should return array of Time objects when restriction is array of Time objects" do
time1, time2 = Time.now, 1.day.ago time1, time2 = Time.now, 1.day.ago
restriction_value([time1, time2], :datetime).should == [time2, time1] evaluate_option_value([time1, time2], :datetime).should == [time2, time1]
end end
it "should return array of Time objects when restriction is array of strings" do it "should return array of Time objects when restriction is array of strings" do
time1, time2 = "2000-01-02", "2000-01-01" 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)] evaluate_option_value([time1, time2], :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
end end
it "should return array of Time objects when restriction is Range of Time objects" do it "should return array of Time objects when restriction is Range of Time objects" do
time1, time2 = Time.now, 1.day.ago time1, time2 = Time.now, 1.day.ago
restriction_value(time1..time2, :datetime).should == [time2, time1] evaluate_option_value(time1..time2, :datetime).should == [time2, time1]
end end
it "should return array of Time objects when restriction is Range of time strings" do it "should return array of Time objects when restriction is Range of time strings" do
time1, time2 = "2000-01-02", "2000-01-01" 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)] evaluate_option_value(time1..time2, :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
end end
def restriction_value(restriction, type) def evaluate_option_value(restriction, type)
configure_validator(:type => type) configure_validator(:type => type)
validator.send(:restriction_value, restriction, person) validator.class.send(:evaluate_option_value, restriction, type, person)
end end
end end
@ -347,6 +346,37 @@ describe ValidatesTimeliness::Validator do
end end
end end
describe "instance with :with_time option" do
it "should validate date attribute as datetime combining value of :with_time against restrictions " do
configure_validator(:type => :date, :with_time => '12:31', :on_or_before => Time.mktime(2000,1,1,12,30))
validate_with(:birth_date, "2000-01-01")
should_have_error(:birth_date, :on_or_before)
end
it "should skip restriction validation if :with_time value is nil" do
configure_validator(:type => :date, :with_time => nil, :on_or_before => Time.mktime(2000,1,1,12,30))
validate_with(:birth_date, "2000-01-01")
should_have_no_error(:birth_date, :on_or_before)
end
end
describe "instance with :with_date option" do
it "should validate time attribute as datetime combining value of :with_date against restrictions " do
configure_validator(:type => :time, :with_date => '2009-01-01', :on_or_before => Time.mktime(2000,1,1,12,30))
validate_with(:birth_date, "12:30")
should_have_error(:birth_date, :on_or_before)
end
it "should skip restriction validation if :with_date value is nil" do
configure_validator(:type => :time, :with_date => nil, :on_or_before => Time.mktime(2000,1,1,12,30))
validate_with(:birth_date, "12:30")
should_have_no_error(:birth_date, :on_or_before)
end
end
describe "instance with mixed value and restriction types" do describe "instance with mixed value and restriction types" do
it "should validate datetime attribute with Date restriction" do it "should validate datetime attribute with Date restriction" do