mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-25 07:16:41 +00:00
Merge branch '2.0.0'
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
require 'validates_timeliness/formats'
|
||||
require 'validates_timeliness/parser'
|
||||
require 'validates_timeliness/validator'
|
||||
require 'validates_timeliness/validation_methods'
|
||||
require 'validates_timeliness/spec/rails/matchers/validate_timeliness' if ENV['RAILS_ENV'] == 'test'
|
||||
@@ -14,9 +15,11 @@ require 'validates_timeliness/core_ext/date_time'
|
||||
module ValidatesTimeliness
|
||||
|
||||
mattr_accessor :default_timezone
|
||||
|
||||
self.default_timezone = :utc
|
||||
|
||||
mattr_accessor :use_time_zones
|
||||
self.use_time_zones = false
|
||||
|
||||
LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/validates_timeliness/locale/en.yml')
|
||||
|
||||
class << self
|
||||
@@ -31,25 +34,18 @@ module ValidatesTimeliness
|
||||
I18n.load_path += [ LOCALE_PATH ]
|
||||
I18n.reload!
|
||||
else
|
||||
messages = YAML::load(IO.read(LOCALE_PATH))
|
||||
errors = messages['en']['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h }
|
||||
defaults = YAML::load(IO.read(LOCALE_PATH))['en']
|
||||
errors = defaults['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h }
|
||||
::ActiveRecord::Errors.default_error_messages.update(errors)
|
||||
|
||||
ValidatesTimeliness::Validator.error_value_formats = defaults['validates_timeliness']['error_value_formats'].symbolize_keys
|
||||
end
|
||||
end
|
||||
|
||||
def default_error_messages
|
||||
if Rails::VERSION::STRING < '2.2'
|
||||
::ActiveRecord::Errors.default_error_messages
|
||||
else
|
||||
I18n.translate('activerecord.errors.messages')
|
||||
end
|
||||
end
|
||||
|
||||
def setup_for_rails
|
||||
major, minor = Rails::VERSION::MAJOR, Rails::VERSION::MINOR
|
||||
self.default_timezone = ::ActiveRecord::Base.default_timezone
|
||||
self.enable_datetime_select_extension!
|
||||
self.load_error_messages
|
||||
self.use_time_zones = ::ActiveRecord::Base.time_zone_aware_attributes rescue false
|
||||
load_error_messages
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,7 +37,7 @@ module ValidatesTimeliness
|
||||
return value_without_timeliness(object)
|
||||
end
|
||||
|
||||
time_array = ParseDate.parsedate(raw_value)
|
||||
time_array = ValidatesTimeliness::Formats.parse(raw_value, :datetime)
|
||||
|
||||
TimelinessDateTime.new(*time_array[0..5])
|
||||
end
|
||||
|
||||
@@ -46,7 +46,7 @@ module ValidatesTimeliness
|
||||
# implementation as it chains the write_attribute method which deletes
|
||||
# the attribute from the cache.
|
||||
def write_date_time_attribute(attr_name, value, type, time_zone_aware)
|
||||
new = self.class.parse_date_time(value, type)
|
||||
new = ValidatesTimeliness::Parser.parse(value, type)
|
||||
|
||||
if new && type != :date
|
||||
new = new.to_time
|
||||
@@ -73,7 +73,7 @@ module ValidatesTimeliness
|
||||
|
||||
if @attributes_cache.has_key?(attr_name)
|
||||
time = read_attribute_before_type_cast(attr_name)
|
||||
time = self.class.parse_date_time(time, type)
|
||||
time = ValidatesTimeliness::Parser.parse(time, type)
|
||||
else
|
||||
time = read_attribute(attr_name)
|
||||
@attributes[attr_name] = (time && time_zone_aware ? time.in_time_zone : time) unless frozen?
|
||||
@@ -83,8 +83,6 @@ module ValidatesTimeliness
|
||||
|
||||
module ClassMethods
|
||||
|
||||
# Define attribute reader and writer method for date, time and
|
||||
# datetime attributes to use plugin parser.
|
||||
def define_attribute_methods_with_timeliness
|
||||
return if generated_methods?
|
||||
columns_hash.each do |name, column|
|
||||
@@ -105,7 +103,6 @@ module ValidatesTimeliness
|
||||
define_attribute_methods_without_timeliness
|
||||
end
|
||||
|
||||
# Define write method for date, time and datetime columns
|
||||
def define_write_method_for_dates_and_times(attr_name, type, time_zone_aware)
|
||||
method_body = <<-EOV
|
||||
def #{attr_name}=(value)
|
||||
|
||||
@@ -38,7 +38,7 @@ module ValidatesTimeliness
|
||||
end
|
||||
|
||||
def time_array_to_string(values, type)
|
||||
values = values.map {|v| v.to_s }
|
||||
values.collect! {|v| v.to_s }
|
||||
|
||||
case type
|
||||
when :date
|
||||
|
||||
@@ -124,13 +124,13 @@ module ValidatesTimeliness
|
||||
{ 's' => [ /s{1}/, '(\d{1,2})', :sec ] },
|
||||
{ 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] },
|
||||
{ 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] },
|
||||
{ 'zo' => [ /zo/, '(?:[+-]\d{2}:?\d{2})'] },
|
||||
{ 'zo' => [ /zo/, '([+-]\d{2}:?\d{2})', :offset ] },
|
||||
{ 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] },
|
||||
{ '_' => [ /_/, '\s?' ] }
|
||||
]
|
||||
|
||||
# Arguments whichs will be passed to the format proc if matched in the
|
||||
# time string. The key must should the key from the format tokens. The array
|
||||
# Arguments which will be passed to the format proc if matched in the
|
||||
# time string. The key must be the key from the format tokens. The array
|
||||
# consists of the arry position of the arg, the arg name, and the code to
|
||||
# place in the time array slot. The position can be nil which means the arg
|
||||
# won't be placed in the array.
|
||||
@@ -146,6 +146,7 @@ module ValidatesTimeliness
|
||||
:min => [4, 'n', 'n'],
|
||||
:sec => [5, 's', 's'],
|
||||
:usec => [6, 'u', 'microseconds(u)'],
|
||||
:offset => [7, 'z', 'offset_in_seconds(z)'],
|
||||
:meridian => [nil, 'md', nil]
|
||||
}
|
||||
|
||||
@@ -161,12 +162,13 @@ module ValidatesTimeliness
|
||||
# pre or post match strings to exist if strict is false. Otherwise wrap
|
||||
# regexp in start and end anchors.
|
||||
# Returns 7 part time array.
|
||||
def parse(string, type, strict=true)
|
||||
def parse(string, type, options={})
|
||||
return string unless string.is_a?(String)
|
||||
options.reverse_merge!(:strict => true)
|
||||
|
||||
matches = nil
|
||||
exp, processor = expression_set(type, string).find do |regexp, proc|
|
||||
full = /\A#{regexp}\Z/ if strict
|
||||
full = /\A#{regexp}\Z/ if options[:strict]
|
||||
full ||= case type
|
||||
when :date then /\A#{regexp}/
|
||||
when :time then /#{regexp}\Z/
|
||||
@@ -174,7 +176,8 @@ module ValidatesTimeliness
|
||||
end
|
||||
matches = full.match(string.strip)
|
||||
end
|
||||
processor.call(*matches[1..7]) if matches
|
||||
last = options[:include_offset] ? 8 : 7
|
||||
processor.call(*matches[1..last]) if matches
|
||||
end
|
||||
|
||||
# Delete formats of specified type. Error raised if format not found.
|
||||
@@ -206,8 +209,7 @@ module ValidatesTimeliness
|
||||
end
|
||||
compile_format_expressions
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Removes formats where the 1 or 2 digit month comes first, to eliminate
|
||||
# formats which are ambiguous with the European style of day then month.
|
||||
# The mmm token is ignored as its not ambigous.
|
||||
@@ -246,17 +248,12 @@ module ValidatesTimeliness
|
||||
# argument in the position indicated by the first element of the proc arg
|
||||
# array.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# 'yyyy-mm-dd hh:nn' => lambda {|y,m,d,h,n| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } }
|
||||
# 'dd/mm/yyyy h:nn_ampm' => lambda {|d,m,y,h,n,md| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } }
|
||||
#
|
||||
def format_proc(order)
|
||||
arg_map = format_proc_args
|
||||
args = order.invert.sort.map {|p| arg_map[p[1]][1] }
|
||||
arr = [nil] * 7
|
||||
order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? }
|
||||
proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.to_i } }"
|
||||
proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.is_a?(Float) ? i : i.to_i } }"
|
||||
eval proc_string
|
||||
end
|
||||
|
||||
@@ -313,6 +310,13 @@ module ValidatesTimeliness
|
||||
def microseconds(usec)
|
||||
(".#{usec}".to_f * 1_000_000).to_i
|
||||
end
|
||||
|
||||
def offset_in_seconds(offset)
|
||||
sign = offset =~ /^-/ ? -1 : 1
|
||||
parts = offset.scan(/\d\d/).map {|p| p.to_f }
|
||||
parts[1] = parts[1].to_f / 60
|
||||
(parts[0] + parts[1]) * sign * 3600
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,3 +11,8 @@ en:
|
||||
after: "must be after {{restriction}}"
|
||||
on_or_after: "must be on or after {{restriction}}"
|
||||
between: "must be between {{earliest}} and {{latest}}"
|
||||
validates_timeliness:
|
||||
error_value_formats:
|
||||
date: '%Y-%m-%d'
|
||||
time: '%H:%M:%S'
|
||||
datetime: '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
46
lib/validates_timeliness/parser.rb
Normal file
46
lib/validates_timeliness/parser.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
module ValidatesTimeliness
|
||||
module Parser
|
||||
|
||||
class << self
|
||||
|
||||
def parse(raw_value, type, options={})
|
||||
return nil if raw_value.blank?
|
||||
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
|
||||
|
||||
options.reverse_merge!(:strict => true)
|
||||
|
||||
time_array = ValidatesTimeliness::Formats.parse(raw_value, type, options)
|
||||
raise if time_array.nil?
|
||||
|
||||
# Rails dummy time date part is defined as 2000-01-01
|
||||
time_array[0..2] = 2000, 1, 1 if type == :time
|
||||
|
||||
# Date.new enforces days per month, unlike Time
|
||||
date = Date.new(*time_array[0..2]) unless type == :time
|
||||
|
||||
return date if type == :date
|
||||
|
||||
make_time(time_array[0..7])
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
def make_time(time_array)
|
||||
if Time.respond_to?(:zone) && ValidatesTimeliness.use_time_zones
|
||||
Time.zone.local(*time_array)
|
||||
else
|
||||
begin
|
||||
time_zone = ValidatesTimeliness.default_timezone
|
||||
Time.send(time_zone, *time_array)
|
||||
rescue ArgumentError, TypeError
|
||||
zone_offset = time_zone == :local ? DateTime.local_offset : 0
|
||||
time_array.pop # remove microseconds
|
||||
DateTime.civil(*(time_array << zone_offset))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -116,7 +116,7 @@ module Spec
|
||||
end
|
||||
|
||||
def error_message_for(option)
|
||||
msg = @validator.send(:error_messages)[option]
|
||||
msg = @validator.error_messages[option]
|
||||
restriction = @validator.class.send(:evaluate_option_value, @validator.configuration[option], @type, @record)
|
||||
|
||||
if restriction
|
||||
@@ -135,7 +135,7 @@ module Spec
|
||||
|
||||
def format_value(value)
|
||||
return value if value.is_a?(String)
|
||||
value.strftime(ValidatesTimeliness::Validator.error_value_formats[@type])
|
||||
value.strftime(@validator.class.error_value_formats[@type])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,27 +7,6 @@ module ValidatesTimeliness
|
||||
|
||||
module ClassMethods
|
||||
|
||||
def parse_date_time(raw_value, type, strict=true)
|
||||
return nil if raw_value.blank?
|
||||
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
|
||||
|
||||
time_array = ValidatesTimeliness::Formats.parse(raw_value, type, strict)
|
||||
raise if time_array.nil?
|
||||
|
||||
# Rails dummy time date part is defined as 2000-01-01
|
||||
time_array[0..2] = 2000, 1, 1 if type == :time
|
||||
|
||||
# Date.new enforces days per month, unlike Time
|
||||
date = Date.new(*time_array[0..2]) unless type == :time
|
||||
|
||||
return date if type == :date
|
||||
|
||||
# Create time object which checks time part, and return time object
|
||||
make_time(time_array)
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
def validates_time(*attr_names)
|
||||
configuration = attr_names.extract_options!
|
||||
configuration[:type] = :time
|
||||
@@ -59,21 +38,6 @@ module ValidatesTimeliness
|
||||
end
|
||||
end
|
||||
|
||||
# Time.zone. Rails 2.0 should be default_timezone.
|
||||
def make_time(time_array)
|
||||
if Time.respond_to?(:zone) && time_zone_aware_attributes
|
||||
Time.zone.local(*time_array)
|
||||
else
|
||||
begin
|
||||
Time.send(::ActiveRecord::Base.default_timezone, *time_array)
|
||||
rescue ArgumentError, TypeError
|
||||
zone_offset = ::ActiveRecord::Base.default_timezone == :local ? DateTime.local_offset : 0
|
||||
time_array.pop # remove microseconds
|
||||
DateTime.civil(*(time_array << zone_offset))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -2,14 +2,7 @@ module ValidatesTimeliness
|
||||
|
||||
class Validator
|
||||
cattr_accessor :ignore_restriction_errors
|
||||
cattr_accessor :error_value_formats
|
||||
|
||||
self.ignore_restriction_errors = false
|
||||
self.error_value_formats = {
|
||||
:time => '%H:%M:%S',
|
||||
:date => '%Y-%m-%d',
|
||||
:datetime => '%Y-%m-%d %H:%M:%S'
|
||||
}
|
||||
|
||||
RESTRICTION_METHODS = {
|
||||
:equal_to => :==,
|
||||
@@ -36,7 +29,7 @@ module ValidatesTimeliness
|
||||
end
|
||||
|
||||
def call(record, attr_name, value)
|
||||
value = record.class.parse_date_time(value, type, false) if value.is_a?(String)
|
||||
value = ValidatesTimeliness::Parser.parse(value, type, :strict => false) if value.is_a?(String)
|
||||
raw_value = raw_value(record, attr_name) || value
|
||||
|
||||
return if (raw_value.nil? && configuration[:allow_nil]) || (raw_value.blank? && configuration[:allow_blank])
|
||||
@@ -47,7 +40,11 @@ module ValidatesTimeliness
|
||||
|
||||
validate_restrictions(record, attr_name, value)
|
||||
end
|
||||
|
||||
|
||||
def error_messages
|
||||
@error_messages ||= self.class.default_error_messages.merge(custom_error_messages)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def raw_value(record, attr_name)
|
||||
@@ -87,7 +84,7 @@ module ValidatesTimeliness
|
||||
restriction = [restriction] unless restriction.is_a?(Array)
|
||||
|
||||
if defined?(I18n)
|
||||
message = custom_error_messages[option] || I18n.translate('activerecord.errors.messages')[option]
|
||||
message = custom_error_messages[option] || I18n.t('activerecord.errors.messages')[option]
|
||||
subs = message.scan(/\{\{([^\}]*)\}\}/)
|
||||
interpolations = {}
|
||||
subs.each_with_index {|s, i| interpolations[s[0].to_sym] = restriction[i].strftime(format) }
|
||||
@@ -120,10 +117,6 @@ module ValidatesTimeliness
|
||||
end
|
||||
end
|
||||
|
||||
def error_messages
|
||||
@error_messages ||= ValidatesTimeliness.default_error_messages.merge(custom_error_messages)
|
||||
end
|
||||
|
||||
def custom_error_messages
|
||||
@custom_error_messages ||= configuration.inject({}) {|msgs, (k, v)|
|
||||
if md = /(.*)_message$/.match(k.to_s)
|
||||
@@ -132,7 +125,7 @@ module ValidatesTimeliness
|
||||
msgs
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def combine_date_and_time(value, record)
|
||||
if type == :date
|
||||
date = value
|
||||
@@ -143,7 +136,7 @@ module ValidatesTimeliness
|
||||
end
|
||||
date, time = self.class.evaluate_option_value(date, :date, record), self.class.evaluate_option_value(time, :time, record)
|
||||
return if date.nil? || time.nil?
|
||||
record.class.send(:make_time, [date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
|
||||
ValidatesTimeliness::Parser.make_time([date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
|
||||
end
|
||||
|
||||
def validate_options(options)
|
||||
@@ -156,9 +149,29 @@ module ValidatesTimeliness
|
||||
# class methods
|
||||
class << self
|
||||
|
||||
def default_error_messages
|
||||
if defined?(I18n)
|
||||
I18n.t('activerecord.errors.messages')
|
||||
else
|
||||
::ActiveRecord::Errors.default_error_messages
|
||||
end
|
||||
end
|
||||
|
||||
def error_value_formats
|
||||
if defined?(I18n)
|
||||
I18n.t('validates_timeliness.error_value_formats')
|
||||
else
|
||||
@@error_value_formats
|
||||
end
|
||||
end
|
||||
|
||||
def error_value_formats=(formats)
|
||||
@@error_value_formats = formats
|
||||
end
|
||||
|
||||
def evaluate_option_value(value, type, record)
|
||||
case value
|
||||
when Time, Date, DateTime
|
||||
when Time, Date
|
||||
value
|
||||
when Symbol
|
||||
evaluate_option_value(record.send(value), type, record)
|
||||
@@ -169,7 +182,7 @@ module ValidatesTimeliness
|
||||
when Range
|
||||
evaluate_option_value([value.first, value.last], type, record)
|
||||
else
|
||||
record.class.parse_date_time(value, type, false)
|
||||
ValidatesTimeliness::Parser.parse(value, type, :strict => false)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -192,7 +205,7 @@ module ValidatesTimeliness
|
||||
nil
|
||||
end
|
||||
if ignore_usec && value.is_a?(Time)
|
||||
::ActiveRecord::Base.send(:make_time, Array(value).reverse[4..9])
|
||||
ValidatesTimeliness::Parser.make_time(Array(value).reverse[4..9])
|
||||
else
|
||||
value
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user