Compare commits

..

24 Commits
1.1.7 ... 2.0.0

Author SHA1 Message Date
Adam Meehan
af3a3b84ab version 2.0.0 2009-04-12 11:13:52 +10:00
Adam Meehan
4df63c7142 another idea in TODO 2009-04-12 11:10:58 +10:00
Adam Meehan
26066dedfe update TODOs 2009-04-12 10:26:31 +10:00
Adam Meehan
3348ca3063 dont rely on add_error return value to exit. sigh, less one liners 2009-04-12 10:23:09 +10:00
Adam Meehan
88ee33ae41 consistently use instance methods vs vars in validator 2009-04-12 10:17:33 +10:00
Adam Meehan
51afb2852e update benchmark file 2009-04-12 09:44:46 +10:00
Adam Meehan
57b8a52f07 add :format option to readme 2009-04-10 11:01:19 +10:00
Adam Meehan
bb94e234bc add :format option to validate and parse with specific a format 2009-04-10 10:57:27 +10:00
Adam Meehan
f041524124 add equal_to option to matcher 2009-04-10 10:48:24 +10:00
Adam Meehan
e1d23d0f2b Merge branch '2.0.0' 2009-04-09 18:54:34 +10:00
Adam Meehan
613001791a limit time_array size explicity 2009-04-09 18:53:28 +10:00
Adam Meehan
6d12790d2b datetime select helper extension activation in readme 2009-04-06 17:34:23 +10:00
Adam Meehan
b197d537f1 fix read attribute bug for frozen record (reported by Les Nightingill) 2009-04-06 13:52:07 +10:00
Adam Meehan
1e3c802031 capture zone offset value in formats to possible usage 2009-03-31 21:35:49 +11:00
Adam Meehan
d89266d9f1 minor stuff 2009-03-31 21:25:05 +11:00
Adam Meehan
9e2d95c3e1 remove brittle and not very useful specs, which are covered elsewhere 2009-03-31 14:22:13 +11:00
Adam Meehan
fb520bbddc use plugin parser in action view extension 2009-03-31 14:21:07 +11:00
Adam Meehan
8303be05c3 little spec consistency 2009-03-30 21:03:51 +11:00
Adam Meehan
f4ed751c26 changed back to using error_value_formats for Rails 2.0/2.1 2009-03-28 19:51:11 +11:00
Adam Meehan
956933f58b disable multiparameter values extension by default for v2 2009-03-28 18:53:47 +11:00
Adam Meehan
7967b5a212 refactored error value formats to use locale file for I18n. Rail 2.0/2.1 to use default_error_value_formats now.
moved default_error_messages_method into validator
2009-03-28 18:49:26 +11:00
Adam Meehan
a836ed8434 changed Formats#parse to take options hash for strict and other possibilities 2009-03-28 17:35:41 +11:00
Adam Meehan
c2a4f45b5a removed old spec 2009-03-28 17:35:21 +11:00
Adam Meehan
312c1510cb refactored AR parsing methods into Parser module to reduce AR method pollution and make more consistent 2009-03-28 17:25:48 +11:00
22 changed files with 328 additions and 208 deletions

View File

@@ -1,3 +1,11 @@
= 2.0.0 [2009-04-12]
- Error value formats are now specified in the i18n locale file instead of updating plugin hash. See OTHER CUSTOMISATION section in README.
- Date/time select helper extension is disabled by default. To enable see DISPLAY INVALID VALUES IN DATE HELPERS section in README to enable.
- Added :format option to limit validation to a single format if desired
- Matcher now supports :equal_to option
- Formats.parse can take :include_offset option to include offset value from string in seconds, if string contains an offset. Offset not used in rest of plugin yet.
- Refactored to remove as much plugin code from ActiveRecord as possible.
= 1.1.7 [2009-03-26] = 1.1.7 [2009-03-26]
- Minor change to multiparameter attributes which I had not properly implemented for chaining - Minor change to multiparameter attributes which I had not properly implemented for chaining

View File

