Compare commits

..

66 Commits
2.1.0 ... 2.3.2

Author SHA1 Message Date
Adam Meehan
e711941744 version 2.3.2 2010-11-07 11:04:42 +11:00
Adam Meehan
5bed56e180 fixes for latest I18n interpolation token detection 2010-11-07 10:59:16 +11:00
Adam Meehan
c3a591a5d8 fix bad spec 2010-11-06 19:08:25 +11:00
Adam Meehan
6265b2d804 fix parsing of datetimes with timezone (thanks sigi) 2010-11-06 19:05:50 +11:00
Adam Meehan
341a481231 install instructions for v2 2010-09-17 15:34:03 +10:00
Adam Meehan
d6ddae9977 don't allow 0 or > 12 for meridian am hour 2010-09-17 14:52:39 +10:00
Adam Meehan
0ea0201051 put install plugin below gem 2010-07-27 07:32:42 +10:00
Adam Meehan
b874e786b6 version 2.3.1 2010-03-19 14:24:19 +11:00
Adam Meehan
b356363791 fix bug preventing custom class methods on date/times 2010-03-19 14:11:13 +11:00
Adam Meehan
e9eb812c9e fix misbehaving spec 2010-03-19 13:39:47 +11:00
Adam Meehan
353f154330 tiny spec clean 2010-02-26 11:23:55 +11:00
Adam Meehan
ccfad007d1 version 2.3.0 gemspec 2010-02-04 13:23:26 +11:00
Adam Meehan
f3c0dd6144 removed most of old gem builder guff 2010-02-04 13:22:50 +11:00
Adam Meehan
6349990243 version 2.3.0 2010-02-04 13:19:57 +11:00
Adam Meehan
220b58ab14 some todos for v3 2010-02-04 13:19:22 +11:00
Adam Meehan
f98e503e25 readme tweaks 2010-02-04 13:18:55 +11:00
Adam Meehan
3e1a9fc107 fix locale spec by adding time formats into dummy locale 2010-02-04 13:08:00 +11:00
Adam Meehan
99d742e3d0 instance_tag time_select spec fix 2010-02-04 13:06:24 +11:00
Adam Meehan
77f338d97c refactored action view extension to use params
this is instead of value before type to avoid issues with partial value parsing
will be extracted in v3
2010-02-04 11:45:28 +11:00
Adam Meehan
d255bbfccf deprecation warning example for :equal_to with messages silenced 2010-01-18 12:03:10 +11:00
Adam Meehan
4df1974524 tweak instance tag spec 2010-01-12 22:13:43 +11:00
Adam Meehan
82c0e1bcd3 changed format of string used in multi param for invalid or partial values 2010-01-12 22:05:14 +11:00
Adam Meehan
f1a0016bf7 added deprecation notice for :equal_to 2010-01-12 22:02:15 +11:00
Adam Meehan
59d9957ab6 fix generated_methods bug where read instead of write method stored
existing spec failed unless run alone as attribute methods already
primed. fixed spec to undef method and empty generated_methods set
2009-12-29 11:13:15 +11:00
Adam Meehan
694c3b0ce3 method rename and words 2009-12-23 14:52:41 +11:00
Adam Meehan
a859827af4 fix regexp for ISO 8601 datetimes (thanks costan) 2009-12-23 13:06:32 +11:00
Adam Meehan
f8b9f72693 geez, fix readme typo 2009-12-11 16:36:35 +11:00
Adam Meehan
7f1ed79a89 change a couple of readme equal_to 2009-12-11 15:33:50 +11:00
Adam Meehan
f3c119e191 change equal_to to is_at to fix overlap with default rails message 2009-12-11 15:20:34 +11:00
Adam Meehan
3bfc7b748f more rails versions in ginger scenarios 2009-12-11 15:19:07 +11:00
Adam Meehan
40369efdff GMT to UTC in spec for 1.8.7 2009-12-11 15:18:22 +11:00
Adam Meehan
f41016af93 spec for missing translation 2009-12-11 15:17:46 +11:00
Adam Meehan
5258256d5e reworked i18n messages, interpolation to behave on missing translation 2009-12-11 15:15:16 +11:00
Adam Meehan
9cfbb2a458 fix for I18n formats lookup in Rails <= 2.3.3 vendored I18n 2009-12-11 15:12:49 +11:00
Adam Meehan
78baa7a3cc move default validator options to constant 2009-12-11 11:13:35 +11:00
Adam Meehan
7d6967da90 tiny cleanup 2009-12-11 11:10:33 +11:00
Adam Meehan
0c38b6abd1 added all I18n error messages in README example 2009-12-11 11:08:52 +11:00
Adam Meehan
a6712de5ff use global error value formats if missing in locale
always loads globals value formats into class accessor
replace old format accessor methods class accessor
2009-12-11 11:06:21 +11:00
Adam Meehan
c580c3e682 use generated_methods method rather ivar 2009-09-19 11:59:08 +10:00
Adam Meehan
9c5db44500 update gemspec 2009-09-19 10:16:49 +10:00
Adam Meehan
f109443fb7 version 2.2.2 2009-09-19 08:19:27 +10:00
Adam Meehan
96bf4bf184 fix dummy to respect timezones by using make_time 2009-09-19 07:30:33 +10:00
Adam Meehan
90be6a6db5 change to github issues for bug reports 2009-09-12 14:39:46 +10:00
Adam Meehan
4d5d82ff20 version 2.2.1 2009-09-12 14:16:44 +10:00
Adam Meehan
b11893eac0 fix dummy date part in Validator.type_cast_value
removed all core extensions
2009-09-12 14:14:37 +10:00
Adam Meehan
76e159b350 version 2.2.0 2009-09-12 13:43:50 +10:00
Adam Meehan
c762b6d4f8 added version file with VERSION constant 2009-09-12 13:42:07 +10:00
Adam Meehan
91f9f65bc0 tiny doc change 2009-09-12 13:36:57 +10:00
Adam Meehan
6db8b7d908 push dummy date value assignment into Formats.parse and allow custom values to be used 2009-09-12 13:07:01 +10:00
Adam Meehan
d3c5101f92 use implied_type in restriction evaluations 2009-09-12 13:05:12 +10:00
Adam Meehan
899e96b880 tiny clean up 2009-09-12 13:05:12 +10:00
Adam Meehan
162faf632a push strict override for format option into Formats.parse 2009-09-12 13:05:12 +10:00
Adam Meehan
1b865cc834 split ignore_sec into own describe 2009-09-12 13:05:11 +10:00
Adam Meehan
c29478df45 fix deprecation for ActiveRecord::Errors#generate_message in Rails 2.3.4
ginger scenario added 2.3.4
2009-09-12 13:05:11 +10:00
Adam Meehan
df3283e5a1 fix ignore_usec for with_date and with_time options 2009-09-12 13:05:04 +10:00
Adam Meehan
0e382e15f2 moved action view extension enable call to relevant spec 2009-09-11 13:13:02 +10:00
Adam Meehan
9a697b9cab finally fixed spec for latest rspec 2009-09-11 13:11:09 +10:00
Adam Meehan
7bf7ed0569 catch both possible exception types 2009-09-09 16:25:04 +10:00
Adam Meehan
a969a49ae8 little tweaks 2009-09-09 16:16:11 +10:00
Adam Meehan
687e61a3f2 Merge branch 'master' of github.com:adzap/validates_timeliness 2009-09-05 19:50:43 +10:00
Adam Meehan
4cc20ae620 fixed some bad rescue behaviour in parse method 2009-09-05 19:47:49 +10:00
adzap
2d510504e6 change to lambda in examples 2009-08-26 21:07:29 -07:00
Adam Meehan
2028d68b17 checking proc arity in option value for ruby 1.9 compat 2009-08-22 14:59:46 +10:00
Adam Meehan
e399c6b510 moved mutliparam helper methods our of AR to reduce method pollution 2009-07-28 12:52:25 +10:00
Adam Meehan
7aa1a87731 require matcher in spec helper 2009-07-28 12:51:32 +10:00
Adam Meehan
1a31e7463d have to manually require matcher now because the presence of the Spec
namespace caused issues for shoulda when not using rspec:wq
2009-07-07 15:32:15 +10:00
30 changed files with 1034 additions and 621 deletions

View File

@@ -1,3 +1,34 @@
= 2.3.2 [2010-11-07]
- Fixed parser for string with timezone offset (thanks sigi)
- Support for latest I18n interpolation tokens in locale file
- Fixed parser allowing an hour over 12 for AM meridian
= 2.3.1 [2010-03-19]
- Fixed bug where custom attribute writer method for date/times were being overriden
= 2.3.0 [2010-02-04]
- Backwards incompatible change to :equal_to option. Fixed error message clash with :equal_to option which exists in Rails already. Option is now :is_at.
- Fixed I18n support so it returns missing translation message instead of error
- Fixed attribute method bug. Write method was bypassed when method was first generated and used Rails default parser.
- Fixed date/time selects when using enable_datetime_select_extension! when some values empty
- Fixed ISO8601 datetime format which is now split into two formats
- Changed I18n error value format to fallback to global default if missing in locale
- Refactored date/time select invalid value extension to use param values. Functionality will be extracted from plugin for v3.
= 2.2.2 [2009-09-19]
- Fixed dummy_time using make_time to respect timezone. Fixes 1.9.1 bug.
= 2.2.1 [2009-09-12]
- Fixed dummy date part for time types in Validator.type_cast_value
- No more core extensions! Removed dummy_time methods.
= 2.2.0 [2009-09-12]
- Ruby 1.9 support!
- Customise dummy date values for time types. See DUMMY DATE FOR TIME TYPES.
- Fixed matcher conflict with Shoulda. Load plugin matcher manually now see matcher section in README
- Fixed :ignore_usec when used with :with_time or :with_date
- Some clean up and refactoring
= 2.1.0 [2009-06-20] = 2.1.0 [2009-06-20]
- Added ambiguous year threshold setting in Formats class to customize the threshold for 2 digit years (See README) - Added ambiguous year threshold setting in Formats class to customize the threshold for 2 digit years (See README)
- Fixed interpolation values in custom error message for Rails 2.2+ - Fixed interpolation values in custom error message for Rails 2.2+
@@ -51,7 +82,7 @@
- Added Rails 2.2 i18n support. Plugin error messages can specified in locale files. See README. - Added Rails 2.2 i18n support. Plugin error messages can specified in locale files. See README.
- ignore_datetime_restriction_errors setting has been moved from AR to ValidatesTimeliness::Validator.ignore_restriction_errors - ignore_datetime_restriction_errors setting has been moved from AR to ValidatesTimeliness::Validator.ignore_restriction_errors
- date_time_error_value_formats setting has been moved from AR to ValidatesTimeliness::Validator.error_value_formats - date_time_error_value_formats setting has been moved from AR to ValidatesTimeliness::Validator.error_value_formats
- Namespaced modules and specs - Namespaced modules and specs
- Clean up of specs - Clean up of specs
- fixed a few bugs - fixed a few bugs
- accessor methods not generating properly due method name stored as symbol in generated_attributes which fails on lookup - accessor methods not generating properly due method name stored as symbol in generated_attributes which fails on lookup

View File

