mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-25 15:22:58 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
897a9a3bd3 | ||
|
|
e9fa4ca20a | ||
|
|
e4760126e2 | ||
|
|
12af4d8d9d | ||
|
|
81330e7aad | ||
|
|
e82b1e2033 | ||
|
|
2ba85772b6 | ||
|
|
64ffb52dae | ||
|
|
ea6ec0cd75 | ||
|
|
a691b4ed35 | ||
|
|
e2790538a8 | ||
|
|
75f3ef34e8 | ||
|
|
4cb51ae602 | ||
|
|
dad55456d5 | ||
|
|
87b0beef5a | ||
|
|
6cd6cd9dc0 | ||
|
|
0c5cc1a536 | ||
|
|
c224db7af8 | ||
|
|
4868746e94 | ||
|
|
bf999170d7 | ||
|
|
dbfd9231b5 | ||
|
|
3da24f0f33 | ||
|
|
6f4306973b | ||
|
|
71f2a43424 | ||
|
|
c386a9cdcf | ||
|
|
1596ffd2cb | ||
|
|
d847d3b95a | ||
|
|
d0e60ece92 | ||
|
|
3ac65b507f | ||
|
|
d71f581e10 | ||
|
|
aa42fb76b6 | ||
|
|
b0647d456e | ||
|
|
9610d79d7d | ||
|
|
412ff22dd9 |
57
CHANGELOG
57
CHANGELOG
@@ -1,28 +1,43 @@
|
|||||||
[2008-11-13]
|
= 1.0.0 [2008-12-06]
|
||||||
- allow uppercase meridian to be valid [reported by Alex (http://alex.digns.com/)]
|
- Gemified!
|
||||||
|
- Refactor of plugin into a Data Mapper style validator class which makes for a cleaner implementation and possible future Merb\Data Mapper support
|
||||||
|
- Added Rails 2.2 i18n support. Plugin error messages can specified in locale files. See README.
|
||||||
|
- ignore_datetime_restriction_errors setting has been moved from AR to ValidatesTimeliness::Validator.ignore_restriction_errors
|
||||||
|
- date_time_error_value_formats setting has been moved from AR to ValidatesTimeliness::Validator.error_value_formats
|
||||||
|
- Namespaced modules and specs
|
||||||
|
- Clean up of specs
|
||||||
|
- fixed a few bugs
|
||||||
|
- accessor methods not generating properly due method name stored as symbol in generated_attributes which fails on lookup
|
||||||
|
- force value assigned to time/datetime attributes to time objects
|
||||||
|
|
||||||
[2008-10-28]
|
= 0.1.0 [2008-12-06]
|
||||||
- fixed bug when dirty attributes not reflecting change when attribute changed from time value to nil [reported by Brad (pvjq)]
|
- Tagged plugin as version 0.1.0
|
||||||
- fixes for Rails 2.2 compatibility. Will refactor in to Rails version specific branches in the future.
|
|
||||||
|
|
||||||
[2008-09-24]
|
= 2008-11-13
|
||||||
- refactored attribute write method definitions
|
- allow uppercase meridian to be valid [reported by Alex (http://alex.digns.com/)]
|
||||||
|
|
||||||
[2008-08-25]
|
= 2008-10-28
|
||||||
- fixed bug for non-timezone write method not updating changed attributes hash [reported by Sylvestre Mergulhão]
|
- fixed bug when dirty attributes not reflecting change when attribute changed from time value to nil [reported by Brad (pvjq)]
|
||||||
|
- fixes for Rails 2.2 compatibility. Will refactor in to Rails version specific branches in the future.
|
||||||
|
|
||||||
[2008-08-22]
|
= 2008-09-24
|
||||||
- fixed bug with attribute cache not clearing on write for date and time columns [reported by Sylvestre Mergulhão]
|
- refactored attribute write method definitions
|
||||||
- parse method returns Date object for date column assigned string as per normal Rails behaviour
|
|
||||||
- parse method returns same object type when assigned Date or Time object as per normal Rails behaviour
|
|
||||||
|
|
||||||
[2008-08-07]
|
= 2008-08-25
|
||||||
- modified matcher option value parsing to allow same value types as validation method
|
- fixed bug for non-timezone write method not updating changed attributes hash [reported by Sylvestre Mergulhão]
|
||||||
- fixed matcher message
|
|
||||||
|
|
||||||
[2008-08-02]
|
= 2008-08-22
|
||||||
- refactored validation
|
- fixed bug with attribute cache not clearing on write for date and time columns [reported by Sylvestre Mergulhão]
|
||||||
- refactored matcher
|
- parse method returns Date object for date column assigned string as per normal Rails behaviour
|
||||||
|
- parse method returns same object type when assigned Date or Time object as per normal Rails behaviour
|
||||||
|
|
||||||
[2008-07-30]
|
= 2008-08-07
|
||||||
- removed setting values to nil when validation fails to preserve before_type_cast value
|
- modified matcher option value parsing to allow same value types as validation method
|
||||||
|
- fixed matcher message
|
||||||
|
|
||||||
|
= 2008-08-02
|
||||||
|
- refactored validation
|
||||||
|
- refactored matcher
|
||||||
|
|
||||||
|
= 2008-07-30
|
||||||
|
- removed setting values to nil when validation fails to preserve before_type_cast value
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
= validates_timeliness
|
= validates_timeliness
|
||||||
|
|
||||||
* Source: http://github.com/adzap/validates_timeliness
|
* Source: http://github.com/adzap/validates_timeliness
|
||||||
* Tickets: http://adzap.lighthouseapp.com/projects/14111-validates_timeliness
|
* Bugs: http://adzap.lighthouseapp.com/projects/14111-validates_timeliness
|
||||||
|
|
||||||
== DESCRIPTION:
|
== DESCRIPTION:
|
||||||
|
|
||||||
@@ -27,9 +27,15 @@ think should be a valid date or time string.
|
|||||||
|
|
||||||
== INSTALLATION:
|
== INSTALLATION:
|
||||||
|
|
||||||
|
As plugin (from master)
|
||||||
|
|
||||||
./script/plugin git://github.com/adzap/validates_timeliness
|
./script/plugin git://github.com/adzap/validates_timeliness
|
||||||
|
|
||||||
|
As gem
|
||||||
|
|
||||||
|
sudo gem install validates_timeliness
|
||||||
|
|
||||||
|
|
||||||
== USAGE:
|
== USAGE:
|
||||||
|
|
||||||
To validate a model with a date, time or datetime attribute you just use the
|
To validate a model with a date, time or datetime attribute you just use the
|
||||||
@@ -51,7 +57,7 @@ The list of validation methods available are as follows:
|
|||||||
The validation methods take the usual options plus some specific ones to restrict
|
The validation methods take the usual options plus some specific ones to restrict
|
||||||
the valid range of dates or times allowed
|
the valid range of dates or times allowed
|
||||||
|
|
||||||
Temporal options:
|
Temporal options (or restrictions):
|
||||||
:before - Attribute must be before this value to be valid
|
:before - Attribute must be before this value to be valid
|
||||||
:on_or_before - Attribute must be equal to or before this value to be valid
|
:on_or_before - Attribute must be equal to or before this value to be valid
|
||||||
:after - Attribute must be after this value to be valid
|
:after - Attribute must be after this value to be valid
|
||||||
@@ -72,14 +78,14 @@ the valid range of dates or times allowed
|
|||||||
:after_message
|
:after_message
|
||||||
:on_or_after_message
|
:on_or_after_message
|
||||||
|
|
||||||
The temporal options can take 4 different value types:
|
The temporal restrictions can take 4 different value types:
|
||||||
|
|
||||||
* String value
|
* String value
|
||||||
* Date, Time, or DateTime object value
|
* Date, Time, or DateTime object value
|
||||||
* Proc or lambda object
|
* Proc or lambda object
|
||||||
* A symbol matching the method name in the model
|
* A symbol matching the method name in the model
|
||||||
|
|
||||||
When an attribute value is compared to temporal options, they are compared as
|
When an attribute value is compared to temporal restrictions, they are compared as
|
||||||
the same type as the validation method type. So using validates_date means all
|
the same type as the validation method type. So using validates_date means all
|
||||||
values are compared as dates.
|
values are compared as dates.
|
||||||
|
|
||||||
@@ -187,7 +193,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
|
when it does date interpretation, and is in keeping PoLS (principle of least
|
||||||
surprise).
|
surprise).
|
||||||
|
|
||||||
To switch to using the European (or Rest of The World) formats put this in an
|
To switch to using the :after => 1.day.from_nowEuropean (or Rest of The World) formats put this in an
|
||||||
initializer or environment.rb
|
initializer or environment.rb
|
||||||
|
|
||||||
ValidatesTimeliness::Formats.remove_us_formats
|
ValidatesTimeliness::Formats.remove_us_formats
|
||||||
@@ -227,23 +233,17 @@ Now a time of '59:30:23' will be interpreted as 11:30:59 pm. This option saves
|
|||||||
you adding a new one and deleting an old one to get it to work.
|
you adding a new one and deleting an old one to get it to work.
|
||||||
|
|
||||||
|
|
||||||
=== TEMPORAL OPTION ERRORS:
|
=== TEMPORAL RESTRICTION ERRORS:
|
||||||
|
|
||||||
When using the validation temporal options there are times when the restriction
|
When using the validation temporal restrictions there are times when the restriction
|
||||||
value itself may be invalid. Normally this will add an error to the model such as
|
value itself may be invalid. Normally this will add an error to the model such as
|
||||||
'restriction :before value was invalid'. These can be annoying if you are using
|
'restriction :before value was invalid'. These can be annoying if you are using
|
||||||
procs or methods as restrictions and don't care if they don't evaluate properly
|
procs or methods as restrictions and don't care if they don't evaluate properly
|
||||||
and you want the validation to complete. In these situations you turn them off.
|
and you want the validation to complete. In these situations you turn them off.
|
||||||
|
|
||||||
To turn them off globally:
|
To turn them off:
|
||||||
|
|
||||||
ActiveRecord::Base.ignore_datetime_restriction_errors = true
|
ValidatesTimeliness::Validator.ignore_restriction_errors = true
|
||||||
|
|
||||||
To switch them on or off per model:
|
|
||||||
|
|
||||||
class Person < ActiveRecord::Base
|
|
||||||
self.ignore_datetime_restriction_errors = false
|
|
||||||
end
|
|
||||||
|
|
||||||
A word of warning though, as this may hide issues with the model and make those
|
A word of warning though, as this may hide issues with the model and make those
|
||||||
corner cases a little harder to test. In general if you are using procs or
|
corner cases a little harder to test. In general if you are using procs or
|
||||||
@@ -252,9 +252,11 @@ return nil in all other situations. Restrictions are skipped if they are nil.
|
|||||||
|
|
||||||
=== OTHER CUSTOMISATION:
|
=== OTHER CUSTOMISATION:
|
||||||
|
|
||||||
The error messages for each temporal option can also be globally overridden by
|
The error messages for each temporal restrictions can also be globally overridden by
|
||||||
updating the default AR error messages like so
|
updating the default AR error messages like so
|
||||||
|
|
||||||
|
For Rails 2.0/2.1:
|
||||||
|
|
||||||
ActiveRecord::Errors.default_error_messages.update(
|
ActiveRecord::Errors.default_error_messages.update(
|
||||||
:invalid_date => "is not a valid date",
|
:invalid_date => "is not a valid date",
|
||||||
:invalid_time => "is not a valid time",
|
:invalid_time => "is not a valid time",
|
||||||
@@ -265,10 +267,24 @@ updating the default AR error messages like so
|
|||||||
:on_or_after => "must be on or after %s"
|
:on_or_after => "must be on or after %s"
|
||||||
)
|
)
|
||||||
|
|
||||||
Or for something a little more specific you can override the format of the values
|
Where %s is the interpolation value for the restriction.
|
||||||
inserted in the error messages for temporal options like so
|
|
||||||
|
|
||||||
ActiveRecord::Errors.date_time_error_value_formats.update(
|
Rails 2.2+ using the I18n system to define new defaults:
|
||||||
|
|
||||||
|
en:
|
||||||
|
activerecord:
|
||||||
|
errors:
|
||||||
|
messages:
|
||||||
|
on_or_before: "must equal to or before {{restriction}}"
|
||||||
|
on_or_after: "must equal to or after {{restriction}}"
|
||||||
|
|
||||||
|
The {{restriction}} signifies where the interpolation value for the restriction
|
||||||
|
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
|
||||||
|
|
||||||
|
ValidatesTimeliness::Validator.error_value_formats.update(
|
||||||
:time => '%H:%M:%S',
|
:time => '%H:%M:%S',
|
||||||
:date => '%Y-%m-%d',
|
:date => '%Y-%m-%d',
|
||||||
:datetime => '%Y-%m-%d %H:%M:%S'
|
:datetime => '%Y-%m-%d %H:%M:%S'
|
||||||
@@ -285,7 +301,6 @@ with the plugin or better yet *before* you write them! You just use the
|
|||||||
validation options you want as you would with the validation method. Those
|
validation options you want as you would with the validation method. Those
|
||||||
options are then verified and reported if they fail. Use it like so:
|
options are then verified and reported if they fail. Use it like so:
|
||||||
|
|
||||||
|
|
||||||
@person.should validate_date(:birth_date, :before => Time.now, :before_message => 'should be before today')
|
@person.should validate_date(:birth_date, :before => Time.now, :before_message => 'should be before today')
|
||||||
|
|
||||||
|
|
||||||
60
Rakefile
60
Rakefile
@@ -1,14 +1,58 @@
|
|||||||
require 'rake'
|
require 'rubygems'
|
||||||
require 'rake/testtask'
|
require 'rake/gempackagetask'
|
||||||
require 'rake/rdoctask'
|
require 'rubygems/specification'
|
||||||
|
require 'date'
|
||||||
require 'spec/rake/spectask'
|
require 'spec/rake/spectask'
|
||||||
|
|
||||||
spec_files = Rake::FileList["spec/**/*_spec.rb"]
|
GEM = "validates_timeliness"
|
||||||
|
GEM_VERSION = "1.0.0"
|
||||||
|
AUTHOR = "Adam Meehan"
|
||||||
|
EMAIL = "adam.meehan@gmail.com"
|
||||||
|
HOMEPAGE = "http://github.com/adzap/validates_timeliness"
|
||||||
|
SUMMARY = "Date and time validation plugin for Rails 2.x which allows custom formats"
|
||||||
|
|
||||||
desc "Run specs"
|
spec = Gem::Specification.new do |s|
|
||||||
Spec::Rake::SpecTask.new do |t|
|
s.name = GEM
|
||||||
t.spec_files = spec_files
|
s.version = GEM_VERSION
|
||||||
t.spec_opts = ["-c"]
|
s.platform = Gem::Platform::RUBY
|
||||||
|
s.rubyforge_project = "validatestime"
|
||||||
|
s.has_rdoc = true
|
||||||
|
s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"]
|
||||||
|
s.summary = SUMMARY
|
||||||
|
s.description = s.summary
|
||||||
|
s.author = AUTHOR
|
||||||
|
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}/**/*")
|
||||||
end
|
end
|
||||||
|
|
||||||
task :default => :spec
|
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)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
Rake::GemPackageTask.new(spec) do |pkg|
|
||||||
|
pkg.gem_spec = spec
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "install the gem locally"
|
||||||
|
task :install => [:package] do
|
||||||
|
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "create a gemspec file"
|
||||||
|
task :make_spec do
|
||||||
|
File.open("#{GEM}.gemspec", "w") do |file|
|
||||||
|
file.puts spec.to_ruby
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
8
TODO
Normal file
8
TODO
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
- :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
|
||||||
@@ -1,21 +1,66 @@
|
|||||||
require 'validates_timeliness/attribute_methods'
|
|
||||||
require 'validates_timeliness/validations'
|
|
||||||
require 'validates_timeliness/formats'
|
require 'validates_timeliness/formats'
|
||||||
require 'validates_timeliness/multiparameter_attributes'
|
require 'validates_timeliness/validator'
|
||||||
require 'validates_timeliness/instance_tag'
|
require 'validates_timeliness/validation_methods'
|
||||||
require 'validates_timeliness/validate_timeliness_matcher' if ENV['RAILS_ENV'] == 'test'
|
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/time'
|
||||||
require 'validates_timeliness/core_ext/date'
|
require 'validates_timeliness/core_ext/date'
|
||||||
require 'validates_timeliness/core_ext/date_time'
|
require 'validates_timeliness/core_ext/date_time'
|
||||||
|
|
||||||
ActiveRecord::Base.send(:include, ValidatesTimeliness::AttributeMethods)
|
module ValidatesTimeliness
|
||||||
ActiveRecord::Base.send(:include, ValidatesTimeliness::Validations)
|
|
||||||
ActiveRecord::Base.send(:include, ValidatesTimeliness::MultiparameterAttributes)
|
mattr_accessor :default_timezone
|
||||||
ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::InstanceTag)
|
|
||||||
|
|
||||||
Time.send(:include, ValidatesTimeliness::CoreExtensions::Time)
|
self.default_timezone = :utc
|
||||||
Date.send(:include, ValidatesTimeliness::CoreExtensions::Date)
|
|
||||||
DateTime.send(:include, ValidatesTimeliness::CoreExtensions::DateTime)
|
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 ]
|
||||||
|
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
|
||||||
|
else
|
||||||
|
I18n.translate('activerecord.errors.messages')
|
||||||
|
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"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ValidatesTimeliness.setup_for_rails
|
||||||
|
|
||||||
ValidatesTimeliness::Formats.compile_format_expressions
|
ValidatesTimeliness::Formats.compile_format_expressions
|
||||||
|
|||||||
45
lib/validates_timeliness/action_view/instance_tag.rb
Normal file
45
lib/validates_timeliness/action_view/instance_tag.rb
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module ActionView
|
||||||
|
|
||||||
|
# Intercepts the date and time select helpers to allow the
|
||||||
|
# attribute value before type cast to be used as in the select helpers.
|
||||||
|
# This means that an invalid date or time will be redisplayed rather than the
|
||||||
|
# type cast value which would be nil if invalid.
|
||||||
|
module InstanceTag
|
||||||
|
|
||||||
|
def self.included(base)
|
||||||
|
selector_method = Rails::VERSION::STRING < '2.2' ? :date_or_time_select : :datetime_selector
|
||||||
|
base.class_eval do
|
||||||
|
alias_method :datetime_selector_without_timeliness, selector_method
|
||||||
|
alias_method selector_method, :datetime_selector_with_timeliness
|
||||||
|
end
|
||||||
|
base.alias_method_chain :value, :timeliness
|
||||||
|
end
|
||||||
|
|
||||||
|
TimelinessDateTime = Struct.new(:year, :month, :day, :hour, :min, :sec)
|
||||||
|
|
||||||
|
def datetime_selector_with_timeliness(*args)
|
||||||
|
@timeliness_date_or_time_tag = true
|
||||||
|
datetime_selector_without_timeliness(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def value_with_timeliness(object)
|
||||||
|
return value_without_timeliness(object) unless @timeliness_date_or_time_tag
|
||||||
|
|
||||||
|
raw_value = value_before_type_cast(object)
|
||||||
|
|
||||||
|
if raw_value.nil? || raw_value.acts_like?(:time) || raw_value.is_a?(Date)
|
||||||
|
return value_without_timeliness(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
time_array = ParseDate.parsedate(raw_value)
|
||||||
|
|
||||||
|
TimelinessDateTime.new(*time_array[0..5])
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::ActionView::InstanceTag)
|
||||||
157
lib/validates_timeliness/active_record/attribute_methods.rb
Normal file
157
lib/validates_timeliness/active_record/attribute_methods.rb
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
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.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
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(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
|
||||||
|
end
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
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)
|
||||||
|
|
||||||
|
unless column.type == :date || new.nil?
|
||||||
|
new = new.to_time rescue new
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.class.send(:create_time_zone_conversion_attribute?, attr_name, column)
|
||||||
|
new = new.in_time_zone rescue nil
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unless instance_method_already_implemented?("#{name}?")
|
||||||
|
define_question_method(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define write method for date, time and datetime columns
|
||||||
|
def define_write_method_for_dates_and_times(attr_name)
|
||||||
|
method_body = <<-EOV
|
||||||
|
def #{attr_name}=(value)
|
||||||
|
write_date_time_attribute('#{attr_name}', value)
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
end
|
||||||
|
EOV
|
||||||
|
evaluate_attribute_method attr_name, method_body
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::AttributeMethods)
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module ActiveRecord
|
||||||
|
|
||||||
|
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.
|
||||||
|
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)
|
||||||
|
begin
|
||||||
|
value = if [:date, :time, :datetime].include?(column.type)
|
||||||
|
time_array_to_string(values, column.type)
|
||||||
|
else
|
||||||
|
klass.new(*values)
|
||||||
|
end
|
||||||
|
send(name + "=", value)
|
||||||
|
rescue => ex
|
||||||
|
errors << ::ActiveRecord::AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
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(":")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::MultiparameterAttributes)
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
module ValidatesTimeliness
|
|
||||||
|
|
||||||
# The crux of the plugin is being able to store raw user entered values
|
|
||||||
# while not interfering with the Rails 2.1 automatic timezone handling. This
|
|
||||||
# requires us to distinguish a user entered value from a value read from the
|
|
||||||
# database. Both maybe in string form, but only the database value should be
|
|
||||||
# interpreted as being in the default timezone which is normally UTC. The user
|
|
||||||
# entered value should be interpreted as being in the current zone as indicated
|
|
||||||
# by Time.zone.
|
|
||||||
#
|
|
||||||
# To do this we must cache the user entered values 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.
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
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(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
|
|
||||||
end
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
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)
|
|
||||||
attr_name = attr_name.to_s
|
|
||||||
column = column_for_attribute(attr_name)
|
|
||||||
old = read_attribute(attr_name) if defined?(ActiveRecord::Dirty)
|
|
||||||
new = self.class.parse_date_time(value, column.type)
|
|
||||||
|
|
||||||
if self.class.send(:create_time_zone_conversion_attribute?, attr_name, column)
|
|
||||||
new = new.in_time_zone rescue nil
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
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.to_sym)
|
|
||||||
else
|
|
||||||
define_read_method(name.to_sym, name, column)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
unless instance_method_already_implemented?("#{name}=")
|
|
||||||
if [:date, :time, :datetime].include?(column.type)
|
|
||||||
define_write_method_for_dates_and_times(name.to_sym)
|
|
||||||
else
|
|
||||||
define_write_method(name.to_sym)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
unless instance_method_already_implemented?("#{name}?")
|
|
||||||
define_question_method(name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Define write method for date, time and datetime columns
|
|
||||||
def define_write_method_for_dates_and_times(attr_name)
|
|
||||||
method_body = <<-EOV
|
|
||||||
def #{attr_name}=(value)
|
|
||||||
write_date_time_attribute('#{attr_name}', value)
|
|
||||||
end
|
|
||||||
EOV
|
|
||||||
evaluate_attribute_method attr_name, method_body
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
end
|
|
||||||
EOV
|
|
||||||
evaluate_attribute_method attr_name, method_body
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -3,9 +3,11 @@ module ValidatesTimeliness
|
|||||||
module Date
|
module Date
|
||||||
|
|
||||||
def to_dummy_time
|
def to_dummy_time
|
||||||
::Time.mktime(2000, 1, 1, 0, 0, 0)
|
::Time.send(ValidatesTimeliness.default_timezone, 2000, 1, 1, 0, 0, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Date.send(:include, ValidatesTimeliness::CoreExtensions::Date)
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ module ValidatesTimeliness
|
|||||||
module DateTime
|
module DateTime
|
||||||
|
|
||||||
def to_dummy_time
|
def to_dummy_time
|
||||||
::Time.mktime(2000, 1, 1, hour, min, sec)
|
::Time.send(ValidatesTimeliness.default_timezone, 2000, 1, 1, hour, min, sec)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
DateTime.send(:include, ValidatesTimeliness::CoreExtensions::DateTime)
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ module ValidatesTimeliness
|
|||||||
module Time
|
module Time
|
||||||
|
|
||||||
def to_dummy_time
|
def to_dummy_time
|
||||||
self.class.mktime(2000, 1, 1, hour, min, sec)
|
self.class.send(ValidatesTimeliness.default_timezone, 2000, 1, 1, hour, min, sec)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Time.send(:include, ValidatesTimeliness::CoreExtensions::Time)
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
module ValidatesTimeliness
|
|
||||||
|
|
||||||
# Intercepts the date and time select helpers to allow the
|
|
||||||
# attribute value before type cast to be used as in the select helpers.
|
|
||||||
# This means that an invalid date or time will be redisplayed rather than the
|
|
||||||
# type cast value which would be nil if invalid.
|
|
||||||
module InstanceTag
|
|
||||||
|
|
||||||
def self.included(base)
|
|
||||||
selector_method = Rails::VERSION::STRING < '2.2' ? :date_or_time_select : :datetime_selector
|
|
||||||
base.class_eval do
|
|
||||||
alias_method :datetime_selector_without_timeliness, selector_method
|
|
||||||
alias_method selector_method, :datetime_selector_with_timeliness
|
|
||||||
end
|
|
||||||
base.alias_method_chain :value, :timeliness
|
|
||||||
end
|
|
||||||
|
|
||||||
TimelinessDateTime = Struct.new(:year, :month, :day, :hour, :min, :sec)
|
|
||||||
|
|
||||||
def datetime_selector_with_timeliness(*args)
|
|
||||||
@timeliness_date_or_time_tag = true
|
|
||||||
datetime_selector_without_timeliness(*args)
|
|
||||||
end
|
|
||||||
|
|
||||||
def value_with_timeliness(object)
|
|
||||||
return value_without_timeliness(object) unless @timeliness_date_or_time_tag
|
|
||||||
|
|
||||||
raw_value = value_before_type_cast(object)
|
|
||||||
|
|
||||||
if raw_value.nil? || raw_value.acts_like?(:time) || raw_value.is_a?(Date)
|
|
||||||
return value_without_timeliness(object)
|
|
||||||
end
|
|
||||||
|
|
||||||
time_array = ParseDate.parsedate(raw_value)
|
|
||||||
|
|
||||||
TimelinessDateTime.new(*time_array[0..5])
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
11
lib/validates_timeliness/locale/en.yml
Normal file
11
lib/validates_timeliness/locale/en.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
en:
|
||||||
|
activerecord:
|
||||||
|
errors:
|
||||||
|
messages:
|
||||||
|
invalid_date: "is not a valid date"
|
||||||
|
invalid_time: "is not a valid time"
|
||||||
|
invalid_datetime: "is not a valid datetime"
|
||||||
|
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}}"
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
module ValidatesTimeliness
|
|
||||||
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.
|
|
||||||
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)
|
|
||||||
begin
|
|
||||||
value = if [:date, :time, :datetime].include?(column.type)
|
|
||||||
time_array_to_string(values, column.type)
|
|
||||||
else
|
|
||||||
klass.new(*values)
|
|
||||||
end
|
|
||||||
send(name + "=", value)
|
|
||||||
rescue => ex
|
|
||||||
errors << ActiveRecord::AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
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(":")
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,27 +1,34 @@
|
|||||||
module Spec
|
module Spec
|
||||||
module Rails
|
module Rails
|
||||||
module Matchers
|
module Matchers
|
||||||
class ValidateTimeliness
|
class ValidateTimeliness
|
||||||
|
cattr_accessor :test_values
|
||||||
|
|
||||||
|
@@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'}
|
||||||
|
}
|
||||||
|
|
||||||
def initialize(attribute, options)
|
def initialize(attribute, options)
|
||||||
@expected, @options = attribute, options
|
@expected, @options = attribute, options
|
||||||
@options.reverse_merge!(error_messages)
|
@validator = ValidatesTimeliness::Validator.new(options)
|
||||||
|
compile_error_messages
|
||||||
end
|
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)
|
def matches?(record)
|
||||||
@record = record
|
@record = record
|
||||||
type = options[:type]
|
type = options[:type]
|
||||||
|
|
||||||
test_values = {
|
invalid_value = @@test_values[type][:fail]
|
||||||
:date => {:pass => '2000-01-01', :fail => '2000-01-32'},
|
valid_value = parse_and_cast(@@test_values[type][:pass])
|
||||||
:time => {:pass => '12:00', :fail => '25:00'},
|
valid = error_matching(invalid_value, /#{messages["invalid_#{type}".to_sym]}/) &&
|
||||||
:datetime => {:pass => '2000-01-01 00:00:00', :fail => '2000-01-32 00:00:00'}
|
no_error_matching(valid_value, /#{messages["invalid_#{type}".to_sym]}/)
|
||||||
}
|
|
||||||
|
|
||||||
invalid_value = test_values[type][:fail]
|
|
||||||
valid_value = parse_and_cast(test_values[type][:pass])
|
|
||||||
valid = error_matching(invalid_value, /#{options["invalid_#{type}_message".to_sym]}/) &&
|
|
||||||
no_error_matching(valid_value, /#{options["invalid_#{type}_message".to_sym]}/)
|
|
||||||
|
|
||||||
valid = test_option(:before, :-) if options[:before] && valid
|
valid = test_option(:before, :-) if options[:before] && valid
|
||||||
valid = test_option(:after, :+) if options[:after] && valid
|
valid = test_option(:after, :+) if options[:after] && valid
|
||||||
@@ -45,7 +52,7 @@ module Spec
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
attr_reader :actual, :expected, :record, :options, :last_failure
|
attr_reader :actual, :expected, :record, :options, :messages, :last_failure, :validator
|
||||||
|
|
||||||
def test_option(option, modifier, settings={})
|
def test_option(option, modifier, settings={})
|
||||||
settings.reverse_merge!(:modify_on => :valid)
|
settings.reverse_merge!(:modify_on => :valid)
|
||||||
@@ -57,23 +64,16 @@ module Spec
|
|||||||
[ boundary, boundary.send(modifier, 1) ]
|
[ boundary, boundary.send(modifier, 1) ]
|
||||||
end
|
end
|
||||||
|
|
||||||
message = options["#{option}_message".to_sym]
|
message = messages[option]
|
||||||
error_matching(invalid_value, /#{message}/) &&
|
error_matching(invalid_value, /#{message}/) &&
|
||||||
no_error_matching(valid_value, /#{message}/)
|
no_error_matching(valid_value, /#{message}/)
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_and_cast(value)
|
def parse_and_cast(value)
|
||||||
value = ActiveRecord::Base.send(:timeliness_restriction_value, value, record, options[:type])
|
value = validator.send(:restriction_value, value, record)
|
||||||
cast_method = ActiveRecord::Base.send(:restriction_type_cast_method, options[:type])
|
validator.send(:type_cast_value, value)
|
||||||
value.send(cast_method) rescue nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_messages
|
|
||||||
messages = ActiveRecord::Base.send(:timeliness_default_error_messages)
|
|
||||||
messages = messages.inject({}) {|h, (k, v)| h[k] = v.sub(' %s', ''); h }
|
|
||||||
@options.reverse_merge!(messages)
|
|
||||||
end
|
|
||||||
|
|
||||||
def error_matching(value, match)
|
def error_matching(value, match)
|
||||||
record.send("#{expected}=", value)
|
record.send("#{expected}=", value)
|
||||||
record.valid?
|
record.valid?
|
||||||
@@ -83,7 +83,7 @@ module Spec
|
|||||||
pass
|
pass
|
||||||
end
|
end
|
||||||
|
|
||||||
def no_error_matching(value, match)
|
def no_error_matching(value, match)
|
||||||
pass = !error_matching(value, match)
|
pass = !error_matching(value, match)
|
||||||
@last_failure = "no error matching #{match.inspect} when value is #{format_value(value)}" unless pass
|
@last_failure = "no error matching #{match.inspect} when value is #{format_value(value)}" unless pass
|
||||||
pass
|
pass
|
||||||
@@ -91,7 +91,7 @@ module Spec
|
|||||||
|
|
||||||
def format_value(value)
|
def format_value(value)
|
||||||
return value if value.is_a?(String)
|
return value if value.is_a?(String)
|
||||||
value.strftime(ActiveRecord::Errors.date_time_error_value_formats[options[:type]])
|
value.strftime(ValidatesTimeliness::Validator.error_value_formats[options[:type]])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -110,10 +110,12 @@ module Spec
|
|||||||
validate_timeliness_of(attribute, options)
|
validate_timeliness_of(attribute, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def validate_timeliness_of(attribute, options={})
|
|
||||||
ValidateTimeliness.new(attribute, options)
|
def validate_timeliness_of(attribute, options={})
|
||||||
end
|
ValidateTimeliness.new(attribute, options)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
82
lib/validates_timeliness/validation_methods.rb
Normal file
82
lib/validates_timeliness/validation_methods.rb
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module ValidationMethods
|
||||||
|
|
||||||
|
def self.included(base)
|
||||||
|
base.extend ClassMethods
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
validates_timeliness_of(attr_names, configuration)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validates_date(*attr_names)
|
||||||
|
configuration = attr_names.extract_options!
|
||||||
|
configuration[:type] = :date
|
||||||
|
validates_timeliness_of(attr_names, configuration)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validates_datetime(*attr_names)
|
||||||
|
configuration = attr_names.extract_options!
|
||||||
|
configuration[:type] = :datetime
|
||||||
|
validates_timeliness_of(attr_names, configuration)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def validates_timeliness_of(attr_names, configuration)
|
||||||
|
validator = ValidatesTimeliness::Validator.new(configuration)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Base.send(:include, ValidatesTimeliness::ValidationMethods)
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
module ValidatesTimeliness
|
|
||||||
# Adds ActiveRecord validation methods for date, time and datetime validation.
|
|
||||||
# The validity of values can be restricted to be before or after certain dates
|
|
||||||
# or times.
|
|
||||||
module Validations
|
|
||||||
|
|
||||||
# Error messages and error value formats added to AR defaults to allow
|
|
||||||
# global override.
|
|
||||||
def self.included(base)
|
|
||||||
base.extend ClassMethods
|
|
||||||
|
|
||||||
base.class_inheritable_accessor :ignore_datetime_restriction_errors
|
|
||||||
base.ignore_datetime_restriction_errors = false
|
|
||||||
|
|
||||||
ActiveRecord::Errors.class_inheritable_accessor :date_time_error_value_formats
|
|
||||||
ActiveRecord::Errors.date_time_error_value_formats = {
|
|
||||||
:time => '%H:%M:%S',
|
|
||||||
:date => '%Y-%m-%d',
|
|
||||||
:datetime => '%Y-%m-%d %H:%M:%S'
|
|
||||||
}
|
|
||||||
|
|
||||||
ActiveRecord::Errors.default_error_messages.update(
|
|
||||||
:invalid_date => "is not a valid date",
|
|
||||||
:invalid_time => "is not a valid time",
|
|
||||||
:invalid_datetime => "is not a valid datetime",
|
|
||||||
: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"
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# The main validation method which can be used directly or called through
|
|
||||||
# the other specific type validation methods.
|
|
||||||
def validates_timeliness_of(*attr_names)
|
|
||||||
configuration = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false }
|
|
||||||
configuration.update(timeliness_default_error_messages)
|
|
||||||
configuration.update(attr_names.extract_options!)
|
|
||||||
|
|
||||||
# we need to check raw value for blank or nil
|
|
||||||
allow_nil = configuration.delete(:allow_nil)
|
|
||||||
allow_blank = configuration.delete(:allow_blank)
|
|
||||||
|
|
||||||
validates_each(attr_names, configuration) do |record, attr_name, value|
|
|
||||||
raw_value = record.send("#{attr_name}_before_type_cast")
|
|
||||||
|
|
||||||
next if (raw_value.nil? && allow_nil) || (raw_value.blank? && allow_blank)
|
|
||||||
|
|
||||||
record.errors.add(attr_name, configuration[:blank_message]) and next if raw_value.blank?
|
|
||||||
|
|
||||||
column = record.column_for_attribute(attr_name)
|
|
||||||
begin
|
|
||||||
unless time = parse_date_time(raw_value, configuration[:type])
|
|
||||||
record.errors.add(attr_name, configuration["invalid_#{configuration[:type]}_message".to_sym])
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
validate_timeliness_restrictions(record, attr_name, time, configuration)
|
|
||||||
rescue Exception => e
|
|
||||||
record.errors.add(attr_name, configuration["invalid_#{configuration[:type]}_message".to_sym])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Use this validation to force validation of values and restrictions
|
|
||||||
# as dummy time
|
|
||||||
def validates_time(*attr_names)
|
|
||||||
configuration = attr_names.extract_options!
|
|
||||||
configuration[:type] = :time
|
|
||||||
validates_timeliness_of(attr_names, configuration)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Use this validation to force validation of values and restrictions
|
|
||||||
# as Date
|
|
||||||
def validates_date(*attr_names)
|
|
||||||
configuration = attr_names.extract_options!
|
|
||||||
configuration[:type] = :date
|
|
||||||
validates_timeliness_of(attr_names, configuration)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Use this validation to force validation of values and restrictions
|
|
||||||
# as Time/DateTime
|
|
||||||
def validates_datetime(*attr_names)
|
|
||||||
configuration = attr_names.extract_options!
|
|
||||||
configuration[:type] = :datetime
|
|
||||||
validates_timeliness_of(attr_names, configuration)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def timeliness_restriction_value(restriction, record, type)
|
|
||||||
case restriction
|
|
||||||
when Time, Date, DateTime
|
|
||||||
restriction
|
|
||||||
when Symbol
|
|
||||||
timeliness_restriction_value(record.send(restriction), record, type)
|
|
||||||
when Proc
|
|
||||||
timeliness_restriction_value(restriction.call(record), record, type)
|
|
||||||
else
|
|
||||||
parse_date_time(restriction, type, false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def restriction_type_cast_method(type)
|
|
||||||
case type
|
|
||||||
when :time then :to_dummy_time
|
|
||||||
when :date then :to_date
|
|
||||||
when :datetime then :to_time
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Validate value against the temporal restrictions. Restriction values
|
|
||||||
# maybe of mixed type, so they are evaluated as a common type, which may
|
|
||||||
# require conversion. The type used is defined by validation type.
|
|
||||||
def validate_timeliness_restrictions(record, attr_name, value, configuration)
|
|
||||||
restriction_methods = {:before => '<', :after => '>', :on_or_before => '<=', :on_or_after => '>='}
|
|
||||||
|
|
||||||
type_cast_method = restriction_type_cast_method(configuration[:type])
|
|
||||||
|
|
||||||
display = ActiveRecord::Errors.date_time_error_value_formats[configuration[:type]]
|
|
||||||
|
|
||||||
value = value.send(type_cast_method)
|
|
||||||
|
|
||||||
restriction_methods.each do |option, method|
|
|
||||||
next unless restriction = configuration[option]
|
|
||||||
begin
|
|
||||||
compare = timeliness_restriction_value(restriction, record, configuration[:type])
|
|
||||||
|
|
||||||
next if compare.nil?
|
|
||||||
|
|
||||||
compare = compare.send(type_cast_method)
|
|
||||||
record.errors.add(attr_name, configuration["#{option}_message".to_sym] % compare.strftime(display)) unless value.send(method, compare)
|
|
||||||
rescue
|
|
||||||
record.errors.add(attr_name, "restriction '#{option}' value was invalid") unless self.ignore_datetime_restriction_errors
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Map error message keys to *_message to merge with validation options
|
|
||||||
def timeliness_default_error_messages
|
|
||||||
defaults = ActiveRecord::Errors.default_error_messages.slice(
|
|
||||||
:blank, :invalid_date, :invalid_time, :invalid_datetime, :before, :on_or_before, :after, :on_or_after)
|
|
||||||
returning({}) do |messages|
|
|
||||||
defaults.each {|k, v| messages["#{k}_message".to_sym] = v }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Create time in correct timezone. For Rails 2.1 that is value in
|
|
||||||
# Time.zone. Rails 2.0 should be default_timezone.
|
|
||||||
def make_time(time_array)
|
|
||||||
if Time.respond_to?(:zone) && time_zone_aware_attributes
|
|
||||||
Time.zone.local(*time_array)
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
Time.send(ActiveRecord::Base.default_timezone, *time_array)
|
|
||||||
rescue ArgumentError, TypeError
|
|
||||||
zone_offset = ActiveRecord::Base.default_timezone == :local ? DateTime.local_offset : 0
|
|
||||||
time_array.pop # remove microseconds
|
|
||||||
DateTime.civil(*(time_array << zone_offset))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
120
lib/validates_timeliness/validator.rb
Normal file
120
lib/validates_timeliness/validator.rb
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
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'
|
||||||
|
}
|
||||||
|
|
||||||
|
attr_reader :configuration, :type
|
||||||
|
|
||||||
|
def initialize(configuration)
|
||||||
|
defaults = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false }
|
||||||
|
@configuration = defaults.merge(configuration)
|
||||||
|
@type = @configuration.delete(:type)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
validate_restrictions(record, attr_name, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def raw_value(record, attr_name)
|
||||||
|
record.send("#{attr_name}_before_type_cast")
|
||||||
|
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|
|
||||||
|
next unless restriction = configuration[option]
|
||||||
|
begin
|
||||||
|
compare = restriction_value(restriction, record)
|
||||||
|
next if compare.nil?
|
||||||
|
compare = type_cast_value(compare)
|
||||||
|
|
||||||
|
unless value.send(method, compare)
|
||||||
|
add_error(record, attr_name, option, :restriction => compare.strftime(display))
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
unless self.class.ignore_restriction_errors
|
||||||
|
add_error(record, attr_name, "restriction '#{option}' value was invalid")
|
||||||
|
end
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def error_messages
|
||||||
|
return @error_messages if defined?(@error_messages)
|
||||||
|
@error_messages = ValidatesTimeliness.default_error_messages.merge(custom_error_messages)
|
||||||
|
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 }
|
||||||
|
end
|
||||||
|
|
||||||
|
def restriction_value(restriction, record)
|
||||||
|
case restriction
|
||||||
|
when Time, Date, DateTime
|
||||||
|
restriction
|
||||||
|
when Symbol
|
||||||
|
restriction_value(record.send(restriction), record)
|
||||||
|
when Proc
|
||||||
|
restriction_value(restriction.call(record), record)
|
||||||
|
else
|
||||||
|
record.class.parse_date_time(restriction, type, false)
|
||||||
|
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
|
||||||
|
else
|
||||||
|
value.to_time(ValidatesTimelines.default_timezone)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||||
|
|
||||||
describe ValidatesTimeliness::InstanceTag, :type => :helper do
|
describe ValidatesTimeliness::ActionView::InstanceTag, :type => :helper do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@person = Person.new
|
@person = Person.new
|
||||||
@@ -1,13 +1,28 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||||
|
|
||||||
describe ValidatesTimeliness::AttributeMethods do
|
describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
||||||
include ValidatesTimeliness::AttributeMethods
|
include ValidatesTimeliness::ActiveRecord::AttributeMethods
|
||||||
include ValidatesTimeliness::Validations
|
include ValidatesTimeliness::ValidationMethods
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@person = Person.new
|
@person = Person.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should call write_date_time_attribute when date attribute assigned value" do
|
||||||
|
@person.should_receive(:write_date_time_attribute)
|
||||||
|
@person.birth_date = "2000-01-01"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should call write_date_time_attribute when time attribute assigned value" do
|
||||||
|
@person.should_receive(:write_date_time_attribute)
|
||||||
|
@person.birth_time = "12:00"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should call write_date_time_attribute when datetime attribute assigned value" do
|
||||||
|
@person.should_receive(:write_date_time_attribute)
|
||||||
|
@person.birth_date_and_time = "2000-01-01 12:00"
|
||||||
|
end
|
||||||
|
|
||||||
it "should call parser on write for datetime attribute" do
|
it "should call parser on write for datetime attribute" do
|
||||||
@person.class.should_receive(:parse_date_time).once
|
@person.class.should_receive(:parse_date_time).once
|
||||||
@person.birth_date_and_time = "2000-01-01 02:03:04"
|
@person.birth_date_and_time = "2000-01-01 02:03:04"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||||
|
|
||||||
describe ValidatesTimeliness::MultiparameterAttributes do
|
describe ValidatesTimeliness::ActiveRecord::MultiparameterAttributes do
|
||||||
def obj
|
def obj
|
||||||
@obj ||= Person.new
|
@obj ||= Person.new
|
||||||
end
|
end
|
||||||
@@ -39,11 +39,9 @@ describe ValidatesTimeliness::MultiparameterAttributes do
|
|||||||
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
|
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
|
||||||
end
|
end
|
||||||
|
|
||||||
unless RAILS_VER < '2.1' # sqlite doesn't support :time attribute in rails 2.0.x
|
it "should store time string for a time column" do
|
||||||
it "should store time string for a time column" do
|
obj.should_receive(:birth_time=).once.with("09:10:11")
|
||||||
obj.should_receive(:birth_time=).once.with("09:10:11")
|
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
|
||||||
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
31
spec/core_ext/dummy_time_spec.rb
Normal file
31
spec/core_ext/dummy_time_spec.rb
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||||
|
|
||||||
|
describe ValidatesTimeliness::CoreExtensions::Date do
|
||||||
|
before do
|
||||||
|
@a_date = Date.new(2008, 7, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should make a date value into a dummy time value" do
|
||||||
|
@a_date.to_dummy_time.should == Time.utc(2000,1,1,0,0,0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ValidatesTimeliness::CoreExtensions::Time do
|
||||||
|
before do
|
||||||
|
@a_time = Time.mktime(2008, 7, 1, 2, 3, 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should make a time value into a dummy time value" do
|
||||||
|
@a_time.to_dummy_time.should == Time.utc(2000,1,1,2,3,4)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ValidatesTimeliness::CoreExtensions::DateTime do
|
||||||
|
before do
|
||||||
|
@a_datetime = DateTime.new(2008, 7, 1, 2, 3, 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should make a datetime value into a dummy time value" do
|
||||||
|
@a_datetime.to_dummy_time.should == Time.utc(2000,1,1,2,3,4)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
|
||||||
|
|
||||||
describe ValidatesTimeliness::CoreExtensions::Date do
|
|
||||||
before do
|
|
||||||
@a_date = Date.new(2008, 7, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should " do
|
|
||||||
@a_date.to_dummy_time.should == Time.mktime(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 " do
|
|
||||||
@a_time.to_dummy_time.should == Time.mktime(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 " do
|
|
||||||
@a_datetime.to_dummy_time.should == Time.mktime(2000,1,1,2,3,4)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
||||||
|
|
||||||
describe ValidatesTimeliness::Formats do
|
describe ValidatesTimeliness::Formats do
|
||||||
attr_reader :formats
|
attr_reader :formats
|
||||||
|
|||||||
19
spec/resources/sqlite_patch.rb
Normal file
19
spec/resources/sqlite_patch.rb
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# patches adapter in rails 2.0 which mistakenly made time attributes map to datetime column type
|
||||||
|
ActiveRecord::ConnectionAdapters::SQLiteAdapter.class_eval do
|
||||||
|
def native_database_types #:nodoc:
|
||||||
|
{
|
||||||
|
:primary_key => default_primary_key_type,
|
||||||
|
:string => { :name => "varchar", :limit => 255 },
|
||||||
|
:text => { :name => "text" },
|
||||||
|
:integer => { :name => "integer" },
|
||||||
|
:float => { :name => "float" },
|
||||||
|
:decimal => { :name => "decimal" },
|
||||||
|
:datetime => { :name => "datetime" },
|
||||||
|
:timestamp => { :name => "datetime" },
|
||||||
|
:time => { :name => "time" },
|
||||||
|
:date => { :name => "date" },
|
||||||
|
:binary => { :name => "blob" },
|
||||||
|
:boolean => { :name => "boolean" }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,26 +1,33 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
|
||||||
|
|
||||||
|
class NoValidation < Person
|
||||||
|
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'
|
||||||
|
validates_time :birth_time,
|
||||||
|
:before => '23:00', :after => '09:00',
|
||||||
|
:on_or_before => '22:00', :on_or_after => '10: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'
|
||||||
|
|
||||||
|
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'
|
||||||
|
end
|
||||||
|
|
||||||
describe "ValidateTimeliness matcher" do
|
describe "ValidateTimeliness matcher" do
|
||||||
attr_accessor :no_validation, :with_validation
|
attr_accessor :no_validation, :with_validation
|
||||||
|
|
||||||
before do
|
before do
|
||||||
class NoValidation < Person
|
|
||||||
alias_attribute :birth_datetime, :birth_date_and_time
|
|
||||||
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'
|
|
||||||
validates_time :birth_time,
|
|
||||||
:before => '23:00', :after => '09:00',
|
|
||||||
:on_or_before => '22:00', :on_or_after => '10: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'
|
|
||||||
|
|
||||||
alias_attribute :birth_datetime, :birth_date_and_time
|
|
||||||
end
|
|
||||||
@no_validation = NoValidation.new
|
@no_validation = NoValidation.new
|
||||||
@with_validation = WithValidation.new
|
@with_validation = WithValidation.new
|
||||||
end
|
end
|
||||||
@@ -134,14 +141,8 @@ describe "ValidateTimeliness matcher" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "custom messages" do
|
describe "custom messages" do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
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'
|
|
||||||
end
|
|
||||||
@person = CustomMessages.new
|
@person = CustomMessages.new
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -16,7 +16,11 @@ else
|
|||||||
require 'ginger'
|
require 'ginger'
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
end
|
end
|
||||||
gem 'rails'
|
if ENV['VERSION']
|
||||||
|
gem 'rails', ENV['VERSION']
|
||||||
|
else
|
||||||
|
gem 'rails'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
RAILS_ROOT = File.dirname(__FILE__)
|
RAILS_ROOT = File.dirname(__FILE__)
|
||||||
@@ -27,17 +31,14 @@ require 'active_record/version'
|
|||||||
require 'action_controller'
|
require 'action_controller'
|
||||||
require 'action_view'
|
require 'action_view'
|
||||||
|
|
||||||
ActiveSupport::Deprecation.silenced = true
|
|
||||||
|
|
||||||
require 'spec/rails'
|
require 'spec/rails'
|
||||||
require 'time_travel/time_travel'
|
require 'time_travel/time_travel'
|
||||||
require 'validates_timeliness'
|
|
||||||
|
|
||||||
RAILS_VER = Rails::VERSION::STRING
|
|
||||||
|
|
||||||
puts "Using #{vendored ? 'vendored' : 'gem'} Rails version #{RAILS_VER} (ActiveRecord version #{ActiveRecord::VERSION::STRING})"
|
|
||||||
|
|
||||||
ActiveRecord::Base.default_timezone = :utc
|
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'
|
if RAILS_VER >= '2.1'
|
||||||
Time.zone_default = ActiveSupport::TimeZone['UTC']
|
Time.zone_default = ActiveSupport::TimeZone['UTC']
|
||||||
@@ -47,5 +48,7 @@ end
|
|||||||
ActiveRecord::Migration.verbose = false
|
ActiveRecord::Migration.verbose = false
|
||||||
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
|
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
|
||||||
|
|
||||||
|
require 'sqlite_patch' if RAILS_VER < '2.1'
|
||||||
|
|
||||||
require 'schema'
|
require 'schema'
|
||||||
require 'person'
|
require 'person'
|
||||||
|
|||||||
61
spec/validation_methods_spec.rb
Normal file
61
spec/validation_methods_spec.rb
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
||||||
|
|
||||||
|
describe ValidatesTimeliness::ValidationMethods do
|
||||||
|
attr_accessor :person
|
||||||
|
|
||||||
|
describe "parse_date_time" 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)
|
||||||
|
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
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return Time object when passed a Time object" do
|
||||||
|
parse_method(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.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
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_method(*args)
|
||||||
|
ActiveRecord::Base.parse_date_time(*args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "make_time" do
|
||||||
|
|
||||||
|
if RAILS_VER >= '2.1'
|
||||||
|
|
||||||
|
it "should create time using current timezone" do
|
||||||
|
Time.zone = 'Melbourne'
|
||||||
|
time = ActiveRecord::Base.send(: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.zone.should == "UTC"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,507 +0,0 @@
|
|||||||
require File.dirname(__FILE__) + '/spec_helper'
|
|
||||||
|
|
||||||
describe ValidatesTimeliness::Validations do
|
|
||||||
attr_accessor :person
|
|
||||||
|
|
||||||
before :all do
|
|
||||||
# freezes time using time_travel plugin
|
|
||||||
Time.now = Time.utc(2000, 1, 1, 0, 0, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
after :all do
|
|
||||||
Time.now = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "parse_date_time" 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)
|
|
||||||
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
|
|
||||||
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
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return Time object when passed a Time object" do
|
|
||||||
parse_method(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.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
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_method(*args)
|
|
||||||
ActiveRecord::Base.parse_date_time(*args)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "timeliness_restriction_value" do
|
|
||||||
it "should return Time object when restriction is Time object" do
|
|
||||||
restriction_value(Time.now, person, :datetime).should be_kind_of(Time)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return Time object when restriction is string" do
|
|
||||||
restriction_value("2007-01-01 12:00", person, :datetime).should be_kind_of(Time)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return Time object when restriction is method symbol which returns Time object" do
|
|
||||||
person.stub!(:datetime_attr).and_return(Time.now)
|
|
||||||
restriction_value(:datetime_attr, person, :datetime).should be_kind_of(Time)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return Time object when restriction is method symbol which returns string" do
|
|
||||||
person.stub!(:datetime_attr).and_return("2007-01-01 12:00")
|
|
||||||
restriction_value(:datetime_attr, person, :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 }, person, :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"}, person, :datetime).should be_kind_of(Time)
|
|
||||||
end
|
|
||||||
|
|
||||||
def restriction_value(*args)
|
|
||||||
ActiveRecord::Base.send(:timeliness_restriction_value, *args)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with no restrictions" do
|
|
||||||
before :all do
|
|
||||||
class BasicValidation < Person
|
|
||||||
validates_datetime :birth_date_and_time, :allow_blank => true
|
|
||||||
validates_date :birth_date, :allow_blank => true
|
|
||||||
validates_time :birth_time, :allow_blank => true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before :each do
|
|
||||||
@person = BasicValidation.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error for invalid date component for datetime column" do
|
|
||||||
person.birth_date_and_time = "2000-01-32 01:02:03"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date_and_time).should == "is not a valid datetime"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error for invalid time component for datetime column" do
|
|
||||||
person.birth_date_and_time = "2000-01-01 25:02:03"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date_and_time).should == "is not a valid datetime"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error for invalid date value for date column" do
|
|
||||||
person.birth_date = "2000-01-32"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date).should == "is not a valid date"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error for invalid time value for time column" do
|
|
||||||
person.birth_time = "25:00"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_time).should == "is not a valid time"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have same value for before_type_cast after failed validation" do
|
|
||||||
person.birth_date_and_time = "2000-01-01 25:02:03"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.birth_date_and_time_before_type_cast.should == "2000-01-01 25:02:03"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid with valid values" do
|
|
||||||
person.birth_date_and_time = "2000-01-01 12:12:12"
|
|
||||||
person.birth_date = "2000-01-31"
|
|
||||||
person.should be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid with value out of range for Time class" do
|
|
||||||
person.birth_date_and_time = "1890-01-01 12:12:12"
|
|
||||||
person.should be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid with nil values when allow_blank is true" do
|
|
||||||
person.birth_date_and_time = nil
|
|
||||||
person.birth_date = nil
|
|
||||||
person.birth_time = nil
|
|
||||||
person.should be_valid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "for datetime type" do
|
|
||||||
|
|
||||||
describe "with before and after restrictions" do
|
|
||||||
before :all do
|
|
||||||
class DateTimeBeforeAfter < Person
|
|
||||||
validates_datetime :birth_date_and_time,
|
|
||||||
:before => lambda { Time.now }, :after => lambda { 1.day.ago}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before :each do
|
|
||||||
@person = DateTimeBeforeAfter.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when past :before restriction" do
|
|
||||||
person.birth_date_and_time = 1.minute.from_now
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date_and_time).should match(/must be before/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when before :after restriction" do
|
|
||||||
person.birth_date_and_time = 2.days.ago
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date_and_time).should match(/must be after/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when on boundary of :before restriction" do
|
|
||||||
person.birth_date_and_time = Time.now
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date_and_time).should match(/must be before/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when on boundary of :after restriction" do
|
|
||||||
person.birth_date_and_time = 1.day.ago
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date_and_time).should match(/must be after/)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with on_or_before and on_or_after restrictions" do
|
|
||||||
before :all do
|
|
||||||
class DateTimeOnOrBeforeAndAfter < Person
|
|
||||||
validates_datetime :birth_date_and_time, :type => :datetime,
|
|
||||||
:on_or_before => lambda { Time.now.at_midnight },
|
|
||||||
:on_or_after => lambda { 1.day.ago }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
@person = DateTimeOnOrBeforeAndAfter.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when past :on_or_before restriction" do
|
|
||||||
person.birth_date_and_time = Time.now.at_midnight + 1
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date_and_time).should match(/must be on or before/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when before :on_or_after restriction" do
|
|
||||||
person.birth_date_and_time = 1.days.ago - 1
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date_and_time).should match(/must be on or after/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid when value equal to :on_or_before restriction" do
|
|
||||||
person.birth_date_and_time = Time.now.at_midnight
|
|
||||||
person.should be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid when value equal to :on_or_after restriction" do
|
|
||||||
person.birth_date_and_time = 1.day.ago
|
|
||||||
person.should be_valid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "for date type" do
|
|
||||||
|
|
||||||
describe "with before and after restrictions" do
|
|
||||||
before :all do
|
|
||||||
class DateBeforeAfter < Person
|
|
||||||
validates_date :birth_date,
|
|
||||||
:before => 1.day.from_now,
|
|
||||||
:after => 1.day.ago
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before :each do
|
|
||||||
@person = DateBeforeAfter.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when past :before restriction" do
|
|
||||||
person.birth_date = 2.days.from_now
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date).should match(/must be before/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when before :after restriction" do
|
|
||||||
person.birth_date = 2.days.ago
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date).should match(/must be after/)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with on_or_before and on_or_after restrictions" do
|
|
||||||
before :all do
|
|
||||||
class DateOnOrBeforeAndAfter < Person
|
|
||||||
validates_date :birth_date,
|
|
||||||
:on_or_before => 1.day.from_now,
|
|
||||||
:on_or_after => 1.day.ago
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before :each do
|
|
||||||
@person = DateOnOrBeforeAndAfter.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when past :on_or_before restriction" do
|
|
||||||
person.birth_date = 2.days.from_now
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date).should match(/must be on or before/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when before :on_or_after restriction" do
|
|
||||||
person.birth_date = 2.days.ago
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date).should match(/must be on or after/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid when value equal to :on_or_before restriction" do
|
|
||||||
person.birth_date = 1.day.from_now
|
|
||||||
person.should be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid when value equal to :on_or_after restriction" do
|
|
||||||
person.birth_date = 1.day.ago
|
|
||||||
person.should be_valid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "for time type" do
|
|
||||||
|
|
||||||
describe "with before and after restrictions" do
|
|
||||||
before :all do
|
|
||||||
class TimeBeforeAfter < Person
|
|
||||||
validates_time :birth_time,
|
|
||||||
:before => "23:00",
|
|
||||||
:after => "06:00"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before :each do
|
|
||||||
@person = TimeBeforeAfter.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when on boundary of :before restriction" do
|
|
||||||
person.birth_time = "23:00"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_time).should match(/must be before/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when on boundary of :after restriction" do
|
|
||||||
person.birth_time = "06:00am"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_time).should match(/must be after/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when past :before restriction" do
|
|
||||||
person.birth_time = "23:01"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_time).should match(/must be before/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when before :after restriction" do
|
|
||||||
person.birth_time = "05:59"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_time).should match(/must be after/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should not have error when before :before restriction" do
|
|
||||||
person.birth_time = "22:59"
|
|
||||||
person.should be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when before :after restriction" do
|
|
||||||
person.birth_time = "06:01"
|
|
||||||
person.should be_valid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with on_or_before and on_or_after restrictions" do
|
|
||||||
before :all do
|
|
||||||
class TimeOnOrBeforeAndAfter < Person
|
|
||||||
validates_time :birth_time,
|
|
||||||
:on_or_before => "23:00",
|
|
||||||
:on_or_after => "06:00"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before :each do
|
|
||||||
@person = TimeOnOrBeforeAndAfter.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when past :on_or_before restriction" do
|
|
||||||
person.birth_time = "23:01"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_time).should match(/must be on or before/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when before :on_or_after restriction" do
|
|
||||||
person.birth_time = "05:59"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_time).should match(/must be on or after/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid when on boundary of :on_or_before restriction" do
|
|
||||||
person.birth_time = "23:00"
|
|
||||||
person.should be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid when on boundary of :on_or_after restriction" do
|
|
||||||
person.birth_time = "06:00"
|
|
||||||
person.should be_valid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with mixed value and restriction types" do
|
|
||||||
before :all do
|
|
||||||
|
|
||||||
class MixedBeforeAndAfter < Person
|
|
||||||
validates_datetime :birth_date_and_time,
|
|
||||||
:before => Date.new(2000,1,2),
|
|
||||||
:after => lambda { "2000-01-01" }
|
|
||||||
validates_date :birth_date,
|
|
||||||
:on_or_before => lambda { "2000-01-01" },
|
|
||||||
:on_or_after => :birth_date_and_time
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before :each do
|
|
||||||
@person = MixedBeforeAndAfter.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should correctly validate time attribute with Date restriction" do
|
|
||||||
person.birth_date_and_time = "2000-01-03 00:00:00"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date_and_time).should match(/must be before/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should correctly validate with proc restriction" do
|
|
||||||
person.birth_date_and_time = "2000-01-01 00:00:00"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date_and_time).should match(/must be after/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should correctly validate date attribute with DateTime restriction" do
|
|
||||||
person.birth_date = "2000-01-03"
|
|
||||||
person.birth_date_and_time = "1890-01-01 00:00:00"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date).should match(/must be on or before/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should correctly validate date attribute with symbol restriction" do
|
|
||||||
person.birth_date = "2000-01-01"
|
|
||||||
person.birth_date_and_time = "2000-01-02 12:00:00"
|
|
||||||
person.should_not be_valid
|
|
||||||
person.errors.on(:birth_date).should match(/must be on or after/)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "ignoring restriction errors" do
|
|
||||||
before :all do
|
|
||||||
class BadRestriction < Person
|
|
||||||
validates_date :birth_date, :before => Proc.new { raise }
|
|
||||||
self.ignore_datetime_restriction_errors = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before :each do
|
|
||||||
@person = BadRestriction.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have no errors when restriction is invalid" do
|
|
||||||
person.birth_date = '2000-01-01'
|
|
||||||
person.should be_valid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "restriction value error message" do
|
|
||||||
describe "default formats" do
|
|
||||||
before :all do
|
|
||||||
class DefaultFormats < Person
|
|
||||||
validates_datetime :birth_date_and_time, :allow_blank => true, :after => 1.day.from_now
|
|
||||||
validates_date :birth_date, :allow_blank => true, :after => 1.day.from_now
|
|
||||||
validates_time :birth_time, :allow_blank => true, :after => '23:59:59'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before :each do
|
|
||||||
@person = DefaultFormats.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should format datetime value of restriction" do
|
|
||||||
person.birth_date_and_time = Time.now
|
|
||||||
person.save
|
|
||||||
person.errors.on(:birth_date_and_time).should match(/after \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\Z/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should format date value of restriction" do
|
|
||||||
person.birth_date = Time.now
|
|
||||||
person.save
|
|
||||||
person.errors.on(:birth_date).should match(/after \d{4}-\d{2}-\d{2}\Z/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should format time value of restriction" do
|
|
||||||
person.birth_time = '12:00:00'
|
|
||||||
person.save
|
|
||||||
person.errors.on(:birth_time).should match(/after \d{2}:\d{2}:\d{2}\Z/)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "custom formats" do
|
|
||||||
before :all do
|
|
||||||
class CustomFormats < Person
|
|
||||||
validates_datetime :birth_date_and_time, :allow_blank => true, :after => 1.day.from_now
|
|
||||||
validates_date :birth_date, :allow_blank => true, :after => 1.day.from_now
|
|
||||||
validates_time :birth_time, :allow_blank => true, :after => '23:59:59'
|
|
||||||
end
|
|
||||||
|
|
||||||
ActiveRecord::Errors.date_time_error_value_formats = {
|
|
||||||
:time => '%H:%M %p',
|
|
||||||
:date => '%d-%m-%Y',
|
|
||||||
:datetime => '%d-%m-%Y %H:%M %p'
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
before :each do
|
|
||||||
@person = CustomFormats.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should format datetime value of restriction" do
|
|
||||||
person.birth_date_and_time = Time.now
|
|
||||||
person.save
|
|
||||||
person.errors.on(:birth_date_and_time).should match(/after \d{2}-\d{2}-\d{4} \d{2}:\d{2} (AM|PM)\Z/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should format date value of restriction" do
|
|
||||||
person.birth_date = Time.now
|
|
||||||
person.save
|
|
||||||
person.errors.on(:birth_date).should match(/after \d{2}-\d{2}-\d{4}\Z/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should format time value of restriction" do
|
|
||||||
person.birth_time = '12:00:00'
|
|
||||||
person.save
|
|
||||||
person.errors.on(:birth_time).should match(/after \d{2}:\d{2} (AM|PM)\Z/)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
438
spec/validator_spec.rb
Normal file
438
spec/validator_spec.rb
Normal file
@@ -0,0 +1,438 @@
|
|||||||
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
||||||
|
|
||||||
|
describe ValidatesTimeliness::Validator do
|
||||||
|
attr_accessor :person, :validator
|
||||||
|
|
||||||
|
before :all do
|
||||||
|
# freezes time using time_travel plugin
|
||||||
|
Time.now = Time.utc(2000, 1, 1, 0, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
after :all do
|
||||||
|
Time.now = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
before :each do
|
||||||
|
@person = Person.new
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "restriction_value" do
|
||||||
|
it "should return Time object when restriction is Time object" do
|
||||||
|
restriction_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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
|
||||||
|
def restriction_value(restriction, type)
|
||||||
|
configure_validator(:type => type)
|
||||||
|
validator.send(:restriction_value, restriction, person)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "instance with defaults" do
|
||||||
|
|
||||||
|
describe "for datetime type" do
|
||||||
|
before do
|
||||||
|
configure_validator(:type => :datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have invalid error when date component is invalid" do
|
||||||
|
validate_with(:birth_date_and_time, "2000-01-32 01:02:03")
|
||||||
|
should_have_error(:birth_date_and_time, :invalid_datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have invalid error when time component is invalid" do
|
||||||
|
validate_with(:birth_date_and_time, "2000-01-01 25:02:03")
|
||||||
|
should_have_error(:birth_date_and_time, :invalid_datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have blank error when value is nil" do
|
||||||
|
validate_with(:birth_date_and_time, nil)
|
||||||
|
should_have_error(:birth_date_and_time, :blank)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have no errors when value is valid" do
|
||||||
|
validate_with(:birth_date_and_time, "2000-01-01 12:00:00")
|
||||||
|
should_have_no_error(:birth_date_and_time, :invalid_datetime)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for date type" do
|
||||||
|
before do
|
||||||
|
configure_validator(:type => :date)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have invalid error when value is invalid" do
|
||||||
|
validate_with(:birth_date, "2000-01-32")
|
||||||
|
should_have_error(:birth_date, :invalid_date)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have blank error when value is nil" do
|
||||||
|
validate_with(:birth_date, nil)
|
||||||
|
should_have_error(:birth_date, :blank)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have no error when value is valid" do
|
||||||
|
validate_with(:birth_date, "2000-01-31")
|
||||||
|
should_have_no_error(:birth_date, :invalid_date)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for time type" do
|
||||||
|
before do
|
||||||
|
configure_validator(:type => :time)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have invalid error when value is invalid" do
|
||||||
|
validate_with(:birth_time, "25:00")
|
||||||
|
should_have_error(:birth_time, :invalid_time)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have blank error when value is nil" do
|
||||||
|
validate_with(:birth_time, nil)
|
||||||
|
should_have_error(:birth_time, :blank)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have no errors when value is valid" do
|
||||||
|
validate_with(:birth_date_and_time, "12:00")
|
||||||
|
should_have_no_error(:birth_time, :invalid_time)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "instance with before and after restrictions" do
|
||||||
|
|
||||||
|
describe "for datetime type" do
|
||||||
|
before :each do
|
||||||
|
configure_validator(:before => lambda { Time.now }, :after => lambda { 1.day.ago})
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have before error when value is past :before restriction" do
|
||||||
|
validate_with(:birth_date_and_time, 1.minute.from_now)
|
||||||
|
should_have_error(:birth_date_and_time, :before)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have before error when value is on boundary of :before restriction" do
|
||||||
|
validate_with(:birth_date_and_time, Time.now)
|
||||||
|
should_have_error(:birth_date_and_time, :before)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have after error when value is before :after restriction" do
|
||||||
|
validate_with(:birth_date_and_time, 2.days.ago)
|
||||||
|
should_have_error(:birth_date_and_time, :after)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have after error when value is on boundary of :after restriction" do
|
||||||
|
validate_with(:birth_date_and_time, 1.day.ago)
|
||||||
|
should_have_error(:birth_date_and_time, :after)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for date type" do
|
||||||
|
before :each do
|
||||||
|
configure_validator(:before => 1.day.from_now, :after => 1.day.ago, :type => :date)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have error when value is past :before restriction" do
|
||||||
|
validate_with(:birth_date, 2.days.from_now)
|
||||||
|
should_have_error(:birth_date, :before)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have error when value is before :after restriction" do
|
||||||
|
validate_with(:birth_date, 2.days.ago)
|
||||||
|
should_have_error(:birth_date, :after)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have no error when value is before :before restriction" do
|
||||||
|
validate_with(:birth_date, Time.now)
|
||||||
|
should_have_no_error(:birth_date, :before)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have no error when value is after :after restriction" do
|
||||||
|
validate_with(:birth_date, Time.now)
|
||||||
|
should_have_no_error(:birth_date, :after)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for time type" do
|
||||||
|
before :each do
|
||||||
|
configure_validator(:before => "23:00", :after => "06:00", :type => :time)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have error when value is on boundary of :before restriction" do
|
||||||
|
validate_with(:birth_time, "23:00")
|
||||||
|
should_have_error(:birth_time, :before)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have error when value is on boundary of :after restriction" do
|
||||||
|
validate_with(:birth_time, "06:00")
|
||||||
|
should_have_error(:birth_time, :after)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have error when value is past :before restriction" do
|
||||||
|
validate_with(:birth_time, "23:01")
|
||||||
|
should_have_error(:birth_time, :before)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have error when value is before :after restriction" do
|
||||||
|
validate_with(:birth_time, "05:59")
|
||||||
|
should_have_error(:birth_time, :after)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not have error when value is before :before restriction" do
|
||||||
|
validate_with(:birth_time, "22:59")
|
||||||
|
should_have_no_error(:birth_time, :before)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have error when value is before :after restriction" do
|
||||||
|
validate_with(:birth_time, "06:01")
|
||||||
|
should_have_no_error(:birth_time, :before)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "instance with on_or_before and on_or_after restrictions" do
|
||||||
|
|
||||||
|
describe "for datetime type" do
|
||||||
|
before do
|
||||||
|
configure_validator(:on_or_before => Time.now.at_midnight, :on_or_after => 1.day.ago)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for time type" do
|
||||||
|
before :each do
|
||||||
|
configure_validator(:on_or_before => "23:00", :on_or_after => "06:00", :type => :time)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "instance with mixed value and restriction types" do
|
||||||
|
|
||||||
|
it "should validate datetime attribute with Date restriction" do
|
||||||
|
configure_validator(:type => :datetime, :on_or_before => Date.new(2000,1,1))
|
||||||
|
validate_with(:birth_date_and_time, "2000-01-01 00:00:00")
|
||||||
|
should_have_no_error(:birth_date_and_time, :on_or_before)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should validate date attribute with DateTime restriction value" do
|
||||||
|
configure_validator(:type => :date, :on_or_before => DateTime.new(2000, 1, 1, 0,0,0))
|
||||||
|
validate_with(:birth_date, "2000-01-01")
|
||||||
|
should_have_no_error(:birth_date, :on_or_before)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should validate date attribute with Time restriction value" do
|
||||||
|
configure_validator(:type => :date, :on_or_before => Time.utc(2000, 1, 1, 0,0,0))
|
||||||
|
validate_with(:birth_date, "2000-01-01")
|
||||||
|
should_have_no_error(:birth_date, :on_or_before)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should validate time attribute with DateTime restriction value" do
|
||||||
|
configure_validator(:type => :time, :on_or_before => DateTime.new(2000, 1, 1, 12,0,0))
|
||||||
|
validate_with(:birth_time, "12:00")
|
||||||
|
should_have_no_error(:birth_time, :on_or_before)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should validate time attribute with Time restriction value" do
|
||||||
|
configure_validator(:type => :time, :on_or_before => Time.utc(2000, 1, 1, 12,0,0))
|
||||||
|
validate_with(:birth_time, "12:00")
|
||||||
|
should_have_no_error(:birth_time, :on_or_before)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "restriction errors" do
|
||||||
|
before :each do
|
||||||
|
configure_validator(:type => :date, :before => lambda { raise })
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be added by default for invalid restriction" do
|
||||||
|
ValidatesTimeliness::Validator.ignore_restriction_errors = false
|
||||||
|
validate_with(:birth_date, Date.today)
|
||||||
|
person.errors.on(:birth_date).should match(/restriction 'before' value was invalid/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be added when ignore switch is true and restriction is invalid" do
|
||||||
|
ValidatesTimeliness::Validator.ignore_restriction_errors = true
|
||||||
|
person.should be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
after :all do
|
||||||
|
ValidatesTimeliness::Validator.ignore_restriction_errors = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "restriction value error message" do
|
||||||
|
|
||||||
|
describe "default formats" do
|
||||||
|
|
||||||
|
it "should format datetime value of restriction" do
|
||||||
|
configure_validator(:type => :datetime, :after => 1.day.from_now)
|
||||||
|
validate_with(:birth_date_and_time, Time.now)
|
||||||
|
person.errors.on(:birth_date_and_time).should match(/after \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\Z/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should format date value of restriction" do
|
||||||
|
configure_validator(:type => :date, :after => 1.day.from_now)
|
||||||
|
validate_with(:birth_date, Time.now)
|
||||||
|
person.errors.on(:birth_date).should match(/after \d{4}-\d{2}-\d{2}\Z/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should format time value of restriction" do
|
||||||
|
configure_validator(:type => :time, :after => '12:00')
|
||||||
|
validate_with(:birth_time, '11:59')
|
||||||
|
person.errors.on(:birth_time).should match(/after \d{2}:\d{2}:\d{2}\Z/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "custom formats" do
|
||||||
|
|
||||||
|
before :all do
|
||||||
|
@@formats = ValidatesTimeliness::Validator.error_value_formats
|
||||||
|
ValidatesTimeliness::Validator.error_value_formats = {
|
||||||
|
:time => '%H:%M %p',
|
||||||
|
:date => '%d-%m-%Y',
|
||||||
|
:datetime => '%d-%m-%Y %H:%M %p'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should format datetime value of restriction" do
|
||||||
|
configure_validator(:type => :datetime, :after => 1.day.from_now)
|
||||||
|
validate_with(:birth_date_and_time, Time.now)
|
||||||
|
person.errors.on(:birth_date_and_time).should match(/after \d{2}-\d{2}-\d{4} \d{2}:\d{2} (AM|PM)\Z/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should format date value of restriction" do
|
||||||
|
configure_validator(:type => :date, :after => 1.day.from_now)
|
||||||
|
validate_with(:birth_date, Time.now)
|
||||||
|
person.errors.on(:birth_date).should match(/after \d{2}-\d{2}-\d{4}\Z/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should format time value of restriction" do
|
||||||
|
configure_validator(:type => :time, :after => '12:00')
|
||||||
|
validate_with(:birth_time, '11:59')
|
||||||
|
person.errors.on(:birth_time).should match(/after \d{2}:\d{2} (AM|PM)\Z/)
|
||||||
|
end
|
||||||
|
|
||||||
|
after :all do
|
||||||
|
ValidatesTimeliness::Validator.error_value_formats = @@formats
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
|
||||||
|
def should_have_error(attr_name, error)
|
||||||
|
message = error_messages[error]
|
||||||
|
person.errors.on(attr_name).should match(/#{message}/)
|
||||||
|
end
|
||||||
|
|
||||||
|
def should_have_no_error(attr_name, error)
|
||||||
|
message = error_messages[error]
|
||||||
|
errors = person.errors.on(attr_name)
|
||||||
|
if errors
|
||||||
|
errors.should_not match(/#{message}/)
|
||||||
|
else
|
||||||
|
errors.should be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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 }
|
||||||
|
end
|
||||||
|
end
|
||||||
31
validates_timeliness.gemspec
Normal file
31
validates_timeliness.gemspec
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = %q{validates_timeliness}
|
||||||
|
s.version = "1.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.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.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.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
|
||||||
|
|
||||||
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
||||||
|
else
|
||||||
|
end
|
||||||
|
else
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user