Compare commits

..

68 Commits
1.1.6 ... 2.2.2

Author SHA1 Message Date
Adam Meehan
f109443fb7 version 2.2.2 2009-09-19 08:19:27 +10:00
Adam Meehan
96bf4bf184 fix dummy to respect timezones by using make_time 2009-09-19 07:30:33 +10:00
Adam Meehan
90be6a6db5 change to github issues for bug reports 2009-09-12 14:39:46 +10:00
Adam Meehan
4d5d82ff20 version 2.2.1 2009-09-12 14:16:44 +10:00
Adam Meehan
b11893eac0 fix dummy date part in Validator.type_cast_value
removed all core extensions
2009-09-12 14:14:37 +10:00
Adam Meehan
76e159b350 version 2.2.0 2009-09-12 13:43:50 +10:00
Adam Meehan
c762b6d4f8 added version file with VERSION constant 2009-09-12 13:42:07 +10:00
Adam Meehan
91f9f65bc0 tiny doc change 2009-09-12 13:36:57 +10:00
Adam Meehan
6db8b7d908 push dummy date value assignment into Formats.parse and allow custom values to be used 2009-09-12 13:07:01 +10:00
Adam Meehan
d3c5101f92 use implied_type in restriction evaluations 2009-09-12 13:05:12 +10:00
Adam Meehan
899e96b880 tiny clean up 2009-09-12 13:05:12 +10:00
Adam Meehan
162faf632a push strict override for format option into Formats.parse 2009-09-12 13:05:12 +10:00
Adam Meehan
1b865cc834 split ignore_sec into own describe 2009-09-12 13:05:11 +10:00
Adam Meehan
c29478df45 fix deprecation for ActiveRecord::Errors#generate_message in Rails 2.3.4
ginger scenario added 2.3.4
2009-09-12 13:05:11 +10:00
Adam Meehan
df3283e5a1 fix ignore_usec for with_date and with_time options 2009-09-12 13:05:04 +10:00
Adam Meehan
0e382e15f2 moved action view extension enable call to relevant spec 2009-09-11 13:13:02 +10:00
Adam Meehan
9a697b9cab finally fixed spec for latest rspec 2009-09-11 13:11:09 +10:00
Adam Meehan
7bf7ed0569 catch both possible exception types 2009-09-09 16:25:04 +10:00
Adam Meehan
a969a49ae8 little tweaks 2009-09-09 16:16:11 +10:00
Adam Meehan
687e61a3f2 Merge branch 'master' of github.com:adzap/validates_timeliness 2009-09-05 19:50:43 +10:00
Adam Meehan
4cc20ae620 fixed some bad rescue behaviour in parse method 2009-09-05 19:47:49 +10:00
adzap
2d510504e6 change to lambda in examples 2009-08-26 21:07:29 -07:00
Adam Meehan
2028d68b17 checking proc arity in option value for ruby 1.9 compat 2009-08-22 14:59:46 +10:00
Adam Meehan
e399c6b510 moved mutliparam helper methods our of AR to reduce method pollution 2009-07-28 12:52:25 +10:00
Adam Meehan
7aa1a87731 require matcher in spec helper 2009-07-28 12:51:32 +10:00
Adam Meehan
1a31e7463d have to manually require matcher now because the presence of the Spec
namespace caused issues for shoulda when not using rspec:wq
2009-07-07 15:32:15 +10:00
Adam Meehan
14c55e3292 version 2.1 for real 2009-06-20 22:55:59 +10:00
Adam Meehan
75a3b2bd83 version 2.1 2009-06-20 22:30:08 +10:00
Adam Meehan
9b300e084b change to spec/autorun 2009-06-20 19:43:13 +10:00
Adam Meehan
d61dddfbc6 fix i18n locale load order to allow customisation
fix interpolation values for i18n
2009-06-19 12:06:36 +10:00
Adam Meehan
4ac4281c8b rename named local var 2009-06-07 08:28:20 +10:00
Adam Meehan
a56cf674b2 removed reload alias by attributes_cache for before_type_cast value 2009-06-07 08:21:46 +10:00
Adam Meehan
7f4d7b38d7 update changelog 2009-06-06 16:56:14 +10:00
Adam Meehan
34c0f25225 dramatically simplify the before_type_cast hack and remove read method override
simplified write method with no dirty attributes hackery
2009-06-06 16:37:06 +10:00
Adam Meehan
57c3fdca88 set RAILS_ENV spec_helper 2009-06-06 11:36:03 +10:00
Adam Meehan
805bb5d7fd added another datetime format 2009-06-06 11:17:35 +10:00
Adam Meehan
7aac14c874 added ambiguous year threshold setting 2009-06-06 11:16:19 +10:00
Adam Meehan
7c0c1afe1c Merge branch 'master' of git@github.com:adzap/validates_timeliness 2009-05-26 12:01:27 +10:00
Adam Meehan
5295a494e9 token typo 2009-05-26 12:00:49 +10:00
adzap
572d80d227 doc fix 2009-04-19 05:40:36 -07:00
Adam Meehan
af3a3b84ab version 2.0.0 2009-04-12 11:13:52 +10:00
Adam Meehan
4df63c7142 another idea in TODO 2009-04-12 11:10:58 +10:00
Adam Meehan
26066dedfe update TODOs 2009-04-12 10:26:31 +10:00
Adam Meehan
3348ca3063 dont rely on add_error return value to exit. sigh, less one liners 2009-04-12 10:23:09 +10:00
Adam Meehan
88ee33ae41 consistently use instance methods vs vars in validator 2009-04-12 10:17:33 +10:00
Adam Meehan
51afb2852e update benchmark file 2009-04-12 09:44:46 +10:00
Adam Meehan
57b8a52f07 add :format option to readme 2009-04-10 11:01:19 +10:00
Adam Meehan
bb94e234bc add :format option to validate and parse with specific a format 2009-04-10 10:57:27 +10:00
Adam Meehan
f041524124 add equal_to option to matcher 2009-04-10 10:48:24 +10:00
Adam Meehan
e1d23d0f2b Merge branch '2.0.0' 2009-04-09 18:54:34 +10:00
Adam Meehan
613001791a limit time_array size explicity 2009-04-09 18:53:28 +10:00
Adam Meehan
6d12790d2b datetime select helper extension activation in readme 2009-04-06 17:34:23 +10:00
Adam Meehan
b197d537f1 fix read attribute bug for frozen record (reported by Les Nightingill) 2009-04-06 13:52:07 +10:00
Adam Meehan
1e3c802031 capture zone offset value in formats to possible usage 2009-03-31 21:35:49 +11:00
Adam Meehan
d89266d9f1 minor stuff 2009-03-31 21:25:05 +11:00
Adam Meehan
9e2d95c3e1 remove brittle and not very useful specs, which are covered elsewhere 2009-03-31 14:22:13 +11:00
Adam Meehan
fb520bbddc use plugin parser in action view extension 2009-03-31 14:21:07 +11:00
Adam Meehan
8303be05c3 little spec consistency 2009-03-30 21:03:51 +11:00
Adam Meehan
f4ed751c26 changed back to using error_value_formats for Rails 2.0/2.1 2009-03-28 19:51:11 +11:00
Adam Meehan
956933f58b disable multiparameter values extension by default for v2 2009-03-28 18:53:47 +11:00
Adam Meehan
7967b5a212 refactored error value formats to use locale file for I18n. Rail 2.0/2.1 to use default_error_value_formats now.
moved default_error_messages_method into validator
2009-03-28 18:49:26 +11:00
Adam Meehan
a836ed8434 changed Formats#parse to take options hash for strict and other possibilities 2009-03-28 17:35:41 +11:00
Adam Meehan
c2a4f45b5a removed old spec 2009-03-28 17:35:21 +11:00
Adam Meehan
312c1510cb refactored AR parsing methods into Parser module to reduce AR method pollution and make more consistent 2009-03-28 17:25:48 +11:00
Adam Meehan
88fce1d679 updated version number (1.1.7) 2009-03-26 16:49:19 +11:00
Adam Meehan
ffd8476f74 version 1.1.7 2009-03-26 16:46:53 +11:00
Adam Meehan
728c5ccda5 safely extract time values from multiparam array 2009-03-26 15:20:08 +11:00
Adam Meehan
4f0c81b6f8 properly chain multiparameter attributes 2009-03-26 15:13:35 +11:00
31 changed files with 648 additions and 601 deletions