@@ -1,14 +1,14 @@
= validates_timeliness = validates_timeliness
* Source: http://github.com/adzap/validates_timeliness * Source: http://github.com/adzap/validates_timeliness
* Bugs: http://adzap.lighthouseapp.com/projects/14111-validates_timeliness * Bugs: http://github.com/adzap/validates_timeliness/issues
== DESCRIPTION: == DESCRIPTION:
Validate dates, times and datetimes for Rails 2.x. Plays nicely with new Rails 2.1 Validate dates, times and datetimes for Rails 2.x. Plays nicely with Rails
features such as automatic timezone handling and dirty attributes. Allows you to automatic timezone handling. Allows you to add custom formats or remove defaults
add custom formats or remove defaults easily. This allows you to control what you easily. This allows you to control what you think should be a valid date or
think should be a valid date or time string. time string.
== FEATURES: == FEATURES:
@@ -22,48 +22,47 @@ think should be a valid date or time string.
* Restores ability to see raw value entered for date/time attributes with * Restores ability to see raw value entered for date/time attributes with
_before_type_cast modifier, which was lost in Rails 2.1. _before_type_cast modifier, which was lost in Rails 2.1.
* Respects new timezone features of Rails 2.1. * Supports Rails timezone handling
* Supports Rails 2.2 I18n for the error messages * Supports Rails I18n for the error messages
* Rspec matcher for testing model validation of dates and times * Rspec matcher for testing model validation of dates and times
== INSTALLATION: == INSTALLATION:
As plugin (from master)
./script/plugin install git://github.com/adzap/validates_timeliness.git
As gem As gem
sudo gem install validates_timeliness gem install validates_timeliness -v '~> 2.3'
# in environment.rb # in environment.rb
config.gem 'validates_timeliness' config.gem 'validates_timeliness', :version => '~> 2.3'
As plugin (from master)
./script/plugin install git://github.com/adzap/validates_timeliness.git -r v2.3
== USAGE: == USAGE:
To validate a model with a date, time or datetime attribute you just use the To validate a model with a date, time or datetime attribute you just use the
validation method validation method
class Person < ActiveRecord::Base class Person < ActiveRecord::Base
validates_date :date_of_birth validates_date :date_of_birth
end end
The list of validation methods available are as follows: The list of validation methods available are as follows:
validates_date - validate value as date validates_date - validate value as date
validates_time - validate value as time only i.e. '12:20pm' validates_time - validate value as time only i.e. '12:20pm'
validates_datetime - validate value as a full date and time validates_datetime - validate value as a full date and time
The validation methods take the usual options plus some specific ones to restrict The validation methods take the usual options plus some specific ones to restrict
the valid range of dates or times allowed the valid range of dates or times allowed
Temporal options (or restrictions): Temporal options (or restrictions):
:equal_to - Attribute must be equal to value to be valid :is_at - Attribute must be equal to value to be valid
:before - Attribute must be before this value to be valid :before - Attribute must be before this value to be valid
:on_or_before - Attribute must be equal to or before this value to be valid :on_or_before - Attribute must be equal to or before this value to be valid
:after - Attribute must be after this value to be valid :after - Attribute must be after this value to be valid
@@ -78,15 +77,15 @@ Regular validation options:
Special options: 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. :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
:invalid_time_message :invalid_time_message
:invalid_datetime_message :invalid_datetime_message
:equal_to_message :is_at_message
:before_message :before_message
:on_or_before_message :on_or_before_message
:after_message :after_message
@@ -99,7 +98,7 @@ The temporal restrictions, with_date and with_time can take 4 different value ty
* Proc or lambda object which may take an optional parameter being the record object * Proc or lambda object which may take an optional parameter being the record object
* A symbol matching the method name in the model * A symbol matching the method name in the model
When an attribute value is compared to temporal restrictions, they are compared as When an attribute value is compared to temporal restrictions, they are compared as
the same type as the validation method type. So using validates_date means all the same type as the validation method type. So using validates_date means all
values are compared as dates. This is except in the case of with_time and with_date values are compared as dates. This is except in the case of with_time and with_date
options which effectively force the value to validated as a datetime against the options which effectively force the value to validated as a datetime against the
@@ -107,27 +106,27 @@ temporal options.
== EXAMPLES: == EXAMPLES:
validates_date :date_of_birth :before => Proc.new { 18.years.ago }, validates_date :date_of_birth :before => lambda { 18.years.ago },
:before_message => "must be at least 18 years old" :before_message => "must be at least 18 years old"
validates_time :breakfast_time, :on_or_after => '6:00am', validates_time :breakfast_time, :on_or_after => '6:00am',
:on_or_after_message => 'must be after opening time', :on_or_after_message => 'must be after opening time',
:before => :second_breakfast_time, :before => :second_breakfast_time,
:allow_nil => true :allow_nil => true
validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now } validates_datetime :appointment_date, :before => lambda { 1.week.from_now }
validates_date :entry_date, :with_time => '17:00', :on_or_before => :competition_closing validates_date :entry_date, :with_time => '17:00', :on_or_before => :competition_closing
=== DATE/TIME FORMATS: === DATE/TIME FORMATS:
So what formats does the plugin allow. Well there are default formats which can So what formats does the plugin allow. Well there are default formats which can
be added to easily using the plugins format rules. Also formats can be easily be added to easily using the plugins format rules. Also formats can be easily
removed without hacking the plugin at all. removed without hacking the plugin at all.
Below are the default formats. If you think they are easy to read then you will Below are the default formats. If you think they are easy to read then you will
be happy to know that is exactly the format you can use to define your own if be happy to know that is exactly the format you can use to define your own if
you want. No complex regular expressions or duck punching (monkey patching) the you want. No complex regular expressions or duck punching (monkey patching) the
plugin is needed. plugin is needed.
@@ -165,13 +164,14 @@ NOTE: To use non-US date formats see US/EURO FORMATS section
yyyy-mm-dd hh:nn:ss yyyy-mm-dd hh:nn:ss
yyyy-mm-dd h:nn yyyy-mm-dd h:nn
ddd mmm d hh:nn:ss zo yyyy # Ruby time string ddd mmm d hh:nn:ss zo yyyy # Ruby time string
yyyy-mm-ddThh:nn:ss(?:Z|zo) # ISO 8601 yyyy-mm-ddThh:nn:ssZ # ISO 8601 without zone offset
yyyy-mm-ddThh:nn:sszo # ISO 8601 with zone offset
NOTE: To use non-US date formats see US/EURO FORMATS section NOTE: To use non-US date formats see US/EURO FORMATS section
Here is what each format token means: Here is what each format token means:
Format tokens: Format tokens:
y = year y = year
m = month m = month
d = day d = day
@@ -184,10 +184,10 @@ Here is what each format token means:
tz = Timezone abbreviation (e.g. UTC, GMT, PST, EST) tz = Timezone abbreviation (e.g. UTC, GMT, PST, EST)
zo = Timezone offset (e.g. +10:00, -08:00, +1000) zo = Timezone offset (e.g. +10:00, -08:00, +1000)
Repeating tokens: Repeating tokens:
x = 1 or 2 digits for unit (e.g. 'h' means an hour can be '9' or '09') x = 1 or 2 digits for unit (e.g. 'h' means an hour can be '9' or '09')
xx = 2 digits exactly for unit (e.g. 'hh' means an hour can only be '09') xx = 2 digits exactly for unit (e.g. 'hh' means an hour can only be '09')
Special Cases: Special Cases:
yy = 2 or 4 digit year yy = 2 or 4 digit year
yyyy = exactly 4 digit year yyyy = exactly 4 digit year
@@ -195,9 +195,9 @@ Here is what each format token means:
ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday) ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
u = microseconds matches 1 to 3 digits u = microseconds matches 1 to 3 digits
All other characters are considered literal. For the technically minded All other characters are considered literal. For the technically minded
(well you are developers), these formats are compiled into regular expressions (well you are developers), these formats are compiled into regular expressions
at runtime so don't add any extra overhead than using regular expressions at runtime so don't add any extra overhead than using regular expressions
directly. So, no, it won't make your app slow! directly. So, no, it won't make your app slow!
To see all defined formats look in lib/validates_timeliness/formats.rb. To see all defined formats look in lib/validates_timeliness/formats.rb.
@@ -208,18 +208,18 @@ The perenial problem for non-US developers or applications not primarily for the
US, is the US date format of m/d/yy. This is ambiguous with the European format US, is the US date format of m/d/yy. This is ambiguous with the European format
of d/m/yy. By default the plugin uses the US formats as this is the Ruby default of d/m/yy. By default the plugin uses the US formats as this is the Ruby default
when it does date interpretation, and is in keeping PoLS (principle of least when it does date interpretation, and is in keeping PoLS (principle of least
surprise). surprise).
To switch to using the European (or Rest of The World) formats put this in an To switch to using the European (or Rest of The World) formats put this in an
initializer or environment.rb initializer or environment.rb
ValidatesTimeliness::Formats.remove_us_formats ValidatesTimeliness::Formats.remove_us_formats
Now '01/02/2000' will be parsed as 1st February 2000, instead of 2nd January 2000. Now '01/02/2000' will be parsed as 1st February 2000, instead of 2nd January 2000.
=== CUSTOMISING FORMATS: === CUSTOMISING FORMATS:
I hear you say "Thats greats but I don't want X format to be valid". Well to I hear you say "Thats greats but I don't want X format to be valid". Well to
remove a format stick this in an initializer file remove a format stick this in an initializer file
ValidatesTimeliness::Formats.remove_formats(:date, 'm\d\yy') ValidatesTimeliness::Formats.remove_formats(:date, 'm\d\yy')
@@ -233,15 +233,15 @@ Ahh, then add it yourself. Again stick this in an initializer file
Now "10 o'clock" will be a valid value. So easy, no more whingeing! Now "10 o'clock" will be a valid value. So easy, no more whingeing!
You can embed regular expressions in the format but no gurantees that it will You can embed regular expressions in the format but no gurantees that it will
remain intact. If you avoid the use of any token characters and regexp dots or remain intact. If you avoid the use of any token characters and regexp dots or
backslashes as special characters in the regexp, it may well work as expected. backslashes as special characters in the regexp, it may well work as expected.
For special characters use POSIX character classes for safety. See the ISO 8601 For special characters use POSIX character classes for safety. See the ISO 8601
datetime for an example of an embedded regular expression. datetime for an example of an embedded regular expression.
Because formats are evaluated in order, adding a format which may be ambiguous Because formats are evaluated in order, adding a format which may be ambiguous
with an existing format, will mean your format is ignored. If you need to make with an existing format, will mean your format is ignored. If you need to make
your new format higher precedence than an existing format, you can include the your new format higher precedence than an existing format, you can include the
before option like so before option like so
ValidatesTimeliness::Formats.add_formats(:time, 'ss:nn:hh', :before => 'hh:nn:ss') ValidatesTimeliness::Formats.add_formats(:time, 'ss:nn:hh', :before => 'hh:nn:ss')
@@ -263,11 +263,23 @@ Now you get:
year of 20 is considered 1920 year of 20 is considered 1920
=== DUMMY DATE FOR TIME TYPES
Given that Ruby has no support for a time-only type, all time type columns are evaluated
as a regular Time class objects with a dummy date value set. Rails defines the dummy date as
2000-01-01. So a time of '12:30' is evaluated as a Time value of '2000-01-01 12:30'. If you
need to customize this for some reason you can do so as follows
ValidatesTimeliness::Formats.dummy_date_for_time_type = [2009, 1, 1]
The value should be an array of 3 values being year, month and day in that order.
=== TEMPORAL RESTRICTION ERRORS: === TEMPORAL RESTRICTION ERRORS:
When using the validation temporal restrictions there are times when the restriction When using the validation temporal restrictions there are times when the restriction
value itself may be invalid. Normally this will add an error to the model such as value itself may be invalid. Normally this will add an error to the model such as
'restriction :before value was invalid'. These can be annoying if you are using 'restriction :before value was invalid'. These can be annoying if you are using
procs or methods as restrictions and don't care if they don't evaluate properly procs or methods as restrictions and don't care if they don't evaluate properly
and you want the validation to complete. In these situations you turn them off. and you want the validation to complete. In these situations you turn them off.
@@ -276,8 +288,8 @@ To turn them off:
ValidatesTimeliness::Validator.ignore_restriction_errors = true ValidatesTimeliness::Validator.ignore_restriction_errors = true
A word of warning though, as this may hide issues with the model and make those A word of warning though, as this may hide issues with the model and make those
corner cases a little harder to test. In general if you are using procs or 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.
@@ -293,10 +305,11 @@ To activate it, put this in an initializer:
ValidatesTimeliness.enable_datetime_select_extension! ValidatesTimeliness.enable_datetime_select_extension!
This will be removed from v3 as it adds too little to maintain.
=== 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
updating the default AR error messages like so updating the default AR error messages like so
For Rails 2.0/2.1: For Rails 2.0/2.1:
@@ -305,6 +318,7 @@ For Rails 2.0/2.1:
:invalid_date => "is not a valid date", :invalid_date => "is not a valid date",
:invalid_time => "is not a valid time", :invalid_time => "is not a valid time",
:invalid_datetime => "is not a valid datetime", :invalid_datetime => "is not a valid datetime",
:is_at => "must be at %s",
:before => "must be before %s", :before => "must be before %s",
:on_or_before => "must be on or before %s", :on_or_before => "must be on or before %s",
:after => "must be after %s", :after => "must be after %s",
@@ -320,8 +334,14 @@ Rails 2.2+ using the I18n system to define new defaults:
activerecord: activerecord:
errors: errors:
messages: messages:
on_or_before: "must be equal to or before {{restriction}}" invalid_date: "is not a valid date"
on_or_after: "must be equal to or after {{restriction}}" invalid_time: "is not a valid time"
invalid_datetime: "is not a valid datetime"
is_at: "must be at {{restriction}}"
before: "must be before {{restriction}}"
on_or_before: "must be on or before {{restriction}}"
after: "must be 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}}"
The {{restriction}} signifies where the interpolation value for the restriction The {{restriction}} signifies where the interpolation value for the restriction
@@ -352,12 +372,18 @@ Those are Ruby strftime formats not the plugin formats.
=== RSPEC MATCHER: === RSPEC MATCHER:
To sweeten the deal that little bit more, you have an Rspec matcher available for To sweeten the deal that little bit more, you have an Rspec matcher available for
you model specs. Now you can easily test the validations you have just written you model specs. Now you can easily test the validations you have just written
with the plugin or better yet *before* you write them! You just use the with the plugin or better yet *before* you write them! You just use the
validation options you want as you would with the validation method. Those validation options you want as you would with the validation method. Those
options are then verified and reported if they fail. Use it like so: options are then verified and reported if they fail.
@person.should validate_date(:birth_date, :before => Time.now, :before_message => 'should be before today') First require it in your spec_helper.rb
require 'validates_timeliness/matcher'
Use it like so:
@person.should validate_date(:birth_date, :before => Time.now, :before_message => 'should be before today')
The matcher names are just the singular of the validation methods. The matcher names are just the singular of the validation methods.
@@ -373,4 +399,4 @@ The matcher names are just the singular of the validation methods.
== LICENSE: == LICENSE:
Copyright (c) 2008 Adam Meehan, released under the MIT license Copyright (c) 2008-2010 Adam Meehan, released under the MIT license

View File

@@ -3,13 +3,10 @@ require 'rake/gempackagetask'
require 'rubygems/specification' require 'rubygems/specification'
require 'date' require 'date'
require 'spec/rake/spectask' require 'spec/rake/spectask'
require 'lib/validates_timeliness/version'
GEM = "validates_timeliness" GEM = "validates_timeliness"
GEM_VERSION = "2.1.0" GEM_VERSION = ValidatesTimeliness::VERSION
AUTHOR = "Adam Meehan"
EMAIL = "adam.meehan@gmail.com"
HOMEPAGE = "http://github.com/adzap/validates_timeliness"
SUMMARY = "Date and time validation plugin for Rails 2.x which allows custom formats"
spec = Gem::Specification.new do |s| spec = Gem::Specification.new do |s|
s.name = GEM s.name = GEM
@@ -18,12 +15,12 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "validatestime" s.rubyforge_project = "validatestime"
s.has_rdoc = true s.has_rdoc = true
s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"] s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"]
s.summary = SUMMARY s.summary = "Date and time validation plugin for Rails 2.x which allows custom formats"
s.description = s.summary s.description = s.summary
s.author = AUTHOR s.author = "Adam Meehan"
s.email = EMAIL s.email = "adam.meehan@gmail.com"
s.homepage = HOMEPAGE s.homepage = "http://github.com/adzap/validates_timeliness"
s.require_path = 'lib' s.require_path = 'lib'
s.autorequire = GEM s.autorequire = GEM
s.files = %w(LICENSE README.rdoc Rakefile TODO CHANGELOG) + Dir.glob("{lib,spec}/**/*") s.files = %w(LICENSE README.rdoc Rakefile TODO CHANGELOG) + Dir.glob("{lib,spec}/**/*")

3
TODO
View File

@@ -3,3 +3,6 @@
- array of values for all temporal options - array of values for all temporal options
- use tz and zo value from time string? - use tz and zo value from time string?
- filter valid formats rather than remove for hot swapping without recompilation - filter valid formats rather than remove for hot swapping without recompilation
- config generator
- move all config into top namespace
- remove action_view stuff

View File

@@ -2,25 +2,30 @@ require 'validates_timeliness/formats'
require 'validates_timeliness/parser' 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/active_record/attribute_methods' require 'validates_timeliness/active_record/attribute_methods'
require 'validates_timeliness/active_record/multiparameter_attributes' require 'validates_timeliness/active_record/multiparameter_attributes'
require 'validates_timeliness/action_view/instance_tag' require 'validates_timeliness/action_view/instance_tag'
begin
require 'validates_timeliness/core_ext/time' i18n_path = $:.grep(/active_support\/vendor\/i18n-/)
require 'validates_timeliness/core_ext/date' if i18n_path.empty?
require 'validates_timeliness/core_ext/date_time' require 'i18n/version'
else
require i18n_path[0] + '/version'
end
rescue LoadError
end if defined?(I18n)
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 mattr_accessor :use_time_zones
self.use_time_zones = false self.use_time_zones = false
LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/validates_timeliness/locale/en.yml') I18N_LATEST = defined?(I18n::VERSION) && I18n::VERSION >= '0.4.0'
locale_file = I18N_LATEST ? 'en.new.yml' : 'en.old.yml'
LOCALE_PATH = File.expand_path(File.join(File.dirname(__FILE__),'validates_timeliness','locale',locale_file))
class << self class << self
@@ -30,18 +35,18 @@ module ValidatesTimeliness
end end
def load_error_messages def load_error_messages
defaults = YAML::load(IO.read(LOCALE_PATH))['en']
ValidatesTimeliness::Validator.error_value_formats = defaults['validates_timeliness']['error_value_formats'].symbolize_keys
if defined?(I18n) if defined?(I18n)
I18n.load_path.unshift(LOCALE_PATH) I18n.load_path.unshift(LOCALE_PATH)
I18n.reload! I18n.reload!
else else
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 } 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 setup_for_rails def setup_for_rails
self.default_timezone = ::ActiveRecord::Base.default_timezone self.default_timezone = ::ActiveRecord::Base.default_timezone
self.use_time_zones = ::ActiveRecord::Base.time_zone_aware_attributes rescue false self.use_time_zones = ::ActiveRecord::Base.time_zone_aware_attributes rescue false

View File

