mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-22 22:06:45 +00:00
Merge branch '2.0.0'
This commit is contained in:
commit
e1d23d0f2b
24
README.rdoc
24
README.rdoc
@ -266,6 +266,20 @@ corner cases a little harder to test. In general if you are using procs or
|
||||
model methods and you only care when they return a value, then they should
|
||||
return nil in all other situations. Restrictions are skipped if they are nil.
|
||||
|
||||
|
||||
=== DISPLAY INVALID VALUES IN DATE HELPERS:
|
||||
|
||||
The plugin has some extensions to ActionView and ActiveRecord by allowing invalid
|
||||
date and time values to be redisplayed to the user as feedback, instead of
|
||||
a blank field which happens by default in Rails. Though the date helpers make this a
|
||||
pretty rare occurence, given the select dropdowns for each date/time component, but
|
||||
it may be something of interest.
|
||||
|
||||
To activate it, put this in an initializer:
|
||||
|
||||
ValidatesTimeliness.enable_datetime_select_extension!
|
||||
|
||||
|
||||
=== OTHER CUSTOMISATION:
|
||||
|
||||
The error messages for each temporal restrictions can also be globally overridden by
|
||||
@ -302,12 +316,22 @@ will be inserted.
|
||||
And for something a little more specific you can override the format of the interpolation
|
||||
values inserted in the error messages for temporal restrictions like so
|
||||
|
||||
For Rails 2.0/2.1:
|
||||
|
||||
ValidatesTimeliness::Validator.error_value_formats.update(
|
||||
:time => '%H:%M:%S',
|
||||
:date => '%Y-%m-%d',
|
||||
:datetime => '%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
Rails 2.2+ using the I18n system to define new defaults:
|
||||
|
||||
validates_timeliness:
|
||||
error_value_formats:
|
||||
date: '%Y-%m-%d'
|
||||
time: '%H:%M:%S'
|
||||
datetime: '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
Those are Ruby strftime formats not the plugin formats.
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -39,17 +39,17 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
||||
end
|
||||
|
||||
it "should call parser on write for datetime attribute" do
|
||||
@person.class.should_receive(:parse_date_time).once
|
||||
ValidatesTimeliness::Parser.should_receive(:parse).once
|
||||
@person.birth_date_and_time = "2000-01-01 02:03:04"
|
||||
end
|
||||
|
||||
it "should call parser on write for date attribute" do
|
||||
@person.class.should_receive(:parse_date_time).once
|
||||
ValidatesTimeliness::Parser.should_receive(:parse).once
|
||||
@person.birth_date = "2000-01-01"
|
||||
end
|
||||
|
||||
it "should call parser on write for time attribute" do
|
||||
@person.class.should_receive(:parse_date_time).once
|
||||
ValidatesTimeliness::Parser.should_receive(:parse).once
|
||||
@person.birth_time = "12:00"
|
||||
end
|
||||
|
||||
|
||||
@ -6,46 +6,6 @@ describe ValidatesTimeliness::Formats do
|
||||
before do
|
||||
@formats = ValidatesTimeliness::Formats
|
||||
end
|
||||
|
||||
describe "expression generator" do
|
||||
it "should generate regexp for time" do
|
||||
generate_regexp_str('hh:nn:ss').should == '/(\d{2}):(\d{2}):(\d{2})/'
|
||||
end
|
||||
|
||||
it "should generate regexp for time with meridian" do
|
||||
generate_regexp_str('hh:nn:ss ampm').should == '/(\d{2}):(\d{2}):(\d{2}) ((?:[aApP])\.?[mM]\.?)/'
|
||||
end
|
||||
|
||||
it "should generate regexp for time with meridian and optional space between" do
|
||||
generate_regexp_str('hh:nn:ss_ampm').should == '/(\d{2}):(\d{2}):(\d{2})\s?((?:[aApP])\.?[mM]\.?)/'
|
||||
end
|
||||
|
||||
it "should generate regexp for time with single or double digits" do
|
||||
generate_regexp_str('h:n:s').should == '/(\d{1,2}):(\d{1,2}):(\d{1,2})/'
|
||||
end
|
||||
|
||||
it "should generate regexp for date" do
|
||||
generate_regexp_str('yyyy-mm-dd').should == '/(\d{4})-(\d{2})-(\d{2})/'
|
||||
end
|
||||
|
||||
it "should generate regexp for date with slashes" do
|
||||
generate_regexp_str('dd/mm/yyyy').should == '/(\d{2})\/(\d{2})\/(\d{4})/'
|
||||
end
|
||||
|
||||
it "should generate regexp for date with dots" do
|
||||
generate_regexp_str('dd.mm.yyyy').should == '/(\d{2})\.(\d{2})\.(\d{4})/'
|
||||
end
|
||||
|
||||
it "should generate regexp for Ruby time string" do
|
||||
expected = '/(\w{3,9}) (\w{3,9}) (\d{2}):(\d{2}):(\d{2}) (?:[+-]\d{2}:?\d{2}) (\d{4})/'
|
||||
generate_regexp_str('ddd mmm hh:nn:ss zo yyyy').should == expected
|
||||
end
|
||||
|
||||
it "should generate regexp for iso8601 datetime" do
|
||||
expected = '/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:Z|(?:[+-]\d{2}:?\d{2}))/'
|
||||
generate_regexp_str('yyyy-mm-ddThh:nn:ss(?:Z|zo)').should == expected
|
||||
end
|
||||
end
|
||||
|
||||
describe "format proc generator" do
|
||||
it "should generate proc which outputs date array with values in correct order" do
|
||||
@ -71,6 +31,10 @@ describe ValidatesTimeliness::Formats do
|
||||
it "should generate proc which outputs time array with microseconds" do
|
||||
generate_proc('hh:nn:ss.u').call('01', '02', '03', '99').should == [0,0,0,1,2,3,990000]
|
||||
end
|
||||
|
||||
it "should generate proc which outputs datetime array with zone offset" do
|
||||
generate_proc('yyyy-mm-dd hh:nn:ss.u zo').call('2001', '02', '03', '04', '05', '06', '99', '+10:00').should == [2001,2,3,4,5,6,990000,36000]
|
||||
end
|
||||
end
|
||||
|
||||
describe "validation regexps" do
|
||||
@ -139,38 +103,43 @@ describe ValidatesTimeliness::Formats do
|
||||
describe "extracting values" do
|
||||
|
||||
it "should return time array from date string" do
|
||||
time_array = formats.parse('12:13:14', :time, true)
|
||||
time_array = formats.parse('12:13:14', :time, :strict => true)
|
||||
time_array.should == [0,0,0,12,13,14,0]
|
||||
end
|
||||
|
||||
it "should return date array from time string" do
|
||||
time_array = formats.parse('2000-02-01', :date, true)
|
||||
time_array = formats.parse('2000-02-01', :date, :strict => true)
|
||||
time_array.should == [2000,2,1,0,0,0,0]
|
||||
end
|
||||
|
||||
it "should return datetime array from string value" do
|
||||
time_array = formats.parse('2000-02-01 12:13:14', :datetime, true)
|
||||
time_array = formats.parse('2000-02-01 12:13:14', :datetime, :strict => true)
|
||||
time_array.should == [2000,2,1,12,13,14,0]
|
||||
end
|
||||
|
||||
it "should parse date string when type is datetime" do
|
||||
time_array = formats.parse('2000-02-01', :datetime, false)
|
||||
time_array = formats.parse('2000-02-01', :datetime, :strict => false)
|
||||
time_array.should == [2000,2,1,0,0,0,0]
|
||||
end
|
||||
|
||||
it "should ignore time when extracting date and strict is false" do
|
||||
time_array = formats.parse('2000-02-01 12:12', :date, false)
|
||||
time_array = formats.parse('2000-02-01 12:13', :date, :strict => false)
|
||||
time_array.should == [2000,2,1,0,0,0,0]
|
||||
end
|
||||
|
||||
it "should ignore time when extracting date from format with trailing year and strict is false" do
|
||||
time_array = formats.parse('01-02-2000 12:12', :date, false)
|
||||
time_array = formats.parse('01-02-2000 12:13', :date, :strict => false)
|
||||
time_array.should == [2000,2,1,0,0,0,0]
|
||||
end
|
||||
|
||||
it "should ignore date when extracting time and strict is false" do
|
||||
time_array = formats.parse('2000-02-01 12:12', :time, false)
|
||||
time_array.should == [0,0,0,12,12,0,0]
|
||||
time_array = formats.parse('2000-02-01 12:13', :time, :strict => false)
|
||||
time_array.should == [0,0,0,12,13,0,0]
|
||||
end
|
||||
|
||||
it "should return zone offset when :include_offset options is true" do
|
||||
time_array = formats.parse('2000-02-01T12:13:14-10:30', :datetime, :include_offset => true)
|
||||
time_array.should == [2000,2,1,12,13,14,0,-37800]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -1,39 +1,39 @@
|
||||
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
||||
|
||||
describe ValidatesTimeliness::ValidationMethods do
|
||||
describe ValidatesTimeliness::Parser do
|
||||
attr_accessor :person
|
||||
|
||||
describe "parse_date_time" do
|
||||
describe "parse" do
|
||||
it "should return time object for valid time string" do
|
||||
parse_method("2000-01-01 12:13:14", :datetime).should be_kind_of(Time)
|
||||
parse("2000-01-01 12:13:14", :datetime).should be_kind_of(Time)
|
||||
end
|
||||
|
||||
it "should return nil for time string with invalid date part" do
|
||||
parse_method("2000-02-30 12:13:14", :datetime).should be_nil
|
||||
parse("2000-02-30 12:13:14", :datetime).should be_nil
|
||||
end
|
||||
|
||||
it "should return nil for time string with invalid time part" do
|
||||
parse_method("2000-02-01 25:13:14", :datetime).should be_nil
|
||||
parse("2000-02-01 25:13:14", :datetime).should be_nil
|
||||
end
|
||||
|
||||
it "should return Time object when passed a Time object" do
|
||||
parse_method(Time.now, :datetime).should be_kind_of(Time)
|
||||
parse(Time.now, :datetime).should be_kind_of(Time)
|
||||
end
|
||||
|
||||
if RAILS_VER >= '2.1'
|
||||
it "should convert time string into current timezone" do
|
||||
Time.zone = 'Melbourne'
|
||||
time = parse_method("2000-01-01 12:13:14", :datetime)
|
||||
time = parse("2000-01-01 12:13:14", :datetime)
|
||||
Time.zone.utc_offset.should == 10.hours
|
||||
end
|
||||
end
|
||||
|
||||
it "should return nil for invalid date string" do
|
||||
parse_method("2000-02-30", :date).should be_nil
|
||||
parse("2000-02-30", :date).should be_nil
|
||||
end
|
||||
|
||||
def parse_method(*args)
|
||||
ActiveRecord::Base.parse_date_time(*args)
|
||||
def parse(*args)
|
||||
ValidatesTimeliness::Parser.parse(*args)
|
||||
end
|
||||
end
|
||||
|
||||
@ -43,14 +43,14 @@ describe ValidatesTimeliness::ValidationMethods do
|
||||
|
||||
it "should create time using current timezone" do
|
||||
Time.zone = 'Melbourne'
|
||||
time = ActiveRecord::Base.send(:make_time, [2000,1,1,12,0,0])
|
||||
time = ValidatesTimeliness::Parser.make_time([2000,1,1,12,0,0])
|
||||
time.zone.should == "EST"
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
it "should create time using default timezone" do
|
||||
time = ActiveRecord::Base.send(:make_time, [2000,1,1,12,0,0])
|
||||
time = ValidatesTimeliness::Parser.make_time([2000,1,1,12,0,0])
|
||||
time.zone.should == "UTC"
|
||||
end
|
||||
|
||||
@ -30,6 +30,7 @@ require 'active_record'
|
||||
require 'active_record/version'
|
||||
require 'action_controller'
|
||||
require 'action_view'
|
||||
require 'action_mailer'
|
||||
|
||||
require 'spec/rails'
|
||||
require 'time_travel/time_travel'
|
||||
@ -38,13 +39,15 @@ ActiveRecord::Base.default_timezone = :utc
|
||||
RAILS_VER = Rails::VERSION::STRING
|
||||
puts "Using #{vendored ? 'vendored' : 'gem'} Rails version #{RAILS_VER} (ActiveRecord version #{ActiveRecord::VERSION::STRING})"
|
||||
|
||||
require 'validates_timeliness'
|
||||
|
||||
if RAILS_VER >= '2.1'
|
||||
Time.zone_default = ActiveSupport::TimeZone['UTC']
|
||||
ActiveRecord::Base.time_zone_aware_attributes = true
|
||||
end
|
||||
|
||||
require 'validates_timeliness'
|
||||
|
||||
ValidatesTimeliness.enable_datetime_select_extension!
|
||||
|
||||
ActiveRecord::Migration.verbose = false
|
||||
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
|
||||
|
||||
|
||||
@ -66,7 +66,7 @@ describe ValidatesTimeliness::Validator do
|
||||
|
||||
it "should return array of Time objects when restriction is array of strings" do
|
||||
time1, time2 = "2000-01-02", "2000-01-01"
|
||||
evaluate_option_value([time1, time2], :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
|
||||
evaluate_option_value([time1, time2], :datetime).should == [parse(time2, :datetime), parse(time1, :datetime)]
|
||||
end
|
||||
|
||||
it "should return array of Time objects when restriction is Range of Time objects" do
|
||||
@ -76,7 +76,7 @@ describe ValidatesTimeliness::Validator do
|
||||
|
||||
it "should return array of Time objects when restriction is Range of time strings" do
|
||||
time1, time2 = "2000-01-02", "2000-01-01"
|
||||
evaluate_option_value(time1..time2, :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
|
||||
evaluate_option_value(time1..time2, :datetime).should == [parse(time2, :datetime), parse(time1, :datetime)]
|
||||
end
|
||||
def evaluate_option_value(restriction, type)
|
||||
configure_validator(:type => type)
|
||||
@ -554,12 +554,18 @@ describe ValidatesTimeliness::Validator do
|
||||
describe "custom formats" do
|
||||
|
||||
before :all do
|
||||
@@formats = ValidatesTimeliness::Validator.error_value_formats
|
||||
ValidatesTimeliness::Validator.error_value_formats = {
|
||||
custom = {
|
||||
:time => '%H:%M %p',
|
||||
:date => '%d-%m-%Y',
|
||||
:datetime => '%d-%m-%Y %H:%M %p'
|
||||
}
|
||||
|
||||
if defined?(I18n)
|
||||
I18n.backend.store_translations 'en', :validates_timeliness => { :error_value_formats => custom }
|
||||
else
|
||||
@@formats = ValidatesTimeliness::Validator.error_value_formats
|
||||
ValidatesTimeliness::Validator.error_value_formats = custom
|
||||
end
|
||||
end
|
||||
|
||||
it "should format datetime value of restriction" do
|
||||
@ -581,12 +587,20 @@ describe ValidatesTimeliness::Validator do
|
||||
end
|
||||
|
||||
after :all do
|
||||
ValidatesTimeliness::Validator.error_value_formats = @@formats
|
||||
if defined?(I18n)
|
||||
I18n.reload!
|
||||
else
|
||||
ValidatesTimeliness::Validator.error_value_formats = @@formats
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def parse(*args)
|
||||
ValidatesTimeliness::Parser.parse(*args)
|
||||
end
|
||||
|
||||
def configure_validator(options={})
|
||||
@validator = ValidatesTimeliness::Validator.new(options)
|
||||
end
|
||||
|
||||
Loading…
Reference in New Issue
Block a user