View File

@@ -1,3 +1,34 @@
= 2.2.2 [2009-09-19]
- Fixed dummy_time using make_time to respect timezone. Fixes 1.9.1 bug.
= 2.2.1 [2009-09-12]
- Fixed dummy date part for time types in Validator.type_cast_value
- No more core extensions! Removed dummy_time methods.
= 2.2.0 [2009-09-12]
- Ruby 1.9 support!
- Customise dummy date values for time types. See DUMMY DATE FOR TIME TYPES.
- Fixed matcher conflict with Shoulda. Load plugin matcher manually now see matcher section in README
- Fixed :ignore_usec when used with :with_time or :with_date
- Some clean up and refactoring
= 2.1.0 [2009-06-20]
- Added ambiguous year threshold setting in Formats class to customize the threshold for 2 digit years (See README)
- Fixed interpolation values in custom error message for Rails 2.2+
- Fixed custom I18n local override of en locale
- Dramatically simplified ActiveRecord monkey patching and hackery
= 2.0.0 [2009-04-12]
- Error value formats are now specified in the i18n locale file instead of updating plugin hash. See OTHER CUSTOMISATION section in README.
- Date/time select helper extension is disabled by default. To enable see DISPLAY INVALID VALUES IN DATE HELPERS section in README to enable.
- Added :format option to limit validation to a single format if desired
- Matcher now supports :equal_to option
- Formats.parse can take :include_offset option to include offset value from string in seconds, if string contains an offset. Offset not used in rest of plugin yet.
- Refactored to remove as much plugin code from ActiveRecord as possible.
= 1.1.7 [2009-03-26]
- 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

View File

@@ -1,7 +1,7 @@
= validates_timeliness
* Source: http://github.com/adzap/validates_timeliness
* Bugs: http://adzap.lighthouseapp.com/projects/14111-validates_timeliness
* Bugs: http://github.com/adzap/validates_timeliness/issues
== DESCRIPTION:
@@ -22,9 +22,9 @@ think should be a valid date or time string.
* Restores ability to see raw value entered for date/time attributes with
_before_type_cast modifier, which was lost in Rails 2.1.
* Respects new timezone features of Rails 2.1.
* Supports Rails timezone handling
* Supports Rails 2.2 I18n for the error messages
* Supports Rails I18n for the error messages
* Rspec matcher for testing model validation of dates and times
@@ -80,6 +80,7 @@ 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
@@ -106,7 +107,7 @@ temporal options.
== EXAMPLES:
validates_date :date_of_birth :before => Proc.new { 18.years.ago },
validates_date :date_of_birth :before => lambda { 18.years.ago },
:before_message => "must be at least 18 years old"
validates_time :breakfast_time, :on_or_after => '6:00am',
@@ -114,7 +115,7 @@ temporal options.
:before => :second_breakfast_time,
:allow_nil => true
validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now }
validates_datetime :appointment_date, :before => lambda { 1.week.from_now }
validates_date :entry_date, :with_time => '17:00', :on_or_before => :competition_closing
@@ -189,7 +190,7 @@ Here is what each format token means:
Special Cases:
yy = 2 or 4 digit year
yyyyy = exactly 4 digit year
yyyy = exactly 4 digit year
mmm = month long name (e.g. 'Jul' or 'July')
ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
u = microseconds matches 1 to 3 digits
@@ -205,7 +206,7 @@ To see all defined formats look in lib/validates_timeliness/formats.rb.
The perenial problem for non-US developers or applications not primarily for the
US, is the US date format of m/d/yy. This is ambiguous with the European format
of d/my/yy. By default the plugin uses the US formats as this is the Ruby default
of d/m/yy. By default the plugin uses the US formats as this is the Ruby default
when it does date interpretation, and is in keeping PoLS (principle of least
surprise).
@@ -249,6 +250,31 @@ 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.
=== AMBIGUOUS YEAR THRESHOLD
When dealing with 2 digit year values, by default a year is interpreted as being
in the last century at or above 30. You can customize this however
ValidatesTimeliness::Formats.ambiguous_year_threshold = 20
Now you get:
year of 19 is considered 2019
year of 20 is considered 1920
=== DUMMY DATE FOR TIME TYPES
Given that Ruby has no support for a time-only type, all time type columns are evaluated
as a regular Time class objects with a dummy date value set. Rails defines the dummy date as
2000-01-01. So a time of '12:30' is evaluated as a Time value of '2000-01-01 12:30'. If you
need to customize this for some reason you can do so as follows
ValidatesTimeliness::Formats.dummy_date_for_time_type = [2009, 1, 1]
The value should be an array of 3 values being year, month and day in that order.
=== TEMPORAL RESTRICTION ERRORS:
When using the validation temporal restrictions there are times when the restriction
@@ -266,6 +292,20 @@ corner cases a little harder to test. In general if you are using procs or
model methods and you only care when they return a value, then they should
return nil in all other situations. Restrictions are skipped if they are nil.
=== DISPLAY INVALID VALUES IN DATE HELPERS:
The plugin has some extensions to ActionView and ActiveRecord by allowing invalid
date and time values to be redisplayed to the user as feedback, instead of
a blank field which happens by default in Rails. Though the date helpers make this a
pretty rare occurence, given the select dropdowns for each date/time component, but
it may be something of interest.
To activate it, put this in an initializer:
ValidatesTimeliness.enable_datetime_select_extension!
=== OTHER CUSTOMISATION:
The error messages for each temporal restrictions can also be globally overridden by
@@ -302,12 +342,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.
@@ -317,9 +367,15 @@ To sweeten the deal that little bit more, you have an Rspec matcher available fo
you model specs. Now you can easily test the validations you have just written
with the plugin or better yet *before* you write them! You just use the
validation options you want as you would with the validation method. Those
options are then verified and reported if they fail. Use it like so:
options are then verified and reported if they fail.
@person.should validate_date(:birth_date, :before => Time.now, :before_message => 'should be before today')
First require it in your spec_helper.rb
require 'validates_timeliness/matcher'
Use it like so:
@person.should validate_date(:birth_date, :before => Time.now, :before_message => 'should be before today')
The matcher names are just the singular of the validation methods.