@@ -1,3 +1,4 @@
# TODO remove this from the plugin for v3.
module ValidatesTimeliness module ValidatesTimeliness
def self.enable_datetime_select_invalid_value_extension! def self.enable_datetime_select_invalid_value_extension!
@@ -6,43 +7,46 @@ module ValidatesTimeliness
module ActionView module ActionView
# Intercepts the date and time select helpers to allow the # Intercepts the date and time select helpers to reuse the values from the
# attribute value before type cast to be used as in the select helpers. # the params rather than the parsed value. This allows invalid date/time
# This means that an invalid date or time will be redisplayed rather than the # values to be redisplayed instead of blanks to aid correction by the user.
# type cast value which would be nil if invalid. # Its a minor usability improvement which is rarely an issue for the user.
module InstanceTag #
module InstanceTag
def self.included(base) def self.included(base)
selector_method = Rails::VERSION::STRING < '2.2' ? :date_or_time_select : :datetime_selector selector_method = Rails::VERSION::STRING.to_f < 2.2 ? :date_or_time_select : :datetime_selector
base.class_eval do base.class_eval do
alias_method :datetime_selector_without_timeliness, selector_method alias_method :datetime_selector_without_timeliness, selector_method
alias_method selector_method, :datetime_selector_with_timeliness alias_method selector_method, :datetime_selector_with_timeliness
end end
base.alias_method_chain :value, :timeliness base.alias_method_chain :value, :timeliness
end end
TimelinessDateTime = Struct.new(:year, :month, :day, :hour, :min, :sec) TimelinessDateTime = Struct.new(:year, :month, :day, :hour, :min, :sec)
def datetime_selector_with_timeliness(*args) def datetime_selector_with_timeliness(*args)
@timeliness_date_or_time_tag = true @timeliness_date_or_time_tag = true
datetime_selector_without_timeliness(*args) datetime_selector_without_timeliness(*args)
end end
def value_with_timeliness(object) def value_with_timeliness(object)
return value_without_timeliness(object) unless @timeliness_date_or_time_tag unless @timeliness_date_or_time_tag && @template_object.params[@object_name]
raw_value = value_before_type_cast(object)
if raw_value.nil? || raw_value.acts_like?(:time) || raw_value.is_a?(Date)
return value_without_timeliness(object) return value_without_timeliness(object)
end end
time_array = ValidatesTimeliness::Formats.parse(raw_value, :datetime) pairs = @template_object.params[@object_name].select {|k,v| k =~ /^#{@method_name}\(/ }
return value_without_timeliness(object) if pairs.empty?
TimelinessDateTime.new(*time_array[0..5])
end values = pairs.map do |(param, value)|
position = param.scan(/\(([0-9]*).*\)/).first.first
[position, value]
end.sort {|a,b| a[0] <=> b[0] }.map {|v| v[1] }
TimelinessDateTime.new(*values)
end
end end
end end
end end

View File

@@ -24,7 +24,6 @@ module ValidatesTimeliness
def write_date_time_attribute(attr_name, value, type, time_zone_aware) def write_date_time_attribute(attr_name, value, type, time_zone_aware)
@attributes_cache["_#{attr_name}_before_type_cast"] = value @attributes_cache["_#{attr_name}_before_type_cast"] = value
value = ValidatesTimeliness::Parser.parse(value, type) value = ValidatesTimeliness::Parser.parse(value, type)
if value && type != :date if value && type != :date
@@ -36,7 +35,8 @@ module ValidatesTimeliness
end end
def read_attribute_before_type_cast_with_timeliness(attr_name) def read_attribute_before_type_cast_with_timeliness(attr_name)
return @attributes_cache["_#{attr_name}_before_type_cast"] if @attributes_cache.has_key?("_#{attr_name}_before_type_cast") cached_attr = "_#{attr_name}_before_type_cast"
return @attributes_cache[cached_attr] if @attributes_cache.has_key?(cached_attr)
read_attribute_before_type_cast_without_timeliness(attr_name) read_attribute_before_type_cast_without_timeliness(attr_name)
end end
@@ -48,19 +48,25 @@ module ValidatesTimeliness
columns_hash.each do |name, column| columns_hash.each do |name, column|
if [:date, :time, :datetime].include?(column.type) if [:date, :time, :datetime].include?(column.type)
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false method_name = "#{name}="
next if instance_method_already_implemented?(method_name)
class_eval <<-EOV time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
def #{name}=(value) define_method(method_name) do |value|
write_date_time_attribute('#{name}', value, #{column.type.inspect}, #{time_zone_aware}) write_date_time_attribute(name, value, column.type, time_zone_aware)
end end
EOV timeliness_methods << method_name
timeliness_methods << name
end end
end end
# Hack to get around instance_method_already_implemented? caching the
# methods in the ivar. It then appears to subsequent calls that the
# methods defined here, have not been and get defined again.
@_defined_class_methods = nil
define_attribute_methods_without_timeliness define_attribute_methods_without_timeliness
@generated_methods += timeliness_methods # add generated methods which is a Set object hence no += method
timeliness_methods.each {|attr| generated_methods << attr }
end end
end end

View File

@@ -5,14 +5,40 @@ module ValidatesTimeliness
end end
module ActiveRecord module ActiveRecord
class << self
def time_array_to_string(values, type)
values.collect! {|v| v.to_s }
case type
when :date
extract_date_from_multiparameter_attributes(values)
when :time
extract_time_from_multiparameter_attributes(values)
when :datetime
extract_date_from_multiparameter_attributes(values) + " " + extract_time_from_multiparameter_attributes(values)
end
end
def extract_date_from_multiparameter_attributes(values)
year = values[0].blank? ? nil : ValidatesTimeliness::Formats.unambiguous_year(values[0].rjust(2, "0"))
[year, *values.slice(1, 2).map { |s| s.blank? ? nil : s.rjust(2, "0") }].join("-")
end
def extract_time_from_multiparameter_attributes(values)
values[3..5].map { |s| s.blank? ? nil : s.rjust(2, "0") }.join(":")
end
end
module MultiparameterAttributes module MultiparameterAttributes
def self.included(base) def self.included(base)
base.alias_method_chain :execute_callstack_for_multiparameter_attributes, :timeliness base.alias_method_chain :execute_callstack_for_multiparameter_attributes, :timeliness
end end
# Assign dates and times as formatted strings to force the use of the plugin parser # Assign dates and times as formatted strings to force the use of the plugin parser
# and store a before_type_cast value for attribute
def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack) def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
errors = [] errors = []
callstack.each do |name, values| callstack.each do |name, values|
@@ -20,10 +46,10 @@ module ValidatesTimeliness
if column && [:date, :time, :datetime].include?(column.type) if column && [:date, :time, :datetime].include?(column.type)
begin begin
callstack.delete(name) callstack.delete(name)
if values.empty? if values.empty? || values.all?(&:nil?)
send("#{name}=", nil) send("#{name}=", nil)
else else
value = time_array_to_string(values, column.type) value = ValidatesTimeliness::ActiveRecord.time_array_to_string(values, column.type)
send("#{name}=", value) send("#{name}=", value)
end end
rescue => ex rescue => ex
@@ -36,29 +62,7 @@ module ValidatesTimeliness
end end
execute_callstack_for_multiparameter_attributes_without_timeliness(callstack) execute_callstack_for_multiparameter_attributes_without_timeliness(callstack)
end end
def time_array_to_string(values, type)
values.collect! {|v| v.to_s }
case type
when :date
extract_date_from_multiparameter_attributes(values)
when :time
extract_time_from_multiparameter_attributes(values)
when :datetime
extract_date_from_multiparameter_attributes(values) + " " + extract_time_from_multiparameter_attributes(values)
end
end
def extract_date_from_multiparameter_attributes(values)
year = ValidatesTimeliness::Formats.unambiguous_year(values[0].rjust(2, "0"))
[year, *values.slice(1, 2).map { |s| s.rjust(2, "0") }].join("-")
end
def extract_time_from_multiparameter_attributes(values)
values[3..5].map { |s| s.rjust(2, "0") }.join(":")
end
end end
end end

View File

@@ -1,13 +0,0 @@
module ValidatesTimeliness
module CoreExtensions
module Date
def to_dummy_time
::Time.send(ValidatesTimeliness.default_timezone, 2000, 1, 1, 0, 0, 0)
end
end
end
end
Date.send(:include, ValidatesTimeliness::CoreExtensions::Date)

View File

@@ -1,13 +0,0 @@
module ValidatesTimeliness
module CoreExtensions
module DateTime
def to_dummy_time
::Time.send(ValidatesTimeliness.default_timezone, 2000, 1, 1, hour, min, sec)
end
end
end
end
DateTime.send(:include, ValidatesTimeliness::CoreExtensions::DateTime)

View File

@@ -1,13 +0,0 @@
module ValidatesTimeliness
module CoreExtensions
module Time
def to_dummy_time
self.class.send(ValidatesTimeliness.default_timezone, 2000, 1, 1, hour, min, sec)
end
end
end
end
Time.send(:include, ValidatesTimeliness::CoreExtensions::Time)

View File

@@ -1,12 +1,10 @@
require 'date' require 'date'
module ValidatesTimeliness module ValidatesTimeliness
# A date and time format regular expression generator. Allows you to # A date and time parsing library which allows you to add custom formats using
# construct a date, time or datetime format using predefined tokens in # simple predefined tokens. This makes it much easier to catalogue and customize
# a string. This makes it much easier to catalogue and customize the formats # the formats rather than dealing directly with regular expressions.
# rather than dealing directly with regular expressions. The formats are then
# compiled into regular expressions for use validating date or time strings.
# #
# Formats can be added or removed to customize the set of valid date or time # Formats can be added or removed to customize the set of valid date or time
# string values. # string values.
@@ -20,19 +18,28 @@ module ValidatesTimeliness
:datetime_expressions, :datetime_expressions,
:format_tokens, :format_tokens,
:format_proc_args :format_proc_args
# Set the threshold value for a two digit year to be considered last century # Set the threshold value for a two digit year to be considered last century
#
# Default: 30 # Default: 30
# #
# Example: # Example:
# year = '29' is considered 2029 # year = '29' is considered 2029
# year = '30' is considered 1930 # year = '30' is considered 1930
# #
cattr_accessor :ambiguous_year_threshold cattr_accessor :ambiguous_year_threshold
self.ambiguous_year_threshold = 30 self.ambiguous_year_threshold = 30
# Format tokens: # Set the dummy date part for a time type value. Should be an array of 3 values
# being year, month and day in that order.
#
# Default: [ 2000, 1, 1 ] same as ActiveRecord
#
cattr_accessor :dummy_date_for_time_type
self.dummy_date_for_time_type = [ 2000, 1, 1 ]
# Format tokens:
# y = year # y = year
# m = month # m = month
# d = day # d = day
@@ -47,14 +54,14 @@ module ValidatesTimeliness
# #
# All other characters are considered literal. You can embed regexp in the # All other characters are considered literal. You can embed regexp in the
# format but no gurantees that it will remain intact. If you avoid the use # format but no gurantees that it will remain intact. If you avoid the use
# of any token characters and regexp dots or backslashes as special characters # of any token characters and regexp dots or backslashes as special characters
# in the regexp, it may well work as expected. For special characters use # in the regexp, it may well work as expected. For special characters use
# POSIX character clsses for safety. # POSIX character clsses for safety.
# #
# Repeating tokens: # Repeating tokens:
# x = 1 or 2 digits for unit (e.g. 'h' means an hour can be '9' or '09') # x = 1 or 2 digits for unit (e.g. 'h' means an hour can be '9' or '09')
# xx = 2 digits exactly for unit (e.g. 'hh' means an hour can only be '09') # xx = 2 digits exactly for unit (e.g. 'hh' means an hour can only be '09')
# #
# Special Cases: # Special Cases:
# yy = 2 or 4 digit year # yy = 2 or 4 digit year
# yyyy = exactly 4 digit year # yyyy = exactly 4 digit year
@@ -62,10 +69,10 @@ module ValidatesTimeliness
# ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday) # ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
# u = microseconds matches 1 to 6 digits # u = microseconds matches 1 to 6 digits
# #
# Any other invalid combination of repeating tokens will be swallowed up # Any other invalid combination of repeating tokens will be swallowed up
# by the next lowest length valid repeating token (e.g. yyy will be # by the next lowest length valid repeating token (e.g. yyy will be
# replaced with yy) # replaced with yy)
@@time_formats = [ @@time_formats = [
'hh:nn:ss', 'hh:nn:ss',
'hh-nn-ss', 'hh-nn-ss',
@@ -79,7 +86,7 @@ module ValidatesTimeliness
'h-nn_ampm', 'h-nn_ampm',
'h_ampm' 'h_ampm'
] ]
@@date_formats = [ @@date_formats = [
'yyyy-mm-dd', 'yyyy-mm-dd',
'yyyy/mm/dd', 'yyyy/mm/dd',
@@ -92,7 +99,7 @@ module ValidatesTimeliness
'd.m.yy', 'd.m.yy',
'd mmm yy' 'd mmm yy'
] ]
@@datetime_formats = [ @@datetime_formats = [
'yyyy-mm-dd hh:nn:ss', 'yyyy-mm-dd hh:nn:ss',
'yyyy-mm-dd h:nn', 'yyyy-mm-dd h:nn',
@@ -106,14 +113,15 @@ module ValidatesTimeliness
'd/m/yy h:nn', 'd/m/yy h:nn',
'ddd, dd mmm yyyy hh:nn:ss (zo|tz)', # RFC 822 'ddd, dd mmm yyyy hh:nn:ss (zo|tz)', # RFC 822
'ddd mmm d hh:nn:ss zo yyyy', # Ruby time string 'ddd mmm d hh:nn:ss zo yyyy', # Ruby time string
'yyyy-mm-ddThh:nn:ss(?:Z|zo)' # iso 8601 'yyyy-mm-ddThh:nn:ssZ', # iso 8601 without zone offset
'yyyy-mm-ddThh:nn:sszo' # iso 8601 with zone offset
] ]
# All tokens available for format construction. The token array is made of # All tokens available for format construction. The token array is made of
# token regexp, validation regexp and key for format proc mapping if any. # token regexp, validation regexp and key for format proc mapping if any.
# If the token needs no format proc arg then the validation regexp should # If the token needs no format proc arg then the validation regexp should
# not have a capturing group, as all captured groups are passed to the # not have a capturing group, as all captured groups are passed to the
# format proc. # format proc.
# #
# The token regexp should only use a capture group if 'look-behind' anchor # The token regexp should only use a capture group if 'look-behind' anchor
@@ -137,17 +145,17 @@ module ValidatesTimeliness
{ '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})', :offset ] }, { 'zo' => [ /zo/, '([+-]\d{2}:?\d{2})', :offset ] },
{ 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] }, { 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] },
{ '_' => [ /_/, '\s?' ] } { '_' => [ /_/, '\s?' ] }
] ]
# Arguments which 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 be 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.
# #
# The code can be used to manipulate the arg value if required, otherwise # The code can be used to manipulate the arg value if required, otherwise
# should just be the arg name. # should just be the arg name.
# #
@@format_proc_args = { @@format_proc_args = {
@@ -161,15 +169,15 @@ module ValidatesTimeliness
:offset => [7, 'z', 'offset_in_seconds(z)'], :offset => [7, 'z', 'offset_in_seconds(z)'],
:meridian => [nil, 'md', nil] :meridian => [nil, 'md', nil]
} }
class << self class << self
def compile_format_expressions def compile_format_expressions
@@time_expressions = compile_formats(@@time_formats) @@time_expressions = compile_formats(@@time_formats)
@@date_expressions = compile_formats(@@date_formats) @@date_expressions = compile_formats(@@date_formats)
@@datetime_expressions = compile_formats(@@datetime_formats) @@datetime_expressions = compile_formats(@@datetime_formats)
end end
# 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.
@@ -179,6 +187,7 @@ module ValidatesTimeliness
options.reverse_merge!(:strict => true) options.reverse_merge!(:strict => true)
sets = if options[:format] sets = if options[:format]
options[:strict] = true
[ send("#{type}_expressions").assoc(options[:format]) ] [ send("#{type}_expressions").assoc(options[:format]) ]
else else
expression_set(type, string) expression_set(type, string)
@@ -195,9 +204,15 @@ module ValidatesTimeliness
break(proc) if matches = full.match(string.strip) break(proc) if matches = full.match(string.strip)
end end
last = options[:include_offset] ? 8 : 7 last = options[:include_offset] ? 8 : 7
processor.call(*matches[1..last]) if matches if matches
end values = processor.call(*matches[1..last])
values[0..2] = dummy_date_for_time_type if type == :time
return values
end
rescue
nil
end
# Delete formats of specified type. Error raised if format not found. # Delete formats of specified type. Error raised if format not found.
def remove_formats(type, *remove_formats) def remove_formats(type, *remove_formats)
remove_formats.each do |format| remove_formats.each do |format|
@@ -207,10 +222,10 @@ module ValidatesTimeliness
end end
compile_format_expressions compile_format_expressions
end end
# Adds new formats. Must specify format type and can specify a :before # Adds new formats. Must specify format type and can specify a :before
# option to nominate which format the new formats should be inserted in # option to nominate which format the new formats should be inserted in
# front on to take higher precedence. # front on to take higher precedence.
# Error is raised if format already exists or if :before format is not found. # Error is raised if format already exists or if :before format is not found.
def add_formats(type, *add_formats) def add_formats(type, *add_formats)
formats = self.send("#{type}_formats") formats = self.send("#{type}_formats")
@@ -218,7 +233,7 @@ module ValidatesTimeliness
options = add_formats.pop if add_formats.last.is_a?(Hash) options = add_formats.pop if add_formats.last.is_a?(Hash)
before = options[:before] before = options[:before]
raise "Format for :before option #{format} was not found." if before && !formats.include?(before) raise "Format for :before option #{format} was not found." if before && !formats.include?(before)
add_formats.each do |format| add_formats.each do |format|
raise "Format #{format} is already included in #{type} formats" if formats.include?(format) raise "Format #{format} is already included in #{type} formats" if formats.include?(format)
@@ -229,7 +244,7 @@ module ValidatesTimeliness
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.
def remove_us_formats def remove_us_formats
us_format_regexp = /\Am{1,2}[^m]/ us_format_regexp = /\Am{1,2}[^m]/
@@ -237,11 +252,12 @@ module ValidatesTimeliness
datetime_formats.reject! { |format| us_format_regexp =~ format } datetime_formats.reject! { |format| us_format_regexp =~ format }
compile_format_expressions compile_format_expressions
end end
def full_hour(hour, meridian) def full_hour(hour, meridian)
hour = hour.to_i hour = hour.to_i
return hour if meridian.nil? return hour if meridian.nil?
if meridian.delete('.').downcase == 'am' if meridian.delete('.').downcase == 'am'
raise if hour == 0 || hour > 12
hour == 12 ? 0 : hour hour == 12 ? 0 : hour
else else
hour == 12 ? hour : hour + 12 hour == 12 ? hour : hour + 12
@@ -282,18 +298,18 @@ module ValidatesTimeliness
end end
private private
# Compile formats into validation regexps and format procs # Generate regular expression and processor from format string
def format_expression_generator(string_format) def generate_format_expression(string_format)
regexp = string_format.dup regexp = string_format.dup
order = {} order = {}
regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes
format_tokens.each do |token| format_tokens.each do |token|
token_name = token.keys.first token_name = token.keys.first
token_regexp, regexp_str, arg_key = *token.values.first token_regexp, regexp_str, arg_key = *token.values.first
# hack for lack of look-behinds. If has a capture group then is # hack for lack of look-behinds. If has a capture group then is
# considered an anchor to put straight back in the regexp string. # considered an anchor to put straight back in the regexp string.
regexp.gsub!(token_regexp) {|m| "#{$1}" + regexp_str } regexp.gsub!(token_regexp) {|m| "#{$1}" + regexp_str }
order[arg_key] = $~.begin(0) if $~ && !arg_key.nil? order[arg_key] = $~.begin(0) if $~ && !arg_key.nil?
@@ -303,8 +319,8 @@ module ValidatesTimeliness
rescue rescue
raise "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}." raise "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}."
end end
# Generates a proc which when executed maps the regexp capture groups to a # Generates a proc which when executed maps the regexp capture groups to a
# proc argument based on order captured. A time array is built using the proc # proc argument based on order captured. A time array is built using the proc
# 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.
@@ -315,19 +331,19 @@ module ValidatesTimeliness
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 = <<-EOL proc_string = <<-EOL
lambda {|#{args.join(',')}| lambda {|#{args.join(',')}|
md ||= nil md ||= nil
[#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.is_a?(Float) ? i : i.to_i } [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.is_a?(Float) ? i : i.to_i }
} }
EOL EOL
eval proc_string eval proc_string
end end
def compile_formats(formats) def compile_formats(formats)
formats.map { |format| [ format, *format_expression_generator(format) ] } formats.map { |format| [ format, *generate_format_expression(format) ] }
end end
# Pick expression set and combine date and datetimes for # Pick expression set and combine date and datetimes for
# datetime attributes to allow date string as datetime # datetime attributes to allow date string as datetime
def expression_set(type, string) def expression_set(type, string)
case type case type
@@ -344,7 +360,7 @@ module ValidatesTimeliness
end end
end end
end end
end end
end end
end end