@@ -80,6 +80,7 @@ Special options:
:with_time - Validate a date attribute value combined with a time value against any temporal restrictions :with_time - Validate a date attribute value combined with a time value against any temporal restrictions
:with_date - Validate a time attribute value combined with a date value against any temporal restrictions :with_date - Validate a time attribute value combined with a date value against any temporal restrictions
:ignore_usec - Ignores microsecond value on datetime restrictions :ignore_usec - Ignores microsecond value on datetime restrictions
:format - Limit validation to a single format for special cases. Takes plugin format value.
Message options: - Use these to override the default error messages Message options: - Use these to override the default error messages
:invalid_date_message :invalid_date_message
@@ -266,6 +267,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 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. 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: === OTHER CUSTOMISATION:
The error messages for each temporal restrictions can also be globally overridden by The error messages for each temporal restrictions can also be globally overridden by
@@ -302,12 +317,22 @@ will be inserted.
And for something a little more specific you can override the format of the interpolation 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 values inserted in the error messages for temporal restrictions like so
For Rails 2.0/2.1:
ValidatesTimeliness::Validator.error_value_formats.update( ValidatesTimeliness::Validator.error_value_formats.update(
:time => '%H:%M:%S', :time => '%H:%M:%S',
:date => '%Y-%m-%d', :date => '%Y-%m-%d',
:datetime => '%Y-%m-%d %H:%M:%S' :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. Those are Ruby strftime formats not the plugin formats.

View File

@@ -5,7 +5,7 @@ require 'date'
require 'spec/rake/spectask' require 'spec/rake/spectask'
GEM = "validates_timeliness" GEM = "validates_timeliness"
GEM_VERSION = "1.1.7" GEM_VERSION = "2.0.0"
AUTHOR = "Adam Meehan" AUTHOR = "Adam Meehan"
EMAIL = "adam.meehan@gmail.com" EMAIL = "adam.meehan@gmail.com"
HOMEPAGE = "http://github.com/adzap/validates_timeliness" HOMEPAGE = "http://github.com/adzap/validates_timeliness"

4
TODO
View File

@@ -1,3 +1,5 @@
- :format option
- valid formats could come from locale file - valid formats could come from locale file
- add replace_formats instead add_formats :before - add replace_formats instead add_formats :before
- array of values for all temporal options
- use tz and zo value from time string?
- filter valid formats rather than remove for hot swapping without recompilation

View File

@@ -4,73 +4,74 @@ require 'date'
require 'parsedate' require 'parsedate'
require 'benchmark' require 'benchmark'
require 'rubygems' require 'rubygems'
require 'active_support'
require 'active_record' require 'active_record'
require 'action_controller'
require 'rails/version'
require 'validates_timeliness' require 'validates_timeliness'
def parse(*args)
ValidatesTimeliness::Parser.parse(*args)
end
n = 10000 n = 10000
Benchmark.bm do |x| Benchmark.bm do |x|
x.report('timeliness - datetime') { x.report('timeliness - datetime') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("2000-01-04 12:12:12", :datetime) parse("2000-01-04 12:12:12", :datetime)
end end
} }
x.report('timeliness - date') { x.report('timeliness - date') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("2000-01-04", :date) parse("2000-01-04", :date)
end end
} }
x.report('timeliness - date as datetime') { x.report('timeliness - date as datetime') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("2000-01-04", :datetime) parse("2000-01-04", :datetime)
end end
} }
x.report('timeliness - time') { x.report('timeliness - time') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("12:01:02", :time) parse("12:01:02", :time)
end end
} }
x.report('timeliness - invalid format datetime') { x.report('timeliness - invalid format datetime') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("20xx-01-04 12:12:12", :datetime) parse("20xx-01-04 12:12:12", :datetime)
end end
} }
x.report('timeliness - invalid format date') { x.report('timeliness - invalid format date') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("20xx-01-04", :date) parse("20xx-01-04", :date)
end end
} }
x.report('timeliness - invalid format time') { x.report('timeliness - invalid format time') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("12:xx:02", :time) parse("12:xx:02", :time)
end end
} }
x.report('timeliness - invalid value datetime') { x.report('timeliness - invalid value datetime') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("2000-01-32 12:12:12", :datetime) parse("2000-01-32 12:12:12", :datetime)
end end
} }
x.report('timeliness - invalid value date') { x.report('timeliness - invalid value date') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("2000-01-32", :date) parse("2000-01-32", :date)
end end
} }
x.report('timeliness - invalid value time') { x.report('timeliness - invalid value time') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("12:61:02", :time) parse("12:61:02", :time)
end end
} }
x.report('date/time') { x.report('date/time') {
@@ -96,4 +97,3 @@ Benchmark.bm do |x|
end end
} }
end end

View File