View File

@@ -3,9 +3,10 @@ require 'rake/gempackagetask'
require 'rubygems/specification'
require 'date'
require 'spec/rake/spectask'
require 'lib/validates_timeliness/version'
GEM = "validates_timeliness"
GEM_VERSION = "1.1.6"
GEM_VERSION = ValidatesTimeliness::VERSION
AUTHOR = "Adam Meehan"
EMAIL = "adam.meehan@gmail.com"
HOMEPAGE = "http://github.com/adzap/validates_timeliness"
@@ -34,7 +35,7 @@ task :default => :spec
desc "Run specs"
Spec::Rake::SpecTask.new do |t|
t.spec_files = FileList['spec/**/*_spec.rb']
t.spec_opts = %w(-fs --color)
t.spec_opts = %w(--color)
end

4
TODO
View File

@@ -1,3 +1,5 @@
- :format option
- 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

View File

@@ -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

View File

@@ -1,22 +1,20 @@
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'
require 'validates_timeliness/active_record/attribute_methods'
require 'validates_timeliness/active_record/multiparameter_attributes'
require 'validates_timeliness/action_view/instance_tag'
require 'validates_timeliness/core_ext/time'
require 'validates_timeliness/core_ext/date'
require 'validates_timeliness/core_ext/date_time'
module ValidatesTimeliness
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
@@ -28,28 +26,22 @@ module ValidatesTimeliness
def load_error_messages
if defined?(I18n)
I18n.load_path += [ LOCALE_PATH ]
I18n.load_path.unshift(LOCALE_PATH)
I18n.reload!
else
messages = YAML::load(IO.read(LOCALE_PATH))
errors = messages['en']['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h }
defaults = YAML::load(IO.read(LOCALE_PATH))['en']
errors = defaults['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h }
::ActiveRecord::Errors.default_error_messages.update(errors)
ValidatesTimeliness::Validator.error_value_formats = defaults['validates_timeliness']['error_value_formats'].symbolize_keys
end
end
def default_error_messages
if Rails::VERSION::STRING < '2.2'
::ActiveRecord::Errors.default_error_messages
else
I18n.translate('activerecord.errors.messages')
end
end
def setup_for_rails
major, minor = Rails::VERSION::MAJOR, Rails::VERSION::MINOR
self.default_timezone = ::ActiveRecord::Base.default_timezone
self.enable_datetime_select_extension!
self.load_error_messages
self.use_time_zones = ::ActiveRecord::Base.time_zone_aware_attributes rescue false
self.enable_active_record_datetime_parser!
load_error_messages
end
end
end

View File

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

View File

@@ -1,127 +1,65 @@
module ValidatesTimeliness
def self.enable_active_record_datetime_parser!
::ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::AttributeMethods)
end
module ActiveRecord
# Rails 2.1 removed the ability to retrieve the raw value of a time or datetime
# attribute. The raw value is necessary to properly validate a string time or
# datetime value instead of the internal Rails type casting which is very limited
# and does not allow custom formats. These methods restore that ability while
# respecting the automatic timezone handling.
# Overrides write method for date, time and datetime columns
# to use plugin parser. Also adds mechanism to store value
# before type cast.
#
# The automatic timezone handling sets the assigned attribute value to the current
# zone in Time.zone. To preserve this localised value and capture the raw value
# we cache the localised value on write and store the raw value in the attributes
# hash for later retrieval and possibly validation. Any value from the database
# will not be in the attribute cache on first read so will be considered in default
# timezone and converted to local time. It is then stored back in the attributes
# hash and cached to avoid the need for any subsequent differentiation.
module AttributeMethods
def self.included(base)
base.extend ClassMethods
base.class_eval do
alias_method_chain :read_attribute, :timeliness
alias_method_chain :read_attribute_before_type_cast, :timeliness
class << self
alias_method_chain :define_attribute_methods, :timeliness
end
end
end
# Adds check for cached date/time attributes which have been type cast already
# and value can be used from cache. This prevents the raw date/time value from
# being type cast using default Rails type casting when writing values
# to the database.
def read_attribute_with_timeliness(attr_name)
attr_name = attr_name.to_s
if !(value = @attributes[attr_name]).nil?
column = column_for_attribute(attr_name)
if column && [:date, :time, :datetime].include?(column.type) && @attributes_cache.has_key?(attr_name)
return @attributes_cache[attr_name]
end
end
read_attribute_without_timeliness(attr_name)
end
# If Rails dirty attributes is enabled then the value is added to
# changed attributes if changed. Can't use the default dirty checking
# implementation as it chains the write_attribute method which deletes
# the attribute from the cache.
def write_date_time_attribute(attr_name, value, type, time_zone_aware)
new = self.class.parse_date_time(value, type)
@attributes_cache["_#{attr_name}_before_type_cast"] = value
if new && type != :date
new = new.to_time
new = new.in_time_zone if time_zone_aware
value = ValidatesTimeliness::Parser.parse(value, type)
if value && type != :date
value = value.to_time
value = value.in_time_zone if time_zone_aware
end
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
@attributes[attr_name] = value
write_attribute(attr_name.to_sym, 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 = self.class.parse_date_time(time, type)
else
time = read_attribute(attr_name)
@attributes[attr_name] = time && time_zone_aware ? time.in_time_zone : time
end
@attributes_cache[attr_name] = time && time_zone_aware ? time.in_time_zone : time
def read_attribute_before_type_cast_with_timeliness(attr_name)
cached_attr = "_#{attr_name}_before_type_cast"
return @attributes_cache[cached_attr] if @attributes_cache.has_key?(cached_attr)
read_attribute_before_type_cast_without_timeliness(attr_name)
end
module ClassMethods
# Define attribute reader and writer method for date, time and
# datetime attributes to use plugin parser.
def define_attribute_methods_with_timeliness
return if generated_methods?
columns_hash.each do |name, column|
unless instance_method_already_implemented?(name)
if [:date, :time, :datetime].include?(column.type)
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
define_read_method_for_dates_and_times(name, column.type, time_zone_aware)
end
end
timeliness_methods = []
unless instance_method_already_implemented?("#{name}=")
if [:date, :time, :datetime].include?(column.type)
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
define_write_method_for_dates_and_times(name, column.type, time_zone_aware)
columns_hash.each do |name, column|
if [:date, :time, :datetime].include?(column.type)
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
define_method("#{name}=") do |value|
write_date_time_attribute(name, value, column.type, time_zone_aware)
end
timeliness_methods << 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, type, time_zone_aware)
method_body = <<-EOV
def #{attr_name}=(value)
write_date_time_attribute('#{attr_name}', value, #{type.inspect}, #{time_zone_aware})
end
EOV
evaluate_attribute_method attr_name, method_body, "#{attr_name}="
end
def define_read_method_for_dates_and_times(attr_name, type, time_zone_aware)
method_body = <<-EOV
def #{attr_name}(reload = false)
read_date_time_attribute('#{attr_name}', #{type.inspect}, #{time_zone_aware}, reload)
end
EOV
evaluate_attribute_method attr_name, method_body
@generated_methods += timeliness_methods
end
end
@@ -130,5 +68,3 @@ module ValidatesTimeliness
end
end
ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::AttributeMethods)

View File

@@ -5,29 +5,54 @@ module ValidatesTimeliness
end
module ActiveRecord
class << self
def time_array_to_string(values, type)
values.collect! {|v| v.to_s }
case type
when :date
extract_date_from_multiparameter_attributes(values)
when :time
extract_time_from_multiparameter_attributes(values)
when :datetime
extract_date_from_multiparameter_attributes(values) + " " + extract_time_from_multiparameter_attributes(values)
end
end
def extract_date_from_multiparameter_attributes(values)
year = ValidatesTimeliness::Formats.unambiguous_year(values[0].rjust(2, "0"))
[year, *values.slice(1, 2).map { |s| s.rjust(2, "0") }].join("-")
end
def extract_time_from_multiparameter_attributes(values)
values[3..5].map { |s| s.rjust(2, "0") }.join(":")
end
end
module MultiparameterAttributes
def self.included(base)
base.alias_method_chain :execute_callstack_for_multiparameter_attributes, :timeliness
end
# Overrides AR method to store multiparameter time and dates as string
# allowing validation later.
# Assign dates and times as formatted strings to force the use of the plugin parser
# and store a before_type_cast value for attribute
def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
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 = ValidatesTimeliness::ActiveRecord.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
@@ -36,28 +61,7 @@ module ValidatesTimeliness
unless errors.empty?
raise ::ActiveRecord::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
end
end
def time_array_to_string(values, type)
values = values.map(&:to_s)
case type
when :date
extract_date_from_multiparameter_attributes(values)
when :time
extract_time_from_multiparameter_attributes(values)
when :datetime
date_values, time_values = values.slice!(0, 3), values
extract_date_from_multiparameter_attributes(date_values) + " " + extract_time_from_multiparameter_attributes(time_values)
end
end
def extract_date_from_multiparameter_attributes(values)
[values[0], *values.slice(1, 2).map { |s| s.rjust(2, "0") }].join("-")
end
def extract_time_from_multiparameter_attributes(values)
values.last(3).map { |s| s.rjust(2, "0") }.join(":")
execute_callstack_for_multiparameter_attributes_without_timeliness(callstack)
end
end

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,26 @@ module ValidatesTimeliness
:format_tokens,
:format_proc_args
# Set the threshold value for a two digit year to be considered last century
#
# Default: 30
#
# Example:
# year = '29' is considered 2029
# year = '30' is considered 1930
#
cattr_accessor :ambiguous_year_threshold
self.ambiguous_year_threshold = 30
# Set the dummy date part for a time type value. Should be an array of 3 values
# being year, month and day in that order.
#
# Default: [ 2000, 1, 1 ] same as ActiveRecord
#
cattr_accessor :dummy_date_for_time_type
self.dummy_date_for_time_type = [ 2000, 1, 1 ]
# Format tokens:
# y = year
# m = month
@@ -46,7 +66,7 @@ module ValidatesTimeliness
#
# Special Cases:
# yy = 2 or 4 digit year
# yyyyy = exactly 4 digit year
# yyyy = exactly 4 digit year
# mmm = month long name (e.g. 'Jul' or 'July')
# ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
# u = microseconds matches 1 to 6 digits
@@ -85,6 +105,7 @@ module ValidatesTimeliness
@@datetime_formats = [
'yyyy-mm-dd hh:nn:ss',
'yyyy-mm-dd h:nn',
'yyyy-mm-dd h:nn_ampm',
'yyyy-mm-dd hh:nn:ss.u',
'm/d/yy h:nn:ss',
'm/d/yy h:nn_ampm',
@@ -124,13 +145,13 @@ module ValidatesTimeliness
{ 's' => [ /s{1}/, '(\d{1,2})', :sec ] },
{ 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] },
{ 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] },
{ 'zo' => [ /zo/, '(?:[+-]\d{2}:?\d{2})'] },
{ 'zo' => [ /zo/, '([+-]\d{2}:?\d{2})', :offset ] },
{ 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] },
{ '_' => [ /_/, '\s?' ] }
]
# Arguments whichs will be passed to the format proc if matched in the
# time string. The key must should the key from the format tokens. The array
# Arguments which will be passed to the format proc if matched in the
# time string. The key must be the key from the format tokens. The array
# consists of the arry position of the arg, the arg name, and the code to
# place in the time array slot. The position can be nil which means the arg
# won't be placed in the array.
@@ -146,6 +167,7 @@ module ValidatesTimeliness
:min => [4, 'n', 'n'],
:sec => [5, 's', 's'],
:usec => [6, 'u', 'microseconds(u)'],
:offset => [7, 'z', 'offset_in_seconds(z)'],
:meridian => [nil, 'md', nil]
}
@@ -160,21 +182,34 @@ 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)
options.reverse_merge!(:strict => true)
sets = if options[:format]
options[:strict] = true
[ send("#{type}_expressions").assoc(options[:format]) ]
else
expression_set(type, string)
end
matches = nil
exp, processor = expression_set(type, string).find do |regexp, proc|
full = /\A#{regexp}\Z/ if strict
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
matches = full.match(string.strip)
break(proc) if matches = full.match(string.strip)
end
last = options[:include_offset] ? 8 : 7
if matches
values = processor.call(*matches[1..last])
values[0..2] = dummy_date_for_time_type if type == :time
return values
end
processor.call(*matches[1..7]) if matches
end
# Delete formats of specified type. Error raised if format not found.
@@ -206,8 +241,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.
@@ -218,6 +252,49 @@ module ValidatesTimeliness
compile_format_expressions
end
def full_hour(hour, meridian)
hour = hour.to_i
return hour if meridian.nil?
if meridian.delete('.').downcase == 'am'
hour == 12 ? 0 : hour
else
hour == 12 ? hour : hour + 12
end
end
def unambiguous_year(year)
if year.length <= 2
century = Time.now.year.to_s[0..1].to_i
century -= 1 if year.to_i >= ambiguous_year_threshold
year = "#{century}#{year.rjust(2,'0')}"
end
year.to_i
end
def month_index(month)
return month.to_i if month.to_i.nonzero?
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
private
# Compile formats into validation regexps and format procs
@@ -246,22 +323,22 @@ 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 = <<-EOL
lambda {|#{args.join(',')}|
md ||= nil
[#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.is_a?(Float) ? i : i.to_i }
}
EOL
eval proc_string
end
def compile_formats(formats)
formats.map { |format| 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
@@ -282,37 +359,6 @@ module ValidatesTimeliness
end
end
def full_hour(hour, meridian)
hour = hour.to_i
return hour if meridian.nil?
if meridian.delete('.').downcase == 'am'
hour == 12 ? 0 : hour
else
hour == 12 ? hour : hour + 12
end
end
def unambiguous_year(year, threshold=30)
year = "#{year.to_i < threshold ? '20' : '19'}#{year}" if year.length == 2
year.to_i
end
def month_index(month)
return month.to_i if month.to_i.nonzero?
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
end
end
end

View File

@@ -11,3 +11,8 @@ en:
after: "must be after {{restriction}}"
on_or_after: "must be on or after {{restriction}}"
between: "must be between {{earliest}} and {{latest}}"
validates_timeliness:
error_value_formats:
date: '%Y-%m-%d'
time: '%H:%M:%S'
datetime: '%Y-%m-%d %H:%M:%S'

View File

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

View File

@@ -0,0 +1,44 @@
module ValidatesTimeliness
module Parser
class << self
def parse(raw_value, type, options={})
return nil if raw_value.blank?
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
time_array = ValidatesTimeliness::Formats.parse(raw_value, type, options.reverse_merge(:strict => true))
return nil if time_array.nil?
if type == :date
Date.new(*time_array[0..2]) rescue nil
else
make_time(time_array[0..7])
end
end
def make_time(time_array)
# Enforce date part validity which Time class does not
return nil unless Date.valid_civil?(*time_array[0..2])
if Time.respond_to?(:zone) && ValidatesTimeliness.use_time_zones
Time.zone.local(*time_array)
else
# Older AR way of handling times with datetime fallback
begin
time_zone = ValidatesTimeliness.default_timezone
Time.send(time_zone, *time_array)
rescue ArgumentError, TypeError
zone_offset = time_zone == :local ? DateTime.local_offset : 0
time_array.pop # remove microseconds
DateTime.civil(*(time_array << zone_offset))
end
end
rescue ArgumentError, TypeError
nil
end
end
end
end

View File

@@ -10,6 +10,7 @@ module Spec
}
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 },
@@ -27,13 +28,12 @@ module Spec
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) 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
valid = test_option(:equal_to) if valid && @options[:equal_to]
valid = test_option(:before) if valid && @options[:before]
valid = test_option(:after) if valid && @options[:after]
valid = test_option(:on_or_before) if valid && @options[:on_or_before]
valid = test_option(:on_or_after) if valid && @options[:on_or_after]
valid = test_between if valid && @options[:between]
return valid
end
@@ -116,16 +116,21 @@ module Spec
end
def error_message_for(option)
msg = @validator.send(:error_messages)[option]
msg = @validator.error_messages[option]
restriction = @validator.class.send(:evaluate_option_value, @validator.configuration[option], @type, @record)
if restriction
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)
msg = if defined?(ActiveRecord::Error)
ActiveRecord::Error.new(@record, @expected, option, interpolate).message
else
@record.errors.generate_message(@expected, option, interpolate)
end
else
msg = msg % interpolate
end
@@ -135,7 +140,7 @@ module Spec
def format_value(value)
return value if value.is_a?(String)
value.strftime(ValidatesTimeliness::Validator.error_value_formats[@type])
value.strftime(@validator.class.error_value_formats[@type])
end
end