View File

@@ -0,0 +1,18 @@
en:
activerecord:
errors:
messages:
invalid_date: "is not a valid date"
invalid_time: "is not a valid time"
invalid_datetime: "is not a valid datetime"
is_at: "must be at %{restriction}"
before: "must be before %{restriction}"
on_or_before: "must be on or before %{restriction}"
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'

View File

@@ -5,7 +5,7 @@ en:
invalid_date: "is not a valid date" invalid_date: "is not a valid date"
invalid_time: "is not a valid time" invalid_time: "is not a valid time"
invalid_datetime: "is not a valid datetime" invalid_datetime: "is not a valid datetime"
equal_to: "must be equal to {{restriction}}" is_at: "must be at {{restriction}}"
before: "must be before {{restriction}}" before: "must be before {{restriction}}"
on_or_before: "must be on or before {{restriction}}" on_or_before: "must be on or before {{restriction}}"
after: "must be after {{restriction}}" after: "must be after {{restriction}}"

View File

@@ -0,0 +1 @@
require 'validates_timeliness/spec/rails/matchers/validate_timeliness'

View File

@@ -7,28 +7,24 @@ module ValidatesTimeliness
return nil if raw_value.blank? return nil if raw_value.blank?
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date) 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.reverse_merge(:strict => true))
return nil if time_array.nil?
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 if type == :date
time_array[0..2] = 2000, 1, 1 if type == :time Date.new(*time_array[0..2]) rescue nil
else
# Date.new enforces days per month, unlike Time make_time(time_array[0..6])
date = Date.new(*time_array[0..2]) unless type == :time end
return date if type == :date
make_time(time_array[0..7])
rescue
nil
end end
def make_time(time_array) def make_time(time_array)
# Enforce date part validity which Time class does not
return nil unless Date.valid_civil?(*time_array[0..2])
if Time.respond_to?(:zone) && ValidatesTimeliness.use_time_zones if Time.respond_to?(:zone) && ValidatesTimeliness.use_time_zones
Time.zone.local(*time_array) Time.zone.local(*time_array)
else else
# Older AR way of handling times with datetime fallback
begin begin
time_zone = ValidatesTimeliness.default_timezone time_zone = ValidatesTimeliness.default_timezone
Time.send(time_zone, *time_array) Time.send(time_zone, *time_array)
@@ -38,6 +34,8 @@ module ValidatesTimeliness
DateTime.civil(*(time_array << zone_offset)) DateTime.civil(*(time_array << zone_offset))
end end
end end
rescue ArgumentError, TypeError
nil
end end
end end

View File

@@ -2,7 +2,7 @@ module Spec
module Rails module Rails
module Matchers module Matchers
class ValidateTimeliness class ValidateTimeliness
VALIDITY_TEST_VALUES = { VALIDITY_TEST_VALUES = {
:date => {:pass => '2000-01-01', :fail => '2000-01-32'}, :date => {:pass => '2000-01-01', :fail => '2000-01-32'},
:time => {:pass => '12:00', :fail => '25:00'}, :time => {:pass => '12:00', :fail => '25:00'},
@@ -10,7 +10,7 @@ module Spec
} }
OPTION_TEST_SETTINGS = { OPTION_TEST_SETTINGS = {
:equal_to => { :method => :+, :modify_on => :invalid }, :is_at => { :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 },
@@ -25,34 +25,33 @@ module Spec
def matches?(record) def matches?(record)
@record = record @record = record
@type = @options[:type] @type = @options[:type]
valid = test_validity valid = test_validity
valid = test_option(:equal_to) if @options[:equal_to] && valid valid = test_option(:is_at) if valid && @options[:is_at]
valid = test_option(:before) if @options[:before] && valid valid = test_option(:before) if valid && @options[:before]
valid = test_option(:after) if @options[:after] && valid valid = test_option(:after) if valid && @options[:after]
valid = test_option(:on_or_before) if @options[:on_or_before] && valid valid = test_option(:on_or_before) if valid && @options[:on_or_before]
valid = test_option(:on_or_after) if @options[:on_or_after] && valid valid = test_option(:on_or_after) if valid && @options[:on_or_after]
valid = test_between if valid && @options[:between]
valid = test_between if @options[:between] && valid
return valid return valid
end end
def failure_message def failure_message
"expected model to validate #{@type} attribute #{@expected.inspect} with #{@last_failure}" "expected model to validate #{@type} attribute #{@expected.inspect} with #{@last_failure}"
end end
def negative_failure_message def negative_failure_message
"expected not to validate #{@type} attribute #{@expected.inspect}" "expected not to validate #{@type} attribute #{@expected.inspect}"
end end
def description def description
"have validated #{@type} attribute #{@expected.inspect}" "have validated #{@type} attribute #{@expected.inspect}"
end end
private private
def test_validity def test_validity
invalid_value = VALIDITY_TEST_VALUES[@type][:fail] invalid_value = VALIDITY_TEST_VALUES[@type][:fail]
valid_value = parse_and_cast(VALIDITY_TEST_VALUES[@type][:pass]) valid_value = parse_and_cast(VALIDITY_TEST_VALUES[@type][:pass])
@@ -63,7 +62,7 @@ module Spec
def test_option(option) def test_option(option)
settings = OPTION_TEST_SETTINGS[option] settings = OPTION_TEST_SETTINGS[option]
boundary = parse_and_cast(@options[option]) boundary = parse_and_cast(@options[option])
method = settings[:method] method = settings[:method]
valid_value, invalid_value = if settings[:modify_on] == :valid valid_value, invalid_value = if settings[:modify_on] == :valid
@@ -71,27 +70,27 @@ module Spec
else else
[ boundary, boundary.send(method, 1) ] [ boundary, boundary.send(method, 1) ]
end end
error_matching(invalid_value, option) && error_matching(invalid_value, option) &&
no_error_matching(valid_value, option) no_error_matching(valid_value, option)
end end
def test_before def test_before
before = parse_and_cast(@options[:before]) before = parse_and_cast(@options[:before])
error_matching(before - 1, :before) && error_matching(before - 1, :before) &&
no_error_matching(before, :before) no_error_matching(before, :before)
end end
def test_between def test_between
between = parse_and_cast(@options[:between]) between = parse_and_cast(@options[:between])
error_matching(between.first - 1, :between) && error_matching(between.first - 1, :between) &&
error_matching(between.last + 1, :between) && error_matching(between.last + 1, :between) &&
no_error_matching(between.first, :between) && no_error_matching(between.first, :between) &&
no_error_matching(between.last, :between) no_error_matching(between.last, :between)
end end
def parse_and_cast(value) def parse_and_cast(value)
value = @validator.class.send(:evaluate_option_value, value, @type, @record) value = @validator.class.send(:evaluate_option_value, value, @type, @record)
@validator.class.send(:type_cast_value, value, @type) @validator.class.send(:type_cast_value, value, @type)
@@ -106,7 +105,7 @@ module Spec
@last_failure = "error matching '#{match}' when value is #{format_value(value)}" unless pass @last_failure = "error matching '#{match}' when value is #{format_value(value)}" unless pass
pass pass
end end
def no_error_matching(value, option) def no_error_matching(value, option)
pass = !error_matching(value, option) pass = !error_matching(value, option)
unless pass unless pass
@@ -116,24 +115,28 @@ module Spec
pass pass
end end
def error_message_for(option) def error_message_for(message)
msg = @validator.error_messages[option] restriction = @validator.class.send(:evaluate_option_value, @validator.configuration[message], @type, @record)
restriction = @validator.class.send(:evaluate_option_value, @validator.configuration[option], @type, @record)
if restriction if restriction
restriction = [restriction] unless restriction.is_a?(Array) restriction = @validator.class.send(:type_cast_value, restriction, @type)
restriction.map! {|r| @validator.class.send(:type_cast_value, r, @type) } interpolate = @validator.send(:interpolation_values, message, restriction)
interpolate = @validator.send(:interpolation_values, option, restriction ) end
# get I18n message if defined and has interpolation keys in msg
if defined?(I18n) && !@validator.send(:custom_error_messages).include?(option) if defined?(I18n)
msg = @record.errors.generate_message(@expected, option, interpolate) interpolate ||= {}
options = interpolate.merge(:default => @validator.send(:custom_error_messages)[message])
if defined?(ActiveRecord::Error)
ActiveRecord::Error.new(@record, @expected, message, options).message
else else
msg = msg % interpolate @record.errors.generate_message(@expected, message, options)
end end
end else
msg interpolate ||= nil
@validator.error_messages[message] % interpolate
end
end end
def format_value(value) def format_value(value)
return value if value.is_a?(String) return value if value.is_a?(String)
value.strftime(@validator.class.error_value_formats[@type]) value.strftime(@validator.class.error_value_formats[@type])

View File

@@ -1,58 +1,54 @@
#TODO remove deprecated option :equal_to
module ValidatesTimeliness module ValidatesTimeliness
class Validator class Validator
cattr_accessor :error_value_formats
cattr_accessor :ignore_restriction_errors cattr_accessor :ignore_restriction_errors
self.ignore_restriction_errors = false self.ignore_restriction_errors = false
RESTRICTION_METHODS = { RESTRICTION_METHODS = {
:is_at => :==,
:equal_to => :==, :equal_to => :==,
:before => :<, :before => :<,
:after => :>, :after => :>,
:on_or_before => :<=, :on_or_before => :<=,
:on_or_after => :>=, :on_or_after => :>=,
:between => lambda {|v, r| (r.first..r.last).include?(v) } :between => lambda {|v, r| (r.first..r.last).include?(v) }
} }
VALID_OPTIONS = [ VALID_OPTION_KEYS = [
:on, :if, :unless, :allow_nil, :empty, :allow_blank, :on, :if, :unless, :allow_nil, :empty, :allow_blank,
:with_time, :with_date, :ignore_usec, :format, :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
DEFAULT_OPTIONS = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false, :ignore_usec => false }
attr_reader :configuration, :type attr_reader :configuration, :type
def initialize(configuration) def initialize(configuration)
defaults = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false, :ignore_usec => false } @configuration = DEFAULT_OPTIONS.merge(configuration)
@configuration = defaults.merge(configuration)
@type = @configuration.delete(:type) @type = @configuration.delete(:type)
validate_options(@configuration) validate_options(@configuration)
end end
def call(record, attr_name, value) def call(record, attr_name, value)
raw_value = raw_value(record, attr_name) || value raw_value = raw_value(record, attr_name) || value
if value.is_a?(String) || configuration[:format] if value.is_a?(String) || configuration[:format]
strict = !configuration[:format].nil? value = ValidatesTimeliness::Parser.parse(raw_value, type, :strict => false, :format => configuration[:format])
value = ValidatesTimeliness::Parser.parse(raw_value, type, :strict => strict, :format => configuration[:format])
end 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])
if raw_value.blank? return add_error(record, attr_name, :blank) if raw_value.blank?
add_error(record, attr_name, :blank) return add_error(record, attr_name, "invalid_#{type}".to_sym) if value.nil?
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 def error_messages
@error_messages ||= self.class.default_error_messages.merge(custom_error_messages) @error_messages ||= ::ActiveRecord::Errors.default_error_messages.merge(custom_error_messages)
end end
private private
@@ -60,23 +56,22 @@ module ValidatesTimeliness
def raw_value(record, attr_name) def raw_value(record, attr_name)
record.send("#{attr_name}_before_type_cast") rescue nil record.send("#{attr_name}_before_type_cast") rescue nil
end end
def validate_restrictions(record, attr_name, value) def validate_restrictions(record, attr_name, value)
value = if configuration[:with_time] || configuration[:with_date] if configuration[:with_time] || configuration[:with_date]
restriction_type = :datetime value = combine_date_and_time(value, record)
combine_date_and_time(value, record)
else
restriction_type = type
self.class.type_cast_value(value, type, configuration[:ignore_usec])
end end
value = self.class.type_cast_value(value, implied_type, configuration[:ignore_usec])
return if value.nil? return if value.nil?
RESTRICTION_METHODS.each do |option, method| RESTRICTION_METHODS.each do |option, method|
next unless restriction = configuration[option] next unless restriction = configuration[option]
begin begin
restriction = self.class.evaluate_option_value(restriction, restriction_type, record) restriction = self.class.evaluate_option_value(restriction, implied_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, implied_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))
@@ -90,14 +85,13 @@ module ValidatesTimeliness
end end
def interpolation_values(option, restriction) def interpolation_values(option, restriction)
format = self.class.error_value_formats[type] format = self.class.error_value_format_for(type)
restriction = [restriction] unless restriction.is_a?(Array) restriction = [restriction] unless restriction.is_a?(Array)
if defined?(I18n) if defined?(I18n)
message = I18n.t('activerecord.errors.messages')[option]
subs = message.scan(/\{\{([^\}]*)\}\}/)
interpolations = {} interpolations = {}
subs.each_with_index {|s, i| interpolations[s[0].to_sym] = restriction[i].strftime(format) } keys = restriction.size == 1 ? [:restriction] : [:earliest, :latest]
keys.each_with_index {|key, i| interpolations[key] = restriction[i].strftime(format) }
interpolations interpolations
else else
restriction.map {|r| r.strftime(format) } restriction.map {|r| r.strftime(format) }
@@ -114,21 +108,20 @@ module ValidatesTimeliness
comparator.call(value, restriction) comparator.call(value, restriction)
end end
end end
def add_error(record, attr_name, message, interpolate=nil) def add_error(record, attr_name, message, interpolate=nil)
if defined?(I18n) if defined?(I18n)
custom = custom_error_messages[message] custom = custom_error_messages[message]
record.errors.add(attr_name, message, { :default => custom }.merge(interpolate || {})) record.errors.add(attr_name, message, { :default => custom }.merge(interpolate || {}))
else else
message = error_messages[message] if message.is_a?(Symbol) message = error_messages[message] if message.is_a?(Symbol)
message = message % interpolate record.errors.add(attr_name, message % interpolate)
record.errors.add(attr_name, message)
end end
end 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)
msgs[md[1].to_sym] = v msgs[md[1].to_sym] = v
end end
msgs msgs
@@ -149,35 +142,34 @@ module ValidatesTimeliness
end end
def validate_options(options) def validate_options(options)
if options.key?(:equal_to)
::ActiveSupport::Deprecation.warn("ValidatesTimeliness :equal_to option is deprecated due to clash with a default Rails option. Use :is_at instead. You will need to fix any I18n error message references to this option date/time attributes now.")
options[:is_at] = options.delete(:equal_to)
options[:is_at_message] = options.delete(:equal_to_message)
end
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_OPTION_KEYS - invalid_for_type)
end
def implied_type
@implied_type ||= configuration[:with_date] || configuration[:with_time] ? :datetime : type
end end
# class methods # class methods
class << self class << self
def default_error_messages def error_value_format_for(type)
if defined?(I18n) if defined?(I18n)
I18n.t('activerecord.errors.messages') # work around for syntax check in vendored I18n for Rails <= 2.3.3
I18n.t('validates_timeliness.error_value_formats')[type] || error_value_formats[type]
else else
::ActiveRecord::Errors.default_error_messages error_value_formats[type]
end end
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 when Time, Date
@@ -185,7 +177,8 @@ module ValidatesTimeliness
when Symbol when Symbol
evaluate_option_value(record.send(value), type, record) evaluate_option_value(record.send(value), type, record)
when Proc when Proc
evaluate_option_value(value.call(record), type, record) result = value.arity > 0 ? value.call(record) : value.call
evaluate_option_value(result, type, record)
when Array when Array
value.map {|r| evaluate_option_value(r, type, record) }.sort value.map {|r| evaluate_option_value(r, type, record) }.sort
when Range when Range
@@ -201,11 +194,11 @@ module ValidatesTimeliness
else else
value = case type value = case type
when :time when :time
value.to_dummy_time dummy_time(value)
when :date when :date
value.to_date value.to_date
when :datetime when :datetime
if value.is_a?(DateTime) || value.is_a?(Time) if value.is_a?(Time) || value.is_a?(DateTime)
value.to_time value.to_time
else else
value.to_time(ValidatesTimeliness.default_timezone) value.to_time(ValidatesTimeliness.default_timezone)
@@ -221,6 +214,16 @@ module ValidatesTimeliness
end end
end end
def dummy_time(value)
if value.is_a?(Time) || value.is_a?(DateTime)
time = [value.hour, value.min, value.sec]
else
time = [0,0,0]
end
dummy_date = ValidatesTimeliness::Formats.dummy_date_for_time_type
ValidatesTimeliness::Parser.make_time(dummy_date + time)
end
end end
end end

