diff --git a/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb b/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb index 2616d77..f215554 100644 --- a/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb +++ b/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb @@ -92,8 +92,8 @@ module Spec end def parse_and_cast(value) - value = @validator.send(:restriction_value, value, @record) - @validator.send(:type_cast_value, value) + value = @validator.class.send(:evaluate_option_value, value, @type, @record) + @validator.class.send(:type_cast_value, value, @type) end def error_matching(value, option) @@ -117,11 +117,11 @@ module Spec def error_message_for(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 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 ) # get I18n message if defined and has interpolation keys in msg if defined?(I18n) && !@validator.send(:custom_error_messages).include?(option) diff --git a/lib/validates_timeliness/validator.rb b/lib/validates_timeliness/validator.rb index be04891..880f4f1 100644 --- a/lib/validates_timeliness/validator.rb +++ b/lib/validates_timeliness/validator.rb @@ -20,7 +20,8 @@ module ValidatesTimeliness } VALID_OPTIONS = [ - :on, :allow_nil, :empty, :allow_blank, :blank, :invalid_time_message, :invalid_date_message, :invalid_datetime_message + :on, :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 attr_reader :configuration, :type @@ -52,14 +53,21 @@ module ValidatesTimeliness end 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| next unless restriction = configuration[option] begin - restriction = restriction_value(restriction, record) + restriction = self.class.evaluate_option_value(restriction, restriction_type, record) next if restriction.nil? - restriction = type_cast_value(restriction) + restriction = self.class.type_cast_value(restriction, restriction_type) unless evaluate_restriction(restriction, value, method) add_error(record, attr_name, option, interpolation_values(option, restriction)) @@ -123,49 +131,67 @@ module ValidatesTimeliness } end - def restriction_value(restriction, record) - case restriction - when Time, Date, DateTime - restriction - 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) + def combine_date_and_time(value, record) + if type == :date + date = value + time = @configuration[:with_time] else - record.class.parse_date_time(restriction, type, false) - end - end - - def type_cast_value(value) - if value.is_a?(Array) - value.map {|v| type_cast_value(v) } - else - case type - when :time - value.to_dummy_time - when :date - value.to_date - when :datetime - if value.is_a?(DateTime) || value.is_a?(Time) - value.to_time - else - value.to_time(ValidatesTimeliness.default_timezone) - end - else - nil - end + 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_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) + 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 + + def type_cast_value(value, type) + if value.is_a?(Array) + value.map {|v| type_cast_value(v, type) } + else + case type + when :time + value.to_dummy_time + when :date + value.to_date + when :datetime + if value.is_a?(DateTime) || value.is_a?(Time) + value.to_time + else + value.to_time(ValidatesTimeliness.default_timezone) + end + else + nil + end + end + end + end end diff --git a/spec/validator_spec.rb b/spec/validator_spec.rb index 623fc17..c2618d0 100644 --- a/spec/validator_spec.rb +++ b/spec/validator_spec.rb @@ -18,9 +18,8 @@ describe ValidatesTimeliness::Validator do describe "option keys validation" do before do - @valid_options = ValidatesTimeliness::Validator::VALID_OPTIONS.inject({}) {|hash, opt| hash[opt] = nil; hash } - @valid_options.delete(:invalid_date_message) - @valid_options.delete(:invalid_time_message) + keys = ValidatesTimeliness::Validator::VALID_OPTIONS - [:invalid_date_message, :invalid_time_message, :with_date, :with_time] + @valid_options = keys.inject({}) {|hash, opt| hash[opt] = nil; hash } end it "should raise error if invalid option key passed" do @@ -33,55 +32,55 @@ describe ValidatesTimeliness::Validator do end end - describe "restriction_value" do + describe "evaluate_option_value" 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 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 it "should return Time object when restriction is method and method returns Time object" do 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 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") - restriction_value(:datetime_attr, :datetime).should be_kind_of(Time) + evaluate_option_value(:datetime_attr, :datetime).should be_kind_of(Time) end 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 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 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] + evaluate_option_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)] + evaluate_option_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] + evaluate_option_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)] + evaluate_option_value(time1..time2, :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)] end - def restriction_value(restriction, type) + def evaluate_option_value(restriction, type) configure_validator(:type => type) - validator.send(:restriction_value, restriction, person) + validator.class.send(:evaluate_option_value, restriction, type, person) end end @@ -347,6 +346,37 @@ describe ValidatesTimeliness::Validator do 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 it "should validate datetime attribute with Date restriction" do