View File

@@ -7,27 +7,6 @@ module ValidatesTimeliness
module ClassMethods
def parse_date_time(raw_value, type, strict=true)
return nil if raw_value.blank?
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
time_array = ValidatesTimeliness::Formats.parse(raw_value, type, strict)
raise if time_array.nil?
# Rails dummy time date part is defined as 2000-01-01
time_array[0..2] = 2000, 1, 1 if type == :time
# Date.new enforces days per month, unlike Time
date = Date.new(*time_array[0..2]) unless type == :time
return date if type == :date
# Create time object which checks time part, and return time object
make_time(time_array)
rescue
nil
end
def validates_time(*attr_names)
configuration = attr_names.extract_options!
configuration[:type] = :time
@@ -59,21 +38,6 @@ module ValidatesTimeliness
end
end
# Time.zone. Rails 2.0 should be default_timezone.
def make_time(time_array)
if Time.respond_to?(:zone) && time_zone_aware_attributes
Time.zone.local(*time_array)
else
begin
Time.send(::ActiveRecord::Base.default_timezone, *time_array)
rescue ArgumentError, TypeError
zone_offset = ::ActiveRecord::Base.default_timezone == :local ? DateTime.local_offset : 0
time_array.pop # remove microseconds
DateTime.civil(*(time_array << zone_offset))
end
end
end
end
end

