mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-25 15:22:58 +00:00
Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b874e786b6 | ||
|
|
b356363791 | ||
|
|
e9eb812c9e | ||
|
|
353f154330 | ||
|
|
ccfad007d1 | ||
|
|
f3c0dd6144 | ||
|
|
6349990243 | ||
|
|
220b58ab14 | ||
|
|
f98e503e25 | ||
|
|
3e1a9fc107 | ||
|
|
99d742e3d0 | ||
|
|
77f338d97c | ||
|
|
d255bbfccf | ||
|
|
4df1974524 | ||
|
|
82c0e1bcd3 | ||
|
|
f1a0016bf7 | ||
|
|
59d9957ab6 | ||
|
|
694c3b0ce3 | ||
|
|
a859827af4 | ||
|
|
f8b9f72693 | ||
|
|
7f1ed79a89 | ||
|
|
f3c119e191 | ||
|
|
3bfc7b748f | ||
|
|
40369efdff | ||
|
|
f41016af93 | ||
|
|
5258256d5e | ||
|
|
9cfbb2a458 | ||
|
|
78baa7a3cc | ||
|
|
7d6967da90 | ||
|
|
0c38b6abd1 | ||
|
|
a6712de5ff | ||
|
|
c580c3e682 | ||
|
|
9c5db44500 | ||
|
|
f109443fb7 | ||
|
|
96bf4bf184 | ||
|
|
90be6a6db5 | ||
|
|
4d5d82ff20 | ||
|
|
b11893eac0 | ||
|
|
76e159b350 | ||
|
|
c762b6d4f8 | ||
|
|
91f9f65bc0 | ||
|
|
6db8b7d908 | ||
|
|
d3c5101f92 | ||
|
|
899e96b880 | ||
|
|
162faf632a | ||
|
|
1b865cc834 | ||
|
|
c29478df45 | ||
|
|
df3283e5a1 | ||
|
|
0e382e15f2 | ||
|
|
9a697b9cab | ||
|
|
7bf7ed0569 | ||
|
|
a969a49ae8 | ||
|
|
687e61a3f2 | ||
|
|
4cc20ae620 | ||
|
|
2d510504e6 | ||
|
|
2028d68b17 | ||
|
|
e399c6b510 | ||
|
|
7aa1a87731 | ||
|
|
1a31e7463d | ||
|
|
14c55e3292 | ||
|
|
75a3b2bd83 | ||
|
|
9b300e084b | ||
|
|
d61dddfbc6 | ||
|
|
4ac4281c8b | ||
|
|
a56cf674b2 | ||
|
|
7f4d7b38d7 | ||
|
|
34c0f25225 | ||
|
|
57c3fdca88 | ||
|
|
805bb5d7fd | ||
|
|
7aac14c874 | ||
|
|
7c0c1afe1c | ||
|
|
5295a494e9 | ||
|
|
572d80d227 | ||
|
|
af3a3b84ab | ||
|
|
4df63c7142 | ||
|
|
26066dedfe | ||
|
|
3348ca3063 | ||
|
|
88ee33ae41 | ||
|
|
51afb2852e | ||
|
|
57b8a52f07 | ||
|
|
bb94e234bc | ||
|
|
f041524124 | ||
|
|
e1d23d0f2b | ||
|
|
613001791a | ||
|
|
6d12790d2b | ||
|
|
b197d537f1 | ||
|
|
1e3c802031 | ||
|
|
d89266d9f1 | ||
|
|
9e2d95c3e1 | ||
|
|
fb520bbddc | ||
|
|
8303be05c3 | ||
|
|
f4ed751c26 | ||
|
|
956933f58b | ||
|
|
7967b5a212 | ||
|
|
a836ed8434 | ||
|
|
c2a4f45b5a | ||
|
|
312c1510cb |
42
CHANGELOG
42
CHANGELOG
@@ -1,3 +1,43 @@
|
|||||||
|
= 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]
|
||||||
|
- 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 custom I18n local override of en locale
|
||||||
|
- Dramatically simplified ActiveRecord monkey patching and hackery
|
||||||
|
|
||||||
|
= 2.0.0 [2009-04-12]
|
||||||
|
- Error value formats are now specified in the i18n locale file instead of updating plugin hash. See OTHER CUSTOMISATION section in README.
|
||||||
|
- Date/time select helper extension is disabled by default. To enable see DISPLAY INVALID VALUES IN DATE HELPERS section in README to enable.
|
||||||
|
- Added :format option to limit validation to a single format if desired
|
||||||
|
- Matcher now supports :equal_to option
|
||||||
|
- Formats.parse can take :include_offset option to include offset value from string in seconds, if string contains an offset. Offset not used in rest of plugin yet.
|
||||||
|
- Refactored to remove as much plugin code from ActiveRecord as possible.
|
||||||
|
|
||||||
= 1.1.7 [2009-03-26]
|
= 1.1.7 [2009-03-26]
|
||||||
- Minor change to multiparameter attributes which I had not properly implemented for chaining
|
- Minor change to multiparameter attributes which I had not properly implemented for chaining
|
||||||
|
|
||||||
@@ -37,7 +77,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
|
||||||
|
|||||||
173
README.rdoc
173
README.rdoc
@@ -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,9 +22,9 @@ 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
|
||||||
|
|
||||||
@@ -46,24 +46,24 @@ As gem
|
|||||||
|
|
||||||
== 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,14 +78,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.
|
||||||
|
|
||||||
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
|
||||||
@@ -98,7 +99,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
|
||||||
@@ -106,27 +107,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.
|
||||||
|
|
||||||
@@ -164,13 +165,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
|
||||||
@@ -183,20 +185,20 @@ 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
|
||||||
yyyyy = exactly 4 digit year
|
yyyy = exactly 4 digit year
|
||||||
mmm = month long name (e.g. 'Jul' or 'July')
|
mmm = month long name (e.g. 'Jul' or 'July')
|
||||||
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.
|
||||||
@@ -205,20 +207,20 @@ To see all defined formats look in lib/validates_timeliness/formats.rb.
|
|||||||
|
|
||||||
The perenial problem for non-US developers or applications not primarily for the
|
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/my/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')
|
||||||
@@ -232,15 +234,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')
|
||||||
@@ -249,11 +251,36 @@ Now a time of '59:30:23' will be interpreted as 11:30:59 pm. This option saves
|
|||||||
you adding a new one and deleting an old one to get it to work.
|
you adding a new one and deleting an old one to get it to work.
|
||||||
|
|
||||||
|
|
||||||
|
=== AMBIGUOUS YEAR THRESHOLD
|
||||||
|
|
||||||
|
When dealing with 2 digit year values, by default a year is interpreted as being
|
||||||
|
in the last century at or above 30. You can customize this however
|
||||||
|
|
||||||
|
ValidatesTimeliness::Formats.ambiguous_year_threshold = 20
|
||||||
|
|
||||||
|
Now you get:
|
||||||
|
|
||||||
|
year of 19 is considered 2019
|
||||||
|
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.
|
||||||
|
|
||||||
@@ -262,13 +289,28 @@ 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.
|
||||||
|
|
||||||
|
|
||||||
|
=== DISPLAY INVALID VALUES IN DATE HELPERS:
|
||||||
|
|
||||||
|
The plugin has some extensions to ActionView and ActiveRecord by allowing invalid
|
||||||
|
date and time values to be redisplayed to the user as feedback, instead of
|
||||||
|
a blank field which happens by default in Rails. Though the date helpers make this a
|
||||||
|
pretty rare occurence, given the select dropdowns for each date/time component, but
|
||||||
|
it may be something of interest.
|
||||||
|
|
||||||
|
To activate it, put this in an initializer:
|
||||||
|
|
||||||
|
ValidatesTimeliness.enable_datetime_select_extension!
|
||||||
|
|
||||||
|
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:
|
||||||
@@ -277,6 +319,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",
|
||||||
@@ -292,8 +335,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
|
||||||
@@ -302,24 +351,40 @@ will be inserted.
|
|||||||
And for something a little more specific you can override the format of the interpolation
|
And for something a little more specific you can override the format of the interpolation
|
||||||
values inserted in the error messages for temporal restrictions like so
|
values inserted in the error messages for temporal restrictions like so
|
||||||
|
|
||||||
|
For Rails 2.0/2.1:
|
||||||
|
|
||||||
ValidatesTimeliness::Validator.error_value_formats.update(
|
ValidatesTimeliness::Validator.error_value_formats.update(
|
||||||
:time => '%H:%M:%S',
|
:time => '%H:%M:%S',
|
||||||
:date => '%Y-%m-%d',
|
:date => '%Y-%m-%d',
|
||||||
:datetime => '%Y-%m-%d %H:%M:%S'
|
:datetime => '%Y-%m-%d %H:%M:%S'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Rails 2.2+ using the I18n system to define new defaults:
|
||||||
|
|
||||||
|
validates_timeliness:
|
||||||
|
error_value_formats:
|
||||||
|
date: '%Y-%m-%d'
|
||||||
|
time: '%H:%M:%S'
|
||||||
|
datetime: '%Y-%m-%d %H:%M:%S'
|
||||||
|
|
||||||
Those are Ruby strftime formats not the plugin formats.
|
Those are Ruby strftime formats not the plugin formats.
|
||||||
|
|
||||||
|
|
||||||
=== 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.
|
||||||
@@ -335,4 +400,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
|
||||||
|
|||||||
19
Rakefile
19
Rakefile
@@ -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 = "1.1.7"
|
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}/**/*")
|
||||||
@@ -34,7 +31,7 @@ task :default => :spec
|
|||||||
desc "Run specs"
|
desc "Run specs"
|
||||||
Spec::Rake::SpecTask.new do |t|
|
Spec::Rake::SpecTask.new do |t|
|
||||||
t.spec_files = FileList['spec/**/*_spec.rb']
|
t.spec_files = FileList['spec/**/*_spec.rb']
|
||||||
t.spec_opts = %w(-fs --color)
|
t.spec_opts = %w(--color)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
7
TODO
7
TODO
@@ -1,3 +1,8 @@
|
|||||||
- :format option
|
|
||||||
- valid formats could come from locale file
|
- valid formats could come from locale file
|
||||||
- add replace_formats instead add_formats :before
|
- add replace_formats instead add_formats :before
|
||||||
|
- array of values for all temporal options
|
||||||
|
- use tz and zo value from time string?
|
||||||
|
- filter valid formats rather than remove for hot swapping without recompilation
|
||||||
|
- config generator
|
||||||
|
- move all config into top namespace
|
||||||
|
- remove action_view stuff
|
||||||
|
|||||||
28
benchmark.rb
28
benchmark.rb
@@ -4,73 +4,74 @@ require 'date'
|
|||||||
require 'parsedate'
|
require 'parsedate'
|
||||||
require 'benchmark'
|
require 'benchmark'
|
||||||
require 'rubygems'
|
require 'rubygems'
|
||||||
require 'active_support'
|
|
||||||
require 'active_record'
|
require 'active_record'
|
||||||
require 'action_controller'
|
|
||||||
require 'rails/version'
|
|
||||||
|
|
||||||
require 'validates_timeliness'
|
require 'validates_timeliness'
|
||||||
|
|
||||||
|
def parse(*args)
|
||||||
|
ValidatesTimeliness::Parser.parse(*args)
|
||||||
|
end
|
||||||
|
|
||||||
n = 10000
|
n = 10000
|
||||||
Benchmark.bm do |x|
|
Benchmark.bm do |x|
|
||||||
x.report('timeliness - datetime') {
|
x.report('timeliness - datetime') {
|
||||||
n.times do
|
n.times do
|
||||||
ActiveRecord::Base.parse_date_time("2000-01-04 12:12:12", :datetime)
|
parse("2000-01-04 12:12:12", :datetime)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
x.report('timeliness - date') {
|
x.report('timeliness - date') {
|
||||||
n.times do
|
n.times do
|
||||||
ActiveRecord::Base.parse_date_time("2000-01-04", :date)
|
parse("2000-01-04", :date)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
x.report('timeliness - date as datetime') {
|
x.report('timeliness - date as datetime') {
|
||||||
n.times do
|
n.times do
|
||||||
ActiveRecord::Base.parse_date_time("2000-01-04", :datetime)
|
parse("2000-01-04", :datetime)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
x.report('timeliness - time') {
|
x.report('timeliness - time') {
|
||||||
n.times do
|
n.times do
|
||||||
ActiveRecord::Base.parse_date_time("12:01:02", :time)
|
parse("12:01:02", :time)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
x.report('timeliness - invalid format datetime') {
|
x.report('timeliness - invalid format datetime') {
|
||||||
n.times do
|
n.times do
|
||||||
ActiveRecord::Base.parse_date_time("20xx-01-04 12:12:12", :datetime)
|
parse("20xx-01-04 12:12:12", :datetime)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
x.report('timeliness - invalid format date') {
|
x.report('timeliness - invalid format date') {
|
||||||
n.times do
|
n.times do
|
||||||
ActiveRecord::Base.parse_date_time("20xx-01-04", :date)
|
parse("20xx-01-04", :date)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
x.report('timeliness - invalid format time') {
|
x.report('timeliness - invalid format time') {
|
||||||
n.times do
|
n.times do
|
||||||
ActiveRecord::Base.parse_date_time("12:xx:02", :time)
|
parse("12:xx:02", :time)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
x.report('timeliness - invalid value datetime') {
|
x.report('timeliness - invalid value datetime') {
|
||||||
n.times do
|
n.times do
|
||||||
ActiveRecord::Base.parse_date_time("2000-01-32 12:12:12", :datetime)
|
parse("2000-01-32 12:12:12", :datetime)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
x.report('timeliness - invalid value date') {
|
x.report('timeliness - invalid value date') {
|
||||||
n.times do
|
n.times do
|
||||||
ActiveRecord::Base.parse_date_time("2000-01-32", :date)
|
parse("2000-01-32", :date)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
x.report('timeliness - invalid value time') {
|
x.report('timeliness - invalid value time') {
|
||||||
n.times do
|
n.times do
|
||||||
ActiveRecord::Base.parse_date_time("12:61:02", :time)
|
parse("12:61:02", :time)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
x.report('date/time') {
|
x.report('date/time') {
|
||||||
@@ -96,4 +97,3 @@ Benchmark.bm do |x|
|
|||||||
end
|
end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
require 'validates_timeliness/formats'
|
require 'validates_timeliness/formats'
|
||||||
|
require 'validates_timeliness/parser'
|
||||||
require 'validates_timeliness/validator'
|
require 'validates_timeliness/validator'
|
||||||
require 'validates_timeliness/validation_methods'
|
require 'validates_timeliness/validation_methods'
|
||||||
require 'validates_timeliness/spec/rails/matchers/validate_timeliness' if ENV['RAILS_ENV'] == 'test'
|
|
||||||
|
|
||||||
require 'validates_timeliness/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'
|
||||||
|
|
||||||
require 'validates_timeliness/core_ext/time'
|
|
||||||
require 'validates_timeliness/core_ext/date'
|
|
||||||
require 'validates_timeliness/core_ext/date_time'
|
|
||||||
|
|
||||||
module ValidatesTimeliness
|
module ValidatesTimeliness
|
||||||
|
|
||||||
mattr_accessor :default_timezone
|
|
||||||
|
|
||||||
self.default_timezone = :utc
|
mattr_accessor :default_timezone
|
||||||
|
self.default_timezone = :utc
|
||||||
|
|
||||||
|
mattr_accessor :use_time_zones
|
||||||
|
self.use_time_zones = false
|
||||||
|
|
||||||
LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/validates_timeliness/locale/en.yml')
|
LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/validates_timeliness/locale/en.yml')
|
||||||
|
|
||||||
@@ -27,29 +25,23 @@ 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 += [ LOCALE_PATH ]
|
I18n.load_path.unshift(LOCALE_PATH)
|
||||||
I18n.reload!
|
I18n.reload!
|
||||||
else
|
else
|
||||||
messages = YAML::load(IO.read(LOCALE_PATH))
|
errors = defaults['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h }
|
||||||
errors = messages['en']['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)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_error_messages
|
|
||||||
if Rails::VERSION::STRING < '2.2'
|
|
||||||
::ActiveRecord::Errors.default_error_messages
|
|
||||||
else
|
|
||||||
I18n.translate('activerecord.errors.messages')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup_for_rails
|
def setup_for_rails
|
||||||
major, minor = Rails::VERSION::MAJOR, Rails::VERSION::MINOR
|
|
||||||
self.default_timezone = ::ActiveRecord::Base.default_timezone
|
self.default_timezone = ::ActiveRecord::Base.default_timezone
|
||||||
self.enable_datetime_select_extension!
|
self.use_time_zones = ::ActiveRecord::Base.time_zone_aware_attributes rescue false
|
||||||
self.load_error_messages
|
self.enable_active_record_datetime_parser!
|
||||||
|
load_error_messages
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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 = ParseDate.parsedate(raw_value)
|
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
|
||||||
|
|||||||
@@ -1,127 +1,72 @@
|
|||||||
module ValidatesTimeliness
|
module ValidatesTimeliness
|
||||||
|
|
||||||
|
def self.enable_active_record_datetime_parser!
|
||||||
|
::ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::AttributeMethods)
|
||||||
|
end
|
||||||
|
|
||||||
module ActiveRecord
|
module ActiveRecord
|
||||||
|
|
||||||
# Rails 2.1 removed the ability to retrieve the raw value of a time or datetime
|
# Overrides write method for date, time and datetime columns
|
||||||
# attribute. The raw value is necessary to properly validate a string time or
|
# to use plugin parser. Also adds mechanism to store value
|
||||||
# datetime value instead of the internal Rails type casting which is very limited
|
# before type cast.
|
||||||
# and does not allow custom formats. These methods restore that ability while
|
|
||||||
# respecting the automatic timezone handling.
|
|
||||||
#
|
#
|
||||||
# The automatic timezone handling sets the assigned attribute value to the current
|
|
||||||
# zone in Time.zone. To preserve this localised value and capture the raw value
|
|
||||||
# we cache the localised value on write and store the raw value in the attributes
|
|
||||||
# hash for later retrieval and possibly validation. Any value from the database
|
|
||||||
# will not be in the attribute cache on first read so will be considered in default
|
|
||||||
# timezone and converted to local time. It is then stored back in the attributes
|
|
||||||
# hash and cached to avoid the need for any subsequent differentiation.
|
|
||||||
module AttributeMethods
|
module AttributeMethods
|
||||||
|
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
base.extend ClassMethods
|
base.extend ClassMethods
|
||||||
base.class_eval do
|
base.class_eval do
|
||||||
alias_method_chain :read_attribute, :timeliness
|
alias_method_chain :read_attribute_before_type_cast, :timeliness
|
||||||
class << self
|
class << self
|
||||||
alias_method_chain :define_attribute_methods, :timeliness
|
alias_method_chain :define_attribute_methods, :timeliness
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds check for cached date/time attributes which have been type cast already
|
|
||||||
# and value can be used from cache. This prevents the raw date/time value from
|
|
||||||
# being type cast using default Rails type casting when writing values
|
|
||||||
# to the database.
|
|
||||||
def read_attribute_with_timeliness(attr_name)
|
|
||||||
attr_name = attr_name.to_s
|
|
||||||
if !(value = @attributes[attr_name]).nil?
|
|
||||||
column = column_for_attribute(attr_name)
|
|
||||||
if column && [:date, :time, :datetime].include?(column.type) && @attributes_cache.has_key?(attr_name)
|
|
||||||
return @attributes_cache[attr_name]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
read_attribute_without_timeliness(attr_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
# If Rails dirty attributes is enabled then the value is added to
|
|
||||||
# changed attributes if changed. Can't use the default dirty checking
|
|
||||||
# implementation as it chains the write_attribute method which deletes
|
|
||||||
# the attribute from the cache.
|
|
||||||
def write_date_time_attribute(attr_name, value, type, time_zone_aware)
|
def write_date_time_attribute(attr_name, value, type, time_zone_aware)
|
||||||
new = self.class.parse_date_time(value, type)
|
@attributes_cache["_#{attr_name}_before_type_cast"] = value
|
||||||
|
value = ValidatesTimeliness::Parser.parse(value, type)
|
||||||
|
|
||||||
if new && type != :date
|
if value && type != :date
|
||||||
new = new.to_time
|
value = value.to_time
|
||||||
new = new.in_time_zone if time_zone_aware
|
value = value.in_time_zone if time_zone_aware
|
||||||
end
|
end
|
||||||
|
|
||||||
if defined?(::ActiveRecord::Dirty) && !changed_attributes.include?(attr_name)
|
write_attribute(attr_name.to_sym, value)
|
||||||
old = read_attribute(attr_name)
|
|
||||||
if old != new
|
|
||||||
changed_attributes[attr_name] = (old.duplicable? ? old.clone : old)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@attributes_cache[attr_name] = new
|
|
||||||
@attributes[attr_name] = value
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# If reloading then check if cached, which means its in local time.
|
def read_attribute_before_type_cast_with_timeliness(attr_name)
|
||||||
# If local, convert with parser as local timezone, otherwise use
|
cached_attr = "_#{attr_name}_before_type_cast"
|
||||||
# read_attribute method for quick default type cast of values from
|
return @attributes_cache[cached_attr] if @attributes_cache.has_key?(cached_attr)
|
||||||
# database using default timezone.
|
read_attribute_before_type_cast_without_timeliness(attr_name)
|
||||||
def read_date_time_attribute(attr_name, type, time_zone_aware, reload = false)
|
|
||||||
cached = @attributes_cache[attr_name]
|
|
||||||
return cached if @attributes_cache.has_key?(attr_name) && !reload
|
|
||||||
|
|
||||||
if @attributes_cache.has_key?(attr_name)
|
|
||||||
time = read_attribute_before_type_cast(attr_name)
|
|
||||||
time = self.class.parse_date_time(time, type)
|
|
||||||
else
|
|
||||||
time = read_attribute(attr_name)
|
|
||||||
@attributes[attr_name] = time && time_zone_aware ? time.in_time_zone : time
|
|
||||||
end
|
|
||||||
@attributes_cache[attr_name] = time && time_zone_aware ? time.in_time_zone : time
|
|
||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
|
||||||
# Define attribute reader and writer method for date, time and
|
|
||||||
# datetime attributes to use plugin parser.
|
|
||||||
def define_attribute_methods_with_timeliness
|
def define_attribute_methods_with_timeliness
|
||||||
return if generated_methods?
|
return if generated_methods?
|
||||||
columns_hash.each do |name, column|
|
timeliness_methods = []
|
||||||
unless instance_method_already_implemented?(name)
|
|
||||||
if [:date, :time, :datetime].include?(column.type)
|
|
||||||
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
|
|
||||||
define_read_method_for_dates_and_times(name, column.type, time_zone_aware)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
unless instance_method_already_implemented?("#{name}=")
|
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}="
|
||||||
define_write_method_for_dates_and_times(name, column.type, time_zone_aware)
|
next if instance_method_already_implemented?(method_name)
|
||||||
|
|
||||||
|
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
|
||||||
|
define_method(method_name) do |value|
|
||||||
|
write_date_time_attribute(name, value, column.type, time_zone_aware)
|
||||||
end
|
end
|
||||||
|
timeliness_methods << method_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
|
||||||
end
|
# add generated methods which is a Set object hence no += method
|
||||||
|
timeliness_methods.each {|attr| generated_methods << attr }
|
||||||
# Define write method for date, time and datetime columns
|
|
||||||
def define_write_method_for_dates_and_times(attr_name, type, time_zone_aware)
|
|
||||||
method_body = <<-EOV
|
|
||||||
def #{attr_name}=(value)
|
|
||||||
write_date_time_attribute('#{attr_name}', value, #{type.inspect}, #{time_zone_aware})
|
|
||||||
end
|
|
||||||
EOV
|
|
||||||
evaluate_attribute_method attr_name, method_body, "#{attr_name}="
|
|
||||||
end
|
|
||||||
|
|
||||||
def define_read_method_for_dates_and_times(attr_name, type, time_zone_aware)
|
|
||||||
method_body = <<-EOV
|
|
||||||
def #{attr_name}(reload = false)
|
|
||||||
read_date_time_attribute('#{attr_name}', #{type.inspect}, #{time_zone_aware}, reload)
|
|
||||||
end
|
|
||||||
EOV
|
|
||||||
evaluate_attribute_method attr_name, method_body
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -130,5 +75,3 @@ module ValidatesTimeliness
|
|||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::AttributeMethods)
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
# Overrides AR method to store multiparameter time and dates as string
|
# Assign dates and times as formatted strings to force the use of the plugin parser
|
||||||
# allowing validation later.
|
|
||||||
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,30 +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 = values.map {|v| v.to_s }
|
|
||||||
|
|
||||||
case type
|
|
||||||
when :date
|
|
||||||
extract_date_from_multiparameter_attributes(values)
|
|
||||||
when :time
|
|
||||||
extract_time_from_multiparameter_attributes(values)
|
|
||||||
when :datetime
|
|
||||||
date_values, time_values = values.slice!(0, 3), values
|
|
||||||
extract_date_from_multiparameter_attributes(date_values) + " " + extract_time_from_multiparameter_attributes(time_values)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_date_from_multiparameter_attributes(values)
|
|
||||||
[values[0], *values.slice(1, 2).map { |s| s.rjust(2, "0") }].join("-")
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_time_from_multiparameter_attributes(values)
|
|
||||||
values = values.size > 3 ? values[3..5] : values
|
|
||||||
values.map { |s| s.rjust(2, "0") }.join(":")
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
@@ -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,8 +18,28 @@ module ValidatesTimeliness
|
|||||||
:datetime_expressions,
|
:datetime_expressions,
|
||||||
:format_tokens,
|
:format_tokens,
|
||||||
:format_proc_args
|
:format_proc_args
|
||||||
|
|
||||||
# Format tokens:
|
|
||||||
|
# Set the threshold value for a two digit year to be considered last century
|
||||||
|
#
|
||||||
|
# Default: 30
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# year = '29' is considered 2029
|
||||||
|
# year = '30' is considered 1930
|
||||||
|
#
|
||||||
|
cattr_accessor :ambiguous_year_threshold
|
||||||
|
self.ambiguous_year_threshold = 30
|
||||||
|
|
||||||
|
# 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
|
||||||
@@ -36,25 +54,25 @@ 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
|
||||||
# yyyyy = exactly 4 digit year
|
# yyyy = exactly 4 digit year
|
||||||
# mmm = month long name (e.g. 'Jul' or 'July')
|
# mmm = month long name (e.g. 'Jul' or 'July')
|
||||||
# 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',
|
||||||
@@ -68,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',
|
||||||
@@ -81,10 +99,11 @@ 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',
|
||||||
|
'yyyy-mm-dd h:nn_ampm',
|
||||||
'yyyy-mm-dd hh:nn:ss.u',
|
'yyyy-mm-dd hh:nn:ss.u',
|
||||||
'm/d/yy h:nn:ss',
|
'm/d/yy h:nn:ss',
|
||||||
'm/d/yy h:nn_ampm',
|
'm/d/yy h:nn_ampm',
|
||||||
@@ -94,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
|
||||||
@@ -124,18 +144,18 @@ module ValidatesTimeliness
|
|||||||
{ 's' => [ /s{1}/, '(\d{1,2})', :sec ] },
|
{ 's' => [ /s{1}/, '(\d{1,2})', :sec ] },
|
||||||
{ 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] },
|
{ 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] },
|
||||||
{ 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] },
|
{ 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] },
|
||||||
{ 'zo' => [ /zo/, '(?:[+-]\d{2}:?\d{2})'] },
|
{ 'zo' => [ /zo/, '([+-]\d{2}:?\d{2})', :offset ] },
|
||||||
{ 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] },
|
{ 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] },
|
||||||
{ '_' => [ /_/, '\s?' ] }
|
{ '_' => [ /_/, '\s?' ] }
|
||||||
]
|
]
|
||||||
|
|
||||||
# Arguments whichs will be passed to the format proc if matched in the
|
# Arguments which will be passed to the format proc if matched in the
|
||||||
# time string. The key must should the key from the format tokens. The array
|
# time string. The key must be the key from the format tokens. The array
|
||||||
# consists of the arry position of the arg, the arg name, and the code to
|
# consists of the arry position of the arg, the arg name, and the code to
|
||||||
# place in the time array slot. The position can be nil which means the arg
|
# place in the time array slot. The position can be nil which means the arg
|
||||||
# won't be placed in the array.
|
# won't be placed in the array.
|
||||||
#
|
#
|
||||||
# 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 = {
|
||||||
@@ -146,37 +166,51 @@ module ValidatesTimeliness
|
|||||||
:min => [4, 'n', 'n'],
|
:min => [4, 'n', 'n'],
|
||||||
:sec => [5, 's', 's'],
|
:sec => [5, 's', 's'],
|
||||||
:usec => [6, 'u', 'microseconds(u)'],
|
:usec => [6, 'u', 'microseconds(u)'],
|
||||||
|
:offset => [7, 'z', 'offset_in_seconds(z)'],
|
||||||
:meridian => [nil, 'md', nil]
|
:meridian => [nil, 'md', nil]
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
||||||
# Returns 7 part time array.
|
# Returns time array if matches a format, nil otherwise.
|
||||||
def parse(string, type, strict=true)
|
def parse(string, type, options={})
|
||||||
return string unless string.is_a?(String)
|
return string unless string.is_a?(String)
|
||||||
|
options.reverse_merge!(:strict => true)
|
||||||
|
|
||||||
|
sets = if options[:format]
|
||||||
|
options[:strict] = true
|
||||||
|
[ send("#{type}_expressions").assoc(options[:format]) ]
|
||||||
|
else
|
||||||
|
expression_set(type, string)
|
||||||
|
end
|
||||||
|
|
||||||
matches = nil
|
matches = nil
|
||||||
exp, processor = expression_set(type, string).find do |regexp, proc|
|
processor = sets.each do |format, regexp, proc|
|
||||||
full = /\A#{regexp}\Z/ if strict
|
full = /\A#{regexp}\Z/ if options[:strict]
|
||||||
full ||= case type
|
full ||= case type
|
||||||
when :date then /\A#{regexp}/
|
when :date then /\A#{regexp}/
|
||||||
when :time then /#{regexp}\Z/
|
when :time then /#{regexp}\Z/
|
||||||
when :datetime then /\A#{regexp}\Z/
|
when :datetime then /\A#{regexp}\Z/
|
||||||
end
|
end
|
||||||
matches = full.match(string.strip)
|
break(proc) if matches = full.match(string.strip)
|
||||||
end
|
end
|
||||||
processor.call(*matches[1..7]) if matches
|
last = options[:include_offset] ? 8 : 7
|
||||||
end
|
if matches
|
||||||
|
values = processor.call(*matches[1..last])
|
||||||
|
values[0..2] = dummy_date_for_time_type if type == :time
|
||||||
|
return values
|
||||||
|
end
|
||||||
|
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|
|
||||||
@@ -186,10 +220,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")
|
||||||
@@ -197,7 +231,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)
|
||||||
|
|
||||||
@@ -206,10 +240,9 @@ module ValidatesTimeliness
|
|||||||
end
|
end
|
||||||
compile_format_expressions
|
compile_format_expressions
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Removes formats where the 1 or 2 digit month comes first, to eliminate
|
# Removes formats where the 1 or 2 digit month comes first, to eliminate
|
||||||
# formats which are ambiguous with the European style of day then month.
|
# formats which are ambiguous with the European style of day then month.
|
||||||
# The mmm token is ignored as its not ambigous.
|
# The mmm token is ignored as its not ambigous.
|
||||||
def remove_us_formats
|
def remove_us_formats
|
||||||
us_format_regexp = /\Am{1,2}[^m]/
|
us_format_regexp = /\Am{1,2}[^m]/
|
||||||
@@ -217,71 +250,7 @@ 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
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Compile formats into validation regexps and format procs
|
|
||||||
def format_expression_generator(string_format)
|
|
||||||
regexp = string_format.dup
|
|
||||||
order = {}
|
|
||||||
regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes
|
|
||||||
|
|
||||||
format_tokens.each do |token|
|
|
||||||
token_name = token.keys.first
|
|
||||||
token_regexp, regexp_str, arg_key = *token.values.first
|
|
||||||
|
|
||||||
# hack for lack of look-behinds. If has a capture group then is
|
|
||||||
# considered an anchor to put straight back in the regexp string.
|
|
||||||
regexp.gsub!(token_regexp) {|m| "#{$1}" + regexp_str }
|
|
||||||
order[arg_key] = $~.begin(0) if $~ && !arg_key.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
return Regexp.new(regexp), format_proc(order)
|
|
||||||
rescue
|
|
||||||
raise "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}."
|
|
||||||
end
|
|
||||||
|
|
||||||
# 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
|
|
||||||
# argument in the position indicated by the first element of the proc arg
|
|
||||||
# array.
|
|
||||||
#
|
|
||||||
# Examples:
|
|
||||||
#
|
|
||||||
# 'yyyy-mm-dd hh:nn' => lambda {|y,m,d,h,n| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } }
|
|
||||||
# 'dd/mm/yyyy h:nn_ampm' => lambda {|d,m,y,h,n,md| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } }
|
|
||||||
#
|
|
||||||
def format_proc(order)
|
|
||||||
arg_map = format_proc_args
|
|
||||||
args = order.invert.sort.map {|p| arg_map[p[1]][1] }
|
|
||||||
arr = [nil] * 7
|
|
||||||
order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? }
|
|
||||||
proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.to_i } }"
|
|
||||||
eval proc_string
|
|
||||||
end
|
|
||||||
|
|
||||||
def compile_formats(formats)
|
|
||||||
formats.map { |format| regexp, format_proc = format_expression_generator(format) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Pick expression set and combine date and datetimes for
|
|
||||||
# datetime attributes to allow date string as datetime
|
|
||||||
def expression_set(type, string)
|
|
||||||
case type
|
|
||||||
when :date
|
|
||||||
date_expressions
|
|
||||||
when :time
|
|
||||||
time_expressions
|
|
||||||
when :datetime
|
|
||||||
# gives a speed-up for date string as datetime attributes
|
|
||||||
if string.length < 11
|
|
||||||
date_expressions + datetime_expressions
|
|
||||||
else
|
|
||||||
datetime_expressions + date_expressions
|
|
||||||
end
|
|
||||||
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?
|
||||||
@@ -291,12 +260,16 @@ module ValidatesTimeliness
|
|||||||
hour == 12 ? hour : hour + 12
|
hour == 12 ? hour : hour + 12
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unambiguous_year(year, threshold=30)
|
def unambiguous_year(year)
|
||||||
year = "#{year.to_i < threshold ? '20' : '19'}#{year}" if year.length == 2
|
if year.length <= 2
|
||||||
|
century = Time.now.year.to_s[0..1].to_i
|
||||||
|
century -= 1 if year.to_i >= ambiguous_year_threshold
|
||||||
|
year = "#{century}#{year.rjust(2,'0')}"
|
||||||
|
end
|
||||||
year.to_i
|
year.to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
def month_index(month)
|
def month_index(month)
|
||||||
return month.to_i if month.to_i.nonzero?
|
return month.to_i if month.to_i.nonzero?
|
||||||
abbr_month_names.index(month.capitalize) || month_names.index(month.capitalize)
|
abbr_month_names.index(month.capitalize) || month_names.index(month.capitalize)
|
||||||
@@ -313,6 +286,78 @@ module ValidatesTimeliness
|
|||||||
def microseconds(usec)
|
def microseconds(usec)
|
||||||
(".#{usec}".to_f * 1_000_000).to_i
|
(".#{usec}".to_f * 1_000_000).to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def offset_in_seconds(offset)
|
||||||
|
sign = offset =~ /^-/ ? -1 : 1
|
||||||
|
parts = offset.scan(/\d\d/).map {|p| p.to_f }
|
||||||
|
parts[1] = parts[1].to_f / 60
|
||||||
|
(parts[0] + parts[1]) * sign * 3600
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Generate regular expression and processor from format string
|
||||||
|
def generate_format_expression(string_format)
|
||||||
|
regexp = string_format.dup
|
||||||
|
order = {}
|
||||||
|
regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes
|
||||||
|
|
||||||
|
format_tokens.each do |token|
|
||||||
|
token_name = token.keys.first
|
||||||
|
token_regexp, regexp_str, arg_key = *token.values.first
|
||||||
|
|
||||||
|
# hack for lack of look-behinds. If has a capture group then is
|
||||||
|
# considered an anchor to put straight back in the regexp string.
|
||||||
|
regexp.gsub!(token_regexp) {|m| "#{$1}" + regexp_str }
|
||||||
|
order[arg_key] = $~.begin(0) if $~ && !arg_key.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
return Regexp.new(regexp), format_proc(order)
|
||||||
|
rescue
|
||||||
|
raise "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}."
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# argument in the position indicated by the first element of the proc arg
|
||||||
|
# array.
|
||||||
|
#
|
||||||
|
def format_proc(order)
|
||||||
|
arg_map = format_proc_args
|
||||||
|
args = order.invert.sort.map {|p| arg_map[p[1]][1] }
|
||||||
|
arr = [nil] * 7
|
||||||
|
order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? }
|
||||||
|
proc_string = <<-EOL
|
||||||
|
lambda {|#{args.join(',')}|
|
||||||
|
md ||= nil
|
||||||
|
[#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.is_a?(Float) ? i : i.to_i }
|
||||||
|
}
|
||||||
|
EOL
|
||||||
|
eval proc_string
|
||||||
|
end
|
||||||
|
|
||||||
|
def compile_formats(formats)
|
||||||
|
formats.map { |format| [ format, *generate_format_expression(format) ] }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Pick expression set and combine date and datetimes for
|
||||||
|
# datetime attributes to allow date string as datetime
|
||||||
|
def expression_set(type, string)
|
||||||
|
case type
|
||||||
|
when :date
|
||||||
|
date_expressions
|
||||||
|
when :time
|
||||||
|
time_expressions
|
||||||
|
when :datetime
|
||||||
|
# gives a speed-up for date string as datetime attributes
|
||||||
|
if string.length < 11
|
||||||
|
date_expressions + datetime_expressions
|
||||||
|
else
|
||||||
|
datetime_expressions + date_expressions
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,9 +5,14 @@ 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}}"
|
||||||
on_or_after: "must be on or after {{restriction}}"
|
on_or_after: "must be on or after {{restriction}}"
|
||||||
between: "must be between {{earliest}} and {{latest}}"
|
between: "must be between {{earliest}} and {{latest}}"
|
||||||
|
validates_timeliness:
|
||||||
|
error_value_formats:
|
||||||
|
date: '%Y-%m-%d'
|
||||||
|
time: '%H:%M:%S'
|
||||||
|
datetime: '%Y-%m-%d %H:%M:%S'
|
||||||
|
|||||||
1
lib/validates_timeliness/matcher.rb
Normal file
1
lib/validates_timeliness/matcher.rb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
require 'validates_timeliness/spec/rails/matchers/validate_timeliness'
|
||||||
44
lib/validates_timeliness/parser.rb
Normal file
44
lib/validates_timeliness/parser.rb
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module Parser
|
||||||
|
|
||||||
|
class << self
|
||||||
|
|
||||||
|
def parse(raw_value, type, options={})
|
||||||
|
return nil if raw_value.blank?
|
||||||
|
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
|
||||||
|
|
||||||
|
time_array = ValidatesTimeliness::Formats.parse(raw_value, type, options.reverse_merge(:strict => true))
|
||||||
|
return nil if time_array.nil?
|
||||||
|
|
||||||
|
if type == :date
|
||||||
|
Date.new(*time_array[0..2]) rescue nil
|
||||||
|
else
|
||||||
|
make_time(time_array[0..7])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
Time.zone.local(*time_array)
|
||||||
|
else
|
||||||
|
# Older AR way of handling times with datetime fallback
|
||||||
|
begin
|
||||||
|
time_zone = ValidatesTimeliness.default_timezone
|
||||||
|
Time.send(time_zone, *time_array)
|
||||||
|
rescue ArgumentError, TypeError
|
||||||
|
zone_offset = time_zone == :local ? DateTime.local_offset : 0
|
||||||
|
time_array.pop # remove microseconds
|
||||||
|
DateTime.civil(*(time_array << zone_offset))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue ArgumentError, TypeError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -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,6 +10,7 @@ module Spec
|
|||||||
}
|
}
|
||||||
|
|
||||||
OPTION_TEST_SETTINGS = {
|
OPTION_TEST_SETTINGS = {
|
||||||
|
: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 },
|
||||||
@@ -24,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(:before) if @options[:before] && valid
|
valid = test_option(:is_at) if valid && @options[:is_at]
|
||||||
valid = test_option(:after) if @options[:after] && valid
|
valid = test_option(:before) if valid && @options[:before]
|
||||||
|
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])
|
||||||
@@ -62,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
|
||||||
@@ -70,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)
|
||||||
@@ -105,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
|
||||||
@@ -115,27 +115,31 @@ module Spec
|
|||||||
pass
|
pass
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_message_for(option)
|
def error_message_for(message)
|
||||||
msg = @validator.send(: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(ValidatesTimeliness::Validator.error_value_formats[@type])
|
value.strftime(@validator.class.error_value_formats[@type])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -7,27 +7,6 @@ module ValidatesTimeliness
|
|||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
|
||||||
def parse_date_time(raw_value, type, strict=true)
|
|
||||||
return nil if raw_value.blank?
|
|
||||||
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
|
|
||||||
|
|
||||||
time_array = ValidatesTimeliness::Formats.parse(raw_value, type, strict)
|
|
||||||
raise if time_array.nil?
|
|
||||||
|
|
||||||
# Rails dummy time date part is defined as 2000-01-01
|
|
||||||
time_array[0..2] = 2000, 1, 1 if type == :time
|
|
||||||
|
|
||||||
# Date.new enforces days per month, unlike Time
|
|
||||||
date = Date.new(*time_array[0..2]) unless type == :time
|
|
||||||
|
|
||||||
return date if type == :date
|
|
||||||
|
|
||||||
# Create time object which checks time part, and return time object
|
|
||||||
make_time(time_array)
|
|
||||||
rescue
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def validates_time(*attr_names)
|
def validates_time(*attr_names)
|
||||||
configuration = attr_names.extract_options!
|
configuration = attr_names.extract_options!
|
||||||
configuration[:type] = :time
|
configuration[:type] = :time
|
||||||
@@ -59,21 +38,6 @@ module ValidatesTimeliness
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Time.zone. Rails 2.0 should be default_timezone.
|
|
||||||
def make_time(time_array)
|
|
||||||
if Time.respond_to?(:zone) && time_zone_aware_attributes
|
|
||||||
Time.zone.local(*time_array)
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
Time.send(::ActiveRecord::Base.default_timezone, *time_array)
|
|
||||||
rescue ArgumentError, TypeError
|
|
||||||
zone_offset = ::ActiveRecord::Base.default_timezone == :local ? DateTime.local_offset : 0
|
|
||||||
time_array.pop # remove microseconds
|
|
||||||
DateTime.civil(*(time_array << zone_offset))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,75 +1,77 @@
|
|||||||
|
#TODO remove deprecated option :equal_to
|
||||||
module ValidatesTimeliness
|
module ValidatesTimeliness
|
||||||
|
|
||||||
class Validator
|
class Validator
|
||||||
cattr_accessor :ignore_restriction_errors
|
|
||||||
cattr_accessor :error_value_formats
|
cattr_accessor :error_value_formats
|
||||||
|
cattr_accessor :ignore_restriction_errors
|
||||||
self.ignore_restriction_errors = false
|
self.ignore_restriction_errors = false
|
||||||
self.error_value_formats = {
|
|
||||||
:time => '%H:%M:%S',
|
|
||||||
:date => '%Y-%m-%d',
|
|
||||||
:datetime => '%Y-%m-%d %H:%M:%S'
|
|
||||||
}
|
|
||||||
|
|
||||||
RESTRICTION_METHODS = {
|
RESTRICTION_METHODS = {
|
||||||
|
: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, :blank,
|
:on, :if, :unless, :allow_nil, :empty, :allow_blank,
|
||||||
:with_time, :with_date, :ignore_usec,
|
:with_time, :with_date, :ignore_usec, :format,
|
||||||
:invalid_time_message, :invalid_date_message, :invalid_datetime_message
|
:invalid_time_message, :invalid_date_message, :invalid_datetime_message
|
||||||
] + RESTRICTION_METHODS.keys.map {|option| [option, "#{option}_message".to_sym] }.flatten
|
] + RESTRICTION_METHODS.keys.map {|option| [option, "#{option}_message".to_sym] }.flatten
|
||||||
|
|
||||||
|
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)
|
||||||
value = record.class.parse_date_time(value, type, false) if value.is_a?(String)
|
|
||||||
raw_value = raw_value(record, attr_name) || value
|
raw_value = raw_value(record, attr_name) || value
|
||||||
|
|
||||||
|
if value.is_a?(String) || configuration[:format]
|
||||||
|
value = ValidatesTimeliness::Parser.parse(raw_value, type, :strict => false, :format => configuration[:format])
|
||||||
|
end
|
||||||
|
|
||||||
return if (raw_value.nil? && configuration[:allow_nil]) || (raw_value.blank? && configuration[:allow_blank])
|
return if (raw_value.nil? && configuration[:allow_nil]) || (raw_value.blank? && configuration[:allow_blank])
|
||||||
|
|
||||||
add_error(record, attr_name, :blank) and return if raw_value.blank?
|
return add_error(record, attr_name, :blank) if raw_value.blank?
|
||||||
|
return add_error(record, attr_name, "invalid_#{type}".to_sym) if value.nil?
|
||||||
add_error(record, attr_name, "invalid_#{type}".to_sym) and return unless value
|
|
||||||
|
|
||||||
validate_restrictions(record, attr_name, value)
|
validate_restrictions(record, attr_name, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def error_messages
|
||||||
|
@error_messages ||= ::ActiveRecord::Errors.default_error_messages.merge(custom_error_messages)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
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))
|
||||||
@@ -83,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 = custom_error_messages[option] || I18n.translate('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) }
|
||||||
@@ -107,69 +108,83 @@ 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)
|
||||||
# use i18n support in AR for message or use custom message passed to validation method
|
|
||||||
custom = custom_error_messages[message]
|
custom = custom_error_messages[message]
|
||||||
record.errors.add(attr_name, custom || message, interpolate || {})
|
record.errors.add(attr_name, 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 error_messages
|
|
||||||
@error_messages ||= ValidatesTimeliness.default_error_messages.merge(custom_error_messages)
|
|
||||||
end
|
|
||||||
|
|
||||||
def custom_error_messages
|
def custom_error_messages
|
||||||
@custom_error_messages ||= configuration.inject({}) {|msgs, (k, v)|
|
@custom_error_messages ||= configuration.inject({}) {|msgs, (k, v)|
|
||||||
if md = /(.*)_message$/.match(k.to_s)
|
if md = /(.*)_message$/.match(k.to_s)
|
||||||
msgs[md[1].to_sym] = v
|
msgs[md[1].to_sym] = v
|
||||||
end
|
end
|
||||||
msgs
|
msgs
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def combine_date_and_time(value, record)
|
def combine_date_and_time(value, record)
|
||||||
if type == :date
|
if type == :date
|
||||||
date = value
|
date = value
|
||||||
time = @configuration[:with_time]
|
time = configuration[:with_time]
|
||||||
else
|
else
|
||||||
date = @configuration[:with_date]
|
date = configuration[:with_date]
|
||||||
time = value
|
time = value
|
||||||
end
|
end
|
||||||
date, time = self.class.evaluate_option_value(date, :date, record), self.class.evaluate_option_value(time, :time, record)
|
date, time = self.class.evaluate_option_value(date, :date, record), self.class.evaluate_option_value(time, :time, record)
|
||||||
return if date.nil? || time.nil?
|
return if date.nil? || time.nil?
|
||||||
record.class.send(:make_time, [date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
|
ValidatesTimeliness::Parser.make_time([date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_options(options)
|
def validate_options(options)
|
||||||
invalid_for_type = ([:time, :date, :datetime] - [@type]).map {|k| "invalid_#{k}_message".to_sym }
|
if options.key?(:equal_to)
|
||||||
invalid_for_type << :with_date unless @type == :time
|
::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.")
|
||||||
invalid_for_type << :with_time unless @type == :date
|
options[:is_at] = options.delete(:equal_to)
|
||||||
options.assert_valid_keys(VALID_OPTIONS - invalid_for_type)
|
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 << :with_date unless type == :time
|
||||||
|
invalid_for_type << :with_time unless type == :date
|
||||||
|
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 error_value_format_for(type)
|
||||||
|
if defined?(I18n)
|
||||||
|
# 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
|
||||||
|
error_value_formats[type]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def evaluate_option_value(value, type, record)
|
def evaluate_option_value(value, type, record)
|
||||||
case value
|
case value
|
||||||
when Time, Date, DateTime
|
when Time, Date
|
||||||
value
|
value
|
||||||
when Symbol
|
when Symbol
|
||||||
evaluate_option_value(record.send(value), type, record)
|
evaluate_option_value(record.send(value), type, record)
|
||||||
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
|
||||||
evaluate_option_value([value.first, value.last], type, record)
|
evaluate_option_value([value.first, value.last], type, record)
|
||||||
else
|
else
|
||||||
record.class.parse_date_time(value, type, false)
|
ValidatesTimeliness::Parser.parse(value, type, :strict => false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -179,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)
|
||||||
@@ -192,13 +207,23 @@ module ValidatesTimeliness
|
|||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
if ignore_usec && value.is_a?(Time)
|
if ignore_usec && value.is_a?(Time)
|
||||||
::ActiveRecord::Base.send(:make_time, Array(value).reverse[4..9])
|
ValidatesTimeliness::Parser.make_time(Array(value).reverse[4..9])
|
||||||
else
|
else
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
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
|
||||||
|
|||||||
3
lib/validates_timeliness/version.rb
Normal file
3
lib/validates_timeliness/version.rb
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
VERSION = "2.3.1"
|
||||||
|
end
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -20,36 +23,18 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
|||||||
@person.birth_date_and_time = "2000-01-01 12:00"
|
@person.birth_date_and_time = "2000-01-01 12:00"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should call read_date_time_attribute when date attribute is retrieved" do
|
|
||||||
@person.should_receive(:read_date_time_attribute)
|
|
||||||
@person.birth_date = "2000-01-01"
|
|
||||||
@person.birth_date
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should call read_date_time_attribute when time attribute is retrieved" do
|
|
||||||
@person.should_receive(:read_date_time_attribute)
|
|
||||||
@person.birth_time = "12:00"
|
|
||||||
@person.birth_time
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should call read_date_time_attribute when datetime attribute is retrieved" do
|
|
||||||
@person.should_receive(:read_date_time_attribute)
|
|
||||||
@person.birth_date_and_time = "2000-01-01 12:00"
|
|
||||||
@person.birth_date_and_time
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should call parser on write for datetime attribute" do
|
it "should call parser on write for datetime attribute" do
|
||||||
@person.class.should_receive(:parse_date_time).once
|
ValidatesTimeliness::Parser.should_receive(:parse).once
|
||||||
@person.birth_date_and_time = "2000-01-01 02:03:04"
|
@person.birth_date_and_time = "2000-01-01 02:03:04"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should call parser on write for date attribute" do
|
it "should call parser on write for date attribute" do
|
||||||
@person.class.should_receive(:parse_date_time).once
|
ValidatesTimeliness::Parser.should_receive(:parse).once
|
||||||
@person.birth_date = "2000-01-01"
|
@person.birth_date = "2000-01-01"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should call parser on write for time attribute" do
|
it "should call parser on write for time attribute" do
|
||||||
@person.class.should_receive(:parse_date_time).once
|
ValidatesTimeliness::Parser.should_receive(:parse).once
|
||||||
@person.birth_time = "12:00"
|
@person.birth_time = "12:00"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -58,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)
|
||||||
@@ -77,33 +62,44 @@ 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
|
||||||
|
|
||||||
unless RAILS_VER < '2.1'
|
if RAILS_VER < '2.1'
|
||||||
|
|
||||||
|
it "should return time object from database in default timezone" do
|
||||||
|
ActiveRecord::Base.default_timezone = :utc
|
||||||
|
time_string = "2000-01-01 09:00:00"
|
||||||
|
@person.birth_date_and_time = time_string
|
||||||
|
@person.save
|
||||||
|
@person.reload
|
||||||
|
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z').should == time_string + ' UTC'
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
it "should return stored time string as Time with correct timezone" do
|
it "should return stored time string as Time with correct timezone" do
|
||||||
Time.zone = 'Melbourne'
|
Time.zone = 'Melbourne'
|
||||||
time_string = "2000-06-01 02:03:04"
|
time_string = "2000-06-01 02:03:04"
|
||||||
@@ -111,114 +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
|
||||||
|
|
||||||
describe "dirty attributes" do
|
|
||||||
|
|
||||||
it "should return true for attribute changed? when value updated" do
|
|
||||||
time_string = "2000-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.birth_date_and_time_changed?.should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should show changes when time attribute changed from nil to Time object" do
|
|
||||||
time_string = "2000-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
time = @person.birth_date_and_time
|
|
||||||
@person.changes.should == {"birth_date_and_time" => [nil, time]}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should show changes when time attribute changed from Time object to nil" do
|
|
||||||
time_string = "2020-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save false
|
|
||||||
@person.reload
|
|
||||||
time = @person.birth_date_and_time
|
|
||||||
@person.birth_date_and_time = nil
|
|
||||||
@person.changes.should == {"birth_date_and_time" => [time, nil]}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should show no changes when assigned same value as Time object" do
|
|
||||||
time_string = "2020-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save false
|
|
||||||
@person.reload
|
|
||||||
time = @person.birth_date_and_time
|
|
||||||
@person.birth_date_and_time = time
|
|
||||||
@person.changes.should == {}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should show no changes when assigned same value as time string" do
|
|
||||||
time_string = "2020-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save false
|
|
||||||
@person.reload
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.changes.should == {}
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
else
|
|
||||||
|
|
||||||
it "should return time object from database in default timezone" do
|
|
||||||
ActiveRecord::Base.default_timezone = :utc
|
|
||||||
time_string = "2000-01-01 09:00:00"
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save
|
|
||||||
@person.reload
|
|
||||||
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z').should == time_string + ' GMT'
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return same time object on repeat reads on existing object" do
|
|
||||||
Time.zone = 'Melbourne' unless RAILS_VER < '2.1'
|
|
||||||
time_string = "2000-01-01 09:00:00"
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save!
|
|
||||||
@person.reload
|
|
||||||
time = @person.birth_date_and_time
|
|
||||||
@person.birth_date_and_time.should == time
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return same date object on repeat reads on existing object" do
|
|
||||||
date_string = Date.today
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date = date_string
|
|
||||||
@person.save!
|
|
||||||
@person.reload
|
|
||||||
date = @person.birth_date
|
|
||||||
@person.birth_date.should == date
|
|
||||||
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
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -1,82 +1,41 @@
|
|||||||
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 "expression generator" do
|
|
||||||
it "should generate regexp for time" do
|
|
||||||
generate_regexp_str('hh:nn:ss').should == '/(\d{2}):(\d{2}):(\d{2})/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for time with meridian" do
|
|
||||||
generate_regexp_str('hh:nn:ss ampm').should == '/(\d{2}):(\d{2}):(\d{2}) ((?:[aApP])\.?[mM]\.?)/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for time with meridian and optional space between" do
|
|
||||||
generate_regexp_str('hh:nn:ss_ampm').should == '/(\d{2}):(\d{2}):(\d{2})\s?((?:[aApP])\.?[mM]\.?)/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for time with single or double digits" do
|
|
||||||
generate_regexp_str('h:n:s').should == '/(\d{1,2}):(\d{1,2}):(\d{1,2})/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for date" do
|
|
||||||
generate_regexp_str('yyyy-mm-dd').should == '/(\d{4})-(\d{2})-(\d{2})/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for date with slashes" do
|
|
||||||
generate_regexp_str('dd/mm/yyyy').should == '/(\d{2})\/(\d{2})\/(\d{4})/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for date with dots" do
|
|
||||||
generate_regexp_str('dd.mm.yyyy').should == '/(\d{2})\.(\d{2})\.(\d{4})/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for Ruby time string" do
|
|
||||||
expected = '/(\w{3,9}) (\w{3,9}) (\d{2}):(\d{2}):(\d{2}) (?:[+-]\d{2}:?\d{2}) (\d{4})/'
|
|
||||||
generate_regexp_str('ddd mmm hh:nn:ss zo yyyy').should == expected
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for iso8601 datetime" do
|
|
||||||
expected = '/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:Z|(?:[+-]\d{2}:?\d{2}))/'
|
|
||||||
generate_regexp_str('yyyy-mm-ddThh:nn:ss(?:Z|zo)').should == expected
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "format proc generator" do
|
describe "format proc generator" do
|
||||||
it "should generate proc which outputs date array with values in correct order" do
|
it "should generate proc which outputs date array with values in correct order" do
|
||||||
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
|
||||||
|
|
||||||
|
it "should generate proc which outputs datetime array with zone offset" do
|
||||||
|
generate_proc('yyyy-mm-dd hh:nn:ss.u zo').call('2001', '02', '03', '04', '05', '06', '99', '+10:00').should == [2001,2,3,4,5,6,990000,36000]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "validation regexps" do
|
describe "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']},
|
||||||
@@ -95,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']},
|
||||||
@@ -109,135 +68,198 @@ 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 "extracting values" do
|
describe "parse" do
|
||||||
|
|
||||||
it "should return time array from date string" do
|
it "should return time array from date string" do
|
||||||
time_array = formats.parse('12:13:14', :time, true)
|
time_array = formats.parse('12:13:14', :time, :strict => true)
|
||||||
time_array.should == [0,0,0,12,13,14,0]
|
time_array.should == [2000,1,1,12,13,14,0]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return date array from time string" do
|
it "should return date array from time string" do
|
||||||
time_array = formats.parse('2000-02-01', :date, true)
|
time_array = formats.parse('2000-02-01', :date, :strict => true)
|
||||||
time_array.should == [2000,2,1,0,0,0,0]
|
time_array.should == [2000,2,1,0,0,0,0]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return datetime array from string value" do
|
it "should return datetime array from string value" do
|
||||||
time_array = formats.parse('2000-02-01 12:13:14', :datetime, true)
|
time_array = formats.parse('2000-02-01 12:13:14', :datetime, :strict => true)
|
||||||
time_array.should == [2000,2,1,12,13,14,0]
|
time_array.should == [2000,2,1,12,13,14,0]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should parse date string when type is datetime" do
|
it "should parse date string when type is datetime" do
|
||||||
time_array = formats.parse('2000-02-01', :datetime, false)
|
time_array = formats.parse('2000-02-01', :datetime, :strict => false)
|
||||||
time_array.should == [2000,2,1,0,0,0,0]
|
time_array.should == [2000,2,1,0,0,0,0]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should ignore time when extracting date and strict is false" do
|
it "should ignore time when extracting date and strict is false" do
|
||||||
time_array = formats.parse('2000-02-01 12:12', :date, false)
|
time_array = formats.parse('2000-02-01 12:13', :date, :strict => false)
|
||||||
time_array.should == [2000,2,1,0,0,0,0]
|
time_array.should == [2000,2,1,0,0,0,0]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should ignore time when extracting date from format with trailing year and strict is false" do
|
it "should ignore time when extracting date from format with trailing year and strict is false" do
|
||||||
time_array = formats.parse('01-02-2000 12:12', :date, false)
|
time_array = formats.parse('01-02-2000 12:13', :date, :strict => false)
|
||||||
time_array.should == [2000,2,1,0,0,0,0]
|
time_array.should == [2000,2,1,0,0,0,0]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should ignore date when extracting time and strict is false" do
|
it "should ignore date when extracting time and strict is false" do
|
||||||
time_array = formats.parse('2000-02-01 12:12', :time, false)
|
time_array = formats.parse('2000-02-01 12:13', :time, :strict => false)
|
||||||
time_array.should == [0,0,0,12,12,0,0]
|
time_array.should == [2000,1,1,12,13,0,0]
|
||||||
|
end
|
||||||
|
|
||||||
|
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.should == [2000,2,1,12,13,14,0,-37800]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "removing formats" do
|
describe "parse with format option" do
|
||||||
before do
|
it "should return values if string matches specified format" do
|
||||||
formats.compile_format_expressions
|
time_array = formats.parse('2000-02-01 12:13:14', :datetime, :format => 'yyyy-mm-dd hh:nn:ss')
|
||||||
|
time_array.should == [2000,2,1,12,13,14,0]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should remove format from format array" do
|
it "should return nil if string does not match specified format" do
|
||||||
|
time_array = formats.parse('2000-02-01 12:13', :datetime, :format => 'yyyy-mm-dd hh:nn:ss')
|
||||||
|
time_array.should be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "parsing date with ambiguous year" do
|
||||||
|
it "should return year in current century if year below threshold" do
|
||||||
|
time_array = formats.parse('01-02-29', :date)
|
||||||
|
time_array.should == [2029,2,1,0,0,0,0]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return year in last century if year at or above threshold" do
|
||||||
|
time_array = formats.parse('01-02-30', :date)
|
||||||
|
time_array.should == [1930,2,1,0,0,0,0]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should allow custom threshold" do
|
||||||
|
default = ValidatesTimeliness::Formats.ambiguous_year_threshold
|
||||||
|
ValidatesTimeliness::Formats.ambiguous_year_threshold = 40
|
||||||
|
time_array = formats.parse('01-02-39', :date)
|
||||||
|
time_array.should == [2039,2,1,0,0,0,0]
|
||||||
|
time_array = formats.parse('01-02-40', :date)
|
||||||
|
time_array.should == [1940,2,1,0,0,0,0]
|
||||||
|
ValidatesTimeliness::Formats.ambiguous_year_threshold = default
|
||||||
|
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
|
||||||
|
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'
|
||||||
# reload class instead
|
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)
|
||||||
@@ -246,33 +268,38 @@ 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 |(regexp, processor)|
|
formats.send("#{type}_expressions").each do |format, regexp, processor|
|
||||||
valid = true and break if /\A#{regexp}\Z/ =~ time_string
|
valid = true and break if /\A#{regexp}\Z/ =~ time_string
|
||||||
end
|
end
|
||||||
valid
|
valid
|
||||||
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
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
# 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']
|
||||||
|
|
||||||
rails_versions.each do |v|
|
rails_versions.each do |v|
|
||||||
g = Ginger::Scenario.new
|
g = Ginger::Scenario.new("Rails #{v}")
|
||||||
g['rails'] = v
|
g['rails'] = v
|
||||||
config.scenarios << g.dup
|
config.scenarios << g.dup
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
||||||
|
|
||||||
describe ValidatesTimeliness::ValidationMethods do
|
describe ValidatesTimeliness::Parser do
|
||||||
attr_accessor :person
|
attr_accessor :person
|
||||||
|
|
||||||
describe "parse_date_time" do
|
describe "parse" do
|
||||||
it "should return time object for valid time string" do
|
it "should return time object for valid time string" do
|
||||||
parse_method("2000-01-01 12:13:14", :datetime).should be_kind_of(Time)
|
parse("2000-01-01 12:13:14", :datetime).should be_kind_of(Time)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return nil for time string with invalid date part" do
|
it "should return nil for time string with invalid date part" do
|
||||||
parse_method("2000-02-30 12:13:14", :datetime).should be_nil
|
parse("2000-02-30 12:13:14", :datetime).should be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return nil for time string with invalid time part" do
|
it "should return nil for time string with invalid time part" do
|
||||||
parse_method("2000-02-01 25:13:14", :datetime).should be_nil
|
parse("2000-02-01 25:13:14", :datetime).should be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return Time object when passed a Time object" do
|
it "should return Time object when passed a Time object" do
|
||||||
parse_method(Time.now, :datetime).should be_kind_of(Time)
|
parse(Time.now, :datetime).should be_kind_of(Time)
|
||||||
end
|
end
|
||||||
|
|
||||||
if RAILS_VER >= '2.1'
|
if RAILS_VER >= '2.1'
|
||||||
it "should convert time string into current timezone" do
|
it "should convert time string into current timezone" do
|
||||||
Time.zone = 'Melbourne'
|
Time.zone = 'Melbourne'
|
||||||
time = parse_method("2000-01-01 12:13:14", :datetime)
|
time = parse("2000-01-01 12:13:14", :datetime)
|
||||||
Time.zone.utc_offset.should == 10.hours
|
Time.zone.utc_offset.should == 10.hours
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return nil for invalid date string" do
|
it "should return nil for invalid date string" do
|
||||||
parse_method("2000-02-30", :date).should be_nil
|
parse("2000-02-30", :date).should be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_method(*args)
|
def parse(*args)
|
||||||
ActiveRecord::Base.parse_date_time(*args)
|
ValidatesTimeliness::Parser.parse(*args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -43,14 +43,14 @@ describe ValidatesTimeliness::ValidationMethods do
|
|||||||
|
|
||||||
it "should create time using current timezone" do
|
it "should create time using current timezone" do
|
||||||
Time.zone = 'Melbourne'
|
Time.zone = 'Melbourne'
|
||||||
time = ActiveRecord::Base.send(:make_time, [2000,1,1,12,0,0])
|
time = ValidatesTimeliness::Parser.make_time([2000,1,1,12,0,0])
|
||||||
time.zone.should == "EST"
|
time.zone.should == "EST"
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
it "should create time using default timezone" do
|
it "should create time using default timezone" do
|
||||||
time = ActiveRecord::Base.send(:make_time, [2000,1,1,12,0,0])
|
time = ValidatesTimeliness::Parser.make_time([2000,1,1,12,0,0])
|
||||||
time.zone.should == "UTC"
|
time.zone.should == "UTC"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1,24 +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,
|
||||||
|
: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,
|
||||||
|
: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,
|
||||||
|
: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',
|
||||||
@@ -42,134 +46,157 @@ 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 is_at option" do
|
||||||
|
test_values = {
|
||||||
|
:date => ['2000-01-01', '2000-01-02'],
|
||||||
|
:time => ['09:00', '09:01'],
|
||||||
|
:datetime => ['2000-01-01 09:00', '2000-01-01 09:01']
|
||||||
|
}
|
||||||
|
|
||||||
|
[:date, :time, :datetime].each do |type|
|
||||||
|
|
||||||
|
it "should report that #{type} is validated" do
|
||||||
|
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :is_at => test_values[type][0])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should report that #{type} is not validated when option value is incorrect" do
|
||||||
|
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :is_at => test_values[type][1])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should report that #{type} is not validated with option" do
|
||||||
|
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :is_at => test_values[type][0])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "with before option" do
|
describe "with before option" do
|
||||||
test_values = {
|
test_values = {
|
||||||
:date => ['2000-01-10', '2000-01-11'],
|
:date => ['2000-01-10', '2000-01-11'],
|
||||||
: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
|
||||||
@@ -181,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)
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ $:.unshift(File.dirname(__FILE__) + '/../lib')
|
|||||||
$:.unshift(File.dirname(__FILE__))
|
$:.unshift(File.dirname(__FILE__))
|
||||||
$:.unshift(File.dirname(__FILE__) + '/resources')
|
$:.unshift(File.dirname(__FILE__) + '/resources')
|
||||||
|
|
||||||
ENV['RAILS_ENV'] = 'test'
|
RAILS_ENV = ENV['RAILS_ENV'] = 'test'
|
||||||
|
|
||||||
require 'rubygems'
|
require 'rubygems'
|
||||||
require 'spec'
|
require 'spec/autorun'
|
||||||
|
|
||||||
vendored_rails = File.dirname(__FILE__) + '/../../../../vendor/rails'
|
vendored_rails = File.dirname(__FILE__) + '/../../../../vendor/rails'
|
||||||
|
|
||||||
@@ -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']
|
||||||
@@ -30,6 +30,7 @@ require 'active_record'
|
|||||||
require 'active_record/version'
|
require 'active_record/version'
|
||||||
require 'action_controller'
|
require 'action_controller'
|
||||||
require 'action_view'
|
require 'action_view'
|
||||||
|
require 'action_mailer'
|
||||||
|
|
||||||
require 'spec/rails'
|
require 'spec/rails'
|
||||||
require 'time_travel/time_travel'
|
require 'time_travel/time_travel'
|
||||||
@@ -38,13 +39,16 @@ ActiveRecord::Base.default_timezone = :utc
|
|||||||
RAILS_VER = Rails::VERSION::STRING
|
RAILS_VER = Rails::VERSION::STRING
|
||||||
puts "Using #{vendored ? 'vendored' : 'gem'} Rails version #{RAILS_VER} (ActiveRecord version #{ActiveRecord::VERSION::STRING})"
|
puts "Using #{vendored ? 'vendored' : 'gem'} Rails version #{RAILS_VER} (ActiveRecord version #{ActiveRecord::VERSION::STRING})"
|
||||||
|
|
||||||
require 'validates_timeliness'
|
|
||||||
|
|
||||||
if RAILS_VER >= '2.1'
|
if RAILS_VER >= '2.1'
|
||||||
Time.zone_default = ActiveSupport::TimeZone['UTC']
|
Time.zone_default = ActiveSupport::TimeZone['UTC']
|
||||||
ActiveRecord::Base.time_zone_aware_attributes = true
|
ActiveRecord::Base.time_zone_aware_attributes = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require 'validates_timeliness'
|
||||||
|
require 'validates_timeliness/matcher'
|
||||||
|
|
||||||
|
ValidatesTimeliness.enable_datetime_select_extension!
|
||||||
|
|
||||||
ActiveRecord::Migration.verbose = false
|
ActiveRecord::Migration.verbose = false
|
||||||
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
|
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,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 +34,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
|
||||||
@@ -66,7 +79,7 @@ describe ValidatesTimeliness::Validator do
|
|||||||
|
|
||||||
it "should return array of Time objects when restriction is array of strings" do
|
it "should return array of Time objects when restriction is array of strings" do
|
||||||
time1, time2 = "2000-01-02", "2000-01-01"
|
time1, time2 = "2000-01-02", "2000-01-01"
|
||||||
evaluate_option_value([time1, time2], :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
|
evaluate_option_value([time1, time2], :datetime).should == [parse(time2, :datetime), parse(time1, :datetime)]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return array of Time objects when restriction is Range of Time objects" do
|
it "should return array of Time objects when restriction is Range of Time objects" do
|
||||||
@@ -76,7 +89,7 @@ describe ValidatesTimeliness::Validator do
|
|||||||
|
|
||||||
it "should return array of Time objects when restriction is Range of time strings" do
|
it "should return array of Time objects when restriction is Range of time strings" do
|
||||||
time1, time2 = "2000-01-02", "2000-01-01"
|
time1, time2 = "2000-01-02", "2000-01-01"
|
||||||
evaluate_option_value(time1..time2, :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
|
evaluate_option_value(time1..time2, :datetime).should == [parse(time2, :datetime), parse(time1, :datetime)]
|
||||||
end
|
end
|
||||||
def evaluate_option_value(restriction, type)
|
def evaluate_option_value(restriction, type)
|
||||||
configure_validator(:type => type)
|
configure_validator(:type => type)
|
||||||
@@ -305,7 +318,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 +359,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 +434,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
|
||||||
|
|
||||||
@@ -473,6 +495,52 @@ describe ValidatesTimeliness::Validator do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "instance with format option" do
|
||||||
|
it "should validate attribute when value matches format" do
|
||||||
|
configure_validator(:type => :time, :format => 'hh:nn:ss')
|
||||||
|
validate_with(:birth_time, "12:00:00")
|
||||||
|
should_have_no_error(:birth_time, :invalid_time)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not validate attribute when value does not match format" do
|
||||||
|
configure_validator(:type => :time, :format => 'hh:nn:ss')
|
||||||
|
validate_with(:birth_time, "12:00")
|
||||||
|
should_have_error(:birth_time, :invalid_time)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if defined?(I18n)
|
||||||
|
|
||||||
|
describe "localized error messages" do
|
||||||
|
before(:all) do
|
||||||
|
translations = {
|
||||||
|
:activerecord => {:errors => {:messages => { :after => 'retfa {{restriction}}' }}},
|
||||||
|
: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')
|
||||||
@@ -492,12 +560,6 @@ describe ValidatesTimeliness::Validator do
|
|||||||
configure_validator(:type => :date, :before => before)
|
configure_validator(:type => :date, :before => before)
|
||||||
validator.send(:interpolation_values, :before, before.to_date).should == {:restriction => before}
|
validator.send(:interpolation_values, :before, before.to_date).should == {:restriction => before}
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return empty hash if no interpolation keys are in message" do
|
|
||||||
before = '1900-01-01'
|
|
||||||
configure_validator(:type => :date, :before => before, :before_message => 'too late')
|
|
||||||
validator.send(:interpolation_values, :before, before.to_date).should be_empty
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
it "should return array of interpolation values" do
|
it "should return array of interpolation values" do
|
||||||
before = '1900-01-01'
|
before = '1900-01-01'
|
||||||
@@ -549,17 +611,41 @@ 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
|
||||||
|
I18n.backend.store_translations 'zz', :activerecord => {:errors => {:messages => { :after => 'after {{restriction}}' }}}
|
||||||
|
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
|
||||||
|
|
||||||
before :all do
|
before :all do
|
||||||
@@formats = ValidatesTimeliness::Validator.error_value_formats
|
custom = {
|
||||||
ValidatesTimeliness::Validator.error_value_formats = {
|
|
||||||
:time => '%H:%M %p',
|
:time => '%H:%M %p',
|
||||||
:date => '%d-%m-%Y',
|
:date => '%d-%m-%Y',
|
||||||
:datetime => '%d-%m-%Y %H:%M %p'
|
:datetime => '%d-%m-%Y %H:%M %p'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if defined?(I18n)
|
||||||
|
I18n.backend.store_translations 'en', :validates_timeliness => { :error_value_formats => custom }
|
||||||
|
else
|
||||||
|
@@formats = ValidatesTimeliness::Validator.error_value_formats
|
||||||
|
ValidatesTimeliness::Validator.error_value_formats = custom
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should format datetime value of restriction" do
|
it "should format datetime value of restriction" do
|
||||||
@@ -581,12 +667,20 @@ describe ValidatesTimeliness::Validator do
|
|||||||
end
|
end
|
||||||
|
|
||||||
after :all do
|
after :all do
|
||||||
ValidatesTimeliness::Validator.error_value_formats = @@formats
|
if defined?(I18n)
|
||||||
|
I18n.reload!
|
||||||
|
else
|
||||||
|
ValidatesTimeliness::Validator.error_value_formats = @@formats
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse(*args)
|
||||||
|
ValidatesTimeliness::Parser.parse(*args)
|
||||||
|
end
|
||||||
|
|
||||||
def configure_validator(options={})
|
def configure_validator(options={})
|
||||||
@validator = ValidatesTimeliness::Validator.new(options)
|
@validator = ValidatesTimeliness::Validator.new(options)
|
||||||
end
|
end
|
||||||
@@ -613,7 +707,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|\{\{\w*\}\}).*/, ''); h }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,26 +2,25 @@
|
|||||||
|
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = %q{validates_timeliness}
|
s.name = %q{validates_timeliness}
|
||||||
s.version = "1.1.7"
|
s.version = "2.3.1"
|
||||||
|
|
||||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||||
s.authors = ["Adam Meehan"]
|
s.authors = ["Adam Meehan"]
|
||||||
s.autorequire = %q{validates_timeliness}
|
s.autorequire = %q{validates_timeliness}
|
||||||
s.date = %q{2009-03-26}
|
s.date = %q{2010-03-19}
|
||||||
s.description = %q{Date and time validation plugin for Rails 2.x which allows custom formats}
|
s.description = %q{Date and time validation plugin for Rails 2.x which allows custom formats}
|
||||||
s.email = %q{adam.meehan@gmail.com}
|
s.email = %q{adam.meehan@gmail.com}
|
||||||
s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"]
|
s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"]
|
||||||
s.files = ["LICENSE", "README.rdoc", "Rakefile", "TODO", "CHANGELOG", "lib/validates_timeliness", "lib/validates_timeliness/core_ext", "lib/validates_timeliness/core_ext/date.rb", "lib/validates_timeliness/core_ext/date_time.rb", "lib/validates_timeliness/core_ext/time.rb", "lib/validates_timeliness/action_view", "lib/validates_timeliness/action_view/instance_tag.rb", "lib/validates_timeliness/locale", "lib/validates_timeliness/locale/en.yml", "lib/validates_timeliness/validation_methods.rb", "lib/validates_timeliness/active_record", "lib/validates_timeliness/active_record/attribute_methods.rb", "lib/validates_timeliness/active_record/multiparameter_attributes.rb", "lib/validates_timeliness/formats.rb", "lib/validates_timeliness/validator.rb", "lib/validates_timeliness/spec", "lib/validates_timeliness/spec/rails", "lib/validates_timeliness/spec/rails/matchers", "lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb", "lib/validates_timeliness.rb", "spec/core_ext", "spec/core_ext/dummy_time_spec.rb", "spec/validator_spec.rb", "spec/action_view", "spec/action_view/instance_tag_spec.rb", "spec/ginger_scenarios.rb", "spec/validation_methods_spec.rb", "spec/spec_helper.rb", "spec/formats_spec.rb", "spec/active_record", "spec/active_record/attribute_methods_spec.rb", "spec/active_record/multiparameter_attributes_spec.rb", "spec/time_travel", "spec/time_travel/time_travel.rb", "spec/time_travel/time_extensions.rb", "spec/time_travel/MIT-LICENSE", "spec/spec", "spec/spec/rails", "spec/spec/rails/matchers", "spec/spec/rails/matchers/validate_timeliness_spec.rb", "spec/resources", "spec/resources/person.rb", "spec/resources/sqlite_patch.rb", "spec/resources/schema.rb", "spec/resources/application.rb"]
|
s.files = ["LICENSE", "README.rdoc", "Rakefile", "TODO", "CHANGELOG", "lib/validates_timeliness", "lib/validates_timeliness/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.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.has_rdoc = true
|
|
||||||
s.homepage = %q{http://github.com/adzap/validates_timeliness}
|
s.homepage = %q{http://github.com/adzap/validates_timeliness}
|
||||||
s.require_paths = ["lib"]
|
s.require_paths = ["lib"]
|
||||||
s.rubyforge_project = %q{validatestime}
|
s.rubyforge_project = %q{validatestime}
|
||||||
s.rubygems_version = %q{1.3.1}
|
s.rubygems_version = %q{1.3.5}
|
||||||
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 = 2
|
s.specification_version = 3
|
||||||
|
|
||||||
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user