mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-23 06:16:44 +00:00
refactored error message handling and specs
This commit is contained in:
parent
aa42fb76b6
commit
d71f581e10
@ -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
|
||||
|
||||
@ -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,7 +64,7 @@ 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
|
||||
@ -68,18 +75,12 @@ module Spec
|
||||
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
|
||||
|
||||
@ -115,6 +116,7 @@ module Spec
|
||||
def validate_timeliness_of(attribute, options={})
|
||||
ValidateTimeliness.new(attribute, options)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
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)
|
||||
rescue Exception => e
|
||||
@errors << configuration["invalid_#{configuration[:type]}_message".to_sym]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
@ -65,26 +34,41 @@ 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
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
||||
|
||||
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
|
||||
@ -19,8 +14,20 @@ describe "ValidateTimeliness matcher" do
|
||||
: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
|
||||
|
||||
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
|
||||
@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
|
||||
|
||||
|
||||
@ -48,13 +48,11 @@ 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
|
||||
end
|
||||
|
||||
before :each do
|
||||
@person = BasicValidation.new
|
||||
@ -112,12 +110,10 @@ 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
|
||||
end
|
||||
|
||||
before :each do
|
||||
@person = DateTimeBeforeAfter.new
|
||||
@ -149,13 +145,11 @@ 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
|
||||
end
|
||||
|
||||
before do
|
||||
@person = DateTimeOnOrBeforeAndAfter.new
|
||||
@ -254,13 +248,11 @@ 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
|
||||
end
|
||||
|
||||
before :each do
|
||||
@person = TimeBeforeAfter.new
|
||||
@ -302,13 +294,11 @@ 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
|
||||
end
|
||||
|
||||
before :each do
|
||||
@person = TimeOnOrBeforeAndAfter.new
|
||||
@ -339,8 +329,6 @@ 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),
|
||||
@ -349,7 +337,6 @@ describe ValidatesTimeliness::ValidationMethods do
|
||||
:on_or_before => lambda { "2000-01-01" },
|
||||
:on_or_after => :birth_date_and_time
|
||||
end
|
||||
end
|
||||
|
||||
before :each do
|
||||
@person = MixedBeforeAndAfter.new
|
||||
@ -384,11 +371,16 @@ describe ValidatesTimeliness::ValidationMethods do
|
||||
end
|
||||
|
||||
describe "ignoring restriction errors" do
|
||||
before :all do
|
||||
ValidatesTimeliness::Validator.ignore_datetime_restriction_errors = true
|
||||
class BadRestriction < Person
|
||||
validates_date :birth_date, :before => Proc.new { raise }
|
||||
end
|
||||
|
||||
before :all do
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user