forked from smtlaissezfaire/validates_date_time
/
validates_date_time.rb
150 lines (123 loc) · 4.82 KB
/
validates_date_time.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
require File.dirname(__FILE__) + '/parser'
require File.dirname(__FILE__) + '/multiparameter_attributes'
module ValidatesDateTime
def self.included(base)
base.class_eval do
extend ClassMethods
include MultiparameterAttributes
end
end
mattr_accessor :us_date_format
us_date_format = false
DEFAULT_TEMPORAL_VALIDATION_OPTIONS = {
:before_message => "must be before %s",
:after_message => "must be after %s",
:on => :save
}.freeze
class Restriction < Struct.new(:raw_value, :parse_method)
def value(record)
@last_value = case raw_value
when Symbol
record.send(raw_value)
when Proc
raw_value.call(record)
else
raw_value
end
@last_value = parse(@last_value)
end
def parse(string)
ActiveRecord::ConnectionAdapters::Column.send("string_to_#{parse_method}", string)
end
def last_value
@last_value
end
def to_s
if raw_value.is_a?(Symbol)
raw_value.to_s.humanize
else
@last_value.to_s
end
end
end
module ClassMethods
def validates_date(*args)
options = temporal_validation_options({ :message => "is an invalid date" }, args)
attr_names = args
# We must remove this from the configuration that is passed to validates_each because
# we want to have our own definition of nil that uses the before_type_cast value
allow_nil = options.delete(:allow_nil)
prepare_restrictions(options, :date)
validates_each(attr_names, options) do |record, attr_name, value|
raw_value = record.send("#{attr_name}_before_type_cast")
# If value that is unable to be parsed, and a blank value where allow_nil is not set are both invalid
if (!raw_value.blank? and !value) || (raw_value.blank? and !allow_nil)
record.errors.add(attr_name, options[:message])
elsif value
validate_before_and_after_restrictions(record, attr_name, value, options)
end
end
end
def validates_time(*args)
options = temporal_validation_options({ :message => "is an invalid time" }, args)
attr_names = args
allow_nil = options.delete(:allow_nil)
prepare_restrictions(options, :dummy_time)
validates_each(attr_names, options) do |record, attr_name, value|
raw_value = record.send("#{attr_name}_before_type_cast")
if (!raw_value.blank? and !value) || (raw_value.blank? and !allow_nil)
record.errors.add(attr_name, options[:message])
elsif value
validate_before_and_after_restrictions(record, attr_name, value, options)
end
end
end
def validates_date_time(*args)
options = temporal_validation_options({ :message => "is an invalid date time" }, args)
attr_names = args
allow_nil = options.delete(:allow_nil)
prepare_restrictions(options, :time)
validates_each(attr_names, options) do |record, attr_name, value|
raw_value = record.send("#{attr_name}_before_type_cast")
if (!raw_value.blank? and !value) || (raw_value.blank? and !allow_nil)
record.errors.add(attr_name, options[:message])
elsif value
validate_before_and_after_restrictions(record, attr_name, value, options)
end
end
end
private
def validate_before_and_after_restrictions(record, attr_name, value, options)
if options[:before]
options[:before].each do |r|
if r.value(record) and value >= r.last_value
record.errors.add(attr_name, :before, :value => r, :default => options[:before_message] % r)
break
end
end
end
if options[:after]
options[:after].each do |r|
if r.value(record) and value <= r.last_value
record.errors.add(attr_name, :after, :value => r, :default => options[:after_message] % r)
break
end
end
end
end
def prepare_restrictions(options, parse_method)
options[:before] = [*options[:before]].compact.map { |r| Restriction.new(r, parse_method) }
options[:after] = [*options[:after]].compact.map { |r| Restriction.new(r, parse_method) }
end
def temporal_validation_options(options, args)
returning options do
options.reverse_merge!(DEFAULT_TEMPORAL_VALIDATION_OPTIONS)
options.update(args.pop) if args.last.is_a?(Hash)
options.assert_valid_keys :message, :before_message, :after_message, :before, :after, :if, :on, :allow_nil
end
end
end
end
class ActiveRecord::Base
include ValidatesDateTime
end