View File

@@ -0,0 +1,3 @@
module ValidatesTimeliness
VERSION = "2.3.2"
end

View File

@@ -1,38 +1,194 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe ValidatesTimeliness::ActionView::InstanceTag, :type => :helper do describe 'ValidatesTimeliness::ActionView::InstanceTag' do
include ActionView::Helpers::DateHelper
include ActionController::Assertions::SelectorAssertions
before do before do
@person = Person.new @person = Person.new
end end
it "should display invalid datetime as datetime_select values" do def params
@person.birth_date_and_time = "2008-02-30 12:00:22" @params ||= {}
output = datetime_select(:person, :birth_date_and_time, :include_blank => true, :include_seconds => true) end
output.should have_tag('select[id=person_birth_date_and_time_1i]') do describe "datetime_select" do
with_tag('option[selected=selected]', '2008') it "should use param values when attribute is nil" do
params["person"] = {
"birth_date_and_time(1i)" => 2009,
"birth_date_and_time(2i)" => 2,
"birth_date_and_time(3i)" => 29,
"birth_date_and_time(4i)" => 12,
"birth_date_and_time(5i)" => 13,
"birth_date_and_time(6i)" => 14,
}
@person.birth_date_and_time = nil
output = datetime_select(:person, :birth_date_and_time, :include_blank => true, :include_seconds => true)
output.should have_tag('select[id=person_birth_date_and_time_1i] option[selected=selected]', '2009')
output.should have_tag('select[id=person_birth_date_and_time_2i] option[selected=selected]', 'February')
output.should have_tag('select[id=person_birth_date_and_time_3i] option[selected=selected]', '29')
output.should have_tag('select[id=person_birth_date_and_time_4i] option[selected=selected]', '12')
output.should have_tag('select[id=person_birth_date_and_time_5i] option[selected=selected]', '13')
output.should have_tag('select[id=person_birth_date_and_time_6i] option[selected=selected]', '14')
end end
output.should have_tag('select[id=person_birth_date_and_time_2i]') do
with_tag('option[selected=selected]', 'February') it "should override object values and use params if present" do
params["person"] = {
"birth_date_and_time(1i)" => 2009,
"birth_date_and_time(2i)" => 2,
"birth_date_and_time(3i)" => 29,
"birth_date_and_time(4i)" => 13,
"birth_date_and_time(5i)" => 14,
"birth_date_and_time(6i)" => 15,
}
@person.birth_date_and_time = "2009-03-01 13:14:15"
output = datetime_select(:person, :birth_date_and_time, :include_blank => true, :include_seconds => true)
output.should have_tag('select[id=person_birth_date_and_time_1i] option[selected=selected]', '2009')
output.should have_tag('select[id=person_birth_date_and_time_2i] option[selected=selected]', 'February')
output.should have_tag('select[id=person_birth_date_and_time_3i] option[selected=selected]', '29')
output.should have_tag('select[id=person_birth_date_and_time_4i] option[selected=selected]', '13')
output.should have_tag('select[id=person_birth_date_and_time_5i] option[selected=selected]', '14')
output.should have_tag('select[id=person_birth_date_and_time_6i] option[selected=selected]', '15')
end end
output.should have_tag('select[id=person_birth_date_and_time_3i]') do
with_tag('option[selected=selected]', '30') it "should select attribute values from object if no params" do
@person.birth_date_and_time = "2009-01-02 13:14:15"
output = datetime_select(:person, :birth_date_and_time, :include_blank => true, :include_seconds => true)
output.should have_tag('select[id=person_birth_date_and_time_1i] option[selected=selected]', '2009')
output.should have_tag('select[id=person_birth_date_and_time_2i] option[selected=selected]', 'January')
output.should have_tag('select[id=person_birth_date_and_time_3i] option[selected=selected]', '2')
output.should have_tag('select[id=person_birth_date_and_time_4i] option[selected=selected]', '13')
output.should have_tag('select[id=person_birth_date_and_time_5i] option[selected=selected]', '14')
output.should have_tag('select[id=person_birth_date_and_time_6i] option[selected=selected]', '15')
end end
output.should have_tag('select[id=person_birth_date_and_time_4i]') do
with_tag('option[selected=selected]', '12') it "should select attribute values if params does not contain attribute params" do
@person.birth_date_and_time = "2009-01-02 13:14:15"
params["person"] = { }
output = datetime_select(:person, :birth_date_and_time, :include_blank => true, :include_seconds => true)
output.should have_tag('select[id=person_birth_date_and_time_1i] option[selected=selected]', '2009')
output.should have_tag('select[id=person_birth_date_and_time_2i] option[selected=selected]', 'January')
output.should have_tag('select[id=person_birth_date_and_time_3i] option[selected=selected]', '2')
output.should have_tag('select[id=person_birth_date_and_time_4i] option[selected=selected]', '13')
output.should have_tag('select[id=person_birth_date_and_time_5i] option[selected=selected]', '14')
output.should have_tag('select[id=person_birth_date_and_time_6i] option[selected=selected]', '15')
end end
output.should have_tag('select[id=person_birth_date_and_time_5i]') do
with_tag('option[selected=selected]', '00') it "should not select values when attribute value is nil and has no param values" do
end @person.birth_date_and_time = nil
output.should have_tag('select[id=person_birth_date_and_time_6i]') do output = datetime_select(:person, :birth_date_and_time, :include_blank => true, :include_seconds => true)
with_tag('option[selected=selected]', '22') output.should_not have_tag('select[id=person_birth_date_and_time_1i] option[selected=selected]')
output.should_not have_tag('select[id=person_birth_date_and_time_2i] option[selected=selected]')
output.should_not have_tag('select[id=person_birth_date_and_time_3i] option[selected=selected]')
output.should_not have_tag('select[id=person_birth_date_and_time_4i] option[selected=selected]')
output.should_not have_tag('select[id=person_birth_date_and_time_5i] option[selected=selected]')
output.should_not have_tag('select[id=person_birth_date_and_time_6i] option[selected=selected]')
end end
end end
it "should display datetime_select when datetime value is nil" do describe "date_select" do
@person.birth_date_and_time = nil it "should use param values when attribute is nil" do
output = datetime_select(:person, :birth_date_and_time, :include_blank => true, :include_seconds => true) params["person"] = {
output.should have_tag('select', 6) "birth_date(1i)" => 2009,
"birth_date(2i)" => 2,
"birth_date(3i)" => 29,
}
@person.birth_date = nil
output = date_select(:person, :birth_date, :include_blank => true, :include_seconds => true)
output.should have_tag('select[id=person_birth_date_1i] option[selected=selected]', '2009')
output.should have_tag('select[id=person_birth_date_2i] option[selected=selected]', 'February')
output.should have_tag('select[id=person_birth_date_3i] option[selected=selected]', '29')
end
it "should override object values and use params if present" do
params["person"] = {
"birth_date(1i)" => 2009,
"birth_date(2i)" => 2,
"birth_date(3i)" => 29,
}
@person.birth_date = "2009-03-01"
output = date_select(:person, :birth_date, :include_blank => true, :include_seconds => true)
output.should have_tag('select[id=person_birth_date_1i] option[selected=selected]', '2009')
output.should have_tag('select[id=person_birth_date_2i] option[selected=selected]', 'February')
output.should have_tag('select[id=person_birth_date_3i] option[selected=selected]', '29')
end
it "should select attribute values from object if no params" do
@person.birth_date = "2009-01-02"
output = date_select(:person, :birth_date, :include_blank => true, :include_seconds => true)
output.should have_tag('select[id=person_birth_date_1i] option[selected=selected]', '2009')
output.should have_tag('select[id=person_birth_date_2i] option[selected=selected]', 'January')
output.should have_tag('select[id=person_birth_date_3i] option[selected=selected]', '2')
end
it "should select attribute values if params does not contain attribute params" do
@person.birth_date = "2009-01-02"
params["person"] = { }
output = date_select(:person, :birth_date, :include_blank => true, :include_seconds => true)
output.should have_tag('select[id=person_birth_date_1i] option[selected=selected]', '2009')
output.should have_tag('select[id=person_birth_date_2i] option[selected=selected]', 'January')
output.should have_tag('select[id=person_birth_date_3i] option[selected=selected]', '2')
end
it "should not select values when attribute value is nil and has no param values" do
@person.birth_date = nil
output = date_select(:person, :birth_date, :include_blank => true, :include_seconds => true)
output.should_not have_tag('select[id=person_birth_date_1i] option[selected=selected]')
output.should_not have_tag('select[id=person_birth_date_2i] option[selected=selected]')
output.should_not have_tag('select[id=person_birth_date_3i] option[selected=selected]')
end
end end
describe "time_select" do
before :all do
Time.now = Time.mktime(2009,1,1)
end
it "should use param values when attribute is nil" do
params["person"] = {
"birth_time(1i)" => 2000,
"birth_time(2i)" => 1,
"birth_time(3i)" => 1,
"birth_time(4i)" => 12,
"birth_time(5i)" => 13,
"birth_time(6i)" => 14,
}
@person.birth_time = nil
output = time_select(:person, :birth_time, :include_blank => true, :include_seconds => true)
output.should have_tag('input[id=person_birth_time_1i][value=2000]')
output.should have_tag('input[id=person_birth_time_2i][value=1]')
output.should have_tag('input[id=person_birth_time_3i][value=1]')
output.should have_tag('select[id=person_birth_time_4i] option[selected=selected]', '12')
output.should have_tag('select[id=person_birth_time_5i] option[selected=selected]', '13')
output.should have_tag('select[id=person_birth_time_6i] option[selected=selected]', '14')
end
it "should select attribute values from object if no params" do
@person.birth_time = "13:14:15"
output = time_select(:person, :birth_time, :include_blank => true, :include_seconds => true)
output.should have_tag('input[id=person_birth_time_1i][value=2000]')
output.should have_tag('input[id=person_birth_time_2i][value=1]')
output.should have_tag('input[id=person_birth_time_3i][value=1]')
output.should have_tag('select[id=person_birth_time_4i] option[selected=selected]', '13')
output.should have_tag('select[id=person_birth_time_5i] option[selected=selected]', '14')
output.should have_tag('select[id=person_birth_time_6i] option[selected=selected]', '15')
end
it "should not select values when attribute value is nil and has no param values" do
@person.birth_time = nil
output = time_select(:person, :birth_time, :include_blank => true, :include_seconds => true)
output.should have_tag('input[id=person_birth_time_1i][value=""]')
# Annoyingly these may or not have value attribute depending on rails version.
# output.should have_tag('input[id=person_birth_time_2i][value=""]')
# output.should have_tag('input[id=person_birth_time_3i][value=""]')
output.should_not have_tag('select[id=person_birth_time_4i] option[selected=selected]')
output.should_not have_tag('select[id=person_birth_time_5i] option[selected=selected]')
output.should_not have_tag('select[id=person_birth_time_6i] option[selected=selected]')
end
after :all do
Time.now = nil
end
end
end end

View File

