validates_timeliness/lib/validates_timeliness/validations.rb
Adam Meehan ef68b3d0f9 added type to parse method for dummy time check mainly
added default datetime type to call to parse from strict type cast
2008-07-08 10:42:02 +10:00

150 lines
5.5 KiB
Ruby

module ValidatesTimeliness
class DateTimeInvalid < StandardError; end
module Validations
def self.included(base)
base.extend ClassMethods
error_messages = {
:invalid_date => "is not a valid %s",
: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"
}
ActiveRecord::Errors.default_error_messages.update(error_messages)
end
module ClassMethods
# Override this method to use any date parsing algorithm you like such as
# Chronic. Just return nil for an invalid value and a Time object for a
# valid parsed value.
#
# Remember, if the date portion is pre the Unix epoch the return object
# will need to be a datetime. But luckily Rails, since version 2, will
# automatically handle the fall back to a DateTime when you create a time
# which is out of range.
def timeliness_date_time_parse(raw_value, type)
return raw_value.to_time if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
time_array = ParseDate.parsedate(raw_value, true)
if type == :time
# Rails dummy time date part is 2000-01-01
time_array[0..2] = 2000, 1, 1
else
# Date.new enforces days per month, unlike Time
Date.new(*time_array[0..2])
end
Time.mktime(*time_array)
rescue
nil
end
def validates_timeliness_of(*attr_names)
configuration = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false }
configuration.update(timeliness_default_error_messages)
configuration.update(attr_names.extract_options!)
# we need to check raw value for blank or nil
allow_nil = configuration.delete(:allow_nil)
allow_blank = configuration.delete(:allow_blank)
validates_each(attr_names, configuration) do |record, attr_name, value|
raw_value = record.send("#{attr_name}_before_type_cast")
next if (raw_value.nil? && allow_nil) || (raw_value.blank? && allow_blank)
record.errors.add(attr_name, configuration[:blank_message]) and next if raw_value.blank?
column = record.column_for_attribute(attr_name)
begin
unless time = timeliness_date_time_parse(raw_value, configuration[:type])
record.send("#{attr_name}=", nil)
record.errors.add(attr_name, configuration[:invalid_date_message] % configuration[:type])
next
end
validate_timeliness_restrictions(record, attr_name, time, configuration)
rescue Exception => e
record.send("#{attr_name}=", nil)
record.errors.add(attr_name, configuration[:invalid_date_message] % configuration[:type])
next
end
end
end
# Use this validation to force validation of values as dummy time
def validates_time(*attr_names)
configuration = attr_names.extract_options!
configuration[:type] = :time
validates_timeliness_of(attr_names, configuration)
end
# Use this validation to force validation of values as Date
def validates_date(*attr_names)
configuration = attr_names.extract_options!
configuration[:type] = :date
validates_timeliness_of(attr_names, configuration)
end
# Use this validation to force validation of values as Time/DateTime
def validates_datetime(*attr_names)
configuration = attr_names.extract_options!
configuration[:type] = :datetime
validates_timeliness_of(attr_names, configuration)
end
private
def validate_timeliness_restrictions(record, attr_name, value, configuration)
restriction_methods = {:before => '<', :after => '>', :on_or_before => '<=', :on_or_after => '>='}
conversion_method = case configuration[:type]
when :time then :to_dummy_time
when :date then :to_date
when :datetime then :to_time
end
value = value.send(conversion_method)
restriction_methods.each do |option, method|
next unless restriction = configuration[option]
begin
compare = case restriction
when Time, Date, DateTime
restriction
when Symbol
record.send(restriction)
when Proc
restriction.call(record)
else
timeliness_date_time_parse(restriction, configuration[:type])
end
next if compare.nil?
compare = compare.send(conversion_method)
record.errors.add(attr_name, configuration["#{option}_message".to_sym] % compare) unless value.send(method, compare)
rescue
record.errors.add(attr_name, "restriction '#{option}' value was invalid")
end
end
end
def timeliness_default_error_messages
defaults = ActiveRecord::Errors.default_error_messages.slice(:blank, :invalid_date, :before, :on_or_before, :after, :on_or_after)
returning({}) do |messages|
defaults.each {|k, v| messages["#{k}_message".to_sym] = v }
end
end
end
end
end