@@ -1,4 +1,5 @@
require 'validates_timeliness/formats' require 'validates_timeliness/formats'
require 'validates_timeliness/parser'
require 'validates_timeliness/validator' require 'validates_timeliness/validator'
require 'validates_timeliness/validation_methods' require 'validates_timeliness/validation_methods'
require 'validates_timeliness/spec/rails/matchers/validate_timeliness' if ENV['RAILS_ENV'] == 'test' 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 module ValidatesTimeliness
mattr_accessor :default_timezone mattr_accessor :default_timezone
self.default_timezone = :utc 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') LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/validates_timeliness/locale/en.yml')
class << self class << self
@@ -31,25 +34,18 @@ module ValidatesTimeliness
I18n.load_path += [ LOCALE_PATH ] I18n.load_path += [ LOCALE_PATH ]
I18n.reload! I18n.reload!
else else
messages = YAML::load(IO.read(LOCALE_PATH)) defaults = YAML::load(IO.read(LOCALE_PATH))['en']
errors = messages['en']['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h } 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) ::ActiveRecord::Errors.default_error_messages.update(errors)
ValidatesTimeliness::Validator.error_value_formats = defaults['validates_timeliness']['error_value_formats'].symbolize_keys
end end
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 def setup_for_rails
major, minor = Rails::VERSION::MAJOR, Rails::VERSION::MINOR
self.default_timezone = ::ActiveRecord::Base.default_timezone self.default_timezone = ::ActiveRecord::Base.default_timezone
self.enable_datetime_select_extension! self.use_time_zones = ::ActiveRecord::Base.time_zone_aware_attributes rescue false
self.load_error_messages load_error_messages
end end
end end
end end

View File

@@ -37,7 +37,7 @@ module ValidatesTimeliness
return value_without_timeliness(object) return value_without_timeliness(object)
end end
time_array = ParseDate.parsedate(raw_value) time_array = ValidatesTimeliness::Formats.parse(raw_value, :datetime)
TimelinessDateTime.new(*time_array[0..5]) TimelinessDateTime.new(*time_array[0..5])
end end

View File

@@ -46,7 +46,7 @@ module ValidatesTimeliness
# implementation as it chains the write_attribute method which deletes # implementation as it chains the write_attribute method which deletes
# the attribute from the cache. # the attribute from the cache.
def write_date_time_attribute(attr_name, value, type, time_zone_aware) 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 if new && type != :date
new = new.to_time new = new.to_time
@@ -73,18 +73,16 @@ module ValidatesTimeliness
if @attributes_cache.has_key?(attr_name) if @attributes_cache.has_key?(attr_name)
time = read_attribute_before_type_cast(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 else
time = read_attribute(attr_name) time = read_attribute(attr_name)
@attributes[attr_name] = time && time_zone_aware ? time.in_time_zone : time @attributes[attr_name] = (time && time_zone_aware ? time.in_time_zone : time) unless frozen?
end end
@attributes_cache[attr_name] = time && time_zone_aware ? time.in_time_zone : time @attributes_cache[attr_name] = time && time_zone_aware ? time.in_time_zone : time
end end
module ClassMethods module ClassMethods
# Define attribute reader and writer method for date, time and
# datetime attributes to use plugin parser.
def define_attribute_methods_with_timeliness def define_attribute_methods_with_timeliness
return if generated_methods? return if generated_methods?
columns_hash.each do |name, column| columns_hash.each do |name, column|
@@ -105,7 +103,6 @@ module ValidatesTimeliness
define_attribute_methods_without_timeliness define_attribute_methods_without_timeliness
end end
# Define write method for date, time and datetime columns
def define_write_method_for_dates_and_times(attr_name, type, time_zone_aware) def define_write_method_for_dates_and_times(attr_name, type, time_zone_aware)
method_body = <<-EOV method_body = <<-EOV
def #{attr_name}=(value) def #{attr_name}=(value)

View File

@@ -38,7 +38,7 @@ module ValidatesTimeliness
end end
def time_array_to_string(values, type) def time_array_to_string(values, type)
values = values.map {|v| v.to_s } values.collect! {|v| v.to_s }
case type case type
when :date when :date

View File

@@ -124,13 +124,13 @@ module ValidatesTimeliness
{ 's' => [ /s{1}/, '(\d{1,2})', :sec ] }, { 's' => [ /s{1}/, '(\d{1,2})', :sec ] },
{ 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] }, { 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] },
{ 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] }, { 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] },
{ 'zo' => [ /zo/, '(?:[+-]\d{2}:?\d{2})'] }, { 'zo' => [ /zo/, '([+-]\d{2}:?\d{2})', :offset ] },
{ 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] }, { 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] },
{ '_' => [ /_/, '\s?' ] } { '_' => [ /_/, '\s?' ] }
] ]
# Arguments whichs will be passed to the format proc if matched in the # Arguments which 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 # 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 # 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 # place in the time array slot. The position can be nil which means the arg
# won't be placed in the array. # won't be placed in the array.
@@ -146,6 +146,7 @@ module ValidatesTimeliness
:min => [4, 'n', 'n'], :min => [4, 'n', 'n'],
:sec => [5, 's', 's'], :sec => [5, 's', 's'],
:usec => [6, 'u', 'microseconds(u)'], :usec => [6, 'u', 'microseconds(u)'],
:offset => [7, 'z', 'offset_in_seconds(z)'],
:meridian => [nil, 'md', nil] :meridian => [nil, 'md', nil]
} }
@@ -160,21 +161,29 @@ module ValidatesTimeliness
# Loop through format expressions for type and call proc on matches. Allow # Loop through format expressions for type and call proc on matches. Allow
# pre or post match strings to exist if strict is false. Otherwise wrap # pre or post match strings to exist if strict is false. Otherwise wrap
# regexp in start and end anchors. # regexp in start and end anchors.
# Returns 7 part time array. # Returns time array if matches a format, nil otherwise.
def parse(string, type, strict=true) def parse(string, type, options={})
return string unless string.is_a?(String) return string unless string.is_a?(String)
options.reverse_merge!(:strict => true)
sets = if options[:format]
[ send("#{type}_expressions").assoc(options[:format]) ]
else
expression_set(type, string)
end
matches = nil matches = nil
exp, processor = expression_set(type, string).find do |regexp, proc| processor = sets.each do |format, regexp, proc|
full = /\A#{regexp}\Z/ if strict full = /\A#{regexp}\Z/ if options[:strict]
full ||= case type full ||= case type
when :date then /\A#{regexp}/ when :date then /\A#{regexp}/
when :time then /#{regexp}\Z/ when :time then /#{regexp}\Z/
when :datetime then /\A#{regexp}\Z/ when :datetime then /\A#{regexp}\Z/
end end
matches = full.match(string.strip) break(proc) if matches = full.match(string.strip)
end end
processor.call(*matches[1..7]) if matches last = options[:include_offset] ? 8 : 7
processor.call(*matches[1..last]) if matches
end end
# Delete formats of specified type. Error raised if format not found. # Delete formats of specified type. Error raised if format not found.
@@ -206,8 +215,7 @@ module ValidatesTimeliness
end end
compile_format_expressions compile_format_expressions
end end
# Removes formats where the 1 or 2 digit month comes first, to eliminate # 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. # formats which are ambiguous with the European style of day then month.
# The mmm token is ignored as its not ambigous. # The mmm token is ignored as its not ambigous.
@@ -246,22 +254,17 @@ module ValidatesTimeliness
# argument in the position indicated by the first element of the proc arg # argument in the position indicated by the first element of the proc arg
# array. # 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) def format_proc(order)
arg_map = format_proc_args arg_map = format_proc_args
args = order.invert.sort.map {|p| arg_map[p[1]][1] } args = order.invert.sort.map {|p| arg_map[p[1]][1] }
arr = [nil] * 7 arr = [nil] * 7
order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? } 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 eval proc_string
end end
def compile_formats(formats) def compile_formats(formats)
formats.map { |format| regexp, format_proc = format_expression_generator(format) } formats.map { |format| [ format, *format_expression_generator(format) ] }
end end
# Pick expression set and combine date and datetimes for # Pick expression set and combine date and datetimes for
@@ -313,6 +316,13 @@ module ValidatesTimeliness
def microseconds(usec) def microseconds(usec)
(".#{usec}".to_f * 1_000_000).to_i (".#{usec}".to_f * 1_000_000).to_i
end 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 end
end end