@@ -5,9 +5,12 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
@person = Person.new @person = Person.new
end end
it "should call write_date_time_attribute when date attribute assigned value" do it "should define and call write method on first assign" do
@person.should_receive(:write_date_time_attribute) Person.class_eval { @generated_methods = Set.new; @_defined_class_methods = nil }
@person.birth_date = "2000-01-01" Person.send(:undef_method, :birth_date=) if Person.instance_methods.include?('birth_date=')
person = Person.new
person.should_receive(:write_date_time_attribute)
person.birth_date = "2000-01-01"
end end
it "should call write_date_time_attribute when time attribute assigned value" do it "should call write_date_time_attribute when time attribute assigned value" do
@@ -40,7 +43,7 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
@person.birth_date_and_time = time_string @person.birth_date_and_time = time_string
@person.birth_date_and_time_before_type_cast.should == time_string @person.birth_date_and_time_before_type_cast.should == time_string
end end
it "should return Time object for attribute_before_type_cast when written as Time" do it "should return Time object for attribute_before_type_cast when written as Time" do
@person.birth_date_and_time = Time.mktime(2000, 1, 1, 2, 3, 4) @person.birth_date_and_time = Time.mktime(2000, 1, 1, 2, 3, 4)
@person.birth_date_and_time_before_type_cast.should be_kind_of(Time) @person.birth_date_and_time_before_type_cast.should be_kind_of(Time)
@@ -59,42 +62,40 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
it "should return Time object for datetime attribute read method when assigned string" do it "should return Time object for datetime attribute read method when assigned string" do
@person.birth_date_and_time = "2000-01-01 02:03:04" @person.birth_date_and_time = "2000-01-01 02:03:04"
@person.birth_date_and_time.should be_kind_of(Time) @person.birth_date_and_time.should be_kind_of(Time)
end end
it "should return Date object for date attribute read method when assigned Date object" do it "should return Date object for date attribute read method when assigned Date object" do
@person.birth_date = Date.today @person.birth_date = Date.today
@person.birth_date.should be_kind_of(Date) @person.birth_date.should be_kind_of(Date)
end end
it "should return Date object for date attribute read method when assigned string" do it "should return Date object for date attribute read method when assigned string" do
@person.birth_date = '2000-01-01' @person.birth_date = '2000-01-01'
@person.birth_date.should be_kind_of(Date) @person.birth_date.should be_kind_of(Date)
end end
it "should return nil when time is invalid" do it "should return nil when time is invalid" do
@person.birth_date_and_time = "2000-01-32 02:03:04" @person.birth_date_and_time = "2000-01-32 02:03:04"
@person.birth_date_and_time.should be_nil @person.birth_date_and_time.should be_nil
end end
it "should not save invalid date value to database" do it "should not save invalid date value to database" do
time_string = "2000-01-32 02:03:04" time_string = "2000-01-32 02:03:04"
@person = Person.new
@person.birth_date_and_time = time_string @person.birth_date_and_time = time_string
@person.save @person.save
@person.reload @person.reload
@person.birth_date_and_time_before_type_cast.should be_nil @person.birth_date_and_time_before_type_cast.should be_nil
end end
if RAILS_VER < '2.1' if RAILS_VER < '2.1'
it "should return time object from database in default timezone" do it "should return time object from database in default timezone" do
ActiveRecord::Base.default_timezone = :utc ActiveRecord::Base.default_timezone = :utc
time_string = "2000-01-01 09:00:00" time_string = "2000-01-01 09:00:00"
@person = Person.new
@person.birth_date_and_time = time_string @person.birth_date_and_time = time_string
@person.save @person.save
@person.reload @person.reload
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z').should == time_string + ' GMT' @person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z').should == time_string + ' UTC'
end end
else else
@@ -106,36 +107,51 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z %z').should == time_string + ' EST +1000' @person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z %z').should == time_string + ' EST +1000'
end end
it "should return time object from database in correct timezone" do it "should return time object from database in correct timezone" do
Time.zone = 'Melbourne' Time.zone = 'Melbourne'
time_string = "2000-06-01 09:00:00" time_string = "2000-06-01 09:00:00"
@person = Person.new
@person.birth_date_and_time = time_string @person.birth_date_and_time = time_string
@person.save @person.save
@person.reload @person.reload
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z %z').should == time_string + ' EST +1000' @person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z %z').should == time_string + ' EST +1000'
end end
end end
it "should return correct date value after new value assigned" do it "should return correct date value after new value assigned" do
today = Date.today today = Date.today
tomorrow = Date.today + 1.day tomorrow = Date.today + 1.day
@person = Person.new
@person.birth_date = today @person.birth_date = today
@person.birth_date.should == today @person.birth_date.should == today
@person.birth_date = tomorrow @person.birth_date = tomorrow
@person.birth_date.should == tomorrow @person.birth_date.should == tomorrow
end end
it "should update date attribute on existing object" do it "should update date attribute on existing object" do
today = Date.today today = Date.today
tomorrow = Date.today + 1.day tomorrow = Date.today + 1.day
@person = Person.create(:birth_date => today) person = Person.create(:birth_date => today)
@person.birth_date = tomorrow person.birth_date = tomorrow
@person.save! person.save!
@person.reload person.reload
@person.birth_date.should == tomorrow person.birth_date.should == tomorrow
end
describe "attribute writer" do
it "should be able to be overridden in class" do
Person.class_eval { attr_accessor :birth_date }
person = Person.new
person.birth_date = 'overriden'
person.birth_date.should == 'overriden'
end
after do
Person.class_eval do
undef_method(:birth_date)
undef_method(:birth_date=)
end
end
end end
end end

View File

@@ -6,43 +6,113 @@ describe ValidatesTimeliness::ActiveRecord::MultiparameterAttributes do
end end
it "should convert array for datetime type into datetime string" do it "should convert array for datetime type into datetime string" do
time_string = obj.time_array_to_string([2000,2,1,9,10,11], :datetime) time_string = time_array_to_string([2000,2,1,9,10,11], :datetime)
time_string.should == "2000-02-01 09:10:11" time_string.should == "2000-02-01 09:10:11"
end end
it "should convert array for date type into date string" do it "should convert array for date type into date string" do
time_string = obj.time_array_to_string([2000,2,1], :date) time_string = time_array_to_string([2000,2,1], :date)
time_string.should == "2000-02-01" time_string.should == "2000-02-01"
end end
it "should convert array for time type into time string" do it "should convert array for time type into time string" do
time_string = obj.time_array_to_string([2000,1,1,9,10,11], :time) time_string = time_array_to_string([2000,1,1,9,10,11], :time)
time_string.should == "09:10:11" time_string.should == "09:10:11"
end end
describe "execute_callstack_for_multiparameter_attributes" do describe "execute_callstack_for_multiparameter_attributes" do
before do
@callstack = { describe "for valid values" do
'birth_date_and_time' => [2000,2,1,9,10,11], before do
'birth_date' => [2000,2,1,9,10,11], @callstack = {
'birth_time' => [2000,2,1,9,10,11] 'birth_date_and_time' => [2000,2,1,9,10,11],
} 'birth_date' => [2000,2,1,9,10,11],
'birth_time' => [2000,2,1,9,10,11]
}
end
it "should store datetime string for datetime column" do
obj.should_receive(:birth_date_and_time=).once.with("2000-02-01 09:10:11")
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
end
it "should store date string for a date column" do
obj.should_receive(:birth_date=).once.with("2000-02-01")
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
end
it "should store time string for a time column" do
obj.should_receive(:birth_time=).once.with("09:10:11")
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
end
end end
it "should store datetime string for datetime column" do describe "for invalid values" do
obj.should_receive(:birth_date_and_time=).once.with("2000-02-01 09:10:11") before do
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack) @callstack = {
end 'birth_date_and_time' => [2000,13,1,9,10,11],
'birth_date' => [2000,2,41,9,10,11],
it "should store date string for a date column" do 'birth_time' => [2000,2,1,25,10,11]
obj.should_receive(:birth_date=).once.with("2000-02-01") }
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack) end
it "should store invalid datetime string for datetime column" do
obj.should_receive(:birth_date_and_time=).once.with("2000-13-01 09:10:11")
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
end
it "should store invalid date string for a date column" do
obj.should_receive(:birth_date=).once.with("2000-02-41")
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
end
it "should store invalid time string for a time column" do
obj.should_receive(:birth_time=).once.with("25:10:11")
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
end
end end
it "should store time string for a time column" do describe "for missing values" do
obj.should_receive(:birth_time=).once.with("09:10:11") it "should store nil if all datetime values nil" do
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack) obj.should_receive(:birth_date_and_time=).once.with(nil)
callstack = { 'birth_date_and_time' => [nil,nil,nil,nil,nil,nil] }
obj.send(:execute_callstack_for_multiparameter_attributes, callstack)
end
it "should store nil year as empty value in string" do
obj.should_receive(:birth_date_and_time=).once.with("-02-01 09:10:11")
callstack = { 'birth_date_and_time' => [nil,2,1,9,10,11] }
obj.send(:execute_callstack_for_multiparameter_attributes, callstack)
end
it "should store nil month as empty value in string" do
obj.should_receive(:birth_date_and_time=).once.with("2000--01 09:10:11")
callstack = { 'birth_date_and_time' => [2000,nil,1,9,10,11] }
obj.send(:execute_callstack_for_multiparameter_attributes, callstack)
end
it "should store nil day as empty value in string" do
obj.should_receive(:birth_date_and_time=).once.with("2000-02- 09:10:11")
callstack = { 'birth_date_and_time' => [2000,2,nil,9,10,11] }
obj.send(:execute_callstack_for_multiparameter_attributes, callstack)
end
it "should store nil hour as empty value in string" do
obj.should_receive(:birth_date_and_time=).once.with("2000-02-01 :10:11")
callstack = { 'birth_date_and_time' => [2000,2,1,nil,10,11] }
obj.send(:execute_callstack_for_multiparameter_attributes, callstack)
end
it "should store nil minute as empty value in string" do
obj.should_receive(:birth_date_and_time=).once.with("2000-02-01 09:10:")
callstack = { 'birth_date_and_time' => [2000,2,1,9,10,nil] }
obj.send(:execute_callstack_for_multiparameter_attributes, callstack)
end
end end
end end
def time_array_to_string(*args)
ValidatesTimeliness::ActiveRecord.time_array_to_string(*args)
end
end end

View File

@@ -1,31 +0,0 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe ValidatesTimeliness::CoreExtensions::Date do
before do
@a_date = Date.new(2008, 7, 1)
end
it "should make a date value into a dummy time value" do
@a_date.to_dummy_time.should == Time.utc(2000,1,1,0,0,0)
end
end
describe ValidatesTimeliness::CoreExtensions::Time do
before do
@a_time = Time.mktime(2008, 7, 1, 2, 3, 4)
end
it "should make a time value into a dummy time value" do
@a_time.to_dummy_time.should == Time.utc(2000,1,1,2,3,4)
end
end
describe ValidatesTimeliness::CoreExtensions::DateTime do
before do
@a_datetime = DateTime.new(2008, 7, 1, 2, 3, 4)
end
it "should make a datetime value into a dummy time value" do
@a_datetime.to_dummy_time.should == Time.utc(2000,1,1,2,3,4)
end
end

View File

@@ -1,33 +1,28 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper') require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
describe ValidatesTimeliness::Formats do describe ValidatesTimeliness::Formats do
attr_reader :formats
before do
@formats = ValidatesTimeliness::Formats
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
generate_proc('yyyy-mm-dd').call('2000', '1', '2').should == [2000,1,2,0,0,0,0] generate_proc('yyyy-mm-dd').call('2000', '1', '2').should == [2000,1,2,0,0,0,0]
end end
it "should generate proc which outputs date array from format with different order" do it "should generate proc which outputs date array from format with different order" do
generate_proc('dd/mm/yyyy').call('2', '1', '2000').should == [2000,1,2,0,0,0,0] generate_proc('dd/mm/yyyy').call('2', '1', '2000').should == [2000,1,2,0,0,0,0]
end end
it "should generate proc which outputs time array" do it "should generate proc which outputs time array" do
generate_proc('hh:nn:ss').call('01', '02', '03').should == [0,0,0,1,2,3,0] generate_proc('hh:nn:ss').call('01', '02', '03').should == [0,0,0,1,2,3,0]
end end
it "should generate proc which outputs time array with meridian 'pm' adjusted hour" do it "should generate proc which outputs time array with meridian 'pm' adjusted hour" do
generate_proc('hh:nn:ss ampm').call('01', '02', '03', 'pm').should == [0,0,0,13,2,3,0] generate_proc('hh:nn:ss ampm').call('01', '02', '03', 'pm').should == [0,0,0,13,2,3,0]
end end
it "should generate proc which outputs time array with meridian 'am' unadjusted hour" do it "should generate proc which outputs time array with meridian 'am' unadjusted hour" do
generate_proc('hh:nn:ss ampm').call('01', '02', '03', 'am').should == [0,0,0,1,2,3,0] generate_proc('hh:nn:ss ampm').call('01', '02', '03', 'am').should == [0,0,0,1,2,3,0]
end end
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
@@ -36,11 +31,11 @@ describe ValidatesTimeliness::Formats 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] 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 end
describe "validation regexps" do describe "validate regexps" do
describe "for time formats" do describe "for time formats" do
format_tests = { format_tests = {
'hh:nn:ss' => {:pass => ['12:12:12', '01:01:01'], :fail => ['1:12:12', '12:1:12', '12:12:1', '12-12-12']}, 'hh:nn:ss' => {:pass => ['12:12:12', '01:01:01'], :fail => ['1:12:12', '12:1:12', '12:12:1', '12-12-12']},
'hh-nn-ss' => {:pass => ['12-12-12', '01-01-01'], :fail => ['1-12-12', '12-1-12', '12-12-1', '12:12:12']}, 'hh-nn-ss' => {:pass => ['12-12-12', '01-01-01'], :fail => ['1-12-12', '12-1-12', '12-12-1', '12:12:12']},
'h:nn' => {:pass => ['12:12', '1:01'], :fail => ['12:2', '12-12']}, 'h:nn' => {:pass => ['12:12', '1:01'], :fail => ['12:2', '12-12']},
@@ -59,9 +54,9 @@ describe ValidatesTimeliness::Formats do
values[:pass].each {|value| value.should match(regexp)} values[:pass].each {|value| value.should match(regexp)}
values[:fail].each {|value| value.should_not match(regexp)} values[:fail].each {|value| value.should_not match(regexp)}
end end
end end
end end
describe "for date formats" do describe "for date formats" do
format_tests = { format_tests = {
'yyyy/mm/dd' => {:pass => ['2000/02/01'], :fail => ['2000\02\01', '2000/2/1', '00/02/01']}, 'yyyy/mm/dd' => {:pass => ['2000/02/01'], :fail => ['2000\02\01', '2000/2/1', '00/02/01']},
@@ -73,50 +68,57 @@ describe ValidatesTimeliness::Formats do
'd\m\yy' => {:pass => ['1\2\01', '1\02\00', '01\02\2000'], :fail => ['1\2\0', '1/2/01']}, 'd\m\yy' => {:pass => ['1\2\01', '1\02\00', '01\02\2000'], :fail => ['1\2\0', '1/2/01']},
'd-m-yy' => {:pass => ['1-2-01', '1-02-00', '01-02-2000'], :fail => ['1-2-0', '1/2/01']}, 'd-m-yy' => {:pass => ['1-2-01', '1-02-00', '01-02-2000'], :fail => ['1-2-0', '1/2/01']},
'd.m.yy' => {:pass => ['1.2.01', '1.02.00', '01.02.2000'], :fail => ['1.2.0', '1/2/01']}, 'd.m.yy' => {:pass => ['1.2.01', '1.02.00', '01.02.2000'], :fail => ['1.2.0', '1/2/01']},
'd mmm yy' => {:pass => ['1 Feb 00', '1 Feb 2000', '1 February 00', '01 February 2000'], 'd mmm yy' => {:pass => ['1 Feb 00', '1 Feb 2000', '1 February 00', '01 February 2000'],
:fail => ['1 Fe 00', 'Feb 1 2000', '1 Feb 0']} :fail => ['1 Fe 00', 'Feb 1 2000', '1 Feb 0']}
} }
format_tests.each do |format, values| format_tests.each do |format, values|
it "should correctly validate dates in format '#{format}'" do it "should correctly validate dates in format '#{format}'" do
regexp = generate_regexp(format) regexp = generate_regexp(format)
values[:pass].each {|value| value.should match(regexp)} values[:pass].each {|value| value.should match(regexp)}
values[:fail].each {|value| value.should_not match(regexp)} values[:fail].each {|value| value.should_not match(regexp)}
end end
end end
end end
describe "for datetime formats" do describe "for datetime formats" do
format_tests = { format_tests = {
'ddd mmm d hh:nn:ss zo yyyy' => {:pass => ['Sat Jul 19 12:00:00 +1000 2008'], :fail => []}, 'ddd mmm d hh:nn:ss zo yyyy' => {:pass => ['Sat Jul 19 12:00:00 +1000 2008'], :fail => []},
'yyyy-mm-ddThh:nn:ss(?:Z|zo)' => {:pass => ['2008-07-19T12:00:00+10:00', '2008-07-19T12:00:00Z'], :fail => ['2008-07-19T12:00:00Z+10:00']}, 'yyyy-mm-ddThh:nn:ss(?:Z|zo)' => {:pass => ['2008-07-19T12:00:00+10:00', '2008-07-19T12:00:00Z'], :fail => ['2008-07-19T12:00:00Z+10:00']},
} }
format_tests.each do |format, values| format_tests.each do |format, values|
it "should correctly validate datetimes in format '#{format}'" do it "should correctly validate datetimes in format '#{format}'" do
regexp = generate_regexp(format) regexp = generate_regexp(format)
values[:pass].each {|value| value.should match(regexp)} values[:pass].each {|value| value.should match(regexp)}
values[:fail].each {|value| value.should_not match(regexp)} values[:fail].each {|value| value.should_not match(regexp)}
end end
end end
end end
end end
describe "parse" 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, :strict => true) time_array = formats.parse('12:13:14', :time, :strict => true)
time_array.should == [0,0,0,12,13,14,0] time_array.should == [2000,1,1,12,13,14,0]
end end
it "should return nil if time hour is out of range for AM meridian" do
time_array = formats.parse('13:14 am', :time, :strict => true)
time_array.should == nil
time_array = formats.parse('00:14 am', :time, :strict => true)
time_array.should == nil
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, :strict => 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, :strict => 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, :strict => 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]
@@ -131,18 +133,18 @@ describe ValidatesTimeliness::Formats do
time_array = formats.parse('01-02-2000 12:13', :date, :strict => 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:13', :time, :strict => false) time_array = formats.parse('2000-02-01 12:13', :time, :strict => false)
time_array.should == [0,0,0,12,13,0,0] time_array.should == [2000,1,1,12,13,0,0]
end end
it "should return zone offset when :include_offset options is true" do it "should return zone offset when :include_offset option is true" do
time_array = formats.parse('2000-02-01T12:13:14-10:30', :datetime, :include_offset => true) 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] time_array.should == [2000,2,1,12,13,14,0,-37800]
end end
end end
describe "parse with format option" do describe "parse with format option" do
it "should return values if string matches specified format" do it "should return values if string matches specified format" do
time_array = formats.parse('2000-02-01 12:13:14', :datetime, :format => 'yyyy-mm-dd hh:nn:ss') time_array = formats.parse('2000-02-01 12:13:14', :datetime, :format => 'yyyy-mm-dd hh:nn:ss')
@@ -177,66 +179,94 @@ describe ValidatesTimeliness::Formats do
end end
end end
describe "parse with custom dummy date values" do
before(:all) do
@old_dummy_date = formats.dummy_date_for_time_type
formats.dummy_date_for_time_type = [2009,1,1]
end
it "should return time array with custom dummy date" do
time_array = formats.parse('12:13:14', :time, :strict => true)
time_array.should == [2009,1,1,12,13,14,0]
end
after(:all) do
formats.dummy_date_for_time_type = @old_dummy_date
end
end
describe "parse ISO8601 datetime" do
it "should return array without zone offset when no offset in string" do
time_array = formats.parse('2000-02-01T12:13:14Z', :datetime, :strict => true)
time_array.should == [2000,2,1,12,13,14,0]
end
it "should return array with zone offset when offset in string" do
time_array = formats.parse('2000-02-01T12:13:14+10:00', :datetime, :strict => true)
time_array.should == [2000,2,1,12,13,14,0,36000]
end
end
describe "removing formats" do 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")
end end
it "should not match time after its format is removed" do it "should not match time after its format is removed" do
validate('2.12am', :time).should be_true validate('2.12am', :time).should be_true
formats.remove_formats(:time, 'h.nn_ampm') formats.remove_formats(:time, 'h.nn_ampm')
validate('2.12am', :time).should be_false validate('2.12am', :time).should be_false
end end
it "should raise error if format does not exist" do it "should raise error if format does not exist" do
lambda { formats.remove_formats(:time, "ss:hh:nn") }.should raise_error() lambda { formats.remove_formats(:time, "ss:hh:nn") }.should raise_error()
end end
after do after do
formats.time_formats << 'h.nn_ampm' formats.time_formats << 'h.nn_ampm'
formats.compile_format_expressions formats.compile_format_expressions
end end
end end
describe "adding formats" do describe "adding formats" do
before do before do
formats.compile_format_expressions formats.compile_format_expressions
end end
it "should add format to format array" do it "should add format to format array" do
formats.add_formats(:time, "h o'clock") formats.add_formats(:time, "h o'clock")
formats.time_formats.should include("h o'clock") formats.time_formats.should include("h o'clock")
end end
it "should match new format after its added" do it "should match new format after its added" do
validate("12 o'clock", :time).should be_false validate("12 o'clock", :time).should be_false
formats.add_formats(:time, "h o'clock") formats.add_formats(:time, "h o'clock")
validate("12 o'clock", :time).should be_true validate("12 o'clock", :time).should be_true
end end
it "should add format before specified format and be higher precedence" do it "should add format before specified format and be higher precedence" do
formats.add_formats(:time, "ss:hh:nn", :before => 'hh:nn:ss') formats.add_formats(:time, "ss:hh:nn", :before => 'hh:nn:ss')
validate("59:23:58", :time).should be_true validate("59:23:58", :time).should be_true
time_array = formats.parse('59:23:58', :time) time_array = formats.parse('59:23:58', :time)
time_array.should == [0,0,0,23,58,59,0] time_array.should == [2000,1,1,23,58,59,0]
end end
it "should raise error if format exists" do it "should raise error if format exists" do
lambda { formats.add_formats(:time, "hh:nn:ss") }.should raise_error() lambda { formats.add_formats(:time, "hh:nn:ss") }.should raise_error()
end end
it "should raise error if format exists" do it "should raise error if format exists" do
lambda { formats.add_formats(:time, "ss:hh:nn", :before => 'nn:hh:ss') }.should raise_error() lambda { formats.add_formats(:time, "ss:hh:nn", :before => 'nn:hh:ss') }.should raise_error()
end end
after do after do
formats.time_formats.delete("h o'clock") formats.time_formats.delete("h o'clock")
formats.time_formats.delete("ss:hh:nn") formats.time_formats.delete("ss:hh:nn")
# reload class instead # reload class instead
end end
end end
describe "removing US formats" do describe "removing US formats" do
it "should validate a date as European format when US formats removed" do it "should validate a date as European format when US formats removed" do
time_array = formats.parse('01/02/2000', :date) time_array = formats.parse('01/02/2000', :date)
@@ -245,12 +275,17 @@ describe ValidatesTimeliness::Formats do
time_array = formats.parse('01/02/2000', :date) time_array = formats.parse('01/02/2000', :date)
time_array.should == [2000, 2, 1,0,0,0,0] time_array.should == [2000, 2, 1,0,0,0,0]
end end
after do after do
# reload class # reload class
end end
end end
def formats
ValidatesTimeliness::Formats
end
def validate(time_string, type) def validate(time_string, type)
valid = false valid = false
formats.send("#{type}_expressions").each do |format, regexp, processor| formats.send("#{type}_expressions").each do |format, regexp, processor|
@@ -258,20 +293,20 @@ describe ValidatesTimeliness::Formats do
end end
valid valid
end end
def generate_regexp(format) def generate_regexp(format)
# wrap in line start and end anchors to emulate extract values method # wrap in line start and end anchors to emulate extract values method
/\A#{formats.send(:format_expression_generator, format)[0]}\Z/ /\A#{formats.send(:generate_format_expression, format)[0]}\Z/
end end
def generate_regexp_str(format) def generate_regexp_str(format)
formats.send(:format_expression_generator, format)[0].inspect formats.send(:generate_format_expression, format)[0].inspect
end end
def generate_proc(format) def generate_proc(format)
formats.send(:format_expression_generator, format)[1] formats.send(:generate_format_expression, format)[1]
end end
def delete_format(type, format) def delete_format(type, format)
formats.send("#{type}_formats").delete(format) formats.send("#{type}_formats").delete(format)
end end