View File

@@ -2,14 +2,7 @@ module ValidatesTimeliness
class Validator
cattr_accessor :ignore_restriction_errors
cattr_accessor :error_value_formats
self.ignore_restriction_errors = false
self.error_value_formats = {
:time => '%H:%M:%S',
:date => '%Y-%m-%d',
:datetime => '%Y-%m-%d %H:%M:%S'
}
RESTRICTION_METHODS = {
:equal_to => :==,
@@ -21,8 +14,8 @@ module ValidatesTimeliness
}
VALID_OPTIONS = [
:on, :if, :unless, :allow_nil, :empty, :allow_blank, :blank,
:with_time, :with_date, :ignore_usec,
: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
@@ -36,18 +29,24 @@ module ValidatesTimeliness
end
def call(record, attr_name, value)
value = record.class.parse_date_time(value, type, false) if value.is_a?(String)
raw_value = raw_value(record, attr_name) || value
if value.is_a?(String) || configuration[:format]
value = ValidatesTimeliness::Parser.parse(raw_value, type, :strict => false, :format => configuration[:format])
end
return if (raw_value.nil? && configuration[:allow_nil]) || (raw_value.blank? && configuration[:allow_blank])
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
return add_error(record, attr_name, :blank) if raw_value.blank?
return add_error(record, attr_name, "invalid_#{type}".to_sym) if value.nil?
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)
@@ -55,21 +54,20 @@ module ValidatesTimeliness
end
def validate_restrictions(record, attr_name, value)
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])
if configuration[:with_time] || configuration[:with_date]
value = combine_date_and_time(value, record)
end
value = self.class.type_cast_value(value, implied_type, configuration[:ignore_usec])
return if value.nil?
RESTRICTION_METHODS.each do |option, method|
next unless restriction = configuration[option]
begin
restriction = self.class.evaluate_option_value(restriction, restriction_type, record)
restriction = self.class.evaluate_option_value(restriction, implied_type, record)
next if restriction.nil?
restriction = self.class.type_cast_value(restriction, restriction_type, @configuration[:ignore_usec])
restriction = self.class.type_cast_value(restriction, implied_type, configuration[:ignore_usec])
unless evaluate_restriction(restriction, value, method)
add_error(record, attr_name, option, interpolation_values(option, restriction))
@@ -87,7 +85,7 @@ module ValidatesTimeliness
restriction = [restriction] unless restriction.is_a?(Array)
if defined?(I18n)
message = custom_error_messages[option] || I18n.translate('activerecord.errors.messages')[option]
message = 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) }
@@ -110,9 +108,8 @@ module ValidatesTimeliness
def add_error(record, attr_name, message, interpolate=nil)
if defined?(I18n)
# 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 || {})
record.errors.add(attr_name, message, { :default => custom }.merge(interpolate || {}))
else
message = error_messages[message] if message.is_a?(Symbol)
message = message % interpolate
@@ -120,10 +117,6 @@ module ValidatesTimeliness
end
end
def error_messages
@error_messages ||= ValidatesTimeliness.default_error_messages.merge(custom_error_messages)
end
def custom_error_messages
@custom_error_messages ||= configuration.inject({}) {|msgs, (k, v)|
if md = /(.*)_message$/.match(k.to_s)
@@ -132,44 +125,69 @@ module ValidatesTimeliness
msgs
}
end
def combine_date_and_time(value, record)
if type == :date
date = value
time = @configuration[:with_time]
time = configuration[:with_time]
else
date = @configuration[:with_date]
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?
record.class.send(:make_time, [date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
ValidatesTimeliness::Parser.make_time([date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
end
def validate_options(options)
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
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
def implied_type
@implied_type ||= configuration[:with_date] || configuration[:with_time] ? :datetime : 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, DateTime
when Time, Date
value
when Symbol
evaluate_option_value(record.send(value), type, record)
when Proc
evaluate_option_value(value.call(record), type, record)
result = value.arity > 0 ? value.call(record) : value.call
evaluate_option_value(result, type, record)
when Array
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(value, type, false)
ValidatesTimeliness::Parser.parse(value, type, :strict => false)
end
end
@@ -179,11 +197,11 @@ module ValidatesTimeliness
else
value = case type
when :time
value.to_dummy_time
dummy_time(value)
when :date
value.to_date
when :datetime
if value.is_a?(DateTime) || value.is_a?(Time)
if value.is_a?(Time) || value.is_a?(DateTime)
value.to_time
else
value.to_time(ValidatesTimeliness.default_timezone)
@@ -192,13 +210,23 @@ module ValidatesTimeliness
nil
end
if ignore_usec && value.is_a?(Time)
::ActiveRecord::Base.send(:make_time, Array(value).reverse[4..9])
ValidatesTimeliness::Parser.make_time(Array(value).reverse[4..9])
else
value
end
end
end
def dummy_time(value)
if value.is_a?(Time) || value.is_a?(DateTime)
time = [value.hour, value.min, value.sec]
else
time = [0,0,0]
end
dummy_date = ValidatesTimeliness::Formats.dummy_date_for_time_type
ValidatesTimeliness::Parser.make_time(dummy_date + time)
end
end
end

View File

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

View File

@@ -1,6 +1,10 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe ValidatesTimeliness::ActionView::InstanceTag, :type => :helper do
ValidatesTimeliness.enable_datetime_select_extension!
describe 'ValidatesTimeliness::ActionView::InstanceTag' do
include ActionView::Helpers::DateHelper
include ActionController::Assertions::SelectorAssertions
before do
@person = Person.new

View File

@@ -20,36 +20,18 @@ 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
@@ -103,7 +85,20 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
@person.birth_date_and_time_before_type_cast.should be_nil
end
unless RAILS_VER < '2.1'
if RAILS_VER < '2.1'
it "should return time object from database in default timezone" do
ActiveRecord::Base.default_timezone = :utc
time_string = "2000-01-01 09:00:00"
@person = Person.new
@person.birth_date_and_time = time_string
@person.save
@person.reload
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z').should == time_string + ' GMT'
end
else
it "should return stored time string as Time with correct timezone" do
Time.zone = 'Melbourne'
time_string = "2000-06-01 02:03:04"
@@ -121,84 +116,6 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z %z').should == time_string + ' EST +1000'
end
describe "dirty attributes" do
it "should return true for attribute changed? when value updated" do
time_string = "2000-01-01 02:03:04"
@person.birth_date_and_time = time_string
@person.birth_date_and_time_changed?.should be_true
end
it "should show changes when time attribute changed from nil to Time object" do
time_string = "2000-01-01 02:03:04"
@person.birth_date_and_time = time_string
time = @person.birth_date_and_time
@person.changes.should == {"birth_date_and_time" => [nil, time]}
end
it "should show changes when time attribute changed from Time object to nil" do
time_string = "2020-01-01 02:03:04"
@person.birth_date_and_time = time_string
@person.save false
@person.reload
time = @person.birth_date_and_time
@person.birth_date_and_time = nil
@person.changes.should == {"birth_date_and_time" => [time, nil]}
end
it "should show no changes when assigned same value as Time object" do
time_string = "2020-01-01 02:03:04"
@person.birth_date_and_time = time_string
@person.save false
@person.reload
time = @person.birth_date_and_time
@person.birth_date_and_time = time
@person.changes.should == {}
end
it "should show no changes when assigned same value as time string" do
time_string = "2020-01-01 02:03:04"
@person.birth_date_and_time = time_string
@person.save false
@person.reload
@person.birth_date_and_time = time_string
@person.changes.should == {}
end
end
else
it "should return time object from database in default timezone" do
ActiveRecord::Base.default_timezone = :utc
time_string = "2000-01-01 09:00:00"
@person = Person.new
@person.birth_date_and_time = time_string
@person.save
@person.reload
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z').should == time_string + ' GMT'
end
end
it "should return same time object on repeat reads on existing object" do
Time.zone = 'Melbourne' unless RAILS_VER < '2.1'
time_string = "2000-01-01 09:00:00"
@person = Person.new
@person.birth_date_and_time = time_string
@person.save!
@person.reload
time = @person.birth_date_and_time
@person.birth_date_and_time.should == time
end
it "should return same date object on repeat reads on existing object" do
date_string = Date.today
@person = Person.new
@person.birth_date = date_string
@person.save!
@person.reload
date = @person.birth_date
@person.birth_date.should == date
end
it "should return correct date value after new value assigned" do
@@ -220,5 +137,5 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
@person.reload
@person.birth_date.should == tomorrow
end
end

View File

@@ -6,17 +6,17 @@ describe ValidatesTimeliness::ActiveRecord::MultiparameterAttributes do
end
it "should convert array for datetime type into datetime string" do
time_string = obj.time_array_to_string([2000,2,1,9,10,11], :datetime)
time_string = time_array_to_string([2000,2,1,9,10,11], :datetime)
time_string.should == "2000-02-01 09:10:11"
end
it "should convert array for date type into date string" do
time_string = obj.time_array_to_string([2000,2,1], :date)
time_string = time_array_to_string([2000,2,1], :date)
time_string.should == "2000-02-01"
end
it "should convert array for time type into time string" do
time_string = obj.time_array_to_string([2000,1,1,9,10,11], :time)
time_string = time_array_to_string([2000,1,1,9,10,11], :time)
time_string.should == "09:10:11"
end
@@ -44,5 +44,9 @@ describe ValidatesTimeliness::ActiveRecord::MultiparameterAttributes do
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
end
end
def time_array_to_string(*args)
ValidatesTimeliness::ActiveRecord.time_array_to_string(*args)
end
end

View File

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

View File

@@ -1,51 +1,6 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
describe ValidatesTimeliness::Formats do
attr_reader :formats
before do
@formats = ValidatesTimeliness::Formats
end
describe "expression generator" do
it "should generate regexp for time" do
generate_regexp_str('hh:nn:ss').should == '/(\d{2}):(\d{2}):(\d{2})/'
end
it "should generate regexp for time with meridian" do
generate_regexp_str('hh:nn:ss ampm').should == '/(\d{2}):(\d{2}):(\d{2}) ((?:[aApP])\.?[mM]\.?)/'
end
it "should generate regexp for time with meridian and optional space between" do
generate_regexp_str('hh:nn:ss_ampm').should == '/(\d{2}):(\d{2}):(\d{2})\s?((?:[aApP])\.?[mM]\.?)/'
end
it "should generate regexp for time with single or double digits" do
generate_regexp_str('h:n:s').should == '/(\d{1,2}):(\d{1,2}):(\d{1,2})/'
end
it "should generate regexp for date" do
generate_regexp_str('yyyy-mm-dd').should == '/(\d{4})-(\d{2})-(\d{2})/'
end
it "should generate regexp for date with slashes" do
generate_regexp_str('dd/mm/yyyy').should == '/(\d{2})\/(\d{2})\/(\d{4})/'
end
it "should generate regexp for date with dots" do
generate_regexp_str('dd.mm.yyyy').should == '/(\d{2})\.(\d{2})\.(\d{4})/'
end
it "should generate regexp for Ruby time string" do
expected = '/(\w{3,9}) (\w{3,9}) (\d{2}):(\d{2}):(\d{2}) (?:[+-]\d{2}:?\d{2}) (\d{4})/'
generate_regexp_str('ddd mmm hh:nn:ss zo yyyy').should == expected
end
it "should generate regexp for iso8601 datetime" do
expected = '/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:Z|(?:[+-]\d{2}:?\d{2}))/'
generate_regexp_str('yyyy-mm-ddThh:nn:ss(?:Z|zo)').should == expected
end
end
describe "format proc generator" do
it "should generate proc which outputs date array with values in correct order" do
@@ -71,6 +26,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,49 +95,100 @@ 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.should == [0,0,0,12,13,14,0]
time_array = formats.parse('12:13:14', :time, :strict => true)
time_array.should == [2000,1,1,12,13,14,0]
end
it "should return date array from time string" do
time_array = formats.parse('2000-02-01', :date, true)
time_array = formats.parse('2000-02-01', :date, :strict => true)
time_array.should == [2000,2,1,0,0,0,0]
end
it "should return datetime array from string value" do
time_array = formats.parse('2000-02-01 12:13:14', :datetime, true)
time_array = formats.parse('2000-02-01 12:13:14', :datetime, :strict => true)
time_array.should == [2000,2,1,12,13,14,0]
end
it "should parse date string when type is datetime" do
time_array = formats.parse('2000-02-01', :datetime, false)
time_array = formats.parse('2000-02-01', :datetime, :strict => false)
time_array.should == [2000,2,1,0,0,0,0]
end
it "should ignore time when extracting date and strict is false" do
time_array = formats.parse('2000-02-01 12:12', :date, false)
time_array = formats.parse('2000-02-01 12:13', :date, :strict => false)
time_array.should == [2000,2,1,0,0,0,0]
end
it "should ignore time when extracting date from format with trailing year and strict is false" do
time_array = formats.parse('01-02-2000 12:12', :date, false)
time_array = formats.parse('01-02-2000 12:13', :date, :strict => false)
time_array.should == [2000,2,1,0,0,0,0]
end
it "should ignore date when extracting time and strict is false" do
time_array = formats.parse('2000-02-01 12:12', :time, false)
time_array.should == [0,0,0,12,12,0,0]
time_array = formats.parse('2000-02-01 12:13', :time, :strict => false)
time_array.should == [2000,1,1,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 "parsing date with ambiguous year" do
it "should return year in current century if year below threshold" do
time_array = formats.parse('01-02-29', :date)
time_array.should == [2029,2,1,0,0,0,0]
end
it "should return year in last century if year at or above threshold" do
time_array = formats.parse('01-02-30', :date)
time_array.should == [1930,2,1,0,0,0,0]
end
it "should allow custom threshold" do
default = ValidatesTimeliness::Formats.ambiguous_year_threshold
ValidatesTimeliness::Formats.ambiguous_year_threshold = 40
time_array = formats.parse('01-02-39', :date)
time_array.should == [2039,2,1,0,0,0,0]
time_array = formats.parse('01-02-40', :date)
time_array.should == [1940,2,1,0,0,0,0]
ValidatesTimeliness::Formats.ambiguous_year_threshold = default
end
end
describe "parse with custom dummy date values" do
before(:all) do
@old_dummy_date = formats.dummy_date_for_time_type
formats.dummy_date_for_time_type = [2009,1,1]
end
it "should return time array with custom dummy date" do
time_array = formats.parse('12:13:14', :time, :strict => true)
time_array.should == [2009,1,1,12,13,14,0]
end
after(:all) do
formats.dummy_date_for_time_type = @old_dummy_date
end
end
describe "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")
@@ -196,7 +206,7 @@ describe ValidatesTimeliness::Formats do
after do
formats.time_formats << 'h.nn_ampm'
# reload class instead
formats.compile_format_expressions
end
end
@@ -220,7 +230,7 @@ describe ValidatesTimeliness::Formats do
formats.add_formats(:time, "ss:hh:nn", :before => 'hh:nn:ss')
validate("59:23:58", :time).should be_true
time_array = formats.parse('59:23:58', :time)
time_array.should == [0,0,0,23,58,59,0]
time_array.should == [2000,1,1,23,58,59,0]
end
it "should raise error if format exists" do
@@ -252,9 +262,14 @@ describe ValidatesTimeliness::Formats do
end
end
def formats
ValidatesTimeliness::Formats
end
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

View File

@@ -9,10 +9,10 @@
# ginger spec
#
Ginger.configure do |config|
rails_versions = ['2.0.2', '2.1.2', '2.2.2', '2.3.2']
rails_versions = ['2.0.2', '2.1.2', '2.2.2', '2.3.3', '2.3.4']
rails_versions.each do |v|
g = Ginger::Scenario.new
g = Ginger::Scenario.new("Rails #{v}")
g['rails'] = v
config.scenarios << g.dup
end

View File

@@ -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

View File

@@ -1,10 +1,12 @@
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
require 'validates_timeliness/matcher'
class NoValidation < Person
end
class WithValidation < Person
validates_date :birth_date,
:equal_to => '2000-01-01',
:before => '2000-01-10',
:after => '2000-01-01',
:on_or_before => '2000-01-09',
@@ -12,6 +14,7 @@ class WithValidation < Person
:between => ['2000-01-01', '2000-01-03']
validates_time :birth_time,
:equal_to => '09:00',
:before => '23:00',
:after => '09:00',
:on_or_before => '22:00',
@@ -19,6 +22,7 @@ class WithValidation < Person
:between => ['09:00', '17:00']
validates_datetime :birth_date_and_time,
: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',
@@ -61,6 +65,29 @@ describe "ValidateTimeliness matcher" do
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'],

View File

@@ -2,10 +2,10 @@ $:.unshift(File.dirname(__FILE__) + '/../lib')
$:.unshift(File.dirname(__FILE__))
$:.unshift(File.dirname(__FILE__) + '/resources')
ENV['RAILS_ENV'] = 'test'
RAILS_ENV = ENV['RAILS_ENV'] = 'test'
require 'rubygems'
require 'spec'
require 'spec/autorun'
vendored_rails = File.dirname(__FILE__) + '/../../../../vendor/rails'
@@ -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,14 @@ 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'
require 'validates_timeliness/matcher'
ActiveRecord::Migration.verbose = false
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})

View File

@@ -66,7 +66,7 @@ describe ValidatesTimeliness::Validator do
it "should return array of Time objects when restriction is array of strings" do
time1, time2 = "2000-01-02", "2000-01-01"
evaluate_option_value([time1, time2], :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
evaluate_option_value([time1, time2], :datetime).should == [parse(time2, :datetime), parse(time1, :datetime)]
end
it "should return array of Time objects when restriction is Range of Time objects" do
@@ -76,7 +76,7 @@ describe ValidatesTimeliness::Validator do
it "should return array of Time objects when restriction is Range of time strings" do
time1, time2 = "2000-01-02", "2000-01-01"
evaluate_option_value(time1..time2, :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
evaluate_option_value(time1..time2, :datetime).should == [parse(time2, :datetime), parse(time1, :datetime)]
end
def evaluate_option_value(restriction, type)
configure_validator(:type => type)
@@ -358,18 +358,6 @@ describe ValidatesTimeliness::Validator do
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)
@@ -409,6 +397,16 @@ describe ValidatesTimeliness::Validator do
end
end
describe "instance with :ignore_usec option" do
it "should ignore usec on time values when evaluated" 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
end
describe "instance with :with_time option" do
it "should validate date attribute as datetime combining value of :with_time against restrictions " do
@@ -423,20 +421,31 @@ describe ValidatesTimeliness::Validator do
should_have_no_error(:birth_date, :on_or_before)
end
it "should should ignore usec value on combined value if :ignore_usec option is true" do
configure_validator(:type => :date, :with_time => Time.mktime(2000,1,1,12,30,0,500), :equal_to => Time.mktime(2000,1,1,12,30), :ignore_usec => true)
validate_with(:birth_date, "2000-01-01")
should_have_no_error(:birth_date, :equal_to)
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)
validate_with(:birth_time, "12:30")
should_have_error(:birth_time, :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)
validate_with(:birth_time, "12:30")
should_have_no_error(:birth_time, :on_or_before)
end
it "should should ignore usec value on combined value if :ignore_usec option is true" do
configure_validator(:type => :time, :with_date => Date.new(2000,1,1), :on_or_before => Time.mktime(2000,1,1,12,30), :ignore_usec => true)
validate_with(:birth_time, Time.mktime(2000,1,1,12,30,0,50))
should_have_no_error(:birth_time, :on_or_before)
end
end
@@ -473,6 +482,20 @@ 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')
@@ -492,12 +515,6 @@ describe ValidatesTimeliness::Validator do
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'
@@ -554,12 +571,18 @@ describe ValidatesTimeliness::Validator do
describe "custom formats" do
before :all do
@@formats = ValidatesTimeliness::Validator.error_value_formats
ValidatesTimeliness::Validator.error_value_formats = {
custom = {
:time => '%H:%M %p',
:date => '%d-%m-%Y',
:datetime => '%d-%m-%Y %H:%M %p'
}
if defined?(I18n)
I18n.backend.store_translations 'en', :validates_timeliness => { :error_value_formats => custom }
else
@@formats = ValidatesTimeliness::Validator.error_value_formats
ValidatesTimeliness::Validator.error_value_formats = custom
end
end
it "should format datetime value of restriction" do
@@ -581,12 +604,20 @@ describe ValidatesTimeliness::Validator do
end
after :all do
ValidatesTimeliness::Validator.error_value_formats = @@formats
if defined?(I18n)
I18n.reload!
else
ValidatesTimeliness::Validator.error_value_formats = @@formats
end
end
end
end
def parse(*args)
ValidatesTimeliness::Parser.parse(*args)
end
def configure_validator(options={})
@validator = ValidatesTimeliness::Validator.new(options)
end

View File

@@ -2,26 +2,25 @@
Gem::Specification.new do |s|
s.name = %q{validates_timeliness}
s.version = "1.1.6"
s.version = "2.2.1"
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{2009-03-19}
s.date = %q{2009-09-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.has_rdoc = true
s.files = ["LICENSE", "README.rdoc", "Rakefile", "TODO", "CHANGELOG", "lib/validates_timeliness", "lib/validates_timeliness/active_record", "lib/validates_timeliness/active_record/multiparameter_attributes.rb", "lib/validates_timeliness/active_record/attribute_methods.rb", "lib/validates_timeliness/parser.rb", "lib/validates_timeliness/version.rb", "lib/validates_timeliness/validator.rb", "lib/validates_timeliness/validation_methods.rb", "lib/validates_timeliness/locale", "lib/validates_timeliness/locale/en.yml", "lib/validates_timeliness/spec", "lib/validates_timeliness/spec/rails", "lib/validates_timeliness/spec/rails/matchers", "lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb", "lib/validates_timeliness/matcher.rb", "lib/validates_timeliness/action_view", "lib/validates_timeliness/action_view/instance_tag.rb", "lib/validates_timeliness/formats.rb", "lib/validates_timeliness.rb", "spec/active_record", "spec/active_record/multiparameter_attributes_spec.rb", "spec/active_record/attribute_methods_spec.rb", "spec/formats_spec.rb", "spec/parser_spec.rb", "spec/spec_helper.rb", "spec/ginger_scenarios.rb", "spec/time_travel", "spec/time_travel/time_extensions.rb", "spec/time_travel/time_travel.rb", "spec/time_travel/MIT-LICENSE", "spec/spec", "spec/spec/rails", "spec/spec/rails/matchers", "spec/spec/rails/matchers/validate_timeliness_spec.rb", "spec/validator_spec.rb", "spec/action_view", "spec/action_view/instance_tag_spec.rb", "spec/resources", "spec/resources/schema.rb", "spec/resources/application.rb", "spec/resources/person.rb", "spec/resources/sqlite_patch.rb"]
s.homepage = %q{http://github.com/adzap/validates_timeliness}
s.require_paths = ["lib"]
s.rubyforge_project = %q{validatestime}
s.rubygems_version = %q{1.3.1}
s.rubygems_version = %q{1.3.3}
s.summary = %q{Date and time validation plugin for Rails 2.x which allows custom formats}
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 2
s.specification_version = 3
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
else