View File

@@ -11,3 +11,8 @@ en:
after: "must be after {{restriction}}" after: "must be after {{restriction}}"
on_or_after: "must be on or after {{restriction}}" on_or_after: "must be on or after {{restriction}}"
between: "must be between {{earliest}} and {{latest}}" 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'

View 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

View File

@@ -10,6 +10,7 @@ module Spec
} }
OPTION_TEST_SETTINGS = { OPTION_TEST_SETTINGS = {
:equal_to => { :method => :+, :modify_on => :invalid },
:before => { :method => :-, :modify_on => :valid }, :before => { :method => :-, :modify_on => :valid },
:after => { :method => :+, :modify_on => :valid }, :after => { :method => :+, :modify_on => :valid },
:on_or_before => { :method => :+, :modify_on => :invalid }, :on_or_before => { :method => :+, :modify_on => :invalid },
@@ -27,9 +28,9 @@ module Spec
valid = test_validity valid = test_validity
valid = test_option(:equal_to) if @options[:equal_to] && valid
valid = test_option(:before) if @options[:before] && valid valid = test_option(:before) if @options[:before] && valid
valid = test_option(:after) if @options[:after] && valid valid = test_option(:after) if @options[:after] && valid
valid = test_option(:on_or_before) if @options[:on_or_before] && valid valid = test_option(:on_or_before) if @options[:on_or_before] && valid
valid = test_option(:on_or_after) if @options[:on_or_after] && valid valid = test_option(:on_or_after) if @options[:on_or_after] && valid
@@ -116,7 +117,7 @@ module Spec
end end
def error_message_for(option) 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) restriction = @validator.class.send(:evaluate_option_value, @validator.configuration[option], @type, @record)
if restriction if restriction
@@ -135,7 +136,7 @@ module Spec
def format_value(value) def format_value(value)
return value if value.is_a?(String) 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
end end

View File

@@ -7,27 +7,6 @@ module ValidatesTimeliness
module ClassMethods 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) def validates_time(*attr_names)
configuration = attr_names.extract_options! configuration = attr_names.extract_options!
configuration[:type] = :time configuration[:type] = :time
@@ -59,21 +38,6 @@ module ValidatesTimeliness
end end
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
end end

View File