View File

@@ -1,15 +1,15 @@
# For use with the ginger gem to test plugin against multiple versions of Rails. # For use with the ginger gem to test plugin against multiple versions of Rails.
# #
# To use ginger: # To use ginger:
# #
# sudo gem install freelancing-god-ginger --source=http://gems.github.com # gem install ginger
# #
# Then run # Then run
# #
# ginger spec # ginger spec
# #
Ginger.configure do |config| Ginger.configure do |config|
rails_versions = ['2.0.2', '2.1.2', '2.2.2', '2.3.2'] rails_versions = ['2.0.2', '2.1.2', '2.2.2', '2.3.3', '2.3.4', '2.3.5', '2.3.6', '2.3.9']
rails_versions.each do |v| rails_versions.each do |v|
g = Ginger::Scenario.new("Rails #{v}") g = Ginger::Scenario.new("Rails #{v}")

View File

@@ -7,6 +7,10 @@ describe ValidatesTimeliness::Parser do
it "should return time object for valid time string" do it "should return time object for valid time string" do
parse("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 Time object for ISO 8601 string with time zone" do
parse("2000-01-01T12:23:42+09:00", :datetime).should be_kind_of(Time)
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("2000-02-30 12:13:14", :datetime).should be_nil parse("2000-02-30 12:13:14", :datetime).should be_nil
@@ -19,12 +23,12 @@ describe ValidatesTimeliness::Parser do
it "should return Time object when passed a Time object" do it "should return Time object when passed a Time object" do
parse(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("2000-01-01 12:13:14", :datetime) time = parse("2000-06-01 12:13:14", :datetime)
Time.zone.utc_offset.should == 10.hours time.utc_offset.should == 10.hours
end end
end end

View File

@@ -1,27 +1,28 @@
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
require 'validates_timeliness/matcher'
class NoValidation < Person class NoValidation < Person
end end
class WithValidation < Person class WithValidation < Person
validates_date :birth_date, validates_date :birth_date,
:equal_to => '2000-01-01', :is_at => '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',
:on_or_after => '2000-01-02', :on_or_after => '2000-01-02',
: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', :is_at => '09:00',
:before => '23:00', :before => '23:00',
:after => '09:00', :after => '09:00',
:on_or_before => '22:00', :on_or_before => '22:00',
:on_or_after => '10:00', :on_or_after => '10:00',
: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', :is_at => '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',
@@ -45,44 +46,44 @@ end
describe "ValidateTimeliness matcher" do describe "ValidateTimeliness matcher" do
attr_accessor :no_validation, :with_validation attr_accessor :no_validation, :with_validation
@@attribute_for_type = { :date => :birth_date, :time => :birth_time, :datetime => :birth_date_and_time } @@attribute_for_type = { :date => :birth_date, :time => :birth_time, :datetime => :birth_date_and_time }
before do before do
@no_validation = NoValidation.new @no_validation = NoValidation.new
@with_validation = WithValidation.new @with_validation = WithValidation.new
end end
[:date, :time, :datetime].each do |type| [:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type)) with_validation.should self.send("validate_#{type}", attribute_for_type(type))
end end
it "should report that #{type} is not validated" do it "should report that #{type} is not validated" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type)) no_validation.should_not self.send("validate_#{type}", attribute_for_type(type))
end end
end end
describe "with equal_to option" do describe "with is_at option" do
test_values = { test_values = {
:date => ['2000-01-01', '2000-01-02'], :date => ['2000-01-01', '2000-01-02'],
:time => ['09:00', '09:01'], :time => ['09:00', '09:01'],
:datetime => ['2000-01-01 09:00', '2000-01-01 09:01'] :datetime => ['2000-01-01 09:00', '2000-01-01 09:01']
} }
[:date, :time, :datetime].each do |type| [:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do 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]) with_validation.should self.send("validate_#{type}", attribute_for_type(type), :is_at => test_values[type][0])
end end
it "should report that #{type} is not validated when option value is incorrect" do 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]) with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :is_at => test_values[type][1])
end end
it "should report that #{type} is not validated with option" do 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]) no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :is_at => test_values[type][0])
end end
end end
end end
@@ -91,111 +92,111 @@ describe "ValidateTimeliness matcher" do
test_values = { test_values = {
:date => ['2000-01-10', '2000-01-11'], :date => ['2000-01-10', '2000-01-11'],
:time => ['23:00', '22:59'], :time => ['23:00', '22:59'],
:datetime => ['2000-01-10 23:00', '2000-01-10 22:59'] :datetime => ['2000-01-10 23:00', '2000-01-10 22:59']
} }
[:date, :time, :datetime].each do |type| [:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][0]) with_validation.should self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][0])
end end
it "should report that #{type} is not validated when option value is incorrect" do 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), :before => test_values[type][1]) with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][1])
end end
it "should report that #{type} is not validated with option" do it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][0]) no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][0])
end end
end end
end end
describe "with after option" do describe "with after option" do
test_values = { test_values = {
:date => ['2000-01-01', '2000-01-02'], :date => ['2000-01-01', '2000-01-02'],
:time => ['09:00', '09:01'], :time => ['09:00', '09:01'],
:datetime => ['2000-01-01 09:00', '2000-01-01 09:01'] :datetime => ['2000-01-01 09:00', '2000-01-01 09:01']
} }
[:date, :time, :datetime].each do |type| [:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][0]) with_validation.should self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][0])
end end
it "should report that #{type} is not validated when option value is incorrect" do 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), :after => test_values[type][1]) with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][1])
end end
it "should report that #{type} is not validated with option" do it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][0]) no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][0])
end end
end end
end end
describe "with on_or_before option" do describe "with on_or_before option" do
test_values = { test_values = {
:date => ['2000-01-09', '2000-01-08'], :date => ['2000-01-09', '2000-01-08'],
:time => ['22:00', '21:59'], :time => ['22:00', '21:59'],
:datetime => ['2000-01-09 23:00', '2000-01-09 22:59'] :datetime => ['2000-01-09 23:00', '2000-01-09 22:59']
} }
[:date, :time, :datetime].each do |type| [:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][0]) with_validation.should self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][0])
end end
it "should report that #{type} is not validated when option value is incorrect" do 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), :on_or_before => test_values[type][1]) with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][1])
end end
it "should report that #{type} is not validated with option" do it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][0]) no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][0])
end end
end end
end end
describe "with on_or_after option" do describe "with on_or_after option" do
test_values = { test_values = {
:date => ['2000-01-02', '2000-01-03'], :date => ['2000-01-02', '2000-01-03'],
:time => ['10:00', '10:01'], :time => ['10:00', '10:01'],
:datetime => ['2000-01-02 09:00', '2000-01-02 09:01'] :datetime => ['2000-01-02 09:00', '2000-01-02 09:01']
} }
[:date, :time, :datetime].each do |type| [:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][0]) with_validation.should self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][0])
end end
it "should report that #{type} is not validated when option value is incorrect" do 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), :on_or_after => test_values[type][1]) with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][1])
end end
it "should report that #{type} is not validated with option" do it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][0]) no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][0])
end end
end end
end end
describe "between option" do describe "between option" do
test_values = { test_values = {
:date => [ ['2000-01-01', '2000-01-03'], ['2000-01-01', '2000-01-04'] ], :date => [ ['2000-01-01', '2000-01-03'], ['2000-01-01', '2000-01-04'] ],
:time => [ ['09:00', '17:00'], ['09:00', '17:01'] ], :time => [ ['09:00', '17:00'], ['09:00', '17:01'] ],
:datetime => [ ['2000-01-01 09:00', '2000-01-01 17:00'], ['2000-01-01 09:00', '2000-01-01 17:01'] ] :datetime => [ ['2000-01-01 09:00', '2000-01-01 17:00'], ['2000-01-01 09:00', '2000-01-01 17:01'] ]
} }
[:date, :time, :datetime].each do |type| [:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][0]) with_validation.should self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][0])
end end
it "should report that #{type} is not validated when option value is incorrect" do 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), :between => test_values[type][1]) with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][1])
end end
it "should report that #{type} is not validated with option" do it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][0]) no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][0])
end end
@@ -207,35 +208,35 @@ describe "ValidateTimeliness matcher" do
before do before do
@person = CustomMessages.new @person = CustomMessages.new
end end
it "should match error message for invalid" do it "should match error message for invalid" do
@person.should validate_date(:birth_date, :invalid_date_message => 'is not really a date') @person.should validate_date(:birth_date, :invalid_date_message => 'is not really a date')
end end
it "should match error message for before option" do it "should match error message for before option" do
@person.should validate_date(:birth_date, :before => '2000-01-10', @person.should validate_date(:birth_date, :before => '2000-01-10',
:invalid_date_message => 'is not really a date', :invalid_date_message => 'is not really a date',
:before_message => 'is too late') :before_message => 'is too late')
end end
it "should match error message for after option" do it "should match error message for after option" do
@person.should validate_date(:birth_date, :after => '2000-01-01', @person.should validate_date(:birth_date, :after => '2000-01-01',
:invalid_date_message => 'is not really a date', :invalid_date_message => 'is not really a date',
:after_message => 'is too early') :after_message => 'is too early')
end end
it "should match error message for on_or_before option" do it "should match error message for on_or_before option" do
@person.should validate_date(:birth_date, :on_or_before => '2000-01-09', @person.should validate_date(:birth_date, :on_or_before => '2000-01-09',
:invalid_date_message => 'is not really a date', :invalid_date_message => 'is not really a date',
:on_or_before_message => 'is just too late') :on_or_before_message => 'is just too late')
end end
it "should match error message for on_or_after option" do it "should match error message for on_or_after option" do
@person.should validate_date(:birth_date, :on_or_after => '2000-01-02', @person.should validate_date(:birth_date, :on_or_after => '2000-01-02',
:invalid_date_message => 'is not really a date', :invalid_date_message => 'is not really a date',
:on_or_after_message => 'is just too early') :on_or_after_message => 'is just too early')
end end
end end
def attribute_for_type(type) def attribute_for_type(type)

