-
Notifications
You must be signed in to change notification settings - Fork 82
/
date_validator.rb
131 lines (110 loc) · 4.92 KB
/
date_validator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
require 'active_model/validations'
require 'active_support/core_ext/date_time/conversions'
require 'active_support/core_ext/hash/conversions'
# ActiveModel Rails module.
module ActiveModel
# ActiveModel::Validations Rails module. Contains all the default validators.
module Validations
# Date Validator. Inherits from ActiveModel::EachValidator.
#
# Responds to the regular validator API methods `#check_validity` and
# `#validate_each`.
class DateValidator < ActiveModel::EachValidator
# Implemented checks and their associated operators.
CHECKS = { after: :>, after_or_equal_to: :>=,
before: :<, before_or_equal_to: :<=,
equal_to: :==
}.freeze
# Call `#initialize` on the superclass, adding a default
# `allow_nil: false` option.
def initialize(options)
super(options.reverse_merge(allow_nil: false))
end
# Validates the arguments passed to the validator.
#
# They must be either any kind of Time, a Proc, or a Symbol.
def check_validity!
keys = CHECKS.keys
options.slice(*keys).each do |option, value|
next if is_time?(value) || value.is_a?(Proc) || value.is_a?(Symbol) || (defined?(ActiveSupport::TimeWithZone) and value.is_a? ActiveSupport::TimeWithZone)
raise ArgumentError, ":#{option} must be a time, a date, a time_with_zone, a symbol or a proc"
end
end
# Overridden because standard allow_nil and allow_blank checks don't work with
# string expressions that cannot be type cast to dates. We have to validate
# the pre-type cast values.
def validate(record)
attributes.each do |attribute|
value = record.read_attribute_for_validation(attribute)
validate_each(record, attribute, value)
end
end
# The actual validator method. It is called when ActiveRecord iterates
# over all the validators.
def validate_each(record, attr_name, value)
before_type_cast = :"#{attr_name}_before_type_cast"
value_before_type_cast = if record.respond_to?(before_type_cast)
record.send(before_type_cast)
else
nil
end
if value_before_type_cast.present? && value.nil?
record.errors.add(attr_name, :not_a_date, **options)
return
end
return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
unless value
record.errors.add(attr_name, :not_a_date, **options)
return
end
unless is_time?(value)
record.errors.add(attr_name, :not_a_date, **options)
return
end
options.slice(*CHECKS.keys).each do |option, option_value|
option_value = option_value.call(record) if option_value.is_a?(Proc)
option_value = record.send(option_value) if option_value.is_a?(Symbol)
original_value = value
original_option_value = option_value
# To enable to_i conversion, these types must be converted to Datetimes
if defined?(ActiveSupport::TimeWithZone)
option_value = option_value.to_datetime if option_value.is_a?(ActiveSupport::TimeWithZone)
value = value.to_datetime if value.is_a?(ActiveSupport::TimeWithZone)
end
if defined?(Date)
option_value = option_value.to_datetime if option_value.is_a?(Date)
value = value.to_datetime if value.is_a?(Date)
end
unless is_time?(option_value) && value.to_i.send(CHECKS[option], option_value.to_i)
record.errors.add(attr_name, :"date_#{option}", **options.merge(
value: original_value,
date: (I18n.localize(original_option_value) rescue original_option_value)
))
end
end
end
private
def is_time?(object)
object.is_a?(Time) || (defined?(Date) and object.is_a?(Date)) || (defined?(ActiveSupport::TimeWithZone) and object.is_a?(ActiveSupport::TimeWithZone))
end
end
module HelperMethods
# Validates whether the value of the specified attribute is a validate Date
#
# class Person < ActiveRecord::Base
# validates_date_of :payment_date, after: :packaging_date
# validates_date_of :expiration_date, before: Proc.new { Time.now }
# end
#
# Configuration options:
# * <tt>:after</tt> - check that a Date is after the specified one.
# * <tt>:before</tt> - check that a Date is before the specified one.
# * <tt>:after_or_equal_to</tt> - check that a Date is after or equal to the specified one.
# * <tt>:before_or_equal_to</tt> - check that a Date is before or equal to the specified one.
# * <tt>:equal_to</tt> - check that a Date is equal to the specified one.
def validates_date_of(*attr_names)
validates_with DateValidator, _merge_attributes(attr_names)
end
end
end
end