@@ -2,14 +2,7 @@ module ValidatesTimeliness
class Validator class Validator
cattr_accessor :ignore_restriction_errors cattr_accessor :ignore_restriction_errors
cattr_accessor :error_value_formats
self.ignore_restriction_errors = false 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 = { RESTRICTION_METHODS = {
:equal_to => :==, :equal_to => :==,
@@ -21,8 +14,8 @@ module ValidatesTimeliness
} }
VALID_OPTIONS = [ VALID_OPTIONS = [
:on, :if, :unless, :allow_nil, :empty, :allow_blank, :blank, :on, :if, :unless, :allow_nil, :empty, :allow_blank,
:with_time, :with_date, :ignore_usec, :with_time, :with_date, :ignore_usec, :format,
:invalid_time_message, :invalid_date_message, :invalid_datetime_message :invalid_time_message, :invalid_date_message, :invalid_datetime_message
] + RESTRICTION_METHODS.keys.map {|option| [option, "#{option}_message".to_sym] }.flatten ] + RESTRICTION_METHODS.keys.map {|option| [option, "#{option}_message".to_sym] }.flatten
@@ -36,18 +29,32 @@ module ValidatesTimeliness
end end
def call(record, attr_name, value) def call(record, attr_name, value)
value = record.class.parse_date_time(value, type, false) if value.is_a?(String)
raw_value = raw_value(record, attr_name) || value raw_value = raw_value(record, attr_name) || value
if value.is_a?(String) || configuration[:format]
strict = !configuration[:format].nil?
value = ValidatesTimeliness::Parser.parse(raw_value, type, :strict => strict, :format => configuration[:format])
end
return if (raw_value.nil? && configuration[:allow_nil]) || (raw_value.blank? && configuration[:allow_blank]) return if (raw_value.nil? && configuration[:allow_nil]) || (raw_value.blank? && configuration[:allow_blank])
add_error(record, attr_name, :blank) and return if raw_value.blank? if raw_value.blank?
add_error(record, attr_name, :blank)
add_error(record, attr_name, "invalid_#{type}".to_sym) and return unless value return
end
if value.nil?
add_error(record, attr_name, "invalid_#{type}".to_sym)
return
end
validate_restrictions(record, attr_name, value) validate_restrictions(record, attr_name, value)
end end
def error_messages
@error_messages ||= self.class.default_error_messages.merge(custom_error_messages)
end
private private
def raw_value(record, attr_name) def raw_value(record, attr_name)
@@ -55,12 +62,12 @@ module ValidatesTimeliness
end end
def validate_restrictions(record, attr_name, value) def validate_restrictions(record, attr_name, value)
value = if @configuration[:with_time] || @configuration[:with_date] value = if configuration[:with_time] || configuration[:with_date]
restriction_type = :datetime restriction_type = :datetime
combine_date_and_time(value, record) combine_date_and_time(value, record)
else else
restriction_type = type restriction_type = type
self.class.type_cast_value(value, type, @configuration[:ignore_usec]) self.class.type_cast_value(value, type, configuration[:ignore_usec])
end end
return if value.nil? return if value.nil?
@@ -69,7 +76,7 @@ module ValidatesTimeliness
begin begin
restriction = self.class.evaluate_option_value(restriction, restriction_type, record) restriction = self.class.evaluate_option_value(restriction, restriction_type, record)
next if restriction.nil? next if restriction.nil?
restriction = self.class.type_cast_value(restriction, restriction_type, @configuration[:ignore_usec]) restriction = self.class.type_cast_value(restriction, restriction_type, configuration[:ignore_usec])
unless evaluate_restriction(restriction, value, method) unless evaluate_restriction(restriction, value, method)
add_error(record, attr_name, option, interpolation_values(option, restriction)) add_error(record, attr_name, option, interpolation_values(option, restriction))
@@ -87,7 +94,7 @@ module ValidatesTimeliness
restriction = [restriction] unless restriction.is_a?(Array) restriction = [restriction] unless restriction.is_a?(Array)
if defined?(I18n) 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(/\{\{([^\}]*)\}\}/) subs = message.scan(/\{\{([^\}]*)\}\}/)
interpolations = {} interpolations = {}
subs.each_with_index {|s, i| interpolations[s[0].to_sym] = restriction[i].strftime(format) } subs.each_with_index {|s, i| interpolations[s[0].to_sym] = restriction[i].strftime(format) }
@@ -110,7 +117,6 @@ module ValidatesTimeliness
def add_error(record, attr_name, message, interpolate=nil) def add_error(record, attr_name, message, interpolate=nil)
if defined?(I18n) if defined?(I18n)
# use i18n support in AR for message or use custom message passed to validation method
custom = custom_error_messages[message] custom = custom_error_messages[message]
record.errors.add(attr_name, custom || message, interpolate || {}) record.errors.add(attr_name, custom || message, interpolate || {})
else else
@@ -120,10 +126,6 @@ module ValidatesTimeliness
end end
end end
def error_messages
@error_messages ||= ValidatesTimeliness.default_error_messages.merge(custom_error_messages)
end
def custom_error_messages def custom_error_messages
@custom_error_messages ||= configuration.inject({}) {|msgs, (k, v)| @custom_error_messages ||= configuration.inject({}) {|msgs, (k, v)|
if md = /(.*)_message$/.match(k.to_s) if md = /(.*)_message$/.match(k.to_s)
@@ -132,33 +134,53 @@ module ValidatesTimeliness
msgs msgs
} }
end end
def combine_date_and_time(value, record) def combine_date_and_time(value, record)
if type == :date if type == :date
date = value date = value
time = @configuration[:with_time] time = configuration[:with_time]
else else
date = @configuration[:with_date] date = configuration[:with_date]
time = value time = value
end end
date, time = self.class.evaluate_option_value(date, :date, record), self.class.evaluate_option_value(time, :time, record) date, time = self.class.evaluate_option_value(date, :date, record), self.class.evaluate_option_value(time, :time, record)
return if date.nil? || time.nil? 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 end
def validate_options(options) def validate_options(options)
invalid_for_type = ([:time, :date, :datetime] - [@type]).map {|k| "invalid_#{k}_message".to_sym } invalid_for_type = ([:time, :date, :datetime] - [type]).map {|k| "invalid_#{k}_message".to_sym }
invalid_for_type << :with_date unless @type == :time invalid_for_type << :with_date unless type == :time
invalid_for_type << :with_time unless @type == :date invalid_for_type << :with_time unless type == :date
options.assert_valid_keys(VALID_OPTIONS - invalid_for_type) options.assert_valid_keys(VALID_OPTIONS - invalid_for_type)
end end
# class methods # class methods
class << self 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) def evaluate_option_value(value, type, record)
case value case value
when Time, Date, DateTime when Time, Date
value value
when Symbol when Symbol
evaluate_option_value(record.send(value), type, record) evaluate_option_value(record.send(value), type, record)
@@ -169,7 +191,7 @@ module ValidatesTimeliness
when Range when Range
evaluate_option_value([value.first, value.last], type, record) evaluate_option_value([value.first, value.last], type, record)
else else
record.class.parse_date_time(value, type, false) ValidatesTimeliness::Parser.parse(value, type, :strict => false)
end end
end end
@@ -192,7 +214,7 @@ module ValidatesTimeliness
nil nil
end end
if ignore_usec && value.is_a?(Time) 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 else
value value
end end

