mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-25 15:22:58 +00:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af3a3b84ab | ||
|
|
4df63c7142 | ||
|
|
26066dedfe | ||
|
|
3348ca3063 | ||
|
|
88ee33ae41 | ||
|
|
51afb2852e | ||
|
|
57b8a52f07 | ||
|
|
bb94e234bc | ||
|
|
f041524124 | ||
|
|
e1d23d0f2b | ||
|
|
613001791a | ||
|
|
6d12790d2b | ||
|
|
b197d537f1 | ||
|
|
1e3c802031 | ||
|
|
d89266d9f1 | ||
|
|
9e2d95c3e1 | ||
|
|
fb520bbddc | ||
|
|
8303be05c3 | ||
|
|
f4ed751c26 | ||
|
|
956933f58b | ||
|
|
7967b5a212 | ||
|
|
a836ed8434 | ||
|
|
c2a4f45b5a | ||
|
|
312c1510cb | ||
|
|
88fce1d679 | ||
|
|
ffd8476f74 | ||
|
|
728c5ccda5 | ||
|
|
4f0c81b6f8 | ||
|
|
11e643c0fe | ||
|
|
d1ee94248b | ||
|
|
eecef62de4 | ||
|
|
fae38cfecd | ||
|
|
515a1b3126 | ||
|
|
736b1b582f | ||
|
|
903850bc23 | ||
|
|
c3f3edf324 | ||
|
|
1dbac5190b | ||
|
|
497a97e0b0 | ||
|
|
9dd3282a81 | ||
|
|
e7e9a8b238 | ||
|
|
cb962d1157 | ||
|
|
403a91addf | ||
|
|
7ca84f3116 | ||
|
|
19457a6c1d | ||
|
|
5e85649a34 | ||
|
|
e76c53a295 | ||
|
|
f93720177b | ||
|
|
1181b725d0 | ||
|
|
12aa78271e | ||
|
|
862b41f903 | ||
|
|
904c202fb4 | ||
|
|
1001d29c01 | ||
|
|
29c23a7a26 | ||
|
|
7ef9078369 | ||
|
|
a1ae5f9313 | ||
|
|
b3e235a8a1 | ||
|
|
71583805c8 | ||
|
|
2ee971623c | ||
|
|
817e49940c | ||
|
|
a76fc112e7 | ||
|
|
575ff85346 | ||
|
|
7ed76b5161 | ||
|
|
65ed8a657e | ||
|
|
360108c39f | ||
|
|
0ad8ace335 | ||
|
|
7c9ec695f4 | ||
|
|
6af61917dd | ||
|
|
43e6748cd2 | ||
|
|
760a52a2a4 | ||
|
|
9f1642c730 | ||
|
|
011ea070db | ||
|
|
b632093ce2 | ||
|
|
525b3b9941 | ||
|
|
db8dd9ac99 | ||
|
|
1fdfc23cb8 | ||
|
|
7d3ee4bc1b | ||
|
|
694a4bdd69 | ||
|
|
07359c6157 | ||
|
|
215b3dedfd | ||
|
|
753a63417b | ||
|
|
af923014f5 | ||
|
|
a7c6e37333 | ||
|
|
a14bc306b3 | ||
|
|
a71d6f7945 | ||
|
|
45ab815039 | ||
|
|
c308aaf4a9 | ||
|
|
6584d0f1f0 | ||
|
|
5abaec66ae | ||
|
|
ea5452a604 | ||
|
|
40437c970d | ||
|
|
37bfbfe5e7 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
pkg/
|
||||
41
CHANGELOG
41
CHANGELOG
@@ -1,3 +1,44 @@
|
||||
= 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]
|
||||
- Minor change to multiparameter attributes which I had not properly implemented for chaining
|
||||
|
||||
= 1.1.6 [2009-03-19]
|
||||
- Rail 2.3 support
|
||||
- Added :with_date and :with_time options. They allow an attribute to be combined with another attribute or value to make a datetime value for validation against the temporal restrictions
|
||||
- Added :equal_to option
|
||||
- Option key validation
|
||||
- Better behaviour with other plugins using alias_method_chain on read_attribute and define_attribute_methods
|
||||
- Added option to enable datetime_select extension for future use to optionally enable. Enabled by default until version 2.
|
||||
- Added :ignore_usec option for datetime restrictions to be compared without microsecond
|
||||
- some refactoring
|
||||
|
||||
= 1.1.5 [2009-01-21]
|
||||
- Fixed regex for 'yy' format token which wasn't greedy enough for date formats ending with year when a datetime string parsed as date with a 4 digit year
|
||||
|
||||
= 1.1.4 [2009-01-13]
|
||||
- Make months names respect i18n in Formats
|
||||
|
||||
= 1.1.3 [2009-01-13]
|
||||
- Fixed bug where time and date attributes still being parsed on read using Rails default parser [reported by Brad (pvjq)]
|
||||
|
||||
= 1.1.2 [2009-01-12]
|
||||
- Fixed bugs
|
||||
- matcher failing for custom error message without interpolation keys using I18n
|
||||
- validator custom error messages not being extracted
|
||||
|
||||
= 1.1.1 [2009-01-03]
|
||||
- Fixed bug in matcher for options local variable
|
||||
|
||||
= 1.1.0 [2009-01-01]
|
||||
- Added between option
|
||||
|
||||
= 1.0.0 [2008-12-06]
|
||||
- Gemified!
|
||||
- Refactor of plugin into a Data Mapper style validator class which makes for a cleaner implementation and possible future Merb\Data Mapper support
|
||||
|
||||
197
README.rdoc
197
README.rdoc
@@ -1,7 +1,7 @@
|
||||
= validates_timeliness
|
||||
|
||||
* Source: http://github.com/adzap/validates_timeliness
|
||||
* Bugs: http://adzap.lighthouseapp.com/projects/14111-validates_timeliness
|
||||
* Source: http://github.com/adzap/validates_timeliness
|
||||
* Bugs: http://adzap.lighthouseapp.com/projects/14111-validates_timeliness
|
||||
|
||||
== DESCRIPTION:
|
||||
|
||||
@@ -24,17 +24,25 @@ think should be a valid date or time string.
|
||||
|
||||
* Respects new timezone features of Rails 2.1.
|
||||
|
||||
* Supports Rails 2.2 I18n for the error messages
|
||||
|
||||
* Rspec matcher for testing model validation of dates and times
|
||||
|
||||
|
||||
== INSTALLATION:
|
||||
|
||||
As plugin (from master)
|
||||
|
||||
./script/plugin git://github.com/adzap/validates_timeliness
|
||||
./script/plugin install git://github.com/adzap/validates_timeliness.git
|
||||
|
||||
As gem
|
||||
|
||||
sudo gem install validates_timeliness
|
||||
|
||||
# in environment.rb
|
||||
|
||||
config.gem 'validates_timeliness'
|
||||
|
||||
|
||||
== USAGE:
|
||||
|
||||
@@ -47,47 +55,55 @@ validation method
|
||||
end
|
||||
|
||||
The list of validation methods available are as follows:
|
||||
|
||||
* validates_date - validate value as date
|
||||
|
||||
* validates_time - validate value as time only i.e. '12:20pm'
|
||||
|
||||
* validates_datetime - validate value as a full date and time
|
||||
validates_date - validate value as date
|
||||
validates_time - validate value as time only i.e. '12:20pm'
|
||||
validates_datetime - validate value as a full date and time
|
||||
|
||||
The validation methods take the usual options plus some specific ones to restrict
|
||||
the valid range of dates or times allowed
|
||||
|
||||
Temporal options (or restrictions):
|
||||
: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
|
||||
:after - Attribute must be after this value to be valid
|
||||
:on_or_after - Attribute must be equal to or after this value to be valid
|
||||
Temporal options (or restrictions):
|
||||
:equal_to - Attribute must be equal to 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
|
||||
:after - Attribute must be after this value to be valid
|
||||
:on_or_after - Attribute must be equal to or after this value to be valid
|
||||
:between - Attribute must be between the values to be valid. Takes an array of two values or a range
|
||||
|
||||
Regular validation options:
|
||||
:allow_nil - Allow a nil value to be valid
|
||||
:allow_blank - Allows a nil or empty string value to be valid
|
||||
:if - Execute validation when :if evaluates true
|
||||
:unless - Execute validation when :unless evaluates false
|
||||
Regular validation options:
|
||||
:allow_nil - Allow a nil value to be valid
|
||||
:allow_blank - Allows a nil or empty string value to be valid
|
||||
:if - Execute validation when :if evaluates true
|
||||
:unless - Execute validation when :unless evaluates false
|
||||
|
||||
Message options: - Use these to override the default error messages
|
||||
:invalid_date_message
|
||||
:invalid_time_message
|
||||
:invalid_datetime_message
|
||||
:before_message
|
||||
:on_or_before_message
|
||||
:after_message
|
||||
:on_or_after_message
|
||||
Special options:
|
||||
: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
|
||||
: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
|
||||
:invalid_date_message
|
||||
:invalid_time_message
|
||||
:invalid_datetime_message
|
||||
:equal_to_message
|
||||
:before_message
|
||||
:on_or_before_message
|
||||
:after_message
|
||||
:on_or_after_message
|
||||
:between_message
|
||||
|
||||
The temporal restrictions can take 4 different value types:
|
||||
|
||||
* String value
|
||||
* Date, Time, or DateTime object value
|
||||
* Proc or lambda object
|
||||
* A symbol matching the method name in the model
|
||||
The temporal restrictions, with_date and with_time can take 4 different value types:
|
||||
* String value
|
||||
* Date, Time, or DateTime object value
|
||||
* Proc or lambda object which may take an optional parameter being the record object
|
||||
* A symbol matching the method name in the model
|
||||
|
||||
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
|
||||
values are compared as dates.
|
||||
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
|
||||
temporal options.
|
||||
|
||||
== EXAMPLES:
|
||||
|
||||
@@ -100,6 +116,8 @@ values are compared as dates.
|
||||
:allow_nil => true
|
||||
|
||||
validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now }
|
||||
|
||||
validates_date :entry_date, :with_time => '17:00', :on_or_before => :competition_closing
|
||||
|
||||
|
||||
=== DATE/TIME FORMATS:
|
||||
@@ -113,44 +131,43 @@ 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
|
||||
plugin is needed.
|
||||
|
||||
Time formats:
|
||||
hh:nn:ss
|
||||
hh-nn-ss
|
||||
h:nn
|
||||
h.nn
|
||||
h nn
|
||||
h-nn
|
||||
h:nn_ampm
|
||||
h.nn_ampm
|
||||
h nn_ampm
|
||||
h-nn_ampm
|
||||
h_ampm
|
||||
|
||||
NOTE: Any time format without a meridian token (the 'ampm' token) is considered
|
||||
in 24 hour time.
|
||||
|
||||
Date formats:
|
||||
yyyy/mm/dd
|
||||
yyyy-mm-dd
|
||||
yyyy.mm.dd
|
||||
m/d/yy OR d/m/yy
|
||||
m\d\yy OR d\m\yy
|
||||
d-m-yy
|
||||
d.m.yy
|
||||
d mmm yy
|
||||
|
||||
NOTE: To use non-US date formats see US/EURO FORMATS section
|
||||
|
||||
Datetime formats:
|
||||
m/d/yy h:nn:ss OR d/m/yy hh:nn:ss
|
||||
m/d/yy h:nn OR d/m/yy h:nn
|
||||
m/d/yy h:nn_ampm OR d/m/yy h:nn_ampm
|
||||
yyyy-mm-dd hh:nn:ss
|
||||
yyyy-mm-dd h:nn
|
||||
ddd mmm d hh:nn:ss zo yyyy # Ruby time string
|
||||
yyyy-mm-ddThh:nn:ss(?:Z|zo) # ISO 8601
|
||||
==== Time formats:
|
||||
hh:nn:ss
|
||||
hh-nn-ss
|
||||
h:nn
|
||||
h.nn
|
||||
h nn
|
||||
h-nn
|
||||
h:nn_ampm
|
||||
h.nn_ampm
|
||||
h nn_ampm
|
||||
h-nn_ampm
|
||||
h_ampm
|
||||
|
||||
NOTE: To use non-US date formats see US/EURO FORMATS section
|
||||
NOTE: Any time format without a meridian token (the 'ampm' token) is considered in 24 hour time.
|
||||
|
||||
==== Date formats:
|
||||
yyyy/mm/dd
|
||||
yyyy-mm-dd
|
||||
yyyy.mm.dd
|
||||
m/d/yy OR d/m/yy
|
||||
m\d\yy OR d\m\yy
|
||||
d-m-yy
|
||||
d.m.yy
|
||||
d mmm yy
|
||||
|
||||
NOTE: To use non-US date formats see US/EURO FORMATS section
|
||||
|
||||
==== Datetime formats:
|
||||
m/d/yy h:nn:ss OR d/m/yy hh:nn:ss
|
||||
m/d/yy h:nn OR d/m/yy h:nn
|
||||
m/d/yy h:nn_ampm OR d/m/yy h:nn_ampm
|
||||
yyyy-mm-dd hh:nn:ss
|
||||
yyyy-mm-dd h:nn
|
||||
ddd mmm d hh:nn:ss zo yyyy # Ruby time string
|
||||
yyyy-mm-ddThh:nn:ss(?:Z|zo) # ISO 8601
|
||||
|
||||
NOTE: To use non-US date formats see US/EURO FORMATS section
|
||||
|
||||
Here is what each format token means:
|
||||
|
||||
@@ -193,7 +210,7 @@ of d/my/yy. By default the plugin uses the US formats as this is the Ruby defaul
|
||||
when it does date interpretation, and is in keeping PoLS (principle of least
|
||||
surprise).
|
||||
|
||||
To switch to using the :after => 1.day.from_nowEuropean (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
|
||||
|
||||
ValidatesTimeliness::Formats.remove_us_formats
|
||||
@@ -212,7 +229,7 @@ Done! That format is no longer considered valid. Easy!
|
||||
Ok, now I hear you say "Well I have format that I want to use but you don't have it".
|
||||
Ahh, then add it yourself. Again stick this in an initializer file
|
||||
|
||||
ValidatesTimeliness::Formats.add_formats(:time, "d o'clock")
|
||||
ValidatesTimeliness::Formats.add_formats(:time, "d o'clock")
|
||||
|
||||
Now "10 o'clock" will be a valid value. So easy, no more whingeing!
|
||||
|
||||
@@ -227,7 +244,7 @@ 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
|
||||
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')
|
||||
|
||||
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.
|
||||
@@ -250,6 +267,20 @@ corner cases a little harder to test. In general if you are using procs or
|
||||
model methods and you only care when they return a value, then they should
|
||||
return nil in all other situations. Restrictions are skipped if they are nil.
|
||||
|
||||
|
||||
=== DISPLAY INVALID VALUES IN DATE HELPERS:
|
||||
|
||||
The plugin has some extensions to ActionView and ActiveRecord by allowing invalid
|
||||
date and time values to be redisplayed to the user as feedback, instead of
|
||||
a blank field which happens by default in Rails. Though the date helpers make this a
|
||||
pretty rare occurence, given the select dropdowns for each date/time component, but
|
||||
it may be something of interest.
|
||||
|
||||
To activate it, put this in an initializer:
|
||||
|
||||
ValidatesTimeliness.enable_datetime_select_extension!
|
||||
|
||||
|
||||
=== OTHER CUSTOMISATION:
|
||||
|
||||
The error messages for each temporal restrictions can also be globally overridden by
|
||||
@@ -264,7 +295,8 @@ For Rails 2.0/2.1:
|
||||
:before => "must be before %s",
|
||||
:on_or_before => "must be on or before %s",
|
||||
:after => "must be after %s",
|
||||
:on_or_after => "must be on or after %s"
|
||||
:on_or_after => "must be on or after %s",
|
||||
:between => "must be between %s and %s"
|
||||
)
|
||||
|
||||
Where %s is the interpolation value for the restriction.
|
||||
@@ -275,8 +307,9 @@ Rails 2.2+ using the I18n system to define new defaults:
|
||||
activerecord:
|
||||
errors:
|
||||
messages:
|
||||
on_or_before: "must equal to or before {{restriction}}"
|
||||
on_or_after: "must equal to or after {{restriction}}"
|
||||
on_or_before: "must be equal to or before {{restriction}}"
|
||||
on_or_after: "must be equal to or after {{restriction}}"
|
||||
between: "must be between {{earliest}} and {{latest}}"
|
||||
|
||||
The {{restriction}} signifies where the interpolation value for the restriction
|
||||
will be inserted.
|
||||
@@ -284,12 +317,22 @@ will be inserted.
|
||||
And for something a little more specific you can override the format of the interpolation
|
||||
values inserted in the error messages for temporal restrictions like so
|
||||
|
||||
For Rails 2.0/2.1:
|
||||
|
||||
ValidatesTimeliness::Validator.error_value_formats.update(
|
||||
:time => '%H:%M:%S',
|
||||
:date => '%Y-%m-%d',
|
||||
:datetime => '%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
Rails 2.2+ using the I18n system to define new defaults:
|
||||
|
||||
validates_timeliness:
|
||||
error_value_formats:
|
||||
date: '%Y-%m-%d'
|
||||
time: '%H:%M:%S'
|
||||
datetime: '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
Those are Ruby strftime formats not the plugin formats.
|
||||
|
||||
|
||||
|
||||
5
Rakefile
5
Rakefile
@@ -5,7 +5,7 @@ require 'date'
|
||||
require 'spec/rake/spectask'
|
||||
|
||||
GEM = "validates_timeliness"
|
||||
GEM_VERSION = "1.0.0"
|
||||
GEM_VERSION = "2.0.0"
|
||||
AUTHOR = "Adam Meehan"
|
||||
EMAIL = "adam.meehan@gmail.com"
|
||||
HOMEPAGE = "http://github.com/adzap/validates_timeliness"
|
||||
@@ -24,9 +24,6 @@ spec = Gem::Specification.new do |s|
|
||||
s.email = EMAIL
|
||||
s.homepage = HOMEPAGE
|
||||
|
||||
# Uncomment this to add a dependency
|
||||
# s.add_dependency "foo"
|
||||
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = GEM
|
||||
s.files = %w(LICENSE README.rdoc Rakefile TODO CHANGELOG) + Dir.glob("{lib,spec}/**/*")
|
||||
|
||||
11
TODO
11
TODO
@@ -1,8 +1,5 @@
|
||||
- :between option
|
||||
- :format option
|
||||
- :with_date and :with_time options
|
||||
- Merb and Data Mapper support
|
||||
- does it have before_type_cast
|
||||
- timezone handling
|
||||
- view helper support
|
||||
- valid formats could come from locale file
|
||||
- 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
|
||||
|
||||
28
benchmark.rb
28
benchmark.rb
@@ -4,73 +4,74 @@ require 'date'
|
||||
require 'parsedate'
|
||||
require 'benchmark'
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
require 'active_record'
|
||||
require 'action_controller'
|
||||
require 'rails/version'
|
||||
|
||||
require 'validates_timeliness'
|
||||
|
||||
def parse(*args)
|
||||
ValidatesTimeliness::Parser.parse(*args)
|
||||
end
|
||||
|
||||
n = 10000
|
||||
Benchmark.bm do |x|
|
||||
x.report('timeliness - datetime') {
|
||||
n.times do
|
||||
ActiveRecord::Base.parse_date_time("2000-01-04 12:12:12", :datetime)
|
||||
parse("2000-01-04 12:12:12", :datetime)
|
||||
end
|
||||
}
|
||||
|
||||
x.report('timeliness - date') {
|
||||
n.times do
|
||||
ActiveRecord::Base.parse_date_time("2000-01-04", :date)
|
||||
parse("2000-01-04", :date)
|
||||
end
|
||||
}
|
||||
|
||||
x.report('timeliness - date as datetime') {
|
||||
n.times do
|
||||
ActiveRecord::Base.parse_date_time("2000-01-04", :datetime)
|
||||
parse("2000-01-04", :datetime)
|
||||
end
|
||||
}
|
||||
|
||||
x.report('timeliness - time') {
|
||||
n.times do
|
||||
ActiveRecord::Base.parse_date_time("12:01:02", :time)
|
||||
parse("12:01:02", :time)
|
||||
end
|
||||
}
|
||||
|
||||
x.report('timeliness - invalid format datetime') {
|
||||
n.times do
|
||||
ActiveRecord::Base.parse_date_time("20xx-01-04 12:12:12", :datetime)
|
||||
parse("20xx-01-04 12:12:12", :datetime)
|
||||
end
|
||||
}
|
||||
|
||||
x.report('timeliness - invalid format date') {
|
||||
n.times do
|
||||
ActiveRecord::Base.parse_date_time("20xx-01-04", :date)
|
||||
parse("20xx-01-04", :date)
|
||||
end
|
||||
}
|
||||
|
||||
x.report('timeliness - invalid format time') {
|
||||
n.times do
|
||||
ActiveRecord::Base.parse_date_time("12:xx:02", :time)
|
||||
parse("12:xx:02", :time)
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
x.report('timeliness - invalid value datetime') {
|
||||
n.times do
|
||||
ActiveRecord::Base.parse_date_time("2000-01-32 12:12:12", :datetime)
|
||||
parse("2000-01-32 12:12:12", :datetime)
|
||||
end
|
||||
}
|
||||
|
||||
x.report('timeliness - invalid value date') {
|
||||
n.times do
|
||||
ActiveRecord::Base.parse_date_time("2000-01-32", :date)
|
||||
parse("2000-01-32", :date)
|
||||
end
|
||||
}
|
||||
|
||||
x.report('timeliness - invalid value time') {
|
||||
n.times do
|
||||
ActiveRecord::Base.parse_date_time("12:61:02", :time)
|
||||
parse("12:61:02", :time)
|
||||
end
|
||||
}
|
||||
x.report('date/time') {
|
||||
@@ -96,4 +97,3 @@ Benchmark.bm do |x|
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require 'validates_timeliness/formats'
|
||||
require 'validates_timeliness/parser'
|
||||
require 'validates_timeliness/validator'
|
||||
require 'validates_timeliness/validation_methods'
|
||||
require 'validates_timeliness/spec/rails/matchers/validate_timeliness' if ENV['RAILS_ENV'] == 'test'
|
||||
@@ -14,53 +15,39 @@ require 'validates_timeliness/core_ext/date_time'
|
||||
module ValidatesTimeliness
|
||||
|
||||
mattr_accessor :default_timezone
|
||||
|
||||
self.default_timezone = :utc
|
||||
|
||||
mattr_accessor :use_time_zones
|
||||
self.use_time_zones = false
|
||||
|
||||
LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/validates_timeliness/locale/en.yml')
|
||||
|
||||
class << self
|
||||
|
||||
def load_error_messages_with_i18n
|
||||
I18n.load_path += [ LOCALE_PATH ]
|
||||
def enable_datetime_select_extension!
|
||||
enable_datetime_select_invalid_value_extension!
|
||||
enable_multiparameter_attributes_extension!
|
||||
end
|
||||
|
||||
def load_error_messages_without_i18n
|
||||
messages = YAML::load(IO.read(LOCALE_PATH))
|
||||
errors = messages['en']['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h }
|
||||
::ActiveRecord::Errors.default_error_messages.update(errors)
|
||||
end
|
||||
|
||||
def default_error_messages
|
||||
if Rails::VERSION::STRING < '2.2'
|
||||
::ActiveRecord::Errors.default_error_messages
|
||||
def load_error_messages
|
||||
if defined?(I18n)
|
||||
I18n.load_path += [ LOCALE_PATH ]
|
||||
I18n.reload!
|
||||
else
|
||||
I18n.translate('activerecord.errors.messages')
|
||||
defaults = YAML::load(IO.read(LOCALE_PATH))['en']
|
||||
errors = defaults['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h }
|
||||
::ActiveRecord::Errors.default_error_messages.update(errors)
|
||||
|
||||
ValidatesTimeliness::Validator.error_value_formats = defaults['validates_timeliness']['error_value_formats'].symbolize_keys
|
||||
end
|
||||
end
|
||||
|
||||
def setup_for_rails_2_0
|
||||
load_error_messages_without_i18n
|
||||
end
|
||||
|
||||
def setup_for_rails_2_1
|
||||
load_error_messages_without_i18n
|
||||
end
|
||||
|
||||
def setup_for_rails_2_2
|
||||
load_error_messages_with_i18n
|
||||
end
|
||||
|
||||
|
||||
def setup_for_rails
|
||||
major, minor = Rails::VERSION::MAJOR, Rails::VERSION::MINOR
|
||||
self.send("setup_for_rails_#{major}_#{minor}")
|
||||
self.default_timezone = ::ActiveRecord::Base.default_timezone
|
||||
rescue
|
||||
raise "Rails version #{Rails::VERSION::STRING} not yet supported by validates_timeliness plugin"
|
||||
self.use_time_zones = ::ActiveRecord::Base.time_zone_aware_attributes rescue false
|
||||
load_error_messages
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ValidatesTimeliness.setup_for_rails
|
||||
|
||||
ValidatesTimeliness::Formats.compile_format_expressions
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
module ValidatesTimeliness
|
||||
|
||||
def self.enable_datetime_select_invalid_value_extension!
|
||||
::ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::ActionView::InstanceTag)
|
||||
end
|
||||
|
||||
module ActionView
|
||||
|
||||
# Intercepts the date and time select helpers to allow the
|
||||
@@ -32,7 +37,7 @@ module ValidatesTimeliness
|
||||
return value_without_timeliness(object)
|
||||
end
|
||||
|
||||
time_array = ParseDate.parsedate(raw_value)
|
||||
time_array = ValidatesTimeliness::Formats.parse(raw_value, :datetime)
|
||||
|
||||
TimelinessDateTime.new(*time_array[0..5])
|
||||
end
|
||||
@@ -41,5 +46,3 @@ module ValidatesTimeliness
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::ActionView::InstanceTag)
|
||||
|
||||
@@ -14,22 +14,14 @@ module ValidatesTimeliness
|
||||
# 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.
|
||||
#
|
||||
# The wholesale replacement of the Rails time type casting is not done to
|
||||
# preserve the quickest conversion for timestamp columns and also any value
|
||||
# which is never changed during the life of the record object.
|
||||
module AttributeMethods
|
||||
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
|
||||
if Rails::VERSION::STRING < '2.1'
|
||||
base.class_eval do
|
||||
class << self
|
||||
def create_time_zone_conversion_attribute?(name, column)
|
||||
false
|
||||
end
|
||||
end
|
||||
base.class_eval do
|
||||
alias_method_chain :read_attribute, :timeliness
|
||||
class << self
|
||||
alias_method_chain :define_attribute_methods, :timeliness
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -38,110 +30,92 @@ module ValidatesTimeliness
|
||||
# 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(attr_name)
|
||||
def read_attribute_with_timeliness(attr_name)
|
||||
attr_name = attr_name.to_s
|
||||
if !(value = @attributes[attr_name]).nil?
|
||||
if column = column_for_attribute(attr_name)
|
||||
if unserializable_attribute?(attr_name, column)
|
||||
unserialize_attribute(attr_name)
|
||||
elsif [:date, :time, :datetime].include?(column.type) && @attributes_cache.has_key?(attr_name)
|
||||
@attributes_cache[attr_name]
|
||||
else
|
||||
column.type_cast(value)
|
||||
end
|
||||
else
|
||||
value
|
||||
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
|
||||
else
|
||||
nil
|
||||
end
|
||||
read_attribute_without_timeliness(attr_name)
|
||||
end
|
||||
|
||||
# Writes attribute value by storing raw value in attributes hash,
|
||||
# then convert it with parser and cache it.
|
||||
#
|
||||
# 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)
|
||||
column = column_for_attribute(attr_name)
|
||||
old = read_attribute(attr_name) if defined?(::ActiveRecord::Dirty)
|
||||
new = self.class.parse_date_time(value, column.type)
|
||||
def write_date_time_attribute(attr_name, value, type, time_zone_aware)
|
||||
new = ValidatesTimeliness::Parser.parse(value, type)
|
||||
|
||||
unless column.type == :date || new.nil?
|
||||
new = new.to_time rescue new
|
||||
if new && type != :date
|
||||
new = new.to_time
|
||||
new = new.in_time_zone if time_zone_aware
|
||||
end
|
||||
|
||||
if self.class.send(:create_time_zone_conversion_attribute?, attr_name, column)
|
||||
new = new.in_time_zone rescue nil
|
||||
if defined?(::ActiveRecord::Dirty) && !changed_attributes.include?(attr_name)
|
||||
old = read_attribute(attr_name)
|
||||
if old != new
|
||||
changed_attributes[attr_name] = (old.duplicable? ? old.clone : old)
|
||||
end
|
||||
end
|
||||
@attributes_cache[attr_name] = new
|
||||
|
||||
if defined?(::ActiveRecord::Dirty) && !changed_attributes.include?(attr_name) && old != new
|
||||
changed_attributes[attr_name] = (old.duplicable? ? old.clone : old)
|
||||
end
|
||||
@attributes[attr_name] = value
|
||||
end
|
||||
|
||||
# If reloading then check if cached, which means its in local time.
|
||||
# If local, convert with parser as local timezone, otherwise use
|
||||
# read_attribute method for quick default type cast of values from
|
||||
# database using default timezone.
|
||||
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 = ValidatesTimeliness::Parser.parse(time, type)
|
||||
else
|
||||
time = read_attribute(attr_name)
|
||||
@attributes[attr_name] = (time && time_zone_aware ? time.in_time_zone : time) unless frozen?
|
||||
end
|
||||
@attributes_cache[attr_name] = time && time_zone_aware ? time.in_time_zone : time
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
||||
# Override AR method to define attribute reader and writer method for
|
||||
# date, time and datetime attributes to use plugin parser.
|
||||
def define_attribute_methods
|
||||
def define_attribute_methods_with_timeliness
|
||||
return if generated_methods?
|
||||
columns_hash.each do |name, column|
|
||||
unless instance_method_already_implemented?(name)
|
||||
if self.serialized_attributes[name]
|
||||
define_read_method_for_serialized_attribute(name)
|
||||
elsif create_time_zone_conversion_attribute?(name, column)
|
||||
define_read_method_for_time_zone_conversion(name)
|
||||
else
|
||||
define_read_method(name.to_sym, name, column)
|
||||
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}=")
|
||||
if [:date, :time, :datetime].include?(column.type)
|
||||
define_write_method_for_dates_and_times(name)
|
||||
else
|
||||
define_write_method(name.to_sym)
|
||||
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
|
||||
define_write_method_for_dates_and_times(name, column.type, time_zone_aware)
|
||||
end
|
||||
end
|
||||
|
||||
unless instance_method_already_implemented?("#{name}?")
|
||||
define_question_method(name)
|
||||
end
|
||||
end
|
||||
define_attribute_methods_without_timeliness
|
||||
end
|
||||
|
||||
# Define write method for date, time and datetime columns
|
||||
def define_write_method_for_dates_and_times(attr_name)
|
||||
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)
|
||||
write_date_time_attribute('#{attr_name}', value, #{type.inspect}, #{time_zone_aware})
|
||||
end
|
||||
EOV
|
||||
evaluate_attribute_method attr_name, method_body, "#{attr_name}="
|
||||
end
|
||||
|
||||
# Define time attribute reader. If reloading then check if cached,
|
||||
# which means its in local time. If local, convert with parser as local
|
||||
# timezone, otherwise use read_attribute method for quick default type
|
||||
# cast of values from database using default timezone.
|
||||
def define_read_method_for_time_zone_conversion(attr_name)
|
||||
def define_read_method_for_dates_and_times(attr_name, type, time_zone_aware)
|
||||
method_body = <<-EOV
|
||||
def #{attr_name}(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(date, :datetime)
|
||||
else
|
||||
time = read_attribute('#{attr_name}')
|
||||
@attributes['#{attr_name}'] = time.in_time_zone rescue nil
|
||||
end
|
||||
@attributes_cache['#{attr_name}'] = time.in_time_zone rescue nil
|
||||
read_date_time_attribute('#{attr_name}', #{type.inspect}, #{time_zone_aware}, reload)
|
||||
end
|
||||
EOV
|
||||
evaluate_attribute_method attr_name, method_body
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
module ValidatesTimeliness
|
||||
module ActiveRecord
|
||||
|
||||
def self.enable_multiparameter_attributes_extension!
|
||||
::ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::MultiparameterAttributes)
|
||||
end
|
||||
|
||||
module ActiveRecord
|
||||
module MultiparameterAttributes
|
||||
|
||||
def self.included(base)
|
||||
@@ -12,18 +16,16 @@ module ValidatesTimeliness
|
||||
def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
|
||||
errors = []
|
||||
callstack.each do |name, values|
|
||||
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
|
||||
if values.empty?
|
||||
send(name + "=", nil)
|
||||
else
|
||||
column = column_for_attribute(name)
|
||||
column = column_for_attribute(name)
|
||||
if column && [:date, :time, :datetime].include?(column.type)
|
||||
begin
|
||||
value = if [:date, :time, :datetime].include?(column.type)
|
||||
time_array_to_string(values, column.type)
|
||||
callstack.delete(name)
|
||||
if values.empty?
|
||||
send("#{name}=", nil)
|
||||
else
|
||||
klass.new(*values)
|
||||
value = time_array_to_string(values, column.type)
|
||||
send("#{name}=", value)
|
||||
end
|
||||
send(name + "=", value)
|
||||
rescue => ex
|
||||
errors << ::ActiveRecord::AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
|
||||
end
|
||||
@@ -32,19 +34,20 @@ module ValidatesTimeliness
|
||||
unless errors.empty?
|
||||
raise ::ActiveRecord::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
|
||||
end
|
||||
execute_callstack_for_multiparameter_attributes_without_timeliness(callstack)
|
||||
end
|
||||
|
||||
def time_array_to_string(values, type)
|
||||
values = values.map(&:to_s)
|
||||
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
|
||||
date_values, time_values = values.slice!(0, 3), values
|
||||
extract_date_from_multiparameter_attributes(date_values) + " " + extract_time_from_multiparameter_attributes(time_values)
|
||||
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
|
||||
|
||||
@@ -53,12 +56,11 @@ module ValidatesTimeliness
|
||||
end
|
||||
|
||||
def extract_time_from_multiparameter_attributes(values)
|
||||
values.last(3).map { |s| s.rjust(2, "0") }.join(":")
|
||||
values = values.size > 3 ? values[3..5] : values
|
||||
values.map { |s| s.rjust(2, "0") }.join(":")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::MultiparameterAttributes)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require 'date'
|
||||
|
||||
module ValidatesTimeliness
|
||||
|
||||
# A date and time format regular expression generator. Allows you to
|
||||
@@ -10,16 +12,14 @@ module ValidatesTimeliness
|
||||
# string values.
|
||||
#
|
||||
class Formats
|
||||
cattr_accessor :time_formats
|
||||
cattr_accessor :date_formats
|
||||
cattr_accessor :datetime_formats
|
||||
|
||||
cattr_accessor :time_expressions
|
||||
cattr_accessor :date_expressions
|
||||
cattr_accessor :datetime_expressions
|
||||
|
||||
cattr_accessor :format_tokens
|
||||
cattr_accessor :format_proc_args
|
||||
cattr_accessor :time_formats,
|
||||
:date_formats,
|
||||
:datetime_formats,
|
||||
:time_expressions,
|
||||
:date_expressions,
|
||||
:datetime_expressions,
|
||||
:format_tokens,
|
||||
:format_proc_args
|
||||
|
||||
# Format tokens:
|
||||
# y = year
|
||||
@@ -115,7 +115,7 @@ module ValidatesTimeliness
|
||||
{ 'mm' => [ /m{2}/, '(\d{2})', :month ] },
|
||||
{ 'm' => [ /(\A|[^ap])m{1}/, '(\d{1,2})', :month ] },
|
||||
{ 'yyyy' => [ /y{4,}/, '(\d{4})', :year ] },
|
||||
{ 'yy' => [ /y{2,}/, '(\d{2}|\d{4})', :year ] },
|
||||
{ 'yy' => [ /y{2,}/, '(\d{4}|\d{2})', :year ] },
|
||||
{ 'hh' => [ /h{2,}/, '(\d{2})', :hour ] },
|
||||
{ 'h' => [ /h{1}/, '(\d{1,2})', :hour ] },
|
||||
{ 'nn' => [ /n{2,}/, '(\d{2})', :min ] },
|
||||
@@ -124,13 +124,13 @@ module ValidatesTimeliness
|
||||
{ 's' => [ /s{1}/, '(\d{1,2})', :sec ] },
|
||||
{ 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] },
|
||||
{ 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] },
|
||||
{ 'zo' => [ /zo/, '(?:[+-]\d{2}:?\d{2})'] },
|
||||
{ 'zo' => [ /zo/, '([+-]\d{2}:?\d{2})', :offset ] },
|
||||
{ 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] },
|
||||
{ '_' => [ /_/, '\s?' ] }
|
||||
]
|
||||
|
||||
# Arguments whichs will be passed to the format proc if matched in the
|
||||
# time string. The key must should the key from the format tokens. The array
|
||||
# Arguments which will be passed to the format proc if matched in the
|
||||
# time string. The key must be the key from the format tokens. The array
|
||||
# consists of the arry position of the arg, the arg name, and the code to
|
||||
# place in the time array slot. The position can be nil which means the arg
|
||||
# won't be placed in the array.
|
||||
@@ -139,13 +139,14 @@ module ValidatesTimeliness
|
||||
# should just be the arg name.
|
||||
#
|
||||
@@format_proc_args = {
|
||||
:year => [0, 'y', 'unambiguous_year(y)'],
|
||||
:month => [1, 'm', 'month_index(m)'],
|
||||
:day => [2, 'd', 'd'],
|
||||
:hour => [3, 'h', 'full_hour(h,md)'],
|
||||
:min => [4, 'n', 'n'],
|
||||
:sec => [5, 's', 's'],
|
||||
:usec => [6, 'u', 'microseconds(u)'],
|
||||
:year => [0, 'y', 'unambiguous_year(y)'],
|
||||
:month => [1, 'm', 'month_index(m)'],
|
||||
:day => [2, 'd', 'd'],
|
||||
:hour => [3, 'h', 'full_hour(h,md)'],
|
||||
:min => [4, 'n', 'n'],
|
||||
:sec => [5, 's', 's'],
|
||||
:usec => [6, 'u', 'microseconds(u)'],
|
||||
:offset => [7, 'z', 'offset_in_seconds(z)'],
|
||||
:meridian => [nil, 'md', nil]
|
||||
}
|
||||
|
||||
@@ -160,20 +161,29 @@ module ValidatesTimeliness
|
||||
# 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
|
||||
# regexp in start and end anchors.
|
||||
# Returns 7 part time array.
|
||||
def parse(string, type, strict=true)
|
||||
# Returns time array if matches a format, nil otherwise.
|
||||
def parse(string, type, options={})
|
||||
return string unless string.is_a?(String)
|
||||
|
||||
expressions = expression_set(type, string)
|
||||
time_array = nil
|
||||
expressions.each do |(regexp, processor)|
|
||||
regexp = strict || type == :datetime ? /\A#{regexp}\Z/ : (type == :date ? /\A#{regexp}/ : /#{regexp}\Z/)
|
||||
if matches = regexp.match(string.strip)
|
||||
time_array = processor.call(*matches[1..7])
|
||||
break
|
||||
end
|
||||
options.reverse_merge!(:strict => true)
|
||||
|
||||
sets = if options[:format]
|
||||
[ send("#{type}_expressions").assoc(options[:format]) ]
|
||||
else
|
||||
expression_set(type, string)
|
||||
end
|
||||
return time_array
|
||||
|
||||
matches = nil
|
||||
processor = sets.each do |format, regexp, proc|
|
||||
full = /\A#{regexp}\Z/ if options[:strict]
|
||||
full ||= case type
|
||||
when :date then /\A#{regexp}/
|
||||
when :time then /#{regexp}\Z/
|
||||
when :datetime then /\A#{regexp}\Z/
|
||||
end
|
||||
break(proc) if matches = full.match(string.strip)
|
||||
end
|
||||
last = options[:include_offset] ? 8 : 7
|
||||
processor.call(*matches[1..last]) if matches
|
||||
end
|
||||
|
||||
# Delete formats of specified type. Error raised if format not found.
|
||||
@@ -205,8 +215,7 @@ module ValidatesTimeliness
|
||||
end
|
||||
compile_format_expressions
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Removes formats where the 1 or 2 digit month comes first, to eliminate
|
||||
# formats which are ambiguous with the European style of day then month.
|
||||
# The mmm token is ignored as its not ambigous.
|
||||
@@ -223,7 +232,7 @@ module ValidatesTimeliness
|
||||
def format_expression_generator(string_format)
|
||||
regexp = string_format.dup
|
||||
order = {}
|
||||
regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes ]/
|
||||
regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes
|
||||
|
||||
format_tokens.each do |token|
|
||||
token_name = token.keys.first
|
||||
@@ -237,8 +246,7 @@ module ValidatesTimeliness
|
||||
|
||||
return Regexp.new(regexp), format_proc(order)
|
||||
rescue
|
||||
puts "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}."
|
||||
raise
|
||||
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
|
||||
@@ -246,39 +254,34 @@ module ValidatesTimeliness
|
||||
# argument in the position indicated by the first element of the proc arg
|
||||
# array.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# 'yyyy-mm-dd hh:nn' => lambda {|y,m,d,h,n| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } }
|
||||
# 'dd/mm/yyyy h:nn_ampm' => lambda {|d,m,y,h,n,md| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } }
|
||||
#
|
||||
def format_proc(order)
|
||||
arg_map = format_proc_args
|
||||
args = order.invert.sort.map {|p| arg_map[p[1]][1] }
|
||||
arr = [nil] * 7
|
||||
order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? }
|
||||
proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.to_i } }"
|
||||
proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.is_a?(Float) ? i : i.to_i } }"
|
||||
eval proc_string
|
||||
end
|
||||
|
||||
def compile_formats(formats)
|
||||
formats.collect { |format| regexp, format_proc = format_expression_generator(format) }
|
||||
formats.map { |format| [ format, *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
|
||||
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
|
||||
|
||||
@@ -299,12 +302,29 @@ module ValidatesTimeliness
|
||||
|
||||
def month_index(month)
|
||||
return month.to_i if month.to_i.nonzero?
|
||||
Date::ABBR_MONTHNAMES.index(month.capitalize) || Date::MONTHNAMES.index(month.capitalize)
|
||||
abbr_month_names.index(month.capitalize) || month_names.index(month.capitalize)
|
||||
end
|
||||
|
||||
|
||||
def month_names
|
||||
defined?(I18n) ? I18n.t('date.month_names') : Date::MONTHNAMES
|
||||
end
|
||||
|
||||
def abbr_month_names
|
||||
defined?(I18n) ? I18n.t('date.abbr_month_names') : Date::ABBR_MONTHNAMES
|
||||
end
|
||||
|
||||
def microseconds(usec)
|
||||
(".#{usec}".to_f * 1_000_000).to_i
|
||||
end
|
||||
|
||||
def offset_in_seconds(offset)
|
||||
sign = offset =~ /^-/ ? -1 : 1
|
||||
parts = offset.scan(/\d\d/).map {|p| p.to_f }
|
||||
parts[1] = parts[1].to_f / 60
|
||||
(parts[0] + parts[1]) * sign * 3600
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ValidatesTimeliness::Formats.compile_format_expressions
|
||||
|
||||
@@ -5,7 +5,14 @@ en:
|
||||
invalid_date: "is not a valid date"
|
||||
invalid_time: "is not a valid time"
|
||||
invalid_datetime: "is not a valid datetime"
|
||||
equal_to: "must be equal to {{restriction}}"
|
||||
before: "must be before {{restriction}}"
|
||||
on_or_before: "must be on or before {{restriction}}"
|
||||
after: "must be after {{restriction}}"
|
||||
on_or_after: "must be on or after {{restriction}}"
|
||||
between: "must be between {{earliest}} and {{latest}}"
|
||||
validates_timeliness:
|
||||
error_value_formats:
|
||||
date: '%Y-%m-%d'
|
||||
time: '%H:%M:%S'
|
||||
datetime: '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
46
lib/validates_timeliness/parser.rb
Normal file
46
lib/validates_timeliness/parser.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
module ValidatesTimeliness
|
||||
module Parser
|
||||
|
||||
class << self
|
||||
|
||||
def parse(raw_value, type, options={})
|
||||
return nil if raw_value.blank?
|
||||
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
|
||||
|
||||
options.reverse_merge!(:strict => true)
|
||||
|
||||
time_array = ValidatesTimeliness::Formats.parse(raw_value, type, options)
|
||||
raise if time_array.nil?
|
||||
|
||||
# Rails dummy time date part is defined as 2000-01-01
|
||||
time_array[0..2] = 2000, 1, 1 if type == :time
|
||||
|
||||
# Date.new enforces days per month, unlike Time
|
||||
date = Date.new(*time_array[0..2]) unless type == :time
|
||||
|
||||
return date if type == :date
|
||||
|
||||
make_time(time_array[0..7])
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
def make_time(time_array)
|
||||
if Time.respond_to?(:zone) && ValidatesTimeliness.use_time_zones
|
||||
Time.zone.local(*time_array)
|
||||
else
|
||||
begin
|
||||
time_zone = ValidatesTimeliness.default_timezone
|
||||
Time.send(time_zone, *time_array)
|
||||
rescue ArgumentError, TypeError
|
||||
zone_offset = time_zone == :local ? DateTime.local_offset : 0
|
||||
time_array.pop # remove microseconds
|
||||
DateTime.civil(*(time_array << zone_offset))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -2,120 +2,158 @@ module Spec
|
||||
module Rails
|
||||
module Matchers
|
||||
class ValidateTimeliness
|
||||
cattr_accessor :test_values
|
||||
|
||||
@@test_values = {
|
||||
VALIDITY_TEST_VALUES = {
|
||||
:date => {:pass => '2000-01-01', :fail => '2000-01-32'},
|
||||
:time => {:pass => '12:00', :fail => '25:00'},
|
||||
:datetime => {:pass => '2000-01-01 00:00:00', :fail => '2000-01-32 00:00:00'}
|
||||
}
|
||||
|
||||
OPTION_TEST_SETTINGS = {
|
||||
:equal_to => { :method => :+, :modify_on => :invalid },
|
||||
:before => { :method => :-, :modify_on => :valid },
|
||||
:after => { :method => :+, :modify_on => :valid },
|
||||
:on_or_before => { :method => :+, :modify_on => :invalid },
|
||||
:on_or_after => { :method => :-, :modify_on => :invalid }
|
||||
}
|
||||
|
||||
def initialize(attribute, options)
|
||||
@expected, @options = attribute, options
|
||||
@validator = ValidatesTimeliness::Validator.new(options)
|
||||
compile_error_messages
|
||||
end
|
||||
|
||||
def compile_error_messages
|
||||
messages = validator.send(:error_messages)
|
||||
@messages = messages.inject({}) {|h, (k, v)| h[k] = v.gsub(/ (\%s|\{\{\w*\}\})/, ''); h }
|
||||
end
|
||||
|
||||
def matches?(record)
|
||||
@record = record
|
||||
type = options[:type]
|
||||
@type = @options[:type]
|
||||
|
||||
invalid_value = @@test_values[type][:fail]
|
||||
valid_value = parse_and_cast(@@test_values[type][:pass])
|
||||
valid = error_matching(invalid_value, /#{messages["invalid_#{type}".to_sym]}/) &&
|
||||
no_error_matching(valid_value, /#{messages["invalid_#{type}".to_sym]}/)
|
||||
valid = test_validity
|
||||
|
||||
valid = test_option(:before, :-) if options[:before] && valid
|
||||
valid = test_option(:after, :+) if options[:after] && valid
|
||||
|
||||
valid = test_option(:on_or_before, :+, :modify_on => :invalid) if options[:on_or_before] && valid
|
||||
valid = test_option(:on_or_after, :-, :modify_on => :invalid) if options[:on_or_after] && valid
|
||||
valid = test_option(:equal_to) if @options[:equal_to] && valid
|
||||
valid = test_option(:before) if @options[:before] && valid
|
||||
valid = test_option(:after) if @options[:after] && valid
|
||||
valid = test_option(:on_or_before) if @options[:on_or_before] && valid
|
||||
valid = test_option(:on_or_after) if @options[:on_or_after] && valid
|
||||
|
||||
valid = test_between if @options[:between] && valid
|
||||
|
||||
return valid
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"expected model to validate #{options[:type]} attribute #{expected.inspect} with #{last_failure}"
|
||||
"expected model to validate #{@type} attribute #{@expected.inspect} with #{@last_failure}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"expected not to validate #{options[:type]} attribute #{expected.inspect}"
|
||||
"expected not to validate #{@type} attribute #{@expected.inspect}"
|
||||
end
|
||||
|
||||
def description
|
||||
"have validated #{options[:type]} attribute #{expected.inspect}"
|
||||
"have validated #{@type} attribute #{@expected.inspect}"
|
||||
end
|
||||
|
||||
private
|
||||
attr_reader :actual, :expected, :record, :options, :messages, :last_failure, :validator
|
||||
|
||||
def test_option(option, modifier, settings={})
|
||||
settings.reverse_merge!(:modify_on => :valid)
|
||||
boundary = parse_and_cast(options[option])
|
||||
|
||||
def test_validity
|
||||
invalid_value = VALIDITY_TEST_VALUES[@type][:fail]
|
||||
valid_value = parse_and_cast(VALIDITY_TEST_VALUES[@type][:pass])
|
||||
error_matching(invalid_value, "invalid_#{@type}".to_sym) &&
|
||||
no_error_matching(valid_value, "invalid_#{@type}".to_sym)
|
||||
end
|
||||
|
||||
def test_option(option)
|
||||
settings = OPTION_TEST_SETTINGS[option]
|
||||
boundary = parse_and_cast(@options[option])
|
||||
|
||||
method = settings[:method]
|
||||
|
||||
valid_value, invalid_value = if settings[:modify_on] == :valid
|
||||
[ boundary.send(modifier, 1), boundary ]
|
||||
[ boundary.send(method, 1), boundary ]
|
||||
else
|
||||
[ boundary, boundary.send(modifier, 1) ]
|
||||
[ boundary, boundary.send(method, 1) ]
|
||||
end
|
||||
|
||||
message = messages[option]
|
||||
error_matching(invalid_value, /#{message}/) &&
|
||||
no_error_matching(valid_value, /#{message}/)
|
||||
error_matching(invalid_value, option) &&
|
||||
no_error_matching(valid_value, option)
|
||||
end
|
||||
|
||||
def test_before
|
||||
before = parse_and_cast(@options[:before])
|
||||
|
||||
error_matching(before - 1, :before) &&
|
||||
no_error_matching(before, :before)
|
||||
end
|
||||
|
||||
def test_between
|
||||
between = parse_and_cast(@options[:between])
|
||||
|
||||
error_matching(between.first - 1, :between) &&
|
||||
error_matching(between.last + 1, :between) &&
|
||||
no_error_matching(between.first, :between) &&
|
||||
no_error_matching(between.last, :between)
|
||||
end
|
||||
|
||||
def parse_and_cast(value)
|
||||
value = validator.send(:restriction_value, value, record)
|
||||
validator.send(:type_cast_value, value)
|
||||
value = @validator.class.send(:evaluate_option_value, value, @type, @record)
|
||||
@validator.class.send(:type_cast_value, value, @type)
|
||||
end
|
||||
|
||||
def error_matching(value, match)
|
||||
record.send("#{expected}=", value)
|
||||
record.valid?
|
||||
errors = record.errors.on(expected)
|
||||
pass = [ errors ].flatten.any? {|error| match === error }
|
||||
@last_failure = "error matching #{match.inspect} when value is #{format_value(value)}" unless pass
|
||||
def error_matching(value, option)
|
||||
match = error_message_for(option)
|
||||
@record.send("#{@expected}=", value)
|
||||
@record.valid?
|
||||
errors = @record.errors.on(@expected)
|
||||
pass = [ errors ].flatten.any? {|error| /#{match}/ === error }
|
||||
@last_failure = "error matching '#{match}' when value is #{format_value(value)}" unless pass
|
||||
pass
|
||||
end
|
||||
|
||||
def no_error_matching(value, match)
|
||||
pass = !error_matching(value, match)
|
||||
@last_failure = "no error matching #{match.inspect} when value is #{format_value(value)}" unless pass
|
||||
def no_error_matching(value, option)
|
||||
pass = !error_matching(value, option)
|
||||
unless pass
|
||||
error = error_message_for(option)
|
||||
@last_failure = "no error matching '#{error}' when value is #{format_value(value)}"
|
||||
end
|
||||
pass
|
||||
end
|
||||
|
||||
def error_message_for(option)
|
||||
msg = @validator.error_messages[option]
|
||||
restriction = @validator.class.send(:evaluate_option_value, @validator.configuration[option], @type, @record)
|
||||
|
||||
if restriction
|
||||
restriction = [restriction] unless restriction.is_a?(Array)
|
||||
restriction.map! {|r| @validator.class.send(:type_cast_value, r, @type) }
|
||||
interpolate = @validator.send(:interpolation_values, option, restriction )
|
||||
# get I18n message if defined and has interpolation keys in msg
|
||||
if defined?(I18n) && !@validator.send(:custom_error_messages).include?(option)
|
||||
msg = @record.errors.generate_message(@expected, option, interpolate)
|
||||
else
|
||||
msg = msg % interpolate
|
||||
end
|
||||
end
|
||||
msg
|
||||
end
|
||||
|
||||
def format_value(value)
|
||||
return value if value.is_a?(String)
|
||||
value.strftime(ValidatesTimeliness::Validator.error_value_formats[options[:type]])
|
||||
value.strftime(@validator.class.error_value_formats[@type])
|
||||
end
|
||||
end
|
||||
|
||||
def validate_date(attribute, options={})
|
||||
options[:type] = :date
|
||||
validate_timeliness_of(attribute, options)
|
||||
ValidateTimeliness.new(attribute, options)
|
||||
end
|
||||
|
||||
def validate_time(attribute, options={})
|
||||
options[:type] = :time
|
||||
validate_timeliness_of(attribute, options)
|
||||
ValidateTimeliness.new(attribute, options)
|
||||
end
|
||||
|
||||
def validate_datetime(attribute, options={})
|
||||
options[:type] = :datetime
|
||||
validate_timeliness_of(attribute, options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_timeliness_of(attribute, options={})
|
||||
ValidateTimeliness.new(attribute, options)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,27 +7,6 @@ module ValidatesTimeliness
|
||||
|
||||
module ClassMethods
|
||||
|
||||
def parse_date_time(raw_value, type, strict=true)
|
||||
return nil if raw_value.blank?
|
||||
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
|
||||
|
||||
time_array = ValidatesTimeliness::Formats.parse(raw_value, type, strict)
|
||||
raise if time_array.nil?
|
||||
|
||||
# Rails dummy time date part is defined as 2000-01-01
|
||||
time_array[0..2] = 2000, 1, 1 if type == :time
|
||||
|
||||
# Date.new enforces days per month, unlike Time
|
||||
date = Date.new(*time_array[0..2]) unless type == :time
|
||||
|
||||
return date if type == :date
|
||||
|
||||
# Create time object which checks time part, and return time object
|
||||
make_time(time_array)
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
def validates_time(*attr_names)
|
||||
configuration = attr_names.extract_options!
|
||||
configuration[:type] = :time
|
||||
@@ -49,28 +28,13 @@ module ValidatesTimeliness
|
||||
private
|
||||
|
||||
def validates_timeliness_of(attr_names, configuration)
|
||||
validator = ValidatesTimeliness::Validator.new(configuration)
|
||||
validator = ValidatesTimeliness::Validator.new(configuration.symbolize_keys)
|
||||
|
||||
# bypass handling of allow_nil and allow_blank to validate raw value
|
||||
configuration.delete(:allow_nil)
|
||||
configuration.delete(:allow_blank)
|
||||
validates_each(attr_names, configuration) do |record, attr_name, value|
|
||||
validator.call(record, attr_name)
|
||||
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
|
||||
validator.call(record, attr_name, value)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2,59 +2,84 @@ module ValidatesTimeliness
|
||||
|
||||
class Validator
|
||||
cattr_accessor :ignore_restriction_errors
|
||||
cattr_accessor :error_value_formats
|
||||
|
||||
self.ignore_restriction_errors = false
|
||||
self.error_value_formats = {
|
||||
:time => '%H:%M:%S',
|
||||
:date => '%Y-%m-%d',
|
||||
:datetime => '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
RESTRICTION_METHODS = {
|
||||
:equal_to => :==,
|
||||
:before => :<,
|
||||
:after => :>,
|
||||
:on_or_before => :<=,
|
||||
:on_or_after => :>=,
|
||||
:between => lambda {|v, r| (r.first..r.last).include?(v) }
|
||||
}
|
||||
|
||||
VALID_OPTIONS = [
|
||||
:on, :if, :unless, :allow_nil, :empty, :allow_blank,
|
||||
:with_time, :with_date, :ignore_usec, :format,
|
||||
:invalid_time_message, :invalid_date_message, :invalid_datetime_message
|
||||
] + RESTRICTION_METHODS.keys.map {|option| [option, "#{option}_message".to_sym] }.flatten
|
||||
|
||||
attr_reader :configuration, :type
|
||||
|
||||
def initialize(configuration)
|
||||
defaults = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false }
|
||||
defaults = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false, :ignore_usec => false }
|
||||
@configuration = defaults.merge(configuration)
|
||||
@type = @configuration.delete(:type)
|
||||
validate_options(@configuration)
|
||||
end
|
||||
|
||||
def call(record, attr_name)
|
||||
value = record.send(attr_name)
|
||||
value = record.class.parse_date_time(value, type, false) if value.is_a?(String)
|
||||
raw_value = raw_value(record, attr_name)
|
||||
def call(record, attr_name, value)
|
||||
raw_value = raw_value(record, attr_name) || value
|
||||
|
||||
if value.is_a?(String) || configuration[:format]
|
||||
strict = !configuration[:format].nil?
|
||||
value = ValidatesTimeliness::Parser.parse(raw_value, type, :strict => strict, :format => configuration[:format])
|
||||
end
|
||||
|
||||
return if (raw_value.nil? && configuration[:allow_nil]) || (raw_value.blank? && configuration[:allow_blank])
|
||||
|
||||
add_error(record, attr_name, :blank) and return if raw_value.blank?
|
||||
|
||||
add_error(record, attr_name, "invalid_#{type}".to_sym) and return unless value
|
||||
if raw_value.blank?
|
||||
add_error(record, attr_name, :blank)
|
||||
return
|
||||
end
|
||||
|
||||
if value.nil?
|
||||
add_error(record, attr_name, "invalid_#{type}".to_sym)
|
||||
return
|
||||
end
|
||||
|
||||
validate_restrictions(record, attr_name, value)
|
||||
end
|
||||
|
||||
|
||||
def error_messages
|
||||
@error_messages ||= self.class.default_error_messages.merge(custom_error_messages)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def raw_value(record, attr_name)
|
||||
record.send("#{attr_name}_before_type_cast")
|
||||
record.send("#{attr_name}_before_type_cast") rescue nil
|
||||
end
|
||||
|
||||
def validate_restrictions(record, attr_name, value)
|
||||
restriction_methods = {:before => '<', :after => '>', :on_or_before => '<=', :on_or_after => '>='}
|
||||
|
||||
display = self.class.error_value_formats[type]
|
||||
|
||||
value = type_cast_value(value)
|
||||
|
||||
restriction_methods.each do |option, method|
|
||||
value = if configuration[:with_time] || configuration[:with_date]
|
||||
restriction_type = :datetime
|
||||
combine_date_and_time(value, record)
|
||||
else
|
||||
restriction_type = type
|
||||
self.class.type_cast_value(value, type, configuration[:ignore_usec])
|
||||
end
|
||||
return if value.nil?
|
||||
|
||||
RESTRICTION_METHODS.each do |option, method|
|
||||
next unless restriction = configuration[option]
|
||||
begin
|
||||
compare = restriction_value(restriction, record)
|
||||
next if compare.nil?
|
||||
compare = type_cast_value(compare)
|
||||
restriction = self.class.evaluate_option_value(restriction, restriction_type, record)
|
||||
next if restriction.nil?
|
||||
restriction = self.class.type_cast_value(restriction, restriction_type, configuration[:ignore_usec])
|
||||
|
||||
unless value.send(method, compare)
|
||||
add_error(record, attr_name, option, :restriction => compare.strftime(display))
|
||||
unless evaluate_restriction(restriction, value, method)
|
||||
add_error(record, attr_name, option, interpolation_values(option, restriction))
|
||||
end
|
||||
rescue
|
||||
unless self.class.ignore_restriction_errors
|
||||
@@ -63,57 +88,139 @@ module ValidatesTimeliness
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_error(record, attr_name, message, interpolate={})
|
||||
if Rails::VERSION::STRING < '2.2'
|
||||
message = error_messages[message] if message.is_a?(Symbol)
|
||||
message = message % interpolate.values unless interpolate.empty?
|
||||
record.errors.add(attr_name, message)
|
||||
|
||||
def interpolation_values(option, restriction)
|
||||
format = self.class.error_value_formats[type]
|
||||
restriction = [restriction] unless restriction.is_a?(Array)
|
||||
|
||||
if defined?(I18n)
|
||||
message = custom_error_messages[option] || I18n.t('activerecord.errors.messages')[option]
|
||||
subs = message.scan(/\{\{([^\}]*)\}\}/)
|
||||
interpolations = {}
|
||||
subs.each_with_index {|s, i| interpolations[s[0].to_sym] = restriction[i].strftime(format) }
|
||||
interpolations
|
||||
else
|
||||
# use i18n support in AR for message or use custom message passed to validation method
|
||||
custom = custom_error_messages[message]
|
||||
record.errors.add(attr_name, custom || message, interpolate)
|
||||
restriction.map {|r| r.strftime(format) }
|
||||
end
|
||||
end
|
||||
|
||||
def error_messages
|
||||
return @error_messages if defined?(@error_messages)
|
||||
@error_messages = ValidatesTimeliness.default_error_messages.merge(custom_error_messages)
|
||||
def evaluate_restriction(restriction, value, comparator)
|
||||
return true if restriction.nil?
|
||||
|
||||
case comparator
|
||||
when Symbol
|
||||
value.send(comparator, restriction)
|
||||
when Proc
|
||||
comparator.call(value, restriction)
|
||||
end
|
||||
end
|
||||
|
||||
def add_error(record, attr_name, message, interpolate=nil)
|
||||
if defined?(I18n)
|
||||
custom = custom_error_messages[message]
|
||||
record.errors.add(attr_name, custom || message, interpolate || {})
|
||||
else
|
||||
message = error_messages[message] if message.is_a?(Symbol)
|
||||
message = message % interpolate
|
||||
record.errors.add(attr_name, message)
|
||||
end
|
||||
end
|
||||
|
||||
def custom_error_messages
|
||||
return @custom_error_messages if defined?(@custom_error_messages)
|
||||
@custom_error_messages = configuration.inject({}) {|h, (k, v)| h[$1.to_sym] = v if k.to_s =~ /(.*)_message$/;h }
|
||||
@custom_error_messages ||= configuration.inject({}) {|msgs, (k, v)|
|
||||
if md = /(.*)_message$/.match(k.to_s)
|
||||
msgs[md[1].to_sym] = v
|
||||
end
|
||||
msgs
|
||||
}
|
||||
end
|
||||
|
||||
def restriction_value(restriction, record)
|
||||
case restriction
|
||||
when Time, Date, DateTime
|
||||
restriction
|
||||
|
||||
def combine_date_and_time(value, record)
|
||||
if type == :date
|
||||
date = value
|
||||
time = configuration[:with_time]
|
||||
else
|
||||
date = configuration[:with_date]
|
||||
time = value
|
||||
end
|
||||
date, time = self.class.evaluate_option_value(date, :date, record), self.class.evaluate_option_value(time, :time, record)
|
||||
return if date.nil? || time.nil?
|
||||
ValidatesTimeliness::Parser.make_time([date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
|
||||
end
|
||||
|
||||
def validate_options(options)
|
||||
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_OPTIONS - invalid_for_type)
|
||||
end
|
||||
|
||||
# class methods
|
||||
class << self
|
||||
|
||||
def default_error_messages
|
||||
if defined?(I18n)
|
||||
I18n.t('activerecord.errors.messages')
|
||||
else
|
||||
::ActiveRecord::Errors.default_error_messages
|
||||
end
|
||||
end
|
||||
|
||||
def error_value_formats
|
||||
if defined?(I18n)
|
||||
I18n.t('validates_timeliness.error_value_formats')
|
||||
else
|
||||
@@error_value_formats
|
||||
end
|
||||
end
|
||||
|
||||
def error_value_formats=(formats)
|
||||
@@error_value_formats = formats
|
||||
end
|
||||
|
||||
def evaluate_option_value(value, type, record)
|
||||
case value
|
||||
when Time, Date
|
||||
value
|
||||
when Symbol
|
||||
restriction_value(record.send(restriction), record)
|
||||
evaluate_option_value(record.send(value), type, record)
|
||||
when Proc
|
||||
restriction_value(restriction.call(record), record)
|
||||
evaluate_option_value(value.call(record), type, record)
|
||||
when Array
|
||||
value.map {|r| evaluate_option_value(r, type, record) }.sort
|
||||
when Range
|
||||
evaluate_option_value([value.first, value.last], type, record)
|
||||
else
|
||||
record.class.parse_date_time(restriction, type, false)
|
||||
ValidatesTimeliness::Parser.parse(value, type, :strict => false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def type_cast_value(value)
|
||||
case type
|
||||
when :time
|
||||
value.to_dummy_time
|
||||
when :date
|
||||
value.to_date
|
||||
when :datetime
|
||||
if value.is_a?(DateTime) || value.is_a?(Time)
|
||||
value.to_time
|
||||
|
||||
def type_cast_value(value, type, ignore_usec=false)
|
||||
if value.is_a?(Array)
|
||||
value.map {|v| type_cast_value(v, type, ignore_usec) }
|
||||
else
|
||||
value = case type
|
||||
when :time
|
||||
value.to_dummy_time
|
||||
when :date
|
||||
value.to_date
|
||||
when :datetime
|
||||
if value.is_a?(DateTime) || value.is_a?(Time)
|
||||
value.to_time
|
||||
else
|
||||
value.to_time(ValidatesTimeliness.default_timezone)
|
||||
end
|
||||
else
|
||||
value.to_time(ValidatesTimelines.default_timezone)
|
||||
nil
|
||||
end
|
||||
else
|
||||
nil
|
||||
if ignore_usec && value.is_a?(Time)
|
||||
ValidatesTimeliness::Parser.make_time(Array(value).reverse[4..9])
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||
|
||||
describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
||||
include ValidatesTimeliness::ActiveRecord::AttributeMethods
|
||||
include ValidatesTimeliness::ValidationMethods
|
||||
|
||||
before do
|
||||
@person = Person.new
|
||||
end
|
||||
@@ -23,18 +20,36 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
||||
@person.birth_date_and_time = "2000-01-01 12:00"
|
||||
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
|
||||
@person.class.should_receive(:parse_date_time).once
|
||||
ValidatesTimeliness::Parser.should_receive(:parse).once
|
||||
@person.birth_date_and_time = "2000-01-01 02:03:04"
|
||||
end
|
||||
|
||||
it "should call parser on write for date attribute" do
|
||||
@person.class.should_receive(:parse_date_time).once
|
||||
ValidatesTimeliness::Parser.should_receive(:parse).once
|
||||
@person.birth_date = "2000-01-01"
|
||||
end
|
||||
|
||||
it "should call parser on write for time attribute" do
|
||||
@person.class.should_receive(:parse_date_time).once
|
||||
ValidatesTimeliness::Parser.should_receive(:parse).once
|
||||
@person.birth_time = "12:00"
|
||||
end
|
||||
|
||||
@@ -54,6 +69,11 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
||||
@person.birth_date_and_time.should be_kind_of(Time)
|
||||
end
|
||||
|
||||
it "should return Time object for datetime attribute read method when assigned Date object" do
|
||||
@person.birth_date_and_time = Date.today
|
||||
@person.birth_date_and_time.should be_kind_of(Time)
|
||||
end
|
||||
|
||||
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.should be_kind_of(Time)
|
||||
@@ -201,4 +221,14 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
||||
@person.birth_date.should == tomorrow
|
||||
end
|
||||
|
||||
it "should skip storing value in attributes hash on read if record frozen" do
|
||||
@person = Person.new
|
||||
@person.birth_date = Date.today
|
||||
@person.save!
|
||||
@person.reload
|
||||
@person.freeze
|
||||
@person.frozen?.should be_true
|
||||
lambda { @person.birth_date }.should_not raise_error
|
||||
@person.birth_date.should == Date.today
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,46 +6,6 @@ describe ValidatesTimeliness::Formats do
|
||||
before do
|
||||
@formats = ValidatesTimeliness::Formats
|
||||
end
|
||||
|
||||
describe "expression generator" do
|
||||
it "should generate regexp for time" do
|
||||
generate_regexp_str('hh:nn:ss').should == '/(\d{2}):(\d{2}):(\d{2})/'
|
||||
end
|
||||
|
||||
it "should generate regexp for time with meridian" do
|
||||
generate_regexp_str('hh:nn:ss ampm').should == '/(\d{2}):(\d{2}):(\d{2}) ((?:[aApP])\.?[mM]\.?)/'
|
||||
end
|
||||
|
||||
it "should generate regexp for time with meridian and optional space between" do
|
||||
generate_regexp_str('hh:nn:ss_ampm').should == '/(\d{2}):(\d{2}):(\d{2})\s?((?:[aApP])\.?[mM]\.?)/'
|
||||
end
|
||||
|
||||
it "should generate regexp for time with single or double digits" do
|
||||
generate_regexp_str('h:n:s').should == '/(\d{1,2}):(\d{1,2}):(\d{1,2})/'
|
||||
end
|
||||
|
||||
it "should generate regexp for date" do
|
||||
generate_regexp_str('yyyy-mm-dd').should == '/(\d{4})-(\d{2})-(\d{2})/'
|
||||
end
|
||||
|
||||
it "should generate regexp for date with slashes" do
|
||||
generate_regexp_str('dd/mm/yyyy').should == '/(\d{2})\/(\d{2})\/(\d{4})/'
|
||||
end
|
||||
|
||||
it "should generate regexp for date with dots" do
|
||||
generate_regexp_str('dd.mm.yyyy').should == '/(\d{2})\.(\d{2})\.(\d{4})/'
|
||||
end
|
||||
|
||||
it "should generate regexp for Ruby time string" do
|
||||
expected = '/(\w{3,9}) (\w{3,9}) (\d{2}):(\d{2}):(\d{2}) (?:[+-]\d{2}:?\d{2}) (\d{4})/'
|
||||
generate_regexp_str('ddd mmm hh:nn:ss zo yyyy').should == expected
|
||||
end
|
||||
|
||||
it "should generate regexp for iso8601 datetime" do
|
||||
expected = '/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:Z|(?:[+-]\d{2}:?\d{2}))/'
|
||||
generate_regexp_str('yyyy-mm-ddThh:nn:ss(?:Z|zo)').should == expected
|
||||
end
|
||||
end
|
||||
|
||||
describe "format proc generator" do
|
||||
it "should generate proc which outputs date array with values in correct order" do
|
||||
@@ -71,6 +31,10 @@ describe ValidatesTimeliness::Formats do
|
||||
it "should generate proc which outputs time array with microseconds" do
|
||||
generate_proc('hh:nn:ss.u').call('01', '02', '03', '99').should == [0,0,0,1,2,3,990000]
|
||||
end
|
||||
|
||||
it "should generate proc which outputs datetime array with zone offset" do
|
||||
generate_proc('yyyy-mm-dd hh:nn:ss.u zo').call('2001', '02', '03', '04', '05', '06', '99', '+10:00').should == [2001,2,3,4,5,6,990000,36000]
|
||||
end
|
||||
end
|
||||
|
||||
describe "validation regexps" do
|
||||
@@ -136,44 +100,62 @@ describe ValidatesTimeliness::Formats do
|
||||
end
|
||||
end
|
||||
|
||||
describe "extracting values" do
|
||||
describe "parse" do
|
||||
|
||||
it "should return time array from date string" do
|
||||
time_array = formats.parse('12:13:14', :time, true)
|
||||
time_array = formats.parse('12:13:14', :time, :strict => true)
|
||||
time_array.should == [0,0,0,12,13,14,0]
|
||||
end
|
||||
|
||||
it "should return date array from time string" do
|
||||
time_array = formats.parse('2000-02-01', :date, true)
|
||||
time_array = formats.parse('2000-02-01', :date, :strict => true)
|
||||
time_array.should == [2000,2,1,0,0,0,0]
|
||||
end
|
||||
|
||||
it "should return datetime array from string value" do
|
||||
time_array = formats.parse('2000-02-01 12:13:14', :datetime, true)
|
||||
time_array = formats.parse('2000-02-01 12:13:14', :datetime, :strict => true)
|
||||
time_array.should == [2000,2,1,12,13,14,0]
|
||||
end
|
||||
|
||||
it "should parse date string when type is datetime" do
|
||||
time_array = formats.parse('2000-02-01', :datetime, false)
|
||||
time_array = formats.parse('2000-02-01', :datetime, :strict => false)
|
||||
time_array.should == [2000,2,1,0,0,0,0]
|
||||
end
|
||||
|
||||
|
||||
it "should ignore time when extracting date and strict is false" do
|
||||
time_array = formats.parse('2000-02-01 12:12', :date, false)
|
||||
time_array = formats.parse('2000-02-01 12:13', :date, :strict => false)
|
||||
time_array.should == [2000,2,1,0,0,0,0]
|
||||
end
|
||||
|
||||
it "should ignore time when extracting date from format with trailing year and strict is false" do
|
||||
time_array = formats.parse('01-02-2000 12:13', :date, :strict => false)
|
||||
time_array.should == [2000,2,1,0,0,0,0]
|
||||
end
|
||||
|
||||
it "should ignore date when extracting time and strict is false" do
|
||||
time_array = formats.parse('2000-02-01 12:12', :time, false)
|
||||
time_array.should == [0,0,0,12,12,0,0]
|
||||
time_array = formats.parse('2000-02-01 12:13', :time, :strict => false)
|
||||
time_array.should == [0,0,0,12,13,0,0]
|
||||
end
|
||||
|
||||
it "should return zone offset when :include_offset options is true" do
|
||||
time_array = formats.parse('2000-02-01T12:13:14-10:30', :datetime, :include_offset => true)
|
||||
time_array.should == [2000,2,1,12,13,14,0,-37800]
|
||||
end
|
||||
end
|
||||
|
||||
describe "removing formats" do
|
||||
before do
|
||||
formats.compile_format_expressions
|
||||
describe "parse with format option" do
|
||||
it "should return values if string matches specified format" do
|
||||
time_array = formats.parse('2000-02-01 12:13:14', :datetime, :format => 'yyyy-mm-dd hh:nn:ss')
|
||||
time_array.should == [2000,2,1,12,13,14,0]
|
||||
end
|
||||
|
||||
|
||||
it "should return nil if string does not match specified format" do
|
||||
time_array = formats.parse('2000-02-01 12:13', :datetime, :format => 'yyyy-mm-dd hh:nn:ss')
|
||||
time_array.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "removing formats" do
|
||||
it "should remove format from format array" do
|
||||
formats.remove_formats(:time, 'h.nn_ampm')
|
||||
formats.time_formats.should_not include("h o'clock")
|
||||
@@ -191,7 +173,7 @@ describe ValidatesTimeliness::Formats do
|
||||
|
||||
after do
|
||||
formats.time_formats << 'h.nn_ampm'
|
||||
# reload class instead
|
||||
formats.compile_format_expressions
|
||||
end
|
||||
end
|
||||
|
||||
@@ -249,7 +231,7 @@ describe ValidatesTimeliness::Formats do
|
||||
|
||||
def validate(time_string, type)
|
||||
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
|
||||
end
|
||||
valid
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# ginger spec
|
||||
#
|
||||
Ginger.configure do |config|
|
||||
rails_versions = ['2.0.2', '2.1.2', '2.2.2']
|
||||
rails_versions = ['2.0.2', '2.1.2', '2.2.2', '2.3.2']
|
||||
|
||||
rails_versions.each do |v|
|
||||
g = Ginger::Scenario.new
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
||||
|
||||
describe ValidatesTimeliness::ValidationMethods do
|
||||
describe ValidatesTimeliness::Parser do
|
||||
attr_accessor :person
|
||||
|
||||
describe "parse_date_time" do
|
||||
describe "parse" do
|
||||
it "should return time object for valid time string" do
|
||||
parse_method("2000-01-01 12:13:14", :datetime).should be_kind_of(Time)
|
||||
parse("2000-01-01 12:13:14", :datetime).should be_kind_of(Time)
|
||||
end
|
||||
|
||||
it "should return nil for time string with invalid date part" do
|
||||
parse_method("2000-02-30 12:13:14", :datetime).should be_nil
|
||||
parse("2000-02-30 12:13:14", :datetime).should be_nil
|
||||
end
|
||||
|
||||
it "should return nil for time string with invalid time part" do
|
||||
parse_method("2000-02-01 25:13:14", :datetime).should be_nil
|
||||
parse("2000-02-01 25:13:14", :datetime).should be_nil
|
||||
end
|
||||
|
||||
it "should return Time object when passed a Time object" do
|
||||
parse_method(Time.now, :datetime).should be_kind_of(Time)
|
||||
parse(Time.now, :datetime).should be_kind_of(Time)
|
||||
end
|
||||
|
||||
if RAILS_VER >= '2.1'
|
||||
it "should convert time string into current timezone" do
|
||||
Time.zone = 'Melbourne'
|
||||
time = parse_method("2000-01-01 12:13:14", :datetime)
|
||||
time = parse("2000-01-01 12:13:14", :datetime)
|
||||
Time.zone.utc_offset.should == 10.hours
|
||||
end
|
||||
end
|
||||
|
||||
it "should return nil for invalid date string" do
|
||||
parse_method("2000-02-30", :date).should be_nil
|
||||
parse("2000-02-30", :date).should be_nil
|
||||
end
|
||||
|
||||
def parse_method(*args)
|
||||
ActiveRecord::Base.parse_date_time(*args)
|
||||
def parse(*args)
|
||||
ValidatesTimeliness::Parser.parse(*args)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,14 +43,14 @@ describe ValidatesTimeliness::ValidationMethods do
|
||||
|
||||
it "should create time using current timezone" do
|
||||
Time.zone = 'Melbourne'
|
||||
time = ActiveRecord::Base.send(:make_time, [2000,1,1,12,0,0])
|
||||
time = ValidatesTimeliness::Parser.make_time([2000,1,1,12,0,0])
|
||||
time.zone.should == "EST"
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
it "should create time using default timezone" do
|
||||
time = ActiveRecord::Base.send(:make_time, [2000,1,1,12,0,0])
|
||||
time = ValidatesTimeliness::Parser.make_time([2000,1,1,12,0,0])
|
||||
time.zone.should == "UTC"
|
||||
end
|
||||
|
||||
@@ -5,45 +5,88 @@ end
|
||||
|
||||
class WithValidation < Person
|
||||
validates_date :birth_date,
|
||||
:before => '2000-01-10', :after => '2000-01-01',
|
||||
:on_or_before => '2000-01-09', :on_or_after => '2000-01-02'
|
||||
:equal_to => '2000-01-01',
|
||||
:before => '2000-01-10',
|
||||
:after => '2000-01-01',
|
||||
:on_or_before => '2000-01-09',
|
||||
:on_or_after => '2000-01-02',
|
||||
:between => ['2000-01-01', '2000-01-03']
|
||||
|
||||
validates_time :birth_time,
|
||||
:before => '23:00', :after => '09:00',
|
||||
:on_or_before => '22:00', :on_or_after => '10:00'
|
||||
:equal_to => '09:00',
|
||||
:before => '23:00',
|
||||
:after => '09:00',
|
||||
:on_or_before => '22:00',
|
||||
:on_or_after => '10:00',
|
||||
:between => ['09:00', '17:00']
|
||||
|
||||
validates_datetime :birth_date_and_time,
|
||||
:before => '2000-01-10 23:00', :after => '2000-01-01 09:00',
|
||||
:on_or_before => '2000-01-09 23:00', :on_or_after => '2000-01-02 09:00'
|
||||
:equal_to => '2000-01-01 09:00',
|
||||
:before => '2000-01-10 23:00',
|
||||
:after => '2000-01-01 09:00',
|
||||
:on_or_before => '2000-01-09 23:00',
|
||||
:on_or_after => '2000-01-02 09:00',
|
||||
:between => ['2000-01-01 09:00', '2000-01-01 17:00']
|
||||
|
||||
end
|
||||
|
||||
class CustomMessages < Person
|
||||
validates_date :birth_date, :invalid_date_message => 'is not really a date',
|
||||
:before => '2000-01-10', :before_message => 'is too late',
|
||||
:after => '2000-01-01', :after_message => 'is too early',
|
||||
:on_or_before=> '2000-01-09', :on_or_before_message => 'is just too late',
|
||||
:on_or_after => '2000-01-02', :on_or_after_message => 'is just too early'
|
||||
validates_date :birth_date,
|
||||
:invalid_date_message => 'is not really a date',
|
||||
:before => '2000-01-10',
|
||||
:before_message => 'is too late',
|
||||
:after => '2000-01-01',
|
||||
:after_message => 'is too early',
|
||||
:on_or_before => '2000-01-09',
|
||||
:on_or_before_message => 'is just too late',
|
||||
:on_or_after => '2000-01-02',
|
||||
:on_or_after_message => 'is just too early'
|
||||
end
|
||||
|
||||
describe "ValidateTimeliness matcher" do
|
||||
attr_accessor :no_validation, :with_validation
|
||||
|
||||
@@attribute_for_type = { :date => :birth_date, :time => :birth_time, :datetime => :birth_date_and_time }
|
||||
|
||||
before do
|
||||
@no_validation = NoValidation.new
|
||||
@with_validation = WithValidation.new
|
||||
end
|
||||
|
||||
[:date, :time, :datetime].each do |type|
|
||||
attribute = type == :datetime ? :date_and_time : type
|
||||
|
||||
it "should report that #{type} is validated" do
|
||||
with_validation.should self.send("validate_#{type}", "birth_#{attribute}".to_sym)
|
||||
with_validation.should self.send("validate_#{type}", attribute_for_type(type))
|
||||
end
|
||||
|
||||
it "should report that #{type} is not validated" do
|
||||
no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}".to_sym)
|
||||
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type))
|
||||
end
|
||||
end
|
||||
|
||||
describe "with equal_to option" do
|
||||
test_values = {
|
||||
:date => ['2000-01-01', '2000-01-02'],
|
||||
:time => ['09:00', '09:01'],
|
||||
:datetime => ['2000-01-01 09:00', '2000-01-01 09:01']
|
||||
}
|
||||
|
||||
[:date, :time, :datetime].each do |type|
|
||||
|
||||
it "should report that #{type} is validated" do
|
||||
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :equal_to => test_values[type][0])
|
||||
end
|
||||
|
||||
it "should report that #{type} is not validated when option value is incorrect" do
|
||||
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :equal_to => test_values[type][1])
|
||||
end
|
||||
|
||||
it "should report that #{type} is not validated with option" do
|
||||
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :equal_to => test_values[type][0])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "with before option" do
|
||||
test_values = {
|
||||
:date => ['2000-01-10', '2000-01-11'],
|
||||
@@ -52,18 +95,17 @@ describe "ValidateTimeliness matcher" do
|
||||
}
|
||||
|
||||
[:date, :time, :datetime].each do |type|
|
||||
attribute = type == :datetime ? :date_and_time : type
|
||||
|
||||
it "should report that #{type} is validated" do
|
||||
with_validation.should self.send("validate_#{type}", "birth_#{attribute}", :before => test_values[type][0])
|
||||
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :before => 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}", "birth_#{attribute}", :before => test_values[type][1])
|
||||
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][1])
|
||||
end
|
||||
|
||||
it "should report that #{type} is not validated with option" do
|
||||
no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :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
|
||||
@@ -76,18 +118,17 @@ describe "ValidateTimeliness matcher" do
|
||||
}
|
||||
|
||||
[:date, :time, :datetime].each do |type|
|
||||
attribute = type == :datetime ? :date_and_time : type
|
||||
|
||||
it "should report that #{type} is validated" do
|
||||
with_validation.should self.send("validate_#{type}", "birth_#{attribute}", :after => test_values[type][0])
|
||||
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :after => 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}", "birth_#{attribute}", :after => test_values[type][1])
|
||||
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][1])
|
||||
end
|
||||
|
||||
it "should report that #{type} is not validated with option" do
|
||||
no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :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
|
||||
@@ -100,18 +141,17 @@ describe "ValidateTimeliness matcher" do
|
||||
}
|
||||
|
||||
[:date, :time, :datetime].each do |type|
|
||||
attribute = type == :datetime ? :date_and_time : type
|
||||
|
||||
it "should report that #{type} is validated" do
|
||||
with_validation.should self.send("validate_#{type}", "birth_#{attribute}", :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
|
||||
|
||||
it "should report that #{type} is not validated when option value is incorrect" do
|
||||
with_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :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
|
||||
|
||||
it "should report that #{type} is not validated with option" do
|
||||
no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :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
|
||||
@@ -124,22 +164,44 @@ describe "ValidateTimeliness matcher" do
|
||||
}
|
||||
|
||||
[:date, :time, :datetime].each do |type|
|
||||
attribute = type == :datetime ? :date_and_time : type
|
||||
|
||||
it "should report that #{type} is validated" do
|
||||
with_validation.should self.send("validate_#{type}", "birth_#{attribute}", :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
|
||||
|
||||
it "should report that #{type} is not validated when option value is incorrect" do
|
||||
with_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :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
|
||||
|
||||
it "should report that #{type} is not validated with option" do
|
||||
no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :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
|
||||
|
||||
describe "between option" do
|
||||
test_values = {
|
||||
:date => [ ['2000-01-01', '2000-01-03'], ['2000-01-01', '2000-01-04'] ],
|
||||
: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'] ]
|
||||
}
|
||||
|
||||
[: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), :between => 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), :between => 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), :between => test_values[type][0])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "custom messages" do
|
||||
|
||||
before do
|
||||
@@ -175,4 +237,8 @@ describe "ValidateTimeliness matcher" do
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def attribute_for_type(type)
|
||||
@@attribute_for_type[type.to_sym]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,6 +30,7 @@ require 'active_record'
|
||||
require 'active_record/version'
|
||||
require 'action_controller'
|
||||
require 'action_view'
|
||||
require 'action_mailer'
|
||||
|
||||
require 'spec/rails'
|
||||
require 'time_travel/time_travel'
|
||||
@@ -38,13 +39,15 @@ ActiveRecord::Base.default_timezone = :utc
|
||||
RAILS_VER = Rails::VERSION::STRING
|
||||
puts "Using #{vendored ? 'vendored' : 'gem'} Rails version #{RAILS_VER} (ActiveRecord version #{ActiveRecord::VERSION::STRING})"
|
||||
|
||||
require 'validates_timeliness'
|
||||
|
||||
if RAILS_VER >= '2.1'
|
||||
Time.zone_default = ActiveSupport::TimeZone['UTC']
|
||||
ActiveRecord::Base.time_zone_aware_attributes = true
|
||||
end
|
||||
|
||||
require 'validates_timeliness'
|
||||
|
||||
ValidatesTimeliness.enable_datetime_select_extension!
|
||||
|
||||
ActiveRecord::Migration.verbose = false
|
||||
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
|
||||
|
||||
|
||||
@@ -16,36 +16,71 @@ describe ValidatesTimeliness::Validator do
|
||||
@person = Person.new
|
||||
end
|
||||
|
||||
describe "restriction_value" do
|
||||
describe "option keys validation" do
|
||||
before do
|
||||
keys = ValidatesTimeliness::Validator::VALID_OPTIONS - [:invalid_date_message, :invalid_time_message, :with_date, :with_time]
|
||||
@valid_options = keys.inject({}) {|hash, opt| hash[opt] = nil; hash }
|
||||
end
|
||||
|
||||
it "should raise error if invalid option key passed" do
|
||||
@valid_options.update(:invalid_key => 'will not open lock')
|
||||
lambda { Person.validates_datetime(@valid_options) }.should raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "should not raise error if option keys are valid" do
|
||||
lambda { Person.validates_datetime(@valid_options) }.should_not raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "evaluate_option_value" do
|
||||
it "should return Time object when restriction is Time object" do
|
||||
restriction_value(Time.now, :datetime).should be_kind_of(Time)
|
||||
evaluate_option_value(Time.now, :datetime).should be_kind_of(Time)
|
||||
end
|
||||
|
||||
it "should return Time object when restriction is string" do
|
||||
restriction_value("2007-01-01 12:00", :datetime).should be_kind_of(Time)
|
||||
evaluate_option_value("2007-01-01 12:00", :datetime).should be_kind_of(Time)
|
||||
end
|
||||
|
||||
it "should return Time object when restriction is method and method returns Time object" do
|
||||
person.stub!(:datetime_attr).and_return(Time.now)
|
||||
restriction_value(:datetime_attr, :datetime).should be_kind_of(Time)
|
||||
evaluate_option_value(:datetime_attr, :datetime).should be_kind_of(Time)
|
||||
end
|
||||
|
||||
it "should return Time object when restriction is method and method returns string" do
|
||||
person.stub!(:datetime_attr).and_return("2007-01-01 12:00")
|
||||
restriction_value(:datetime_attr, :datetime).should be_kind_of(Time)
|
||||
evaluate_option_value(:datetime_attr, :datetime).should be_kind_of(Time)
|
||||
end
|
||||
|
||||
it "should return Time object when restriction is proc which returns Time object" do
|
||||
restriction_value(lambda { Time.now }, :datetime).should be_kind_of(Time)
|
||||
evaluate_option_value(lambda { Time.now }, :datetime).should be_kind_of(Time)
|
||||
end
|
||||
|
||||
it "should return Time object when restriction is proc which returns string" do
|
||||
restriction_value(lambda {"2007-01-01 12:00"}, :datetime).should be_kind_of(Time)
|
||||
evaluate_option_value(lambda {"2007-01-01 12:00"}, :datetime).should be_kind_of(Time)
|
||||
end
|
||||
|
||||
def restriction_value(restriction, type)
|
||||
it "should return array of Time objects when restriction is array of Time objects" do
|
||||
time1, time2 = Time.now, 1.day.ago
|
||||
evaluate_option_value([time1, time2], :datetime).should == [time2, time1]
|
||||
end
|
||||
|
||||
it "should return array of Time objects when restriction is array of strings" do
|
||||
time1, time2 = "2000-01-02", "2000-01-01"
|
||||
evaluate_option_value([time1, time2], :datetime).should == [parse(time2, :datetime), parse(time1, :datetime)]
|
||||
end
|
||||
|
||||
it "should return array of Time objects when restriction is Range of Time objects" do
|
||||
time1, time2 = Time.now, 1.day.ago
|
||||
evaluate_option_value(time1..time2, :datetime).should == [time2, time1]
|
||||
end
|
||||
|
||||
it "should return array of Time objects when restriction is Range of time strings" do
|
||||
time1, time2 = "2000-01-02", "2000-01-01"
|
||||
evaluate_option_value(time1..time2, :datetime).should == [parse(time2, :datetime), parse(time1, :datetime)]
|
||||
end
|
||||
def evaluate_option_value(restriction, type)
|
||||
configure_validator(:type => type)
|
||||
validator.send(:restriction_value, restriction, person)
|
||||
validator.class.send(:evaluate_option_value, restriction, type, person)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -212,84 +247,196 @@ describe ValidatesTimeliness::Validator do
|
||||
end
|
||||
end
|
||||
|
||||
describe "instance with on_or_before and on_or_after restrictions" do
|
||||
describe "instance with between restriction" do
|
||||
|
||||
describe "for datetime type" do
|
||||
before do
|
||||
configure_validator(:on_or_before => Time.now.at_midnight, :on_or_after => 1.day.ago)
|
||||
configure_validator(:between => [1.day.ago.at_midnight, 1.day.from_now.at_midnight])
|
||||
end
|
||||
|
||||
it "should have error when value is past :on_or_before restriction" do
|
||||
validate_with(:birth_date_and_time, Time.now.at_midnight + 1)
|
||||
should_have_error(:birth_date_and_time, :on_of_before)
|
||||
it "should have error when value is before earlist :between restriction" do
|
||||
validate_with(:birth_date_and_time, 2.days.ago)
|
||||
should_have_error(:birth_date_and_time, :between)
|
||||
end
|
||||
|
||||
it "should be valid when value is equal to :on_or_before restriction" do
|
||||
validate_with(:birth_date_and_time, Time.now.at_midnight)
|
||||
should_have_no_error(:birth_date_and_time, :on_of_before)
|
||||
it "should have error when value is after latest :between restriction" do
|
||||
validate_with(:birth_date_and_time, 2.days.from_now)
|
||||
should_have_error(:birth_date_and_time, :between)
|
||||
end
|
||||
|
||||
it "should have error when value is before :on_or_after restriction" do
|
||||
validate_with(:birth_date_and_time, 1.days.ago - 1)
|
||||
should_have_error(:birth_date_and_time, :on_of_after)
|
||||
it "should be valid when value is equal to earliest :between restriction" do
|
||||
validate_with(:birth_date_and_time, 1.day.ago.at_midnight)
|
||||
should_have_no_error(:birth_date_and_time, :between)
|
||||
end
|
||||
|
||||
it "should be valid when value is value equal to :on_or_after restriction" do
|
||||
validate_with(:birth_date_and_time, 1.day.ago)
|
||||
should_have_no_error(:birth_date_and_time, :on_of_after)
|
||||
it "should be valid when value is equal to latest :between restriction" do
|
||||
validate_with(:birth_date_and_time, 1.day.from_now.at_midnight)
|
||||
should_have_no_error(:birth_date_and_time, :between)
|
||||
end
|
||||
|
||||
it "should allow a range for between restriction" do
|
||||
configure_validator(:type => :datetime, :between => (1.day.ago.at_midnight)..(1.day.from_now.at_midnight))
|
||||
validate_with(:birth_date_and_time, 1.day.from_now.at_midnight)
|
||||
should_have_no_error(:birth_date_and_time, :between)
|
||||
end
|
||||
end
|
||||
|
||||
describe "for date type" do
|
||||
before :each do
|
||||
configure_validator(:on_or_before => 1.day.from_now, :on_or_after => 1.day.ago, :type => :date)
|
||||
before do
|
||||
configure_validator(:type => :date, :between => [1.day.ago.to_date, 1.day.from_now.to_date])
|
||||
end
|
||||
|
||||
it "should have error when value is past :on_or_before restriction" do
|
||||
validate_with(:birth_date, 2.days.from_now)
|
||||
should_have_error(:birth_date, :on_or_before)
|
||||
it "should have error when value is before earlist :between restriction" do
|
||||
validate_with(:birth_date, 2.days.ago.to_date)
|
||||
should_have_error(:birth_date, :between)
|
||||
end
|
||||
|
||||
it "should have error when value is before :on_or_after restriction" do
|
||||
validate_with(:birth_date, 2.days.ago)
|
||||
should_have_error(:birth_date, :on_or_after)
|
||||
it "should have error when value is after latest :between restriction" do
|
||||
validate_with(:birth_date, 2.days.from_now.to_date)
|
||||
should_have_error(:birth_date, :between)
|
||||
end
|
||||
|
||||
it "should be valid when value is equal to :on_or_before restriction" do
|
||||
validate_with(:birth_date, 1.day.from_now)
|
||||
should_have_no_error(:birth_date, :on_or_before)
|
||||
it "should be valid when value is equal to earliest :between restriction" do
|
||||
validate_with(:birth_date, 1.day.ago.to_date)
|
||||
should_have_no_error(:birth_date, :between)
|
||||
end
|
||||
|
||||
it "should be valid when value value is equal to :on_or_after restriction" do
|
||||
validate_with(:birth_date, 1.day.ago)
|
||||
should_have_no_error(:birth_date, :on_or_before)
|
||||
it "should be valid when value is equal to latest :between restriction" do
|
||||
validate_with(:birth_date, 1.day.from_now.to_date)
|
||||
should_have_no_error(:birth_date, :between)
|
||||
end
|
||||
|
||||
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))
|
||||
validate_with(:birth_date, 1.day.from_now.to_date)
|
||||
should_have_no_error(:birth_date, :between)
|
||||
end
|
||||
end
|
||||
|
||||
describe "for time type" do
|
||||
before :each do
|
||||
configure_validator(:on_or_before => "23:00", :on_or_after => "06:00", :type => :time)
|
||||
before do
|
||||
configure_validator(:type => :time, :between => ["09:00", "17:00"])
|
||||
end
|
||||
|
||||
it "should have error when value is past :on_or_before restriction" do
|
||||
validate_with(:birth_time, "23:01")
|
||||
should_have_error(:birth_time, :on_or_before)
|
||||
it "should have error when value is before earlist :between restriction" do
|
||||
validate_with(:birth_time, "08:59")
|
||||
should_have_error(:birth_time, :between)
|
||||
end
|
||||
|
||||
it "should have error when value is before :on_or_after restriction" do
|
||||
validate_with(:birth_time, "05:59")
|
||||
should_have_error(:birth_time, :on_or_after)
|
||||
it "should have error when value is after latest :between restriction" do
|
||||
validate_with(:birth_time, "17:01")
|
||||
should_have_error(:birth_time, :between)
|
||||
end
|
||||
|
||||
it "should be valid when value is on boundary of :on_or_before restriction" do
|
||||
validate_with(:birth_time, "23:00")
|
||||
should_have_no_error(:birth_time, :on_or_before)
|
||||
it "should be valid when value is equal to earliest :between restriction" do
|
||||
validate_with(:birth_time, "09:00")
|
||||
should_have_no_error(:birth_time, :between)
|
||||
end
|
||||
|
||||
it "should be valid when value is on boundary of :on_or_after restriction" do
|
||||
validate_with(:birth_time, "06:00")
|
||||
should_have_no_error(:birth_time, :on_or_after)
|
||||
it "should be valid when value is equal to latest :between restriction" do
|
||||
validate_with(:birth_time, "17:00")
|
||||
should_have_no_error(:birth_time, :between)
|
||||
end
|
||||
|
||||
it "should allow a range for between restriction" do
|
||||
configure_validator(:type => :time, :between => "09:00".."17:00")
|
||||
validate_with(:birth_time, "17:00")
|
||||
should_have_no_error(:birth_time, :between)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "instance with :equal_to restriction" do
|
||||
|
||||
describe "for datetime type" do
|
||||
before do
|
||||
configure_validator(:equal_to => Time.now)
|
||||
end
|
||||
|
||||
it "should have error when value not equal to :equal_to restriction" do
|
||||
validate_with(:birth_date_and_time, Time.now + 1)
|
||||
should_have_error(:birth_date_and_time, :equal_to)
|
||||
end
|
||||
|
||||
it "should have error when value is equal to :equal_to restriction for all values except microscond, and microsecond is not ignored" 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)
|
||||
should_have_no_error(:birth_date_and_time, :equal_to)
|
||||
end
|
||||
end
|
||||
|
||||
describe "for date type" do
|
||||
before do
|
||||
configure_validator(:type => :date, :equal_to => Date.today)
|
||||
end
|
||||
|
||||
it "should have error when value is not equal to :equal_to restriction" do
|
||||
validate_with(:birth_date, Date.today + 1)
|
||||
should_have_error(:birth_date, :equal_to)
|
||||
end
|
||||
|
||||
it "should be valid when value is equal to :equal_to restriction" do
|
||||
validate_with(:birth_date, Date.today)
|
||||
should_have_no_error(:birth_date, :equal_to)
|
||||
end
|
||||
end
|
||||
|
||||
describe "for time type" do
|
||||
before do
|
||||
configure_validator(:type => :time, :equal_to => "09:00:00")
|
||||
end
|
||||
|
||||
it "should have error when value is not equal to :equal_to restriction" do
|
||||
validate_with(:birth_time, "09:00:01")
|
||||
should_have_error(:birth_time, :equal_to)
|
||||
end
|
||||
|
||||
it "should be valid when value is equal to :equal_to restriction" do
|
||||
validate_with(:birth_time, "09:00:00")
|
||||
should_have_no_error(:birth_time, :equal_to)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "instance with :with_time option" do
|
||||
|
||||
it "should validate date attribute as datetime combining value of :with_time against restrictions " do
|
||||
configure_validator(:type => :date, :with_time => '12:31', :on_or_before => Time.mktime(2000,1,1,12,30))
|
||||
validate_with(:birth_date, "2000-01-01")
|
||||
should_have_error(:birth_date, :on_or_before)
|
||||
end
|
||||
|
||||
it "should skip restriction validation if :with_time value is nil" do
|
||||
configure_validator(:type => :date, :with_time => nil, :on_or_before => Time.mktime(2000,1,1,12,30))
|
||||
validate_with(:birth_date, "2000-01-01")
|
||||
should_have_no_error(:birth_date, :on_or_before)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "instance with :with_date option" 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))
|
||||
validate_with(:birth_date, "12:30")
|
||||
should_have_error(:birth_date, :on_or_before)
|
||||
end
|
||||
|
||||
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))
|
||||
validate_with(:birth_date, "12:30")
|
||||
should_have_no_error(:birth_date, :on_or_before)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -326,6 +473,54 @@ describe ValidatesTimeliness::Validator do
|
||||
end
|
||||
end
|
||||
|
||||
describe "instance with format option" do
|
||||
it "should validate attribute when value matches format" do
|
||||
configure_validator(:type => :time, :format => 'hh:nn:ss')
|
||||
validate_with(:birth_time, "12:00:00")
|
||||
should_have_no_error(:birth_time, :invalid_time)
|
||||
end
|
||||
|
||||
it "should not validate attribute when value does not match format" do
|
||||
configure_validator(:type => :time, :format => 'hh:nn:ss')
|
||||
validate_with(:birth_time, "12:00")
|
||||
should_have_error(:birth_time, :invalid_time)
|
||||
end
|
||||
end
|
||||
|
||||
describe "custom_error_messages" do
|
||||
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')
|
||||
validator.send(:custom_error_messages)[:invalid_date].should == 'thats no date'
|
||||
end
|
||||
|
||||
it "should return empty hash if no custom error messages in configuration" do
|
||||
configure_validator(:type => :date)
|
||||
validator.send(:custom_error_messages).should be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "interpolation_values" do
|
||||
if defined?(I18n)
|
||||
it "should return hash of interpolation keys with restriction values" do
|
||||
before = '1900-01-01'
|
||||
configure_validator(:type => :date, :before => before)
|
||||
validator.send(:interpolation_values, :before, before.to_date).should == {:restriction => before}
|
||||
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
|
||||
it "should return array of interpolation values" do
|
||||
before = '1900-01-01'
|
||||
configure_validator(:type => :date, :before => before)
|
||||
validator.send(:interpolation_values, :before, before.to_date).should == [before]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "restriction errors" do
|
||||
before :each do
|
||||
configure_validator(:type => :date, :before => lambda { raise })
|
||||
@@ -373,12 +568,18 @@ describe ValidatesTimeliness::Validator do
|
||||
describe "custom formats" do
|
||||
|
||||
before :all do
|
||||
@@formats = ValidatesTimeliness::Validator.error_value_formats
|
||||
ValidatesTimeliness::Validator.error_value_formats = {
|
||||
custom = {
|
||||
:time => '%H:%M %p',
|
||||
:date => '%d-%m-%Y',
|
||||
:datetime => '%d-%m-%Y %H:%M %p'
|
||||
}
|
||||
|
||||
if defined?(I18n)
|
||||
I18n.backend.store_translations 'en', :validates_timeliness => { :error_value_formats => custom }
|
||||
else
|
||||
@@formats = ValidatesTimeliness::Validator.error_value_formats
|
||||
ValidatesTimeliness::Validator.error_value_formats = custom
|
||||
end
|
||||
end
|
||||
|
||||
it "should format datetime value of restriction" do
|
||||
@@ -400,19 +601,27 @@ describe ValidatesTimeliness::Validator do
|
||||
end
|
||||
|
||||
after :all do
|
||||
ValidatesTimeliness::Validator.error_value_formats = @@formats
|
||||
if defined?(I18n)
|
||||
I18n.reload!
|
||||
else
|
||||
ValidatesTimeliness::Validator.error_value_formats = @@formats
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def parse(*args)
|
||||
ValidatesTimeliness::Parser.parse(*args)
|
||||
end
|
||||
|
||||
def configure_validator(options={})
|
||||
@validator = ValidatesTimeliness::Validator.new(options)
|
||||
end
|
||||
|
||||
def validate_with(attr_name, value)
|
||||
person.send("#{attr_name}=", value)
|
||||
validator.call(person, attr_name)
|
||||
validator.call(person, attr_name, value)
|
||||
end
|
||||
|
||||
def should_have_error(attr_name, error)
|
||||
@@ -433,6 +642,6 @@ describe ValidatesTimeliness::Validator do
|
||||
def error_messages
|
||||
return @error_messages if defined?(@error_messages)
|
||||
messages = validator.send(:error_messages)
|
||||
@error_messages = messages.inject({}) {|h, (k, v)| h[k] = v.gsub(/ (\%s|\{\{\w*\}\})/, ''); h }
|
||||
@error_messages = messages.inject({}) {|h, (k, v)| h[k] = v.sub(/ (\%s|\{\{\w*\}\}).*/, ''); h }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = %q{validates_timeliness}
|
||||
s.version = "1.0.0"
|
||||
s.version = "2.0.0"
|
||||
|
||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||
s.authors = ["Adam Meehan"]
|
||||
s.autorequire = %q{validates_timeliness}
|
||||
s.date = %q{2008-12-07}
|
||||
s.date = %q{2009-04-12}
|
||||
s.description = %q{Date and time validation plugin for Rails 2.x which allows custom formats}
|
||||
s.email = %q{adam.meehan@gmail.com}
|
||||
s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"]
|
||||
s.files = ["LICENSE", "README.rdoc", "Rakefile", "TODO", "CHANGELOG", "lib/validates_timeliness", "lib/validates_timeliness/core_ext", "lib/validates_timeliness/core_ext/date.rb", "lib/validates_timeliness/core_ext/date_time.rb", "lib/validates_timeliness/core_ext/time.rb", "lib/validates_timeliness/action_view", "lib/validates_timeliness/action_view/instance_tag.rb", "lib/validates_timeliness/locale", "lib/validates_timeliness/locale/en.yml", "lib/validates_timeliness/validation_methods.rb", "lib/validates_timeliness/active_record", "lib/validates_timeliness/active_record/attribute_methods.rb", "lib/validates_timeliness/active_record/multiparameter_attributes.rb", "lib/validates_timeliness/formats.rb", "lib/validates_timeliness/validator.rb", "lib/validates_timeliness/spec", "lib/validates_timeliness/spec/rails", "lib/validates_timeliness/spec/rails/matchers", "lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb", "lib/validates_timeliness.rb", "spec/core_ext", "spec/core_ext/dummy_time_spec.rb", "spec/validator_spec.rb", "spec/action_view", "spec/action_view/instance_tag_spec.rb", "spec/ginger_scenarios.rb", "spec/validation_methods_spec.rb", "spec/spec_helper.rb", "spec/formats_spec.rb", "spec/active_record", "spec/active_record/attribute_methods_spec.rb", "spec/active_record/multiparameter_attributes_spec.rb", "spec/time_travel", "spec/time_travel/time_travel.rb", "spec/time_travel/time_extensions.rb", "spec/time_travel/MIT-LICENSE", "spec/spec", "spec/spec/rails", "spec/spec/rails/matchers", "spec/spec/rails/matchers/validate_timeliness_spec.rb", "spec/resources", "spec/resources/person.rb", "spec/resources/sqlite_patch.rb", "spec/resources/schema.rb", "spec/resources/application.rb"]
|
||||
s.files = ["LICENSE", "README.rdoc", "Rakefile", "TODO", "CHANGELOG", "lib/validates_timeliness", "lib/validates_timeliness/core_ext", "lib/validates_timeliness/core_ext/date.rb", "lib/validates_timeliness/core_ext/date_time.rb", "lib/validates_timeliness/core_ext/time.rb", "lib/validates_timeliness/action_view", "lib/validates_timeliness/action_view/instance_tag.rb", "lib/validates_timeliness/locale", "lib/validates_timeliness/locale/en.yml", "lib/validates_timeliness/validation_methods.rb", "lib/validates_timeliness/active_record", "lib/validates_timeliness/active_record/attribute_methods.rb", "lib/validates_timeliness/active_record/multiparameter_attributes.rb", "lib/validates_timeliness/parser.rb", "lib/validates_timeliness/formats.rb", "lib/validates_timeliness/validator.rb", "lib/validates_timeliness/spec", "lib/validates_timeliness/spec/rails", "lib/validates_timeliness/spec/rails/matchers", "lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb", "lib/validates_timeliness.rb", "spec/core_ext", "spec/core_ext/dummy_time_spec.rb", "spec/validator_spec.rb", "spec/action_view", "spec/action_view/instance_tag_spec.rb", "spec/ginger_scenarios.rb", "spec/spec_helper.rb", "spec/formats_spec.rb", "spec/active_record", "spec/active_record/attribute_methods_spec.rb", "spec/active_record/multiparameter_attributes_spec.rb", "spec/time_travel", "spec/time_travel/time_travel.rb", "spec/time_travel/time_extensions.rb", "spec/time_travel/MIT-LICENSE", "spec/parser_spec.rb", "spec/spec", "spec/spec/rails", "spec/spec/rails/matchers", "spec/spec/rails/matchers/validate_timeliness_spec.rb", "spec/resources", "spec/resources/person.rb", "spec/resources/sqlite_patch.rb", "spec/resources/schema.rb", "spec/resources/application.rb"]
|
||||
s.has_rdoc = true
|
||||
s.homepage = %q{http://github.com/adzap/validates_timeliness}
|
||||
s.require_paths = ["lib"]
|
||||
|
||||
Reference in New Issue
Block a user