diff --git a/lib/validates_timeliness.rb b/lib/validates_timeliness.rb index 2bc3aa5..e79704a 100644 --- a/lib/validates_timeliness.rb +++ b/lib/validates_timeliness.rb @@ -21,3 +21,31 @@ Date.send(:include, ValidatesTimeliness::CoreExtensions::Date) DateTime.send(:include, ValidatesTimeliness::CoreExtensions::DateTime) ValidatesTimeliness::Formats.compile_format_expressions + +module ValidatesTimeliness + + mattr_accessor :ignore_datetime_restriction_errors + mattr_accessor :date_time_error_value_formats + mattr_accessor :default_error_messages + + @@ignore_datetime_restriction_errors = false + + @@date_time_error_value_formats = { + :time => '%H:%M:%S', + :date => '%Y-%m-%d', + :datetime => '%Y-%m-%d %H:%M:%S' + } + + @@default_error_messages = { + :empty => "cannot be empty", + :blank => "cannot be blank", + :invalid_date => "is not a valid date", + :invalid_time => "is not a valid time", + :invalid_datetime => "is not a valid datetime", + :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" + } + +end diff --git a/lib/validates_timeliness/validate_timeliness_matcher.rb b/lib/validates_timeliness/validate_timeliness_matcher.rb index 8c8ed8f..c7631f1 100644 --- a/lib/validates_timeliness/validate_timeliness_matcher.rb +++ b/lib/validates_timeliness/validate_timeliness_matcher.rb @@ -2,26 +2,33 @@ module Spec module Rails module Matchers class ValidateTimeliness + cattr_accessor :test_values + @@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'} + } + def initialize(attribute, options) @expected, @options = attribute, options - @options.reverse_merge!(error_messages) + compile_error_messages end + def compile_error_messages + validator = ValidatesTimeliness::Validator.new(options) + messages = validator.send(:error_messages) + @messages = messages.inject({}) {|h, (k, v)| h[k] = v.sub(' %s', ''); h } + end + def matches?(record) @record = record type = options[:type] - 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'} - } - - invalid_value = test_values[type][:fail] - valid_value = parse_and_cast(test_values[type][:pass]) - valid = error_matching(invalid_value, /#{options["invalid_#{type}_message".to_sym]}/) && - no_error_matching(valid_value, /#{options["invalid_#{type}_message".to_sym]}/) + 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_option(:before, :-) if options[:before] && valid valid = test_option(:after, :+) if options[:after] && valid @@ -45,7 +52,7 @@ module Spec end private - attr_reader :actual, :expected, :record, :options, :last_failure + attr_reader :actual, :expected, :record, :options, :messages, :last_failure def test_option(option, modifier, settings={}) settings.reverse_merge!(:modify_on => :valid) @@ -57,29 +64,23 @@ module Spec [ boundary, boundary.send(modifier, 1) ] end - message = options["#{option}_message".to_sym] + message = messages[option] error_matching(invalid_value, /#{message}/) && no_error_matching(valid_value, /#{message}/) end - def parse_and_cast(value) + def parse_and_cast(value) value = ValidatesTimeliness::Validator.send(:restriction_value, value, record, options[:type]) cast_method = ValidatesTimeliness::Validator.send(:restriction_type_cast_method, options[:type]) value.send(cast_method) rescue nil end - def error_messages - messages = ValidatesTimeliness::Validator.send(:mapped_default_error_messages) - messages = messages.inject({}) {|h, (k, v)| h[k] = v.sub(' %s', ''); h } - @options.reverse_merge!(messages) - 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 + @last_failure = "error matching #{match.inspect} when value is #{format_value(value)} #{errors.inspect}" unless pass pass end @@ -91,7 +92,7 @@ module Spec def format_value(value) return value if value.is_a?(String) - value.strftime(ValidatesTimeliness::Validator.date_time_error_value_formats[options[:type]]) + value.strftime(ValidatesTimeliness.date_time_error_value_formats[options[:type]]) end end @@ -112,9 +113,10 @@ module Spec private - def validate_timeliness_of(attribute, options={}) - ValidateTimeliness.new(attribute, options) - end + def validate_timeliness_of(attribute, options={}) + ValidateTimeliness.new(attribute, options) + end + end end end diff --git a/lib/validates_timeliness/validation_methods.rb b/lib/validates_timeliness/validation_methods.rb index 7410968..c8716da 100644 --- a/lib/validates_timeliness/validation_methods.rb +++ b/lib/validates_timeliness/validation_methods.rb @@ -68,8 +68,6 @@ module ValidatesTimeliness validates_each(attr_names, configuration) do |record, attr_name, value| raw_value = record.send("#{attr_name}_before_type_cast") validator.call(record, attr_name, raw_value) - errors = validator.errors - add_errors(record, attr_name, errors) unless errors.empty? end end @@ -88,9 +86,6 @@ module ValidatesTimeliness end end - def add_errors(record, attr_name, errors) - errors.each {|e| record.errors.add(attr_name, e) } - end end end diff --git a/lib/validates_timeliness/validator.rb b/lib/validates_timeliness/validator.rb index f9fadac..2fb3b5e 100644 --- a/lib/validates_timeliness/validator.rb +++ b/lib/validates_timeliness/validator.rb @@ -4,57 +4,26 @@ module ValidatesTimeliness # The validity of values can be restricted to be before or after certain dates # or times. class Validator - attr_accessor :configuration, :errors - - cattr_accessor :ignore_datetime_restriction_errors - cattr_accessor :date_time_error_value_formats - cattr_accessor :default_error_messages - - @@ignore_datetime_restriction_errors = false - - @@date_time_error_value_formats = { - :time => '%H:%M:%S', - :date => '%Y-%m-%d', - :datetime => '%Y-%m-%d %H:%M:%S' - } - - @@default_error_messages = { - :empty => "cannot be empty", - :blank => "cannot be blank", - :invalid_date => "is not a valid date", - :invalid_time => "is not a valid time", - :invalid_datetime => "is not a valid datetime", - :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" - } + attr_reader :configuration, :type, :messages def initialize(configuration) defaults = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false } - defaults.update(self.class.mapped_default_error_messages) @configuration = defaults.merge(configuration) - @errors = [] + @type = @configuration.delete(:type) end # The main validation method which can be used directly or called through # the other specific type validation methods. def call(record, attr_name, value) - @errors = [] return if (value.nil? && configuration[:allow_nil]) || (value.blank? && configuration[:allow_blank]) - @errors << configuration[:blank_message] and return if value.blank? + add_error(record, attr_name, :blank) and return if value.blank? - begin - unless time = record.class.parse_date_time(value, configuration[:type]) - @errors << configuration["invalid_#{configuration[:type]}_message".to_sym] - return - end - - validate_restrictions(record, attr_name, time) - rescue Exception => e - @errors << configuration["invalid_#{configuration[:type]}_message".to_sym] + time = record.class.parse_date_time(value, type) + unless time + add_error(record, attr_name, "invalid_#{type}".to_sym) and return end + validate_restrictions(record, attr_name, time) end private @@ -65,27 +34,42 @@ module ValidatesTimeliness def validate_restrictions(record, attr_name, value) restriction_methods = {:before => '<', :after => '>', :on_or_before => '<=', :on_or_after => '>='} - type_cast_method = self.class.restriction_type_cast_method(configuration[:type]) + type_cast_method = self.class.restriction_type_cast_method(type) - display = @@date_time_error_value_formats[configuration[:type]] + display = ValidatesTimeliness.date_time_error_value_formats[type] value = value.send(type_cast_method) restriction_methods.each do |option, method| next unless restriction = configuration[option] begin - compare = self.class.restriction_value(restriction, record, configuration[:type]) - + compare = self.class.restriction_value(restriction, record, type) next if compare.nil? - compare = compare.send(type_cast_method) - @errors << (configuration["#{option}_message".to_sym] % compare.strftime(display)) unless value.send(method, compare) + + unless value.send(method, compare) + add_error(record, attr_name, error_messages[option] % compare.strftime(display)) + end rescue - @errors << "restriction '#{option}' value was invalid" unless self.ignore_datetime_restriction_errors + unless ValidatesTimeliness.ignore_datetime_restriction_errors + add_error(record, attr_name, "restriction '#{option}' value was invalid") + end end end end + def add_error(record, attr_name, message) + message = error_messages[message] if message.is_a?(Symbol) + record.errors.add(attr_name, message) + end + + def error_messages + return @error_messages if defined?(@error_messages) + custom = {} + configuration.each {|k, v| custom[$1.to_sym] = v if k.to_s =~ /(.*)_message$/ } + @error_messages = ValidatesTimeliness.default_error_messages.merge(custom) + end + def self.restriction_value(restriction, record, type) case restriction when Time, Date, DateTime @@ -106,12 +90,6 @@ module ValidatesTimeliness when :datetime then :to_time end end - # Map error message keys to *_message to merge with validation options - def self.mapped_default_error_messages - returning({}) do |messages| - @@default_error_messages.each {|k, v| messages["#{k}_message".to_sym] = v } - end - end - + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8767403..f8885ce 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -27,8 +27,6 @@ require 'active_record/version' require 'action_controller' require 'action_view' -ActiveSupport::Deprecation.silenced = true - require 'spec/rails' require 'time_travel/time_travel' require 'validates_timeliness' diff --git a/spec/validate_timeliness_matcher_spec.rb b/spec/validate_timeliness_matcher_spec.rb index 0f7dac5..604941e 100644 --- a/spec/validate_timeliness_matcher_spec.rb +++ b/spec/validate_timeliness_matcher_spec.rb @@ -1,26 +1,33 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper') +class NoValidation < Person +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' + validates_time :birth_time, + :before => '23:00', :after => '09:00', + :on_or_before => '22:00', :on_or_after => '10: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' + +end + +class CustomMessages < Person + validates_date :birth_date, :invalid_date_message => 'is not really a date', + :before => '2000-01-10', :before_message => 'is too late', + :after => '2000-01-01', :after_message => 'is too early', + :on_or_before=> '2000-01-09', :on_or_before_message => 'is just too late', + :on_or_after => '2000-01-02', :on_or_after_message => 'is just too early' +end + describe "ValidateTimeliness matcher" do attr_accessor :no_validation, :with_validation before do - class NoValidation < Person - alias_attribute :birth_datetime, :birth_date_and_time - 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' - validates_time :birth_time, - :before => '23:00', :after => '09:00', - :on_or_before => '22:00', :on_or_after => '10: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' - - alias_attribute :birth_datetime, :birth_date_and_time - end @no_validation = NoValidation.new @with_validation = WithValidation.new end @@ -134,14 +141,8 @@ describe "ValidateTimeliness matcher" do end describe "custom messages" do + before do - class CustomMessages < Person - validates_date :birth_date, :invalid_date_message => 'is not really a date', - :before => '2000-01-10', :before_message => 'is too late', - :after => '2000-01-01', :after_message => 'is too early', - :on_or_before=> '2000-01-09', :on_or_before_message => 'is just too late', - :on_or_after => '2000-01-02', :on_or_after_message => 'is just too early' - end @person = CustomMessages.new end diff --git a/spec/validation_methods_spec.rb b/spec/validation_methods_spec.rb index eed20f7..6039ec5 100644 --- a/spec/validation_methods_spec.rb +++ b/spec/validation_methods_spec.rb @@ -48,12 +48,10 @@ describe ValidatesTimeliness::ValidationMethods do describe "with no restrictions" do - before :all do - class BasicValidation < Person - validates_datetime :birth_date_and_time, :allow_blank => true - validates_date :birth_date, :allow_blank => true - validates_time :birth_time, :allow_blank => true - end + class BasicValidation < Person + validates_datetime :birth_date_and_time, :allow_blank => true + validates_date :birth_date, :allow_blank => true + validates_time :birth_time, :allow_blank => true end before :each do @@ -104,7 +102,7 @@ describe ValidatesTimeliness::ValidationMethods do it "should be valid with nil values when allow_blank is true" do person.birth_date_and_time = nil person.birth_date = nil - person.birth_time = nil + person.birth_time = nil person.should be_valid end end @@ -112,11 +110,9 @@ describe ValidatesTimeliness::ValidationMethods do describe "for datetime type" do describe "with before and after restrictions" do - before :all do - class DateTimeBeforeAfter < Person - validates_datetime :birth_date_and_time, - :before => lambda { Time.now }, :after => lambda { 1.day.ago} - end + class DateTimeBeforeAfter < Person + validates_datetime :birth_date_and_time, + :before => lambda { Time.now }, :after => lambda { 1.day.ago} end before :each do @@ -149,12 +145,10 @@ describe ValidatesTimeliness::ValidationMethods do end describe "with on_or_before and on_or_after restrictions" do - before :all do - class DateTimeOnOrBeforeAndAfter < Person - validates_datetime :birth_date_and_time, :type => :datetime, - :on_or_before => lambda { Time.now.at_midnight }, - :on_or_after => lambda { 1.day.ago } - end + class DateTimeOnOrBeforeAndAfter < Person + validates_datetime :birth_date_and_time, :type => :datetime, + :on_or_before => lambda { Time.now.at_midnight }, + :on_or_after => lambda { 1.day.ago } end before do @@ -180,7 +174,7 @@ describe ValidatesTimeliness::ValidationMethods do it "should be valid when value equal to :on_or_after restriction" do person.birth_date_and_time = 1.day.ago - person.should be_valid + person.should be_valid end end @@ -190,11 +184,11 @@ describe ValidatesTimeliness::ValidationMethods do describe "with before and after restrictions" do before :all do - class DateBeforeAfter < Person - validates_date :birth_date, - :before => 1.day.from_now, - :after => 1.day.ago - end + class DateBeforeAfter < Person + validates_date :birth_date, + :before => 1.day.from_now, + :after => 1.day.ago + end end before :each do @@ -216,13 +210,13 @@ describe ValidatesTimeliness::ValidationMethods do describe "with on_or_before and on_or_after restrictions" do before :all do - class DateOnOrBeforeAndAfter < Person - validates_date :birth_date, - :on_or_before => 1.day.from_now, - :on_or_after => 1.day.ago - end + class DateOnOrBeforeAndAfter < Person + validates_date :birth_date, + :on_or_before => 1.day.from_now, + :on_or_after => 1.day.ago end - + end + before :each do @person = DateOnOrBeforeAndAfter.new end @@ -254,12 +248,10 @@ describe ValidatesTimeliness::ValidationMethods do describe "for time type" do describe "with before and after restrictions" do - before :all do - class TimeBeforeAfter < Person - validates_time :birth_time, - :before => "23:00", - :after => "06:00" - end + class TimeBeforeAfter < Person + validates_time :birth_time, + :before => "23:00", + :after => "06:00" end before :each do @@ -302,12 +294,10 @@ describe ValidatesTimeliness::ValidationMethods do end describe "with on_or_before and on_or_after restrictions" do - before :all do - class TimeOnOrBeforeAndAfter < Person - validates_time :birth_time, - :on_or_before => "23:00", - :on_or_after => "06:00" - end + class TimeOnOrBeforeAndAfter < Person + validates_time :birth_time, + :on_or_before => "23:00", + :on_or_after => "06:00" end before :each do @@ -339,16 +329,13 @@ describe ValidatesTimeliness::ValidationMethods do end describe "with mixed value and restriction types" do - before :all do - - class MixedBeforeAndAfter < Person - validates_datetime :birth_date_and_time, - :before => Date.new(2000,1,2), - :after => lambda { "2000-01-01" } - validates_date :birth_date, - :on_or_before => lambda { "2000-01-01" }, - :on_or_after => :birth_date_and_time - end + class MixedBeforeAndAfter < Person + validates_datetime :birth_date_and_time, + :before => Date.new(2000,1,2), + :after => lambda { "2000-01-01" } + validates_date :birth_date, + :on_or_before => lambda { "2000-01-01" }, + :on_or_after => :birth_date_and_time end before :each do @@ -384,11 +371,16 @@ describe ValidatesTimeliness::ValidationMethods do end describe "ignoring restriction errors" do + class BadRestriction < Person + validates_date :birth_date, :before => Proc.new { raise } + end + before :all do - ValidatesTimeliness::Validator.ignore_datetime_restriction_errors = true - class BadRestriction < Person - validates_date :birth_date, :before => Proc.new { raise } - end + ValidatesTimeliness.ignore_datetime_restriction_errors = true + end + + after :all do + ValidatesTimeliness.ignore_datetime_restriction_errors = false end before :each do @@ -402,17 +394,15 @@ describe ValidatesTimeliness::ValidationMethods do end describe "restriction value error message" do - describe "default formats" do - before :all do - class DefaultFormats < Person - validates_datetime :birth_date_and_time, :allow_blank => true, :after => 1.day.from_now - validates_date :birth_date, :allow_blank => true, :after => 1.day.from_now - validates_time :birth_time, :allow_blank => true, :after => '23:59:59' - end - end + class ValueFormats < Person + validates_datetime :birth_date_and_time, :allow_blank => true, :after => 1.day.from_now + validates_date :birth_date, :allow_blank => true, :after => 1.day.from_now + validates_time :birth_time, :allow_blank => true, :after => '23:59:59' + end + describe "default formats" do before :each do - @person = DefaultFormats.new + @person = ValueFormats.new end it "should format datetime value of restriction" do @@ -436,13 +426,7 @@ describe ValidatesTimeliness::ValidationMethods do describe "custom formats" do before :all do - class CustomFormats < Person - validates_datetime :birth_date_and_time, :allow_blank => true, :after => 1.day.from_now - validates_date :birth_date, :allow_blank => true, :after => 1.day.from_now - validates_time :birth_time, :allow_blank => true, :after => '23:59:59' - end - - ValidatesTimeliness::Validator.date_time_error_value_formats = { + ValidatesTimeliness.date_time_error_value_formats = { :time => '%H:%M %p', :date => '%d-%m-%Y', :datetime => '%d-%m-%Y %H:%M %p' @@ -450,7 +434,7 @@ describe ValidatesTimeliness::ValidationMethods do end before :each do - @person = CustomFormats.new + @person = ValueFormats.new end it "should format datetime value of restriction" do