View File

@@ -39,17 +39,17 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
end end
it "should call parser on write for datetime attribute" do 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" @person.birth_date_and_time = "2000-01-01 02:03:04"
end end
it "should call parser on write for date attribute" do 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" @person.birth_date = "2000-01-01"
end end
it "should call parser on write for time attribute" do 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" @person.birth_time = "12:00"
end end
@@ -221,4 +221,14 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
@person.birth_date.should == tomorrow @person.birth_date.should == tomorrow
end end
it "should skip storing value in attributes hash on read if record frozen" do
@person = Person.new
@person.birth_date = Date.today
@person.save!
@person.reload
@person.freeze
@person.frozen?.should be_true
lambda { @person.birth_date }.should_not raise_error
@person.birth_date.should == Date.today
end
end end

View File

@@ -6,46 +6,6 @@ describe ValidatesTimeliness::Formats do
before do before do
@formats = ValidatesTimeliness::Formats @formats = ValidatesTimeliness::Formats
end 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 describe "format proc generator" do
it "should generate proc which outputs date array with values in correct order" 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 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] generate_proc('hh:nn:ss.u').call('01', '02', '03', '99').should == [0,0,0,1,2,3,990000]
end 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 end
describe "validation regexps" do describe "validation regexps" do
@@ -136,49 +100,62 @@ describe ValidatesTimeliness::Formats do
end end
end end
describe "extracting values" do describe "parse" do
it "should return time array from date string" 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] time_array.should == [0,0,0,12,13,14,0]
end end
it "should return date array from time string" do 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] time_array.should == [2000,2,1,0,0,0,0]
end end
it "should return datetime array from string value" do 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] time_array.should == [2000,2,1,12,13,14,0]
end end
it "should parse date string when type is datetime" do 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] time_array.should == [2000,2,1,0,0,0,0]
end end
it "should ignore time when extracting date and strict is false" do 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] time_array.should == [2000,2,1,0,0,0,0]
end end
it "should ignore time when extracting date from format with trailing year and strict is false" do 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] time_array.should == [2000,2,1,0,0,0,0]
end end
it "should ignore date when extracting time and strict is false" do 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 = formats.parse('2000-02-01 12:13', :time, :strict => false)
time_array.should == [0,0,0,12,12,0,0] 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
end end
describe "removing formats" do describe "parse with format option" do
before do it "should return values if string matches specified format" do
formats.compile_format_expressions time_array = formats.parse('2000-02-01 12:13:14', :datetime, :format => 'yyyy-mm-dd hh:nn:ss')
time_array.should == [2000,2,1,12,13,14,0]
end end
it "should return nil if string does not match specified format" do
time_array = formats.parse('2000-02-01 12:13', :datetime, :format => 'yyyy-mm-dd hh:nn:ss')
time_array.should be_nil
end
end
describe "removing formats" do
it "should remove format from format array" do it "should remove format from format array" do
formats.remove_formats(:time, 'h.nn_ampm') formats.remove_formats(:time, 'h.nn_ampm')
formats.time_formats.should_not include("h o'clock") formats.time_formats.should_not include("h o'clock")
@@ -196,7 +173,7 @@ describe ValidatesTimeliness::Formats do
after do after do
formats.time_formats << 'h.nn_ampm' formats.time_formats << 'h.nn_ampm'
# reload class instead formats.compile_format_expressions
end end
end end
@@ -254,7 +231,7 @@ describe ValidatesTimeliness::Formats do
def validate(time_string, type) def validate(time_string, type)
valid = false valid = false
formats.send("#{type}_expressions").each do |(regexp, processor)| formats.send("#{type}_expressions").each do |format, regexp, processor|
valid = true and break if /\A#{regexp}\Z/ =~ time_string valid = true and break if /\A#{regexp}\Z/ =~ time_string
end end
valid valid

View File

@@ -1,39 +1,39 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper') require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
describe ValidatesTimeliness::ValidationMethods do describe ValidatesTimeliness::Parser do
attr_accessor :person attr_accessor :person
describe "parse_date_time" do describe "parse" do
it "should return time object for valid time string" 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 end
it "should return nil for time string with invalid date part" do 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 end
it "should return nil for time string with invalid time part" do 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 end
it "should return Time object when passed a Time object" do 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 end
if RAILS_VER >= '2.1' if RAILS_VER >= '2.1'
it "should convert time string into current timezone" do it "should convert time string into current timezone" do
Time.zone = 'Melbourne' 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 Time.zone.utc_offset.should == 10.hours
end end
end end
it "should return nil for invalid date string" do 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 end
def parse_method(*args) def parse(*args)
ActiveRecord::Base.parse_date_time(*args) ValidatesTimeliness::Parser.parse(*args)
end end
end end
@@ -43,14 +43,14 @@ describe ValidatesTimeliness::ValidationMethods do
it "should create time using current timezone" do it "should create time using current timezone" do
Time.zone = 'Melbourne' 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" time.zone.should == "EST"
end end
else else
it "should create time using default timezone" do 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" time.zone.should == "UTC"
end end

View File