View File

@@ -13,7 +13,7 @@ if vendored = File.exists?(vendored_rails)
Dir.glob(vendored_rails + "/**/lib").each { |dir| $:.unshift dir } Dir.glob(vendored_rails + "/**/lib").each { |dir| $:.unshift dir }
else else
begin begin
require 'ginger' require 'ginger'
rescue LoadError rescue LoadError
end end
if ENV['VERSION'] if ENV['VERSION']
@@ -45,6 +45,7 @@ if RAILS_VER >= '2.1'
end end
require 'validates_timeliness' require 'validates_timeliness'
require 'validates_timeliness/matcher'
ValidatesTimeliness.enable_datetime_select_extension! ValidatesTimeliness.enable_datetime_select_extension!

View File

@@ -3,6 +3,14 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
describe ValidatesTimeliness::Validator do describe ValidatesTimeliness::Validator do
attr_accessor :person, :validator attr_accessor :person, :validator
if ValidatesTimeliness::I18N_LATEST
I18N_REGEXP = /\%\{\w*\}/
I18N_INTERPOLATION = '%{%s}'
else
I18N_REGEXP = /\{\{\w*\}\}/
I18N_INTERPOLATION = '{{%s}}'
end
before :all do before :all do
# freezes time using time_travel plugin # freezes time using time_travel plugin
Time.now = Time.utc(2000, 1, 1, 0, 0, 0) Time.now = Time.utc(2000, 1, 1, 0, 0, 0)
@@ -12,13 +20,17 @@ describe ValidatesTimeliness::Validator do
Time.now = nil Time.now = nil
end end
before :each do def person
@person = Person.new @person ||= Person.new
end end
describe "option keys validation" do describe "option keys validation" do
before(:all) do
ActiveSupport::Deprecation.silenced = true
end
before do before do
keys = ValidatesTimeliness::Validator::VALID_OPTIONS - [:invalid_date_message, :invalid_time_message, :with_date, :with_time] keys = ValidatesTimeliness::Validator::VALID_OPTION_KEYS - [:invalid_date_message, :invalid_time_message, :with_date, :with_time]
@valid_options = keys.inject({}) {|hash, opt| hash[opt] = nil; hash } @valid_options = keys.inject({}) {|hash, opt| hash[opt] = nil; hash }
end end
@@ -30,6 +42,15 @@ describe ValidatesTimeliness::Validator do
it "should not raise error if option keys are valid" do it "should not raise error if option keys are valid" do
lambda { Person.validates_datetime(@valid_options) }.should_not raise_error(ArgumentError) lambda { Person.validates_datetime(@valid_options) }.should_not raise_error(ArgumentError)
end end
it "should display deprecation notice for :equal_to" do
::ActiveSupport::Deprecation.should_receive(:warn)
Person.validates_datetime :equal_to => Time.now
end
after(:all) do
ActiveSupport::Deprecation.silenced = false
end
end end
describe "evaluate_option_value" do describe "evaluate_option_value" do
@@ -305,7 +326,7 @@ describe ValidatesTimeliness::Validator do
validate_with(:birth_date, 1.day.from_now.to_date) validate_with(:birth_date, 1.day.from_now.to_date)
should_have_no_error(:birth_date, :between) should_have_no_error(:birth_date, :between)
end end
it "should allow a range for between restriction" do it "should allow a range for between restriction" do
configure_validator(:type => :date, :between => (1.day.ago.to_date)..(1.day.from_now.to_date)) configure_validator(:type => :date, :between => (1.day.ago.to_date)..(1.day.from_now.to_date))
validate_with(:birth_date, 1.day.from_now.to_date) validate_with(:birth_date, 1.day.from_now.to_date)
@@ -346,69 +367,67 @@ describe ValidatesTimeliness::Validator do
end end
end end
describe "instance with :equal_to restriction" do describe "instance with :is_at restriction" do
describe "for datetime type" do describe "for datetime type" do
before do before do
configure_validator(:equal_to => Time.now) configure_validator(:is_at => Time.now)
end end
it "should have error when value not equal to :equal_to restriction" do it "should have error when value not equal to :is_at restriction" do
validate_with(:birth_date_and_time, Time.now + 1) validate_with(:birth_date_and_time, Time.now + 1)
should_have_error(:birth_date_and_time, :equal_to) should_have_error(:birth_date_and_time, :is_at)
end end
it "should have error when value is equal to :equal_to restriction for all values except microscond, and microsecond is not ignored" do it "should be valid when value is equal to :is_at restriction" do
configure_validator(:equal_to => Time.utc(2000, 1, 1, 0, 0, 0, 0), :ignore_usec => false)
validate_with(:birth_date_and_time, Time.utc(2000, 1, 1, 0, 0, 0, 500))
should_have_error(:birth_date_and_time, :equal_to)
end
it "should be valid when value is equal to :equal_to restriction for all values except microscond, and microsecond is ignored" do
configure_validator(:equal_to => Time.utc(2000, 1, 1, 0, 0, 0, 0), :ignore_usec => true)
validate_with(:birth_date_and_time, Time.utc(2000, 1, 1, 0, 0, 0, 500))
should_have_no_error(:birth_date_and_time, :equal_to)
end
it "should be valid when value is equal to :equal_to restriction" do
validate_with(:birth_date_and_time, Time.now) validate_with(:birth_date_and_time, Time.now)
should_have_no_error(:birth_date_and_time, :equal_to) should_have_no_error(:birth_date_and_time, :is_at)
end end
end end
describe "for date type" do describe "for date type" do
before do before do
configure_validator(:type => :date, :equal_to => Date.today) configure_validator(:type => :date, :is_at => Date.today)
end end
it "should have error when value is not equal to :equal_to restriction" do it "should have error when value is not equal to :is_at restriction" do
validate_with(:birth_date, Date.today + 1) validate_with(:birth_date, Date.today + 1)
should_have_error(:birth_date, :equal_to) should_have_error(:birth_date, :is_at)
end end
it "should be valid when value is equal to :equal_to restriction" do it "should be valid when value is equal to :is_at restriction" do
validate_with(:birth_date, Date.today) validate_with(:birth_date, Date.today)
should_have_no_error(:birth_date, :equal_to) should_have_no_error(:birth_date, :is_at)
end end
end end
describe "for time type" do describe "for time type" do
before do before do
configure_validator(:type => :time, :equal_to => "09:00:00") configure_validator(:type => :time, :is_at => "09:00:00")
end end
it "should have error when value is not equal to :equal_to restriction" do it "should have error when value is not equal to :is_at restriction" do
validate_with(:birth_time, "09:00:01") validate_with(:birth_time, "09:00:01")
should_have_error(:birth_time, :equal_to) should_have_error(:birth_time, :is_at)
end end
it "should be valid when value is equal to :equal_to restriction" do it "should be valid when value is equal to :is_at restriction" do
validate_with(:birth_time, "09:00:00") validate_with(:birth_time, "09:00:00")
should_have_no_error(:birth_time, :equal_to) should_have_no_error(:birth_time, :is_at)
end end
end end
end end
describe "instance with :ignore_usec option" do
it "should ignore usec on time values when evaluated" do
configure_validator(:is_at => Time.utc(2000, 1, 1, 0, 0, 0, 0), :ignore_usec => true)
validate_with(:birth_date_and_time, Time.utc(2000, 1, 1, 0, 0, 0, 500))
should_have_no_error(:birth_date_and_time, :is_at)
end
end
describe "instance with :with_time option" do describe "instance with :with_time option" do
it "should validate date attribute as datetime combining value of :with_time against restrictions " do it "should validate date attribute as datetime combining value of :with_time against restrictions " do
@@ -423,20 +442,31 @@ describe ValidatesTimeliness::Validator do
should_have_no_error(:birth_date, :on_or_before) should_have_no_error(:birth_date, :on_or_before)
end end
it "should should ignore usec value on combined value if :ignore_usec option is true" do
configure_validator(:type => :date, :with_time => Time.mktime(2000,1,1,12,30,0,500), :is_at => Time.mktime(2000,1,1,12,30), :ignore_usec => true)
validate_with(:birth_date, "2000-01-01")
should_have_no_error(:birth_date, :is_at)
end
end end
describe "instance with :with_date option" do describe "instance with :with_date option" do
it "should validate time attribute as datetime combining value of :with_date against restrictions " do it "should validate time attribute as datetime combining value of :with_date against restrictions " do
configure_validator(:type => :time, :with_date => '2009-01-01', :on_or_before => Time.mktime(2000,1,1,12,30)) configure_validator(:type => :time, :with_date => '2009-01-01', :on_or_before => Time.mktime(2000,1,1,12,30))
validate_with(:birth_date, "12:30") validate_with(:birth_time, "12:30")
should_have_error(:birth_date, :on_or_before) should_have_error(:birth_time, :on_or_before)
end end
it "should skip restriction validation if :with_date value is nil" do it "should skip restriction validation if :with_date value is nil" do
configure_validator(:type => :time, :with_date => nil, :on_or_before => Time.mktime(2000,1,1,12,30)) configure_validator(:type => :time, :with_date => nil, :on_or_before => Time.mktime(2000,1,1,12,30))
validate_with(:birth_date, "12:30") validate_with(:birth_time, "12:30")
should_have_no_error(:birth_date, :on_or_before) should_have_no_error(:birth_time, :on_or_before)
end
it "should should ignore usec value on combined value if :ignore_usec option is true" do
configure_validator(:type => :time, :with_date => Date.new(2000,1,1), :on_or_before => Time.mktime(2000,1,1,12,30), :ignore_usec => true)
validate_with(:birth_time, Time.mktime(2000,1,1,12,30,0,50))
should_have_no_error(:birth_time, :on_or_before)
end end
end end
@@ -487,6 +517,39 @@ describe ValidatesTimeliness::Validator do
end end
end end
if defined?(I18n)
describe "localized error messages" do
before(:all) do
message = "retfa #{I18N_INTERPOLATION}" % 'restriction'
translations = {
:activerecord => {:errors => {:messages => { :after => message }}},
:validates_timeliness => {:error_value_formats => {}}
}
I18n.backend.store_translations 'zz', translations
I18n.locale = :zz
end
it "should be used if defined" do
configure_validator(:type => :date, :after => Date.today)
validate_with(:birth_date, 1.day.ago)
person.errors.on(:birth_date).should match(/retfa/)
end
it "should use I18n translation missing message when not defined" do
configure_validator(:type => :date, :on_or_after => Date.today)
validate_with(:birth_date, 1.day.ago)
person.errors.on(:birth_date).should match(/translation missing/)
end
after(:all) do
I18n.locale = :en
end
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')
@@ -557,6 +620,25 @@ describe ValidatesTimeliness::Validator do
validate_with(:birth_time, '11:59') validate_with(:birth_time, '11:59')
person.errors.on(:birth_time).should match(/after \d{2}:\d{2}:\d{2}\Z/) person.errors.on(:birth_time).should match(/after \d{2}:\d{2}:\d{2}\Z/)
end end
if defined?(I18n)
describe "I18n" do
it "should use global default if locale format missing" do
message = "after #{I18N_INTERPOLATION}" % 'restriction'
I18n.backend.store_translations 'zz', :activerecord => {:errors => {:messages => { :after => message }}}
I18n.locale = :zz
configure_validator(:type => :datetime, :after => 1.day.from_now)
validate_with(:birth_date_and_time, Time.now)
person.errors.on(:birth_date_and_time).should match(/after \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\Z/)
end
after do
I18n.locale = :en
end
end
end
end end
describe "custom formats" do describe "custom formats" do
@@ -635,7 +717,7 @@ describe ValidatesTimeliness::Validator do
def error_messages def error_messages
return @error_messages if defined?(@error_messages) return @error_messages if defined?(@error_messages)
messages = validator.send(:error_messages) messages = defined?(I18n) ? I18n.t('activerecord.errors.messages') : validator.send(:error_messages)
@error_messages = messages.inject({}) {|h, (k, v)| h[k] = v.sub(/ (\%s|\{\{\w*\}\}).*/, ''); h } @error_messages = messages.inject({}) {|h, (k, v)| h[k] = v.sub(/ (\%s|#{I18N_REGEXP}).*/, ''); h }
end end
end end

View File

@@ -2,27 +2,27 @@
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = %q{validates_timeliness} s.name = %q{validates_timeliness}
s.version = "2.1.0" s.version = "2.3.2"
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-06-20} s.date = %q{2010-11-07}
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/active_record", "lib/validates_timeliness/active_record/multiparameter_attributes.rb", "lib/validates_timeliness/active_record/attribute_methods.rb", "lib/validates_timeliness/parser.rb", "lib/validates_timeliness/core_ext", "lib/validates_timeliness/core_ext/date.rb", "lib/validates_timeliness/core_ext/time.rb", "lib/validates_timeliness/core_ext/date_time.rb", "lib/validates_timeliness/validator.rb", "lib/validates_timeliness/validation_methods.rb", "lib/validates_timeliness/locale", "lib/validates_timeliness/locale/en.yml", "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/action_view", "lib/validates_timeliness/action_view/instance_tag.rb", "lib/validates_timeliness/formats.rb", "lib/validates_timeliness.rb", "spec/active_record", "spec/active_record/multiparameter_attributes_spec.rb", "spec/active_record/attribute_methods_spec.rb", "spec/formats_spec.rb", "spec/parser_spec.rb", "spec/core_ext", "spec/core_ext/dummy_time_spec.rb", "spec/spec_helper.rb", "spec/ginger_scenarios.rb", "spec/time_travel", "spec/time_travel/time_extensions.rb", "spec/time_travel/time_travel.rb", "spec/time_travel/MIT-LICENSE", "spec/spec", "spec/spec/rails", "spec/spec/rails/matchers", "spec/spec/rails/matchers/validate_timeliness_spec.rb", "spec/validator_spec.rb", "spec/action_view", "spec/action_view/instance_tag_spec.rb", "spec/resources", "spec/resources/schema.rb", "spec/resources/application.rb", "spec/resources/person.rb", "spec/resources/sqlite_patch.rb"] s.files = ["LICENSE", "README.rdoc", "Rakefile", "TODO", "CHANGELOG", "lib/validates_timeliness", "lib/validates_timeliness/action_view", "lib/validates_timeliness/action_view/instance_tag.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/locale", "lib/validates_timeliness/locale/en.new.yml", "lib/validates_timeliness/locale/en.old.yml", "lib/validates_timeliness/matcher.rb", "lib/validates_timeliness/parser.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/validation_methods.rb", "lib/validates_timeliness/validator.rb", "lib/validates_timeliness/version.rb", "lib/validates_timeliness.rb", "spec/action_view", "spec/action_view/instance_tag_spec.rb", "spec/active_record", "spec/active_record/attribute_methods_spec.rb", "spec/active_record/multiparameter_attributes_spec.rb", "spec/formats_spec.rb", "spec/ginger_scenarios.rb", "spec/parser_spec.rb", "spec/resources", "spec/resources/application.rb", "spec/resources/person.rb", "spec/resources/schema.rb", "spec/resources/sqlite_patch.rb", "spec/spec", "spec/spec/rails", "spec/spec/rails/matchers", "spec/spec/rails/matchers/validate_timeliness_spec.rb", "spec/spec_helper.rb", "spec/time_travel", "spec/time_travel/MIT-LICENSE", "spec/time_travel/time_extensions.rb", "spec/time_travel/time_travel.rb", "spec/validator_spec.rb"]
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"]
s.rubyforge_project = %q{validatestime} s.rubyforge_project = %q{validatestime}
s.rubygems_version = %q{1.3.3} s.rubygems_version = %q{1.3.7}
s.summary = %q{Date and time validation plugin for Rails 2.x which allows custom formats} s.summary = %q{Date and time validation plugin for Rails 2.x which allows custom formats}
if s.respond_to? :specification_version then if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3 s.specification_version = 3
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
else else
end end
else else