@@ -5,6 +5,7 @@ end
class WithValidation < Person class WithValidation < Person
validates_date :birth_date, validates_date :birth_date,
:equal_to => '2000-01-01',
:before => '2000-01-10', :before => '2000-01-10',
:after => '2000-01-01', :after => '2000-01-01',
:on_or_before => '2000-01-09', :on_or_before => '2000-01-09',
@@ -12,6 +13,7 @@ class WithValidation < Person
:between => ['2000-01-01', '2000-01-03'] :between => ['2000-01-01', '2000-01-03']
validates_time :birth_time, validates_time :birth_time,
:equal_to => '09:00',
:before => '23:00', :before => '23:00',
:after => '09:00', :after => '09:00',
:on_or_before => '22:00', :on_or_before => '22:00',
@@ -19,6 +21,7 @@ class WithValidation < Person
:between => ['09:00', '17:00'] :between => ['09:00', '17:00']
validates_datetime :birth_date_and_time, validates_datetime :birth_date_and_time,
:equal_to => '2000-01-01 09:00',
:before => '2000-01-10 23:00', :before => '2000-01-10 23:00',
:after => '2000-01-01 09:00', :after => '2000-01-01 09:00',
:on_or_before => '2000-01-09 23:00', :on_or_before => '2000-01-09 23:00',
@@ -61,6 +64,29 @@ describe "ValidateTimeliness matcher" do
end end
end end
describe "with equal_to option" do
test_values = {
:date => ['2000-01-01', '2000-01-02'],
:time => ['09:00', '09:01'],
:datetime => ['2000-01-01 09:00', '2000-01-01 09:01']
}
[:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :equal_to => test_values[type][0])
end
it "should report that #{type} is not validated when option value is incorrect" do
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :equal_to => test_values[type][1])
end
it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :equal_to => test_values[type][0])
end
end
end
describe "with before option" do describe "with before option" do
test_values = { test_values = {
:date => ['2000-01-10', '2000-01-11'], :date => ['2000-01-10', '2000-01-11'],

View File

@@ -30,6 +30,7 @@ require 'active_record'
require 'active_record/version' require 'active_record/version'
require 'action_controller' require 'action_controller'
require 'action_view' require 'action_view'
require 'action_mailer'
require 'spec/rails' require 'spec/rails'
require 'time_travel/time_travel' require 'time_travel/time_travel'
@@ -38,13 +39,15 @@ ActiveRecord::Base.default_timezone = :utc
RAILS_VER = Rails::VERSION::STRING RAILS_VER = Rails::VERSION::STRING
puts "Using #{vendored ? 'vendored' : 'gem'} Rails version #{RAILS_VER} (ActiveRecord version #{ActiveRecord::VERSION::STRING})" puts "Using #{vendored ? 'vendored' : 'gem'} Rails version #{RAILS_VER} (ActiveRecord version #{ActiveRecord::VERSION::STRING})"
require 'validates_timeliness'
if RAILS_VER >= '2.1' if RAILS_VER >= '2.1'
Time.zone_default = ActiveSupport::TimeZone['UTC'] Time.zone_default = ActiveSupport::TimeZone['UTC']
ActiveRecord::Base.time_zone_aware_attributes = true ActiveRecord::Base.time_zone_aware_attributes = true
end end
require 'validates_timeliness'
ValidatesTimeliness.enable_datetime_select_extension!
ActiveRecord::Migration.verbose = false ActiveRecord::Migration.verbose = false
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'}) ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})

View File

@@ -66,7 +66,7 @@ describe ValidatesTimeliness::Validator do
it "should return array of Time objects when restriction is array of strings" do it "should return array of Time objects when restriction is array of strings" do
time1, time2 = "2000-01-02", "2000-01-01" 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 end
it "should return array of Time objects when restriction is Range of Time objects" do 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 it "should return array of Time objects when restriction is Range of time strings" do
time1, time2 = "2000-01-02", "2000-01-01" 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 end
def evaluate_option_value(restriction, type) def evaluate_option_value(restriction, type)
configure_validator(:type => type) configure_validator(:type => type)
@@ -473,6 +473,20 @@ describe ValidatesTimeliness::Validator do
end end
end end
describe "instance with format option" do
it "should validate attribute when value matches format" do
configure_validator(:type => :time, :format => 'hh:nn:ss')
validate_with(:birth_time, "12:00:00")
should_have_no_error(:birth_time, :invalid_time)
end
it "should not validate attribute when value does not match format" do
configure_validator(:type => :time, :format => 'hh:nn:ss')
validate_with(:birth_time, "12:00")
should_have_error(:birth_time, :invalid_time)
end
end
describe "custom_error_messages" do describe "custom_error_messages" do
it "should return hash of custom error messages from configuration with _message truncated from keys" do it "should return hash of custom error messages from configuration with _message truncated from keys" do
configure_validator(:type => :date, :invalid_date_message => 'thats no date') configure_validator(:type => :date, :invalid_date_message => 'thats no date')
@@ -554,12 +568,18 @@ describe ValidatesTimeliness::Validator do
describe "custom formats" do describe "custom formats" do
before :all do before :all do
@@formats = ValidatesTimeliness::Validator.error_value_formats custom = {
ValidatesTimeliness::Validator.error_value_formats = {
:time => '%H:%M %p', :time => '%H:%M %p',
:date => '%d-%m-%Y', :date => '%d-%m-%Y',
:datetime => '%d-%m-%Y %H:%M %p' :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 end
it "should format datetime value of restriction" do it "should format datetime value of restriction" do
@@ -581,12 +601,20 @@ describe ValidatesTimeliness::Validator do
end end
after :all do 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 end
end end
def parse(*args)
ValidatesTimeliness::Parser.parse(*args)
end
def configure_validator(options={}) def configure_validator(options={})
@validator = ValidatesTimeliness::Validator.new(options) @validator = ValidatesTimeliness::Validator.new(options)
end end

View File

@@ -2,16 +2,16 @@
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = %q{validates_timeliness} s.name = %q{validates_timeliness}
s.version = "1.1.7" s.version = "2.0.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Adam Meehan"] s.authors = ["Adam Meehan"]
s.autorequire = %q{validates_timeliness} s.autorequire = %q{validates_timeliness}
s.date = %q{2009-03-26} s.date = %q{2009-04-12}
s.description = %q{Date and time validation plugin for Rails 2.x which allows custom formats} s.description = %q{Date and time validation plugin for Rails 2.x which allows custom formats}
s.email = %q{adam.meehan@gmail.com} s.email = %q{adam.meehan@gmail.com}
s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"] s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"]
s.files = ["LICENSE", "README.rdoc", "Rakefile", "TODO", "CHANGELOG", "lib/validates_timeliness", "lib/validates_timeliness/core_ext", "lib/validates_timeliness/core_ext/date.rb", "lib/validates_timeliness/core_ext/date_time.rb", "lib/validates_timeliness/core_ext/time.rb", "lib/validates_timeliness/action_view", "lib/validates_timeliness/action_view/instance_tag.rb", "lib/validates_timeliness/locale", "lib/validates_timeliness/locale/en.yml", "lib/validates_timeliness/validation_methods.rb", "lib/validates_timeliness/active_record", "lib/validates_timeliness/active_record/attribute_methods.rb", "lib/validates_timeliness/active_record/multiparameter_attributes.rb", "lib/validates_timeliness/formats.rb", "lib/validates_timeliness/validator.rb", "lib/validates_timeliness/spec", "lib/validates_timeliness/spec/rails", "lib/validates_timeliness/spec/rails/matchers", "lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb", "lib/validates_timeliness.rb", "spec/core_ext", "spec/core_ext/dummy_time_spec.rb", "spec/validator_spec.rb", "spec/action_view", "spec/action_view/instance_tag_spec.rb", "spec/ginger_scenarios.rb", "spec/validation_methods_spec.rb", "spec/spec_helper.rb", "spec/formats_spec.rb", "spec/active_record", "spec/active_record/attribute_methods_spec.rb", "spec/active_record/multiparameter_attributes_spec.rb", "spec/time_travel", "spec/time_travel/time_travel.rb", "spec/time_travel/time_extensions.rb", "spec/time_travel/MIT-LICENSE", "spec/spec", "spec/spec/rails", "spec/spec/rails/matchers", "spec/spec/rails/matchers/validate_timeliness_spec.rb", "spec/resources", "spec/resources/person.rb", "spec/resources/sqlite_patch.rb", "spec/resources/schema.rb", "spec/resources/application.rb"] s.files = ["LICENSE", "README.rdoc", "Rakefile", "TODO", "CHANGELOG", "lib/validates_timeliness", "lib/validates_timeliness/core_ext", "lib/validates_timeliness/core_ext/date.rb", "lib/validates_timeliness/core_ext/date_time.rb", "lib/validates_timeliness/core_ext/time.rb", "lib/validates_timeliness/action_view", "lib/validates_timeliness/action_view/instance_tag.rb", "lib/validates_timeliness/locale", "lib/validates_timeliness/locale/en.yml", "lib/validates_timeliness/validation_methods.rb", "lib/validates_timeliness/active_record", "lib/validates_timeliness/active_record/attribute_methods.rb", "lib/validates_timeliness/active_record/multiparameter_attributes.rb", "lib/validates_timeliness/parser.rb", "lib/validates_timeliness/formats.rb", "lib/validates_timeliness/validator.rb", "lib/validates_timeliness/spec", "lib/validates_timeliness/spec/rails", "lib/validates_timeliness/spec/rails/matchers", "lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb", "lib/validates_timeliness.rb", "spec/core_ext", "spec/core_ext/dummy_time_spec.rb", "spec/validator_spec.rb", "spec/action_view", "spec/action_view/instance_tag_spec.rb", "spec/ginger_scenarios.rb", "spec/spec_helper.rb", "spec/formats_spec.rb", "spec/active_record", "spec/active_record/attribute_methods_spec.rb", "spec/active_record/multiparameter_attributes_spec.rb", "spec/time_travel", "spec/time_travel/time_travel.rb", "spec/time_travel/time_extensions.rb", "spec/time_travel/MIT-LICENSE", "spec/parser_spec.rb", "spec/spec", "spec/spec/rails", "spec/spec/rails/matchers", "spec/spec/rails/matchers/validate_timeliness_spec.rb", "spec/resources", "spec/resources/person.rb", "spec/resources/sqlite_patch.rb", "spec/resources/schema.rb", "spec/resources/application.rb"]
s.has_rdoc = true s.has_rdoc = true
s.homepage = %q{http://github.com/adzap/validates_timeliness} s.homepage = %q{http://github.com/adzap/validates_timeliness}
s.require_paths = ["lib"] s.require_paths = ["lib"]