mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-23 22:36:45 +00:00
Compare commits
No commits in common. "1.1.3" and "master" have entirely different histories.
5
.gitignore
vendored
5
.gitignore
vendored
@ -1 +1,6 @@
|
|||||||
pkg/
|
pkg/
|
||||||
|
.bundle/
|
||||||
|
.rvmrc
|
||||||
|
Gemfile.lock
|
||||||
|
gemfiles/*.lock
|
||||||
|
.byebug_history
|
||||||
|
|||||||
4
.rspec
Normal file
4
.rspec
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
--format documentation
|
||||||
|
--color
|
||||||
|
--require spec_helper
|
||||||
|
--require byebug
|
||||||
20
.travis.yml
Normal file
20
.travis.yml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
language: ruby
|
||||||
|
before_install: gem install bundler
|
||||||
|
cache: bundler
|
||||||
|
|
||||||
|
gemfile:
|
||||||
|
- gemfiles/rails_5_0.gemfile
|
||||||
|
- gemfiles/rails_5_1.gemfile
|
||||||
|
- gemfiles/rails_5_2.gemfile
|
||||||
|
|
||||||
|
rvm:
|
||||||
|
- "2.5.3"
|
||||||
|
|
||||||
|
script: 'bundle exec rspec'
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
recipients:
|
||||||
|
- adam.meehan@gmail.com
|
||||||
|
on_failure: change
|
||||||
|
on_success: never
|
||||||
11
Appraisals
Normal file
11
Appraisals
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
appraise "rails_5_0" do
|
||||||
|
gem "rails", "~> 5.0.0"
|
||||||
|
end
|
||||||
|
|
||||||
|
appraise "rails_5_1" do
|
||||||
|
gem "rails", "~> 5.1.0"
|
||||||
|
end
|
||||||
|
|
||||||
|
appraise "rails_5_2" do
|
||||||
|
gem "rails", "~> 5.2.0"
|
||||||
|
end
|
||||||
57
CHANGELOG
57
CHANGELOG
@ -1,57 +0,0 @@
|
|||||||
= 1.1.3 [2009-01-13]
|
|
||||||
- Fixed bug where time and date attributes still being parsed on read using Rails default parser [reported by Brad (pvjq)]
|
|
||||||
|
|
||||||
= 1.1.2 [2009-01-12]
|
|
||||||
- Fixed bugs
|
|
||||||
- matcher failing for custom error message without interpolation keys using I18n
|
|
||||||
- validator custom error messages not being extracted
|
|
||||||
|
|
||||||
= 1.1.1 [2009-01-03]
|
|
||||||
- Fixed bug in matcher for options local variable
|
|
||||||
|
|
||||||
= 1.1.0 [2009-01-01]
|
|
||||||
- Added between option
|
|
||||||
|
|
||||||
= 1.0.0 [2008-12-06]
|
|
||||||
- Gemified!
|
|
||||||
- Refactor of plugin into a Data Mapper style validator class which makes for a cleaner implementation and possible future Merb\Data Mapper support
|
|
||||||
- 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
|
|
||||||
|
|
||||||
= 0.1.0 [2008-12-06]
|
|
||||||
- Tagged plugin as version 0.1.0
|
|
||||||
|
|
||||||
= 2008-11-13
|
|
||||||
- allow uppercase meridian to be valid [reported by Alex (http://alex.digns.com/)]
|
|
||||||
|
|
||||||
= 2008-10-28
|
|
||||||
- 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-09-24
|
|
||||||
- refactored attribute write method definitions
|
|
||||||
|
|
||||||
= 2008-08-25
|
|
||||||
- fixed bug for non-timezone write method not updating changed attributes hash [reported by Sylvestre Mergulhão]
|
|
||||||
|
|
||||||
= 2008-08-22
|
|
||||||
- fixed bug with attribute cache not clearing on write for date and time columns [reported by Sylvestre Mergulhão]
|
|
||||||
- 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
|
|
||||||
- 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
|
|
||||||
220
CHANGELOG.rdoc
Normal file
220
CHANGELOG.rdoc
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
= [UNRELEASED]
|
||||||
|
* Fix DateTimeSelect extension support (AquisTech)
|
||||||
|
* Relaxed Timeliness dependency version which allows for >= 0.4.0 with
|
||||||
|
threadsafety fix for use_us_formats and use_euro_formats for hot switching
|
||||||
|
in a request.
|
||||||
|
* Add initializer to ensure Timeliness v0.4+ ambiguous date config is set
|
||||||
|
correctly when using `use_euro_formats` or `remove_use_formats'.
|
||||||
|
|
||||||
|
Breaking Changes
|
||||||
|
* Update Multiparameter extension to use ActiveRecord type classes with multiparameter handling
|
||||||
|
which stores a hash of multiparamter values as the value before type cast, no longer a mushed datetime string
|
||||||
|
* Removed all custom plugin attribute methods and method overrides in favour using ActiveModel type system
|
||||||
|
|
||||||
|
= 4.1.0 [2019-06-11]
|
||||||
|
* Relaxed Timeliness dependency version to >= 0.3.10 and < 1, which allows
|
||||||
|
version 0.4 with threadsafety fix for use_us_formats and use_euro_formats
|
||||||
|
hot switching in a request.
|
||||||
|
|
||||||
|
= 4.0.2 [2016-01-07]
|
||||||
|
* Fix undefine_generated_methods ivar guard setting to false
|
||||||
|
|
||||||
|
= 4.0.1 [2016-01-06]
|
||||||
|
* Fix undefine_generated_methods thread locking bug
|
||||||
|
* Created an ActiveModel ORM, for manual require if using without any full blown ORM
|
||||||
|
|
||||||
|
= 4.0.0 [2015-12-29]
|
||||||
|
* Extracted mongoid support into https://github.com/adzap/validates_timeliness-mongoid which is broken (not supported anymore).
|
||||||
|
* Fixed Rails 4.0, 4.1 and 4.2 compatability issues
|
||||||
|
* Upgrade specs to RSpec 3
|
||||||
|
* Added travis config
|
||||||
|
* Huge thanks to @johncarney for keeping it alive with his fork (https://github.com/johncarney/validates_timeliness)
|
||||||
|
|
||||||
|
= 3.0.15 [2015-12-29]
|
||||||
|
* Fixes mongoid 3 support and removes mongoid 2 support(johnnyshields)
|
||||||
|
* Some documentation/comments tidying
|
||||||
|
* Some general tidying up
|
||||||
|
|
||||||
|
= 3.0.14 [2012-08-23]
|
||||||
|
* Fix for using validates :timeliness => {} form to correctly add attributes to timeliness validated attributes.
|
||||||
|
|
||||||
|
= 3.0.13 [2012-08-21]
|
||||||
|
* Fix ActiveRecord issues with using plugin parser by using old way of caching values.
|
||||||
|
* Allow any ActiveRecord non-column attribute to be validated
|
||||||
|
|
||||||
|
= 3.0.12 [2012-06-23]
|
||||||
|
* Fix load order issue when relying on Railtie to load ActiveRecord extension
|
||||||
|
|
||||||
|
= 3.0.11 [2012-04-01]
|
||||||
|
* Change dependency on Timeliness version due to a broken release
|
||||||
|
|
||||||
|
= 3.0.10 [2012-03-26]
|
||||||
|
* Fix for ActiveRecord shim and validation with :allow_blank => true in AR 3.1+. Fixes issue#52.
|
||||||
|
|
||||||
|
= 3.0.9 [2012-03-26]
|
||||||
|
* ActiveRecord 3.1+ suport
|
||||||
|
* Fixes for multiparameter extension with empty date values (thanks @mogox, @Sharagoz)
|
||||||
|
|
||||||
|
= 3.0.8 [2011-12-24]
|
||||||
|
* Remove deprecated InstanceMethods module when using AS::Concern (carlosantoniodasilva)
|
||||||
|
* Update Mongoid shim for v2.3 compatability.
|
||||||
|
|
||||||
|
= 3.0.7 [2011-09-21]
|
||||||
|
* Fix ActiveRecord before_type_cast extension for non-dirty attributes.
|
||||||
|
* Don't override AR before_type_cast for >= 3.1.0 which now has it's own implementation for date/time attributes.
|
||||||
|
* Fix DateTimeSelect extension to convert params to integers (#45)
|
||||||
|
* Add #change method to DateTimeSelect extension (@trusche, #45)
|
||||||
|
* Cleanup Mongoid shim.
|
||||||
|
|
||||||
|
= 3.0.6 [2011-05-09]
|
||||||
|
* Fix for AR type conversion for date columns when using plugin parser.
|
||||||
|
* Add timeliness_type_cast_code for ORM specific type casting after parsing.
|
||||||
|
|
||||||
|
= 3.0.5 [2011-01-29]
|
||||||
|
* Fix for Conversion#parse when given nil value (closes issue #34)
|
||||||
|
|
||||||
|
= 3.0.4 [2011-01-22]
|
||||||
|
* Fix :between option which was being ignored (ebeigarts)
|
||||||
|
* Use class_attribute to remove deprecated class_inheritable_accessor
|
||||||
|
* Namespace copied validator class to ActiveModel::Validations::Timeliness for :timeliness option
|
||||||
|
|
||||||
|
= 3.0.3 [2010-12-11]
|
||||||
|
* Fix validation of values which don't respond to to_date or to_time (renatoelias)
|
||||||
|
|
||||||
|
= 3.0.2 [2010-12-04]
|
||||||
|
* Fix AR multiparameter extension for Date columns
|
||||||
|
* Update to Timeliness 0.3.2 for zone abbreviation and offset support
|
||||||
|
|
||||||
|
= 3.0.1 [2010-11-02]
|
||||||
|
* Generate timeliness write methods in an included module to allow overriding in model class (josevalim)
|
||||||
|
|
||||||
|
= 3.0.0 [2010-10-18]
|
||||||
|
* Rails 3 and ActiveModel compatibility
|
||||||
|
* Uses ActiveModel::EachValidator as validator base class.
|
||||||
|
* Configuration settings stored in ValidatesTimeliness module only. ValidatesTimeliness.setup block to configure.
|
||||||
|
* Parser extracted to the Timeliness gem http://github.com/adzap/timeliness
|
||||||
|
* Parser is disabled by default. See initializer for enabling it.
|
||||||
|
* Removed RSpec matcher. Encouraged poor specs by copy-pasting from spec to model, or worse, the other way round.
|
||||||
|
* Method override for parsing and before type cast values is on validated attributes only. Old version handled all date/datetime columns, validates or not. Too intrusive.
|
||||||
|
* Add validation helpers to classes using extend_orms config setting. e.g. conf.extend_orms = [ :active_record ]
|
||||||
|
* Changed :between option so it is split into :on_or_after and :on_or_before option values. The error message for either failing check will be used instead of a between error message.
|
||||||
|
* Provides :timeliness option key for validates class method. Be sure to pass :type option as well e.g. :type => :date.
|
||||||
|
* Allows validation methods to be called on record instances as per ActiveModel API.
|
||||||
|
* Performs parsing (optional) and raw value caching (before_type_cast) on validated attributes only. It used to be all date, time and datetime attributes.
|
||||||
|
|
||||||
|
= 2.3.1 [2010-03-19]
|
||||||
|
* Fixed bug where custom attribute writer method for date/times were being overriden
|
||||||
|
|
||||||
|
= 2.3.0 [2010-02-04]
|
||||||
|
* Backwards incompatible change to :equal_to option. Fixed error message clash with :equal_to option which exists in Rails already. Option is now :is_at.
|
||||||
|
* Fixed I18n support so it returns missing translation message instead of error
|
||||||
|
* Fixed attribute method bug. Write method was bypassed when method was first generated and used Rails default parser.
|
||||||
|
* Fixed date/time selects when using enable_datetime_select_extension! when some values empty
|
||||||
|
* Fixed ISO8601 datetime format which is now split into two formats
|
||||||
|
* Changed I18n error value format to fallback to global default if missing in locale
|
||||||
|
* Refactored date/time select invalid value extension to use param values. Functionality will be extracted from plugin for v3.
|
||||||
|
|
||||||
|
= 2.2.2 [2009-09-19]
|
||||||
|
* Fixed dummy_time using make_time to respect timezone. Fixes 1.9.1 bug.
|
||||||
|
|
||||||
|
= 2.2.1 [2009-09-12]
|
||||||
|
* Fixed dummy date part for time types in Validator.type_cast_value
|
||||||
|
* No more core extensions! Removed dummy_time methods.
|
||||||
|
|
||||||
|
= 2.2.0 [2009-09-12]
|
||||||
|
* Ruby 1.9 support!
|
||||||
|
* Customise dummy date values for time types. See DUMMY DATE FOR TIME TYPES.
|
||||||
|
* Fixed matcher conflict with Shoulda. Load plugin matcher manually now see matcher section in README
|
||||||
|
* Fixed :ignore_usec when used with :with_time or :with_date
|
||||||
|
* Some clean up and refactoring
|
||||||
|
|
||||||
|
= 2.1.0 [2009-06-20]
|
||||||
|
* Added ambiguous year threshold setting in Formats class to customize the threshold for 2 digit years (See README)
|
||||||
|
* Fixed interpolation values in custom error message for Rails 2.2+
|
||||||
|
* Fixed custom I18n local override of en locale
|
||||||
|
* Dramatically simplified ActiveRecord monkey patching and hackery
|
||||||
|
|
||||||
|
= 2.0.0 [2009-04-12]
|
||||||
|
* Error value formats are now specified in the i18n locale file instead of updating plugin hash. See OTHER CUSTOMISATION section in README.
|
||||||
|
* Date/time select helper extension is disabled by default. To enable see DISPLAY INVALID VALUES IN DATE HELPERS section in README to enable.
|
||||||
|
* Added :format option to limit validation to a single format if desired
|
||||||
|
* Matcher now supports :equal_to option
|
||||||
|
* Formats.parse can take :include_offset option to include offset value from string in seconds, if string contains an offset. Offset not used in rest of plugin yet.
|
||||||
|
* Refactored to remove as much plugin code from ActiveRecord as possible.
|
||||||
|
|
||||||
|
= 1.1.7 [2009-03-26]
|
||||||
|
* Minor change to multiparameter attributes which I had not properly implemented for chaining
|
||||||
|
|
||||||
|
= 1.1.6 [2009-03-19]
|
||||||
|
* Rail 2.3 support
|
||||||
|
* Added :with_date and :with_time options. They allow an attribute to be combined with another attribute or value to make a datetime value for validation against the temporal restrictions
|
||||||
|
* Added :equal_to option
|
||||||
|
* Option key validation
|
||||||
|
* Better behaviour with other plugins using alias_method_chain on read_attribute and define_attribute_methods
|
||||||
|
* Added option to enable datetime_select extension for future use to optionally enable. Enabled by default until version 2.
|
||||||
|
* Added :ignore_usec option for datetime restrictions to be compared without microsecond
|
||||||
|
* some refactoring
|
||||||
|
|
||||||
|
= 1.1.5 [2009-01-21]
|
||||||
|
* Fixed regex for 'yy' format token which wasn't greedy enough for date formats ending with year when a datetime string parsed as date with a 4 digit year
|
||||||
|
|
||||||
|
= 1.1.4 [2009-01-13]
|
||||||
|
* Make months names respect i18n in Formats
|
||||||
|
|
||||||
|
= 1.1.3 [2009-01-13]
|
||||||
|
* Fixed bug where time and date attributes still being parsed on read using Rails default parser [reported by Brad (pvjq)]
|
||||||
|
|
||||||
|
= 1.1.2 [2009-01-12]
|
||||||
|
* Fixed bugs
|
||||||
|
* matcher failing for custom error message without interpolation keys using I18n
|
||||||
|
* validator custom error messages not being extracted
|
||||||
|
|
||||||
|
= 1.1.1 [2009-01-03]
|
||||||
|
* Fixed bug in matcher for options local variable
|
||||||
|
|
||||||
|
= 1.1.0 [2009-01-01]
|
||||||
|
* Added between option
|
||||||
|
|
||||||
|
= 1.0.0 [2008-12-06]
|
||||||
|
* Gemified!
|
||||||
|
* Refactor of plugin into a Data Mapper style validator class which makes for a cleaner implementation and possible future Merb\Data Mapper support
|
||||||
|
* 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
|
||||||
|
|
||||||
|
= 0.1.0 [2008-12-06]
|
||||||
|
* Tagged plugin as version 0.1.0
|
||||||
|
|
||||||
|
= 2008-11-13
|
||||||
|
* allow uppercase meridian to be valid [reported by Alex (http://alex.digns.com/)]
|
||||||
|
|
||||||
|
= 2008-10-28
|
||||||
|
* 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-09-24
|
||||||
|
* refactored attribute write method definitions
|
||||||
|
|
||||||
|
= 2008-08-25
|
||||||
|
* fixed bug for non-timezone write method not updating changed attributes hash [reported by Sylvestre Mergulhão]
|
||||||
|
|
||||||
|
= 2008-08-22
|
||||||
|
* fixed bug with attribute cache not clearing on write for date and time columns [reported by Sylvestre Mergulhão]
|
||||||
|
* 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
|
||||||
|
* 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
|
||||||
12
Gemfile
Normal file
12
Gemfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
source 'http://rubygems.org'
|
||||||
|
|
||||||
|
gemspec
|
||||||
|
|
||||||
|
gem 'rails', '~> 5.2.4'
|
||||||
|
gem 'rspec'
|
||||||
|
gem 'rspec-rails', '~> 3.7'
|
||||||
|
gem 'timecop'
|
||||||
|
gem 'byebug'
|
||||||
|
gem 'appraisal'
|
||||||
|
gem 'sqlite3', '~> 1.3.6'
|
||||||
|
gem 'nokogiri', '~> 1.8'
|
||||||
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2008 Adam Meehan
|
Copyright (c) 2008-2010 Adam Meehan
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
a copy of this software and associated documentation files (the
|
a copy of this software and associated documentation files (the
|
||||||
|
|||||||
508
README.rdoc
508
README.rdoc
@ -1,329 +1,299 @@
|
|||||||
= validates_timeliness
|
= ValidatesTimeliness {<img src="https://travis-ci.org/adzap/validates_timeliness.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/adzap/validates_timeliness]
|
||||||
|
|
||||||
* Source: http://github.com/adzap/validates_timeliness
|
* Source: http://github.com/adzap/validates_timeliness
|
||||||
* Bugs: http://adzap.lighthouseapp.com/projects/14111-validates_timeliness
|
* Issues: http://github.com/adzap/validates_timeliness/issues
|
||||||
|
|
||||||
== DESCRIPTION:
|
== Description
|
||||||
|
|
||||||
Validate dates, times and datetimes for Rails 2.x. Plays nicely with new Rails 2.1
|
Complete validation of dates, times and datetimes for Rails 5.x and ActiveModel.
|
||||||
features such as automatic timezone handling and dirty attributes. Allows you to
|
|
||||||
add custom formats or remove defaults easily. This allows you to control what you
|
If you a looking for the old version for Rails 4.x go here [https://github.com/adzap/validates_timeliness/tree/4-0-stable].
|
||||||
think should be a valid date or time string.
|
|
||||||
|
|
||||||
|
|
||||||
== FEATURES:
|
== Features
|
||||||
|
|
||||||
* Adds ActiveRecord validation for dates, times and datetimes
|
* Adds validation for dates, times and datetimes to ActiveModel
|
||||||
|
|
||||||
* Add or remove date/time formats to customize validation
|
* Handles timezones and type casting of values for you
|
||||||
|
|
||||||
* Create new formats using very simple date/time format tokens
|
* Only Rails date/time validation plugin offering complete validation (See ORM/ODM support)
|
||||||
|
|
||||||
* Restores ability to see raw value entered for date/time attributes with
|
* Uses extensible date/time parser (Using {timeliness gem}[http://github.com/adzap/timeliness]. See Plugin Parser)
|
||||||
_before_type_cast modifier, which was lost in Rails 2.1.
|
|
||||||
|
|
||||||
* Respects new timezone features of Rails 2.1.
|
* Adds extensions to fix Rails date/time select issues (See Extensions)
|
||||||
|
|
||||||
* Supports Rails 2.2 I18n for the error messages
|
* Supports I18n for the error messages. For multi-language support try {timeliness-i18n gem}[https://github.com/pedrofurtado/timeliness-i18n].
|
||||||
|
|
||||||
* Rspec matcher for testing model validation of dates and times
|
* Supports all the Rubies (that any sane person would be using in production).
|
||||||
|
|
||||||
|
|
||||||
== INSTALLATION:
|
== Installation
|
||||||
|
|
||||||
As plugin (from master)
|
# in Gemfile
|
||||||
|
gem 'validates_timeliness', '~> 5.0.0.beta1'
|
||||||
|
|
||||||
./script/plugin git://github.com/adzap/validates_timeliness.git
|
# Run bundler
|
||||||
|
$ bundle install
|
||||||
|
|
||||||
As gem
|
Then run
|
||||||
|
|
||||||
|
$ rails generate validates_timeliness:install
|
||||||
|
|
||||||
sudo gem install validates_timeliness
|
This creates configuration initializer and locale files. In the initializer, there are a number of config
|
||||||
|
options to customize the plugin.
|
||||||
|
|
||||||
|
NOTE: You may wish to enable the plugin parser and the extensions to start. Please read those sections first.
|
||||||
|
|
||||||
|
|
||||||
== USAGE:
|
== Examples
|
||||||
|
|
||||||
To validate a model with a date, time or datetime attribute you just use the
|
validates_datetime :occurred_at
|
||||||
|
|
||||||
|
validates_date :date_of_birth, before: lambda { 18.years.ago },
|
||||||
|
before_message: "must be at least 18 years old"
|
||||||
|
|
||||||
|
validates_datetime :finish_time, after: :start_time # Method symbol
|
||||||
|
|
||||||
|
validates_date :booked_at, on: :create, on_or_after: :today # See Restriction Shorthand.
|
||||||
|
|
||||||
|
validates_time :booked_at, between: ['9:00am', '5:00pm'] # On or after 9:00AM and on or before 5:00PM
|
||||||
|
validates_time :booked_at, between: '9:00am'..'5:00pm' # The same as previous example
|
||||||
|
validates_time :booked_at, between: '9:00am'...'5:00pm' # On or after 9:00AM and strictly before 5:00PM
|
||||||
|
|
||||||
|
validates_time :breakfast_time, on_or_after: '6:00am',
|
||||||
|
on_or_after_message: 'must be after opening time',
|
||||||
|
before: :lunchtime,
|
||||||
|
before_message: 'must be before lunch time'
|
||||||
|
|
||||||
|
|
||||||
|
== Usage
|
||||||
|
|
||||||
|
To validate a model with a date, time or datetime attribute you just use the
|
||||||
validation method
|
validation method
|
||||||
|
|
||||||
class Person < ActiveRecord::Base
|
class Person < ActiveRecord::Base
|
||||||
validates_date :date_of_birth
|
validates_date :date_of_birth, on_or_before: lambda { Date.current }
|
||||||
|
# or
|
||||||
|
validates :date_of_birth, timeliness: {on_or_before: lambda { Date.current }, type: :date}
|
||||||
end
|
end
|
||||||
|
|
||||||
The list of validation methods available are as follows:
|
|
||||||
|
|
||||||
* validates_date - validate value as date
|
or even on a specific record, per ActiveModel API.
|
||||||
|
|
||||||
* validates_time - validate value as time only i.e. '12:20pm'
|
@person.validates_date :date_of_birth, on_or_before: lambda { Date.current }
|
||||||
|
|
||||||
* validates_datetime - validate value as a full date and time
|
|
||||||
|
The list of validation methods available are as follows:
|
||||||
|
validates_date - validate value as date
|
||||||
|
validates_time - validate value as time only i.e. '12:20pm'
|
||||||
|
validates_datetime - validate value as a full date and time
|
||||||
|
validates - use the :timeliness key and set the type in the hash.
|
||||||
|
|
||||||
The validation methods take the usual options plus some specific ones to restrict
|
The validation methods take the usual options plus some specific ones to restrict
|
||||||
the valid range of dates or times allowed
|
the valid range of dates or times allowed
|
||||||
|
|
||||||
Temporal options (or restrictions):
|
Temporal options (or restrictions):
|
||||||
:before - Attribute must be before this value to be valid
|
:is_at - Attribute must be equal to value to be valid
|
||||||
:on_or_before - Attribute must be equal to or before this value to be valid
|
:before - Attribute must be before this value to be valid
|
||||||
:after - Attribute must be after this value to be valid
|
:on_or_before - Attribute must be equal to or before this value to be valid
|
||||||
:on_or_after - Attribute must be equal to or after this value to be valid
|
:after - Attribute must be after this value to be valid
|
||||||
:between - Attribute must be between the values to be valid
|
:on_or_after - Attribute must be equal to or after this value to be valid
|
||||||
|
:between - Attribute must be between the values to be valid. Range or Array of 2 values.
|
||||||
|
|
||||||
Regular validation options:
|
Regular validation options:
|
||||||
:allow_nil - Allow a nil value to be valid
|
:allow_nil - Allow a nil value to be valid
|
||||||
:allow_blank - Allows a nil or empty string value to be valid
|
:allow_blank - Allows a nil or empty string value to be valid
|
||||||
:if - Execute validation when :if evaluates true
|
:if - Execute validation when :if evaluates true
|
||||||
:unless - Execute validation when :unless evaluates false
|
:unless - Execute validation when :unless evaluates false
|
||||||
|
:on - Specify validation context e.g :save, :create or :update. Default is :save.
|
||||||
|
|
||||||
|
Special options:
|
||||||
|
:ignore_usec - Ignores microsecond value on datetime restrictions
|
||||||
|
:format - Limit validation to a single format for special cases. Requires plugin parser.
|
||||||
|
|
||||||
|
The temporal restrictions can take 4 different value types:
|
||||||
|
|
||||||
|
* Date, Time, or DateTime object value
|
||||||
|
* Proc or lambda object which may take an optional parameter, being the record object
|
||||||
|
* A symbol matching a method name in the model
|
||||||
|
* String value
|
||||||
|
|
||||||
|
When an attribute value is compared to temporal restrictions, they are compared as
|
||||||
|
the same type as the validation method type. So using validates_date means all
|
||||||
|
values are compared as dates.
|
||||||
|
|
||||||
|
|
||||||
|
== Configuration
|
||||||
|
|
||||||
|
=== ORM/ODM Support
|
||||||
|
|
||||||
|
The plugin adds date/time validation to ActiveModel for any ORM/ODM that supports the ActiveModel validations component.
|
||||||
|
However, there is an issue with most ORM/ODMs which does not allow 100% date/time validation by default. Specifically, when you
|
||||||
|
assign an invalid date/time value to an attribute, most ORM/ODMs will only store a nil value for the attribute. This causes an
|
||||||
|
issue for date/time validation, since we need to know that a value was assigned but was invalid. To fix this, we need to cache
|
||||||
|
the original invalid value to know that the attribute is not just nil.
|
||||||
|
|
||||||
|
Each ORM/ODM requires a specific shim to fix it. The plugin includes a shim for ActiveRecord and Mongoid. You can activate them
|
||||||
|
like so
|
||||||
|
|
||||||
|
ValidatesTimeliness.setup do |config|
|
||||||
|
|
||||||
|
# Extend ORM/ODMs for full support (:active_record).
|
||||||
|
config.extend_orms = [ :active_record ]
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
By default the plugin extends ActiveRecord if loaded. If you wish to extend another ORM then look at the {wiki page}[http://github.com/adzap/validates_timeliness/wiki/ORM-Support] for more information.
|
||||||
|
|
||||||
|
It is not required that you use a shim, but you will not catch errors when the attribute value is invalid and evaluated to nil.
|
||||||
|
|
||||||
|
|
||||||
|
=== Error Messages
|
||||||
|
|
||||||
|
Using the I18n system to define new defaults:
|
||||||
|
|
||||||
|
en:
|
||||||
|
errors:
|
||||||
|
messages:
|
||||||
|
invalid_date: "is not a valid date"
|
||||||
|
invalid_time: "is not a valid time"
|
||||||
|
invalid_datetime: "is not a valid datetime"
|
||||||
|
is_at: "must be at %{restriction}"
|
||||||
|
before: "must be before %{restriction}"
|
||||||
|
on_or_before: "must be on or before %{restriction}"
|
||||||
|
after: "must be after %{restriction}"
|
||||||
|
on_or_after: "must be on or after %{restriction}"
|
||||||
|
|
||||||
|
The %{restriction} signifies where the interpolation value for the restriction will be inserted.
|
||||||
|
|
||||||
|
You can also use validation options for custom error messages. The following option keys are available:
|
||||||
|
|
||||||
Message options: - Use these to override the default error messages
|
|
||||||
:invalid_date_message
|
:invalid_date_message
|
||||||
:invalid_time_message
|
:invalid_time_message
|
||||||
:invalid_datetime_message
|
:invalid_datetime_message
|
||||||
|
:is_at_message
|
||||||
:before_message
|
:before_message
|
||||||
:on_or_before_message
|
:on_or_before_message
|
||||||
:after_message
|
:after_message
|
||||||
:on_or_after_message
|
:on_or_after_message
|
||||||
:between_message
|
|
||||||
|
|
||||||
The temporal restrictions can take 4 different value types:
|
Note: There is no :between_message option. The between error message should be defined using the :on_or_after and :on_or_before
|
||||||
|
(:before in case when :between argument is a Range with excluded high value, see Examples) messages.
|
||||||
|
|
||||||
* String value
|
It is highly recommended you use the I18n system for error messages.
|
||||||
* Date, Time, or DateTime object value
|
|
||||||
* Proc or lambda object
|
|
||||||
* A symbol matching the method name in the model
|
|
||||||
* Between option takes an array of two values or a range
|
|
||||||
|
|
||||||
When an attribute value is compared to temporal restrictions, they are compared as
|
|
||||||
the same type as the validation method type. So using validates_date means all
|
|
||||||
values are compared as dates.
|
|
||||||
|
|
||||||
== EXAMPLES:
|
|
||||||
|
|
||||||
validates_date :date_of_birth :before => Proc.new { 18.years.ago },
|
|
||||||
:before_message => "must be at least 18 years old"
|
|
||||||
|
|
||||||
validates_time :breakfast_time, :on_or_after => '6:00am',
|
|
||||||
:on_or_after_message => 'must be after opening time',
|
|
||||||
:before => :second_breakfast_time,
|
|
||||||
:allow_nil => true
|
|
||||||
|
|
||||||
validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now }
|
|
||||||
|
|
||||||
|
|
||||||
=== DATE/TIME FORMATS:
|
|
||||||
|
|
||||||
So what formats does the plugin allow. Well there are default formats which can
|
|
||||||
be added to easily using the plugins format rules. Also formats can be easily
|
|
||||||
removed without hacking the plugin at all.
|
|
||||||
|
|
||||||
Below are the default formats. If you think they are easy to read then you will
|
|
||||||
be happy to know that is exactly the format you can use to define your own if
|
|
||||||
you want. No complex regular expressions or duck punching (monkey patching) the
|
|
||||||
plugin is needed.
|
|
||||||
|
|
||||||
Time formats:
|
|
||||||
hh:nn:ss
|
|
||||||
hh-nn-ss
|
|
||||||
h:nn
|
|
||||||
h.nn
|
|
||||||
h nn
|
|
||||||
h-nn
|
|
||||||
h:nn_ampm
|
|
||||||
h.nn_ampm
|
|
||||||
h nn_ampm
|
|
||||||
h-nn_ampm
|
|
||||||
h_ampm
|
|
||||||
|
|
||||||
NOTE: Any time format without a meridian token (the 'ampm' token) is considered
|
|
||||||
in 24 hour time.
|
|
||||||
|
|
||||||
Date formats:
|
|
||||||
yyyy/mm/dd
|
|
||||||
yyyy-mm-dd
|
|
||||||
yyyy.mm.dd
|
|
||||||
m/d/yy OR d/m/yy
|
|
||||||
m\d\yy OR d\m\yy
|
|
||||||
d-m-yy
|
|
||||||
d.m.yy
|
|
||||||
d mmm yy
|
|
||||||
|
|
||||||
NOTE: To use non-US date formats see US/EURO FORMATS section
|
|
||||||
|
|
||||||
Datetime formats:
|
|
||||||
m/d/yy h:nn:ss OR d/m/yy hh:nn:ss
|
|
||||||
m/d/yy h:nn OR d/m/yy h:nn
|
|
||||||
m/d/yy h:nn_ampm OR d/m/yy h:nn_ampm
|
|
||||||
yyyy-mm-dd hh:nn:ss
|
|
||||||
yyyy-mm-dd h:nn
|
|
||||||
ddd mmm d hh:nn:ss zo yyyy # Ruby time string
|
|
||||||
yyyy-mm-ddThh:nn:ss(?:Z|zo) # ISO 8601
|
|
||||||
|
|
||||||
NOTE: To use non-US date formats see US/EURO FORMATS section
|
|
||||||
|
|
||||||
Here is what each format token means:
|
|
||||||
|
|
||||||
Format tokens:
|
|
||||||
y = year
|
|
||||||
m = month
|
|
||||||
d = day
|
|
||||||
h = hour
|
|
||||||
n = minute
|
|
||||||
s = second
|
|
||||||
u = micro-seconds
|
|
||||||
ampm = meridian (am or pm) with or without dots (e.g. am, a.m, or a.m.)
|
|
||||||
_ = optional space
|
|
||||||
tz = Timezone abbreviation (e.g. UTC, GMT, PST, EST)
|
|
||||||
zo = Timezone offset (e.g. +10:00, -08:00, +1000)
|
|
||||||
|
|
||||||
Repeating tokens:
|
|
||||||
x = 1 or 2 digits for unit (e.g. 'h' means an hour can be '9' or '09')
|
|
||||||
xx = 2 digits exactly for unit (e.g. 'hh' means an hour can only be '09')
|
|
||||||
|
|
||||||
Special Cases:
|
|
||||||
yy = 2 or 4 digit year
|
|
||||||
yyyyy = exactly 4 digit year
|
|
||||||
mmm = month long name (e.g. 'Jul' or 'July')
|
|
||||||
ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
|
|
||||||
u = microseconds matches 1 to 3 digits
|
|
||||||
|
|
||||||
All other characters are considered literal. For the technically minded
|
|
||||||
(well you are developers), these formats are compiled into regular expressions
|
|
||||||
at runtime so don't add any extra overhead than using regular expressions
|
|
||||||
directly. So, no, it won't make your app slow!
|
|
||||||
|
|
||||||
To see all defined formats look in lib/validates_timeliness/formats.rb.
|
|
||||||
|
|
||||||
=== US/EURO FORMATS
|
|
||||||
|
|
||||||
The perenial problem for non-US developers or applications not primarily for the
|
|
||||||
US, is the US date format of m/d/yy. This is ambiguous with the European format
|
|
||||||
of d/my/yy. By default the plugin uses the US formats as this is the Ruby default
|
|
||||||
when it does date interpretation, and is in keeping PoLS (principle of least
|
|
||||||
surprise).
|
|
||||||
|
|
||||||
To switch to using the European (or Rest of The World) formats put this in an
|
|
||||||
initializer or environment.rb
|
|
||||||
|
|
||||||
ValidatesTimeliness::Formats.remove_us_formats
|
|
||||||
|
|
||||||
Now '01/02/2000' will be parsed as 1st February 2000, instead of 2nd January 2000.
|
|
||||||
|
|
||||||
=== CUSTOMISING FORMATS:
|
|
||||||
|
|
||||||
I hear you say "Thats greats but I don't want X format to be valid". Well to
|
|
||||||
remove a format stick this in an initializer file
|
|
||||||
|
|
||||||
ValidatesTimeliness::Formats.remove_formats(:date, 'm\d\yy')
|
|
||||||
|
|
||||||
Done! That format is no longer considered valid. Easy!
|
|
||||||
|
|
||||||
Ok, now I hear you say "Well I have format that I want to use but you don't have it".
|
|
||||||
Ahh, then add it yourself. Again stick this in an initializer file
|
|
||||||
|
|
||||||
ValidatesTimeliness::Formats.add_formats(:time, "d o'clock")
|
|
||||||
|
|
||||||
Now "10 o'clock" will be a valid value. So easy, no more whingeing!
|
|
||||||
|
|
||||||
You can embed regular expressions in the format but no gurantees that it will
|
|
||||||
remain intact. If you avoid the use of any token characters and regexp dots or
|
|
||||||
backslashes as special characters in the regexp, it may well work as expected.
|
|
||||||
For special characters use POSIX character classes for safety. See the ISO 8601
|
|
||||||
datetime for an example of an embedded regular expression.
|
|
||||||
|
|
||||||
Because formats are evaluated in order, adding a format which may be ambiguous
|
|
||||||
with an existing format, will mean your format is ignored. If you need to make
|
|
||||||
your new format higher precedence than an existing format, you can include the
|
|
||||||
before option like so
|
|
||||||
|
|
||||||
ValidatesTimeliness::Formats.add_formats(:time, 'ss:nn:hh', :before => 'hh:nn:ss')
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
=== TEMPORAL RESTRICTION ERRORS:
|
=== Plugin Parser
|
||||||
|
|
||||||
|
The plugin uses the {timeliness gem}[http://github.com/adzap/timeliness] as a fast, configurable and extensible date and time parser.
|
||||||
|
You can add or remove valid formats for dates, times, and datetimes. It is also more strict than the
|
||||||
|
Ruby parser, which means it won't accept day of the month if it's not a valid number for the month.
|
||||||
|
|
||||||
|
By default the parser is disabled. To enable it:
|
||||||
|
|
||||||
|
# in the setup block
|
||||||
|
config.use_plugin_parser = true
|
||||||
|
|
||||||
|
Enabling the parser will mean that strings assigned to attributes validated with the plugin will be parsed
|
||||||
|
using the gem. See the wiki[http://github.com/adzap/validates_timeliness/wiki/Plugin-Parser] for more details about the parser configuration.
|
||||||
|
|
||||||
|
|
||||||
|
=== Restriction Shorthand
|
||||||
|
|
||||||
|
It is common to restrict an attribute to being on or before the current time or current day.
|
||||||
|
To specify this you need to use a lambda as an option value e.g. <tt>lambda { Time.current }</tt>.
|
||||||
|
This can be tedious noise amongst your validations for something so common. To combat this the
|
||||||
|
plugin allows you to use shorthand symbols for often used relative times or dates.
|
||||||
|
|
||||||
|
Just provide the symbol as the option value like so:
|
||||||
|
|
||||||
|
validates_date :birth_date, on_or_before: :today
|
||||||
|
|
||||||
|
The :today symbol is evaluated as <tt>lambda { Date.today }</tt>. The :now and :today
|
||||||
|
symbols are pre-configured. Configure your own like so:
|
||||||
|
|
||||||
|
# in the setup block
|
||||||
|
config.restriction_shorthand_symbols.update(
|
||||||
|
yesterday: lambda { 1.day.ago }
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
=== Default Timezone
|
||||||
|
|
||||||
|
The plugin needs to know the default timezone you are using when parsing or type casting values. If you are using
|
||||||
|
ActiveRecord then the default is automatically set to the same default zone as ActiveRecord. If you are using
|
||||||
|
another ORM you may need to change this setting.
|
||||||
|
|
||||||
|
# in the setup block
|
||||||
|
config.default_timezone = :utc
|
||||||
|
|
||||||
|
By default it will be UTC if ActiveRecord is not loaded.
|
||||||
|
|
||||||
|
|
||||||
|
=== Dummy Date For Time Types
|
||||||
|
|
||||||
|
Given that Ruby has no support for a time-only type, all time type columns are evaluated
|
||||||
|
as a regular Time class objects with a dummy date value set. Rails defines the dummy date as
|
||||||
|
2000-01-01. So a time of '12:30' is evaluated as a Time value of '2000-01-01 12:30'. If you
|
||||||
|
need to customize this for some reason you can do so as follows
|
||||||
|
|
||||||
|
# in the setup block
|
||||||
|
config.dummy_date_for_time_type = [2009, 1, 1]
|
||||||
|
|
||||||
|
The value should be an array of 3 values being year, month and day in that order.
|
||||||
|
|
||||||
|
|
||||||
|
=== Temporal Restriction Errors
|
||||||
|
|
||||||
When using the validation temporal restrictions there are times when the restriction
|
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
|
option value itself may be invalid. This will add an error to the model such as
|
||||||
'restriction :before value was invalid'. These can be annoying if you are using
|
'Error occurred validating birth_date for :before restriction'. These can be annoying
|
||||||
procs or methods as restrictions and don't care if they don't evaluate properly
|
in development or production as you most likely just want to skip the option if no
|
||||||
and you want the validation to complete. In these situations you turn them off.
|
valid value was returned. By default these errors are displayed in Rails test mode.
|
||||||
|
|
||||||
To turn them off:
|
To turn them on/off:
|
||||||
|
|
||||||
ValidatesTimeliness::Validator.ignore_restriction_errors = true
|
# in the setup block
|
||||||
|
config.ignore_restriction_errors = true
|
||||||
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
|
|
||||||
model methods and you only care when they return a value, then they should
|
|
||||||
return nil in all other situations. Restrictions are skipped if they are nil.
|
|
||||||
|
|
||||||
=== OTHER CUSTOMISATION:
|
|
||||||
|
|
||||||
The error messages for each temporal restrictions can also be globally overridden by
|
|
||||||
updating the default AR error messages like so
|
|
||||||
|
|
||||||
For Rails 2.0/2.1:
|
|
||||||
|
|
||||||
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",
|
|
||||||
:between => "must be between %s and %s"
|
|
||||||
)
|
|
||||||
|
|
||||||
Where %s is the interpolation value for the restriction.
|
|
||||||
|
|
||||||
Rails 2.2+ using the I18n system to define new defaults:
|
|
||||||
|
|
||||||
en:
|
|
||||||
activerecord:
|
|
||||||
errors:
|
|
||||||
messages:
|
|
||||||
on_or_before: "must be equal to or before {{restriction}}"
|
|
||||||
on_or_after: "must be equal to or after {{restriction}}"
|
|
||||||
between: "must be between {{earliest}} and {{latest}}"
|
|
||||||
|
|
||||||
The {{restriction}} signifies where the interpolation value for the restriction
|
|
||||||
will be inserted.
|
|
||||||
|
|
||||||
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',
|
|
||||||
:date => '%Y-%m-%d',
|
|
||||||
:datetime => '%Y-%m-%d %H:%M:%S'
|
|
||||||
)
|
|
||||||
|
|
||||||
Those are Ruby strftime formats not the plugin formats.
|
|
||||||
|
|
||||||
|
|
||||||
=== RSPEC MATCHER:
|
== Extensions
|
||||||
|
|
||||||
To sweeten the deal that little bit more, you have an Rspec matcher available for
|
=== Strict Parsing for Select Helpers
|
||||||
you model specs. Now you can easily test the validations you have just written
|
|
||||||
with the plugin or better yet *before* you write them! You just use the
|
|
||||||
validation options you want as you would with the validation method. Those
|
|
||||||
options are then verified and reported if they fail. Use it like so:
|
|
||||||
|
|
||||||
@person.should validate_date(:birth_date, :before => Time.now, :before_message => 'should be before today')
|
When using date/time select helpers, the component values are handled by ActiveRecord using
|
||||||
|
the Time class to instantiate them into a time value. This means that some invalid dates,
|
||||||
|
such as 31st June, are shifted forward and treated as valid. To handle these cases in a strict
|
||||||
|
way, you can enable the plugin extension to treat them as invalid dates.
|
||||||
|
|
||||||
|
To activate it, uncomment this line in the initializer:
|
||||||
|
|
||||||
|
# in the setup block
|
||||||
|
config.enable_multiparameter_extension!
|
||||||
|
|
||||||
|
|
||||||
The matcher names are just the singular of the validation methods.
|
=== Display Invalid Values in Select Helpers
|
||||||
|
|
||||||
== CREDITS:
|
The plugin offers an extension for ActionView to allowing invalid date and time values to be
|
||||||
|
redisplayed to the user as feedback, instead of a blank field which happens by default in
|
||||||
|
Rails. Though the date helpers make this a pretty rare occurrence, given the select dropdowns
|
||||||
|
for each date/time component, but it may be something of interest.
|
||||||
|
|
||||||
* Adam Meehan (adam.meehan@gmail.com, http://duckpunching.com/)
|
To activate it, uncomment this line in the initializer:
|
||||||
|
|
||||||
* Jonathan Viney (http://workingwithrails.com/person/4985-jonathan-viney)
|
# in the setup block
|
||||||
For his validates_date_time plugin which I have used up until this plugin and
|
config.enable_date_time_select_extension!
|
||||||
which influenced some of the design and I borrowed a little of code from it.
|
|
||||||
|
|
||||||
|
|
||||||
== LICENSE:
|
== Contributors
|
||||||
|
|
||||||
|
To see the generous people who have contributed code, take a look at the {contributors list}[http://github.com/adzap/validates_timeliness/contributors].
|
||||||
|
|
||||||
|
|
||||||
|
== Maintainers
|
||||||
|
|
||||||
|
* {Adam Meehan}[http://github.com/adzap]
|
||||||
|
|
||||||
|
|
||||||
|
== License
|
||||||
|
|
||||||
Copyright (c) 2008 Adam Meehan, released under the MIT license
|
Copyright (c) 2008 Adam Meehan, released under the MIT license
|
||||||
|
|||||||
70
Rakefile
70
Rakefile
@ -1,58 +1,30 @@
|
|||||||
require 'rubygems'
|
require 'bundler'
|
||||||
require 'rake/gempackagetask'
|
require 'bundler/setup'
|
||||||
require 'rubygems/specification'
|
|
||||||
require 'date'
|
|
||||||
require 'spec/rake/spectask'
|
|
||||||
|
|
||||||
GEM = "validates_timeliness"
|
require 'appraisal'
|
||||||
GEM_VERSION = "1.1.3"
|
|
||||||
AUTHOR = "Adam Meehan"
|
|
||||||
EMAIL = "adam.meehan@gmail.com"
|
|
||||||
HOMEPAGE = "http://github.com/adzap/validates_timeliness"
|
|
||||||
SUMMARY = "Date and time validation plugin for Rails 2.x which allows custom formats"
|
|
||||||
|
|
||||||
spec = Gem::Specification.new do |s|
|
Bundler::GemHelper.install_tasks
|
||||||
s.name = GEM
|
|
||||||
s.version = GEM_VERSION
|
|
||||||
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
|
|
||||||
|
|
||||||
task :default => :spec
|
require 'rdoc/task'
|
||||||
|
require 'rspec/core/rake_task'
|
||||||
|
|
||||||
desc "Run specs"
|
desc "Run specs"
|
||||||
Spec::Rake::SpecTask.new do |t|
|
RSpec::Core::RakeTask.new(:spec)
|
||||||
t.spec_files = FileList['spec/**/*_spec.rb']
|
|
||||||
t.spec_opts = %w(-fs --color)
|
desc "Generate code coverage"
|
||||||
|
RSpec::Core::RakeTask.new(:coverage) do |t|
|
||||||
|
t.rcov = true
|
||||||
|
t.rcov_opts = ['--exclude', 'spec']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc 'Generate documentation for plugin.'
|
||||||
Rake::GemPackageTask.new(spec) do |pkg|
|
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||||
pkg.gem_spec = spec
|
rdoc.rdoc_dir = 'rdoc'
|
||||||
|
rdoc.title = 'ValidatesTimeliness'
|
||||||
|
rdoc.options << '--line-numbers' << '--inline-source'
|
||||||
|
rdoc.rdoc_files.include('README')
|
||||||
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "install the gem locally"
|
desc 'Default: run specs.'
|
||||||
task :install => [:package] do
|
task :default => :spec
|
||||||
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
|
|
||||||
|
|||||||
5
TODO
5
TODO
@ -1,5 +0,0 @@
|
|||||||
- :format option
|
|
||||||
- :with_date and :with_time options
|
|
||||||
- valid formats could come from locale file
|
|
||||||
- formats to use month and day names from i18n
|
|
||||||
- add replace_formats instead add_formats :before
|
|
||||||
99
benchmark.rb
99
benchmark.rb
@ -1,99 +0,0 @@
|
|||||||
$:.unshift(File.expand_path('lib'))
|
|
||||||
|
|
||||||
require 'date'
|
|
||||||
require 'parsedate'
|
|
||||||
require 'benchmark'
|
|
||||||
require 'rubygems'
|
|
||||||
require 'active_support'
|
|
||||||
require 'active_record'
|
|
||||||
require 'action_controller'
|
|
||||||
require 'rails/version'
|
|
||||||
|
|
||||||
require 'validates_timeliness'
|
|
||||||
|
|
||||||
n = 10000
|
|
||||||
Benchmark.bm do |x|
|
|
||||||
x.report('timeliness - datetime') {
|
|
||||||
n.times do
|
|
||||||
ActiveRecord::Base.parse_date_time("2000-01-04 12:12:12", :datetime)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
x.report('timeliness - date') {
|
|
||||||
n.times do
|
|
||||||
ActiveRecord::Base.parse_date_time("2000-01-04", :date)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
x.report('timeliness - date as datetime') {
|
|
||||||
n.times do
|
|
||||||
ActiveRecord::Base.parse_date_time("2000-01-04", :datetime)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
x.report('timeliness - time') {
|
|
||||||
n.times do
|
|
||||||
ActiveRecord::Base.parse_date_time("12:01:02", :time)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
x.report('timeliness - invalid format datetime') {
|
|
||||||
n.times do
|
|
||||||
ActiveRecord::Base.parse_date_time("20xx-01-04 12:12:12", :datetime)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
x.report('timeliness - invalid format date') {
|
|
||||||
n.times do
|
|
||||||
ActiveRecord::Base.parse_date_time("20xx-01-04", :date)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
x.report('timeliness - invalid format time') {
|
|
||||||
n.times do
|
|
||||||
ActiveRecord::Base.parse_date_time("12:xx:02", :time)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
x.report('timeliness - invalid value datetime') {
|
|
||||||
n.times do
|
|
||||||
ActiveRecord::Base.parse_date_time("2000-01-32 12:12:12", :datetime)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
x.report('timeliness - invalid value date') {
|
|
||||||
n.times do
|
|
||||||
ActiveRecord::Base.parse_date_time("2000-01-32", :date)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
x.report('timeliness - invalid value time') {
|
|
||||||
n.times do
|
|
||||||
ActiveRecord::Base.parse_date_time("12:61:02", :time)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
x.report('date/time') {
|
|
||||||
n.times do
|
|
||||||
"2000-01-04 12:12:12" =~ /\A(\d{4})-(\d{2})-(\d{2}) (\d{2})[\. :](\d{2})([\. :](\d{2}))?\Z/
|
|
||||||
arr = [$1, $2, $3, $3, $5, $6].map {|i| i.to_i }
|
|
||||||
Date.new(*arr[0..2])
|
|
||||||
Time.mktime(*arr)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
x.report('parsedate') {
|
|
||||||
n.times do
|
|
||||||
arr = ParseDate.parsedate("2000-01-04 12:12:12")
|
|
||||||
Date.new(*arr[0..2])
|
|
||||||
Time.mktime(*arr)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
x.report('strptime') {
|
|
||||||
n.times do
|
|
||||||
DateTime.strptime("2000-01-04 12:12:12", '%Y-%m-%d %H:%M:%s')
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
14
gemfiles/rails_5_0.gemfile
Normal file
14
gemfiles/rails_5_0.gemfile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# This file was generated by Appraisal
|
||||||
|
|
||||||
|
source "http://rubygems.org"
|
||||||
|
|
||||||
|
gem "rails", "~> 5.0.0"
|
||||||
|
gem "rspec"
|
||||||
|
gem "rspec-rails", "~> 3.7"
|
||||||
|
gem "timecop"
|
||||||
|
gem "byebug"
|
||||||
|
gem "appraisal"
|
||||||
|
gem "sqlite3", "~> 1.3.6"
|
||||||
|
gem "nokogiri", "~> 1.8"
|
||||||
|
|
||||||
|
gemspec path: "../"
|
||||||
14
gemfiles/rails_5_1.gemfile
Normal file
14
gemfiles/rails_5_1.gemfile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# This file was generated by Appraisal
|
||||||
|
|
||||||
|
source "http://rubygems.org"
|
||||||
|
|
||||||
|
gem "rails", "~> 5.1.0"
|
||||||
|
gem "rspec"
|
||||||
|
gem "rspec-rails", "~> 3.7"
|
||||||
|
gem "timecop"
|
||||||
|
gem "byebug"
|
||||||
|
gem "appraisal"
|
||||||
|
gem "sqlite3", "~> 1.3.6"
|
||||||
|
gem "nokogiri", "~> 1.8"
|
||||||
|
|
||||||
|
gemspec path: "../"
|
||||||
14
gemfiles/rails_5_2.gemfile
Normal file
14
gemfiles/rails_5_2.gemfile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# This file was generated by Appraisal
|
||||||
|
|
||||||
|
source "http://rubygems.org"
|
||||||
|
|
||||||
|
gem "rails", "~> 5.2.0"
|
||||||
|
gem "rspec"
|
||||||
|
gem "rspec-rails", "~> 3.7"
|
||||||
|
gem "timecop"
|
||||||
|
gem "byebug"
|
||||||
|
gem "appraisal"
|
||||||
|
gem "sqlite3", "~> 1.3.6"
|
||||||
|
gem "nokogiri", "~> 1.8"
|
||||||
|
|
||||||
|
gemspec path: "../"
|
||||||
2
init.rb
2
init.rb
@ -1,3 +1 @@
|
|||||||
raise "Rails version must be 2.0 or greater to use validates_timeliness plugin" if Rails::VERSION::MAJOR < 2
|
|
||||||
|
|
||||||
require 'validates_timeliness'
|
require 'validates_timeliness'
|
||||||
|
|||||||
16
lib/generators/validates_timeliness/install_generator.rb
Normal file
16
lib/generators/validates_timeliness/install_generator.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module Generators
|
||||||
|
class InstallGenerator < Rails::Generators::Base
|
||||||
|
desc "Copy ValidatesTimeliness default files"
|
||||||
|
source_root File.expand_path('../templates', __FILE__)
|
||||||
|
|
||||||
|
def copy_initializers
|
||||||
|
copy_file 'validates_timeliness.rb', 'config/initializers/validates_timeliness.rb'
|
||||||
|
end
|
||||||
|
|
||||||
|
def copy_locale_file
|
||||||
|
copy_file 'en.yml', 'config/locales/validates_timeliness.en.yml'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
16
lib/generators/validates_timeliness/templates/en.yml
Normal file
16
lib/generators/validates_timeliness/templates/en.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
en:
|
||||||
|
errors:
|
||||||
|
messages:
|
||||||
|
invalid_date: "is not a valid date"
|
||||||
|
invalid_time: "is not a valid time"
|
||||||
|
invalid_datetime: "is not a valid datetime"
|
||||||
|
is_at: "must be at %{restriction}"
|
||||||
|
before: "must be before %{restriction}"
|
||||||
|
on_or_before: "must be on or before %{restriction}"
|
||||||
|
after: "must be after %{restriction}"
|
||||||
|
on_or_after: "must be on or after %{restriction}"
|
||||||
|
validates_timeliness:
|
||||||
|
error_value_formats:
|
||||||
|
date: '%Y-%m-%d'
|
||||||
|
time: '%H:%M:%S'
|
||||||
|
datetime: '%Y-%m-%d %H:%M:%S'
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
ValidatesTimeliness.setup do |config|
|
||||||
|
# Extend ORM/ODMs for full support (:active_record included).
|
||||||
|
config.extend_orms = [ :active_record ]
|
||||||
|
#
|
||||||
|
# Default timezone
|
||||||
|
# config.default_timezone = :utc
|
||||||
|
#
|
||||||
|
# Set the dummy date part for a time type values.
|
||||||
|
# config.dummy_date_for_time_type = [ 2000, 1, 1 ]
|
||||||
|
#
|
||||||
|
# Ignore errors when restriction options are evaluated
|
||||||
|
# config.ignore_restriction_errors = false
|
||||||
|
#
|
||||||
|
# Re-display invalid values in date/time selects
|
||||||
|
# config.enable_date_time_select_extension!
|
||||||
|
#
|
||||||
|
# Handle multiparameter date/time values strictly
|
||||||
|
# config.enable_multiparameter_extension!
|
||||||
|
#
|
||||||
|
# Shorthand date and time symbols for restrictions
|
||||||
|
# config.restriction_shorthand_symbols.update(
|
||||||
|
# :now => lambda { Time.current },
|
||||||
|
# :today => lambda { Date.current }
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# Use the plugin date/time parser which is stricter and extendable
|
||||||
|
# config.use_plugin_parser = false
|
||||||
|
#
|
||||||
|
# Add one or more formats making them valid. e.g. add_formats(:date, 'd(st|rd|th) of mmm, yyyy')
|
||||||
|
# config.parser.add_formats()
|
||||||
|
#
|
||||||
|
# Remove one or more formats making them invalid. e.g. remove_formats(:date, 'dd/mm/yyy')
|
||||||
|
# config.parser.remove_formats()
|
||||||
|
#
|
||||||
|
# Change the ambiguous year threshold when parsing a 2 digit year
|
||||||
|
# config.parser.ambiguous_year_threshold = 30
|
||||||
|
#
|
||||||
|
# Treat ambiguous dates, such as 01/02/1950, as a Non-US date.
|
||||||
|
# config.parser.remove_us_formats
|
||||||
|
end
|
||||||
@ -1,67 +1,70 @@
|
|||||||
require 'validates_timeliness/formats'
|
require 'date'
|
||||||
require 'validates_timeliness/validator'
|
require 'active_support/concern'
|
||||||
require 'validates_timeliness/validation_methods'
|
require 'active_support/core_ext/module'
|
||||||
require 'validates_timeliness/spec/rails/matchers/validate_timeliness' if ENV['RAILS_ENV'] == 'test'
|
require 'active_support/core_ext/hash/except'
|
||||||
|
require 'active_support/core_ext/string/conversions'
|
||||||
require 'validates_timeliness/active_record/attribute_methods'
|
require 'active_support/core_ext/date/acts_like'
|
||||||
require 'validates_timeliness/active_record/multiparameter_attributes'
|
require 'active_support/core_ext/date/conversions'
|
||||||
require 'validates_timeliness/action_view/instance_tag'
|
require 'active_support/core_ext/time/acts_like'
|
||||||
|
require 'active_support/core_ext/time/conversions'
|
||||||
require 'validates_timeliness/core_ext/time'
|
require 'active_support/core_ext/date_time/acts_like'
|
||||||
require 'validates_timeliness/core_ext/date'
|
require 'active_support/core_ext/date_time/conversions'
|
||||||
require 'validates_timeliness/core_ext/date_time'
|
require 'timeliness'
|
||||||
|
|
||||||
module ValidatesTimeliness
|
|
||||||
|
|
||||||
mattr_accessor :default_timezone
|
|
||||||
|
|
||||||
self.default_timezone = :utc
|
|
||||||
|
|
||||||
LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/validates_timeliness/locale/en.yml')
|
|
||||||
|
|
||||||
|
Timeliness.module_eval do
|
||||||
class << self
|
class << self
|
||||||
|
alias :dummy_date_for_time_type :date_for_time_type
|
||||||
def load_error_messages_with_i18n
|
alias :dummy_date_for_time_type= :date_for_time_type=
|
||||||
I18n.load_path += [ LOCALE_PATH ]
|
alias :remove_us_formats :use_euro_formats
|
||||||
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
|
|
||||||
puts "Rails version #{Rails::VERSION::STRING} not explicitly supported by validates_timeliness plugin. You may encounter some problems."
|
|
||||||
resume
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ValidatesTimeliness.setup_for_rails
|
module ValidatesTimeliness
|
||||||
|
autoload :VERSION, 'validates_timeliness/version'
|
||||||
|
|
||||||
ValidatesTimeliness::Formats.compile_format_expressions
|
class << self
|
||||||
|
delegate :default_timezone, :default_timezone=, :dummy_date_for_time_type, :dummy_date_for_time_type=, :to => Timeliness
|
||||||
|
|
||||||
|
attr_accessor :extend_orms, :ignore_restriction_errors, :restriction_shorthand_symbols, :use_plugin_parser
|
||||||
|
end
|
||||||
|
|
||||||
|
# Extend ORM/ODMs for full support (:active_record).
|
||||||
|
self.extend_orms = []
|
||||||
|
|
||||||
|
# Ignore errors when restriction options are evaluated
|
||||||
|
self.ignore_restriction_errors = false
|
||||||
|
|
||||||
|
# Shorthand time and date symbols for restrictions
|
||||||
|
self.restriction_shorthand_symbols = {
|
||||||
|
now: proc { Time.current },
|
||||||
|
today: proc { Date.current }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use the plugin date/time parser which is stricter and extensible
|
||||||
|
self.use_plugin_parser = false
|
||||||
|
|
||||||
|
# Default timezone
|
||||||
|
self.default_timezone = :utc
|
||||||
|
|
||||||
|
# Set the dummy date part for a time type values.
|
||||||
|
self.dummy_date_for_time_type = [ 2000, 1, 1 ]
|
||||||
|
|
||||||
|
# Setup method for plugin configuration
|
||||||
|
def self.setup
|
||||||
|
yield self
|
||||||
|
load_orms
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.load_orms
|
||||||
|
extend_orms.each {|orm| require "validates_timeliness/orm/#{orm}" }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.parser; Timeliness end
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'validates_timeliness/converter'
|
||||||
|
require 'validates_timeliness/validator'
|
||||||
|
require 'validates_timeliness/helper_methods'
|
||||||
|
require 'validates_timeliness/attribute_methods'
|
||||||
|
require 'validates_timeliness/extensions'
|
||||||
|
require 'validates_timeliness/railtie' if defined?(Rails)
|
||||||
|
|||||||
@ -1,45 +0,0 @@
|
|||||||
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)
|
|
||||||
@ -1,144 +0,0 @@
|
|||||||
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.
|
|
||||||
module AttributeMethods
|
|
||||||
|
|
||||||
def self.included(base)
|
|
||||||
base.extend ClassMethods
|
|
||||||
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
|
|
||||||
|
|
||||||
# If Rails dirty attributes is enabled then the value is added to
|
|
||||||
# changed attributes if changed. Can't use the default dirty checking
|
|
||||||
# implementation as it chains the write_attribute method which deletes
|
|
||||||
# the attribute from the cache.
|
|
||||||
def write_date_time_attribute(attr_name, value, type, time_zone_aware)
|
|
||||||
old = read_attribute(attr_name) if defined?(::ActiveRecord::Dirty)
|
|
||||||
new = self.class.parse_date_time(value, type)
|
|
||||||
|
|
||||||
unless type == :date || new.nil?
|
|
||||||
new = new.to_time rescue new
|
|
||||||
end
|
|
||||||
|
|
||||||
new = new.in_time_zone if new && time_zone_aware
|
|
||||||
@attributes_cache[attr_name] = new
|
|
||||||
|
|
||||||
if defined?(::ActiveRecord::Dirty) && !changed_attributes.include?(attr_name) && old != new
|
|
||||||
changed_attributes[attr_name] = (old.duplicable? ? old.clone : old)
|
|
||||||
end
|
|
||||||
@attributes[attr_name] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
# If reloading then check if cached, which means its in local time.
|
|
||||||
# If local, convert with parser as local timezone, otherwise use
|
|
||||||
# read_attribute method for quick default type cast of values from
|
|
||||||
# database using default timezone.
|
|
||||||
def read_date_time_attribute(attr_name, type, time_zone_aware, reload = false)
|
|
||||||
cached = @attributes_cache[attr_name]
|
|
||||||
return cached if @attributes_cache.has_key?(attr_name) && !reload
|
|
||||||
|
|
||||||
if @attributes_cache.has_key?(attr_name)
|
|
||||||
time = read_attribute_before_type_cast(attr_name)
|
|
||||||
time = self.class.parse_date_time(date, type)
|
|
||||||
else
|
|
||||||
time = read_attribute(attr_name)
|
|
||||||
@attributes[attr_name] = time && time_zone_aware ? time.in_time_zone : time
|
|
||||||
end
|
|
||||||
@attributes_cache[attr_name] = time && time_zone_aware ? time.in_time_zone : time
|
|
||||||
end
|
|
||||||
|
|
||||||
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 [:date, :time, :datetime].include?(column.type)
|
|
||||||
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
|
|
||||||
define_read_method_for_dates_and_times(name, column.type, time_zone_aware)
|
|
||||||
else
|
|
||||||
define_read_method(name.to_sym, name, column)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
unless instance_method_already_implemented?("#{name}=")
|
|
||||||
if [:date, :time, :datetime].include?(column.type)
|
|
||||||
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
|
|
||||||
define_write_method_for_dates_and_times(name, column.type, time_zone_aware)
|
|
||||||
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, type, time_zone_aware)
|
|
||||||
method_body = <<-EOV
|
|
||||||
def #{attr_name}=(value)
|
|
||||||
write_date_time_attribute('#{attr_name}', value, #{type.inspect}, #{time_zone_aware})
|
|
||||||
end
|
|
||||||
EOV
|
|
||||||
evaluate_attribute_method attr_name, method_body, "#{attr_name}="
|
|
||||||
end
|
|
||||||
|
|
||||||
def define_read_method_for_dates_and_times(attr_name, type, time_zone_aware)
|
|
||||||
method_body = <<-EOV
|
|
||||||
def #{attr_name}(reload = false)
|
|
||||||
read_date_time_attribute('#{attr_name}', #{type.inspect}, #{time_zone_aware}, reload)
|
|
||||||
end
|
|
||||||
EOV
|
|
||||||
evaluate_attribute_method attr_name, method_body
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::AttributeMethods)
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
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)
|
|
||||||
49
lib/validates_timeliness/attribute_methods.rb
Normal file
49
lib/validates_timeliness/attribute_methods.rb
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module AttributeMethods
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
class_attribute :timeliness_validated_attributes
|
||||||
|
self.timeliness_validated_attributes = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveModel::Type::Date.prepend Module.new {
|
||||||
|
def cast_value(value)
|
||||||
|
return super unless ValidatesTimeliness.use_plugin_parser
|
||||||
|
|
||||||
|
if value.is_a?(::String)
|
||||||
|
return if value.empty?
|
||||||
|
value = Timeliness::Parser.parse(value, :date)
|
||||||
|
value.to_date if value
|
||||||
|
elsif value.respond_to?(:to_date)
|
||||||
|
value.to_date
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveModel::Type::Time.prepend Module.new {
|
||||||
|
def user_input_in_time_zone(value)
|
||||||
|
return super unless ValidatesTimeliness.use_plugin_parser
|
||||||
|
|
||||||
|
if value.is_a?(String)
|
||||||
|
dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, Date.current.to_s + ' ')
|
||||||
|
Timeliness::Parser.parse(dummy_time_value, :datetime, zone: :current)
|
||||||
|
else
|
||||||
|
value.in_time_zone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveModel::Type::DateTime.prepend Module.new {
|
||||||
|
def user_input_in_time_zone(value)
|
||||||
|
if value.is_a?(String) && ValidatesTimeliness.use_plugin_parser
|
||||||
|
Timeliness::Parser.parse(value, :datetime, zone: :current)
|
||||||
|
else
|
||||||
|
value.in_time_zone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
84
lib/validates_timeliness/converter.rb
Normal file
84
lib/validates_timeliness/converter.rb
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
class Converter
|
||||||
|
attr_reader :type, :format, :ignore_usec
|
||||||
|
|
||||||
|
def initialize(type:, format: nil, ignore_usec: false, time_zone_aware: false)
|
||||||
|
@type = type
|
||||||
|
@format = format
|
||||||
|
@ignore_usec = ignore_usec
|
||||||
|
@time_zone_aware = time_zone_aware
|
||||||
|
end
|
||||||
|
|
||||||
|
def type_cast_value(value)
|
||||||
|
return nil if value.nil? || !value.respond_to?(:to_time)
|
||||||
|
|
||||||
|
value = value.in_time_zone if value.acts_like?(:time) && time_zone_aware?
|
||||||
|
value = case type
|
||||||
|
when :time
|
||||||
|
dummy_time(value)
|
||||||
|
when :date
|
||||||
|
value.to_date
|
||||||
|
when :datetime
|
||||||
|
value.is_a?(Time) ? value : value.to_time
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
if ignore_usec && value.is_a?(Time)
|
||||||
|
Timeliness::Parser.make_time(Array(value).reverse[4..9], (:current if time_zone_aware?))
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def dummy_time(value)
|
||||||
|
time = if value.acts_like?(:time)
|
||||||
|
value = value.in_time_zone if time_zone_aware?
|
||||||
|
[value.hour, value.min, value.sec]
|
||||||
|
else
|
||||||
|
[0,0,0]
|
||||||
|
end
|
||||||
|
values = ValidatesTimeliness.dummy_date_for_time_type + time
|
||||||
|
Timeliness::Parser.make_time(values, (:current if time_zone_aware?))
|
||||||
|
end
|
||||||
|
|
||||||
|
def evaluate(value, scope=nil)
|
||||||
|
case value
|
||||||
|
when Time, Date
|
||||||
|
value
|
||||||
|
when String
|
||||||
|
parse(value)
|
||||||
|
when Symbol
|
||||||
|
if !scope.respond_to?(value) && restriction_shorthand?(value)
|
||||||
|
ValidatesTimeliness.restriction_shorthand_symbols[value].call
|
||||||
|
else
|
||||||
|
evaluate(scope.send(value))
|
||||||
|
end
|
||||||
|
when Proc
|
||||||
|
result = value.arity > 0 ? value.call(scope) : value.call
|
||||||
|
evaluate(result, scope)
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def restriction_shorthand?(symbol)
|
||||||
|
ValidatesTimeliness.restriction_shorthand_symbols.keys.include?(symbol)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(value)
|
||||||
|
return nil if value.nil?
|
||||||
|
|
||||||
|
if ValidatesTimeliness.use_plugin_parser
|
||||||
|
Timeliness::Parser.parse(value, type, zone: (:current if time_zone_aware?), format: format, strict: false)
|
||||||
|
else
|
||||||
|
time_zone_aware? ? Time.zone.parse(value) : value.to_time(ValidatesTimeliness.default_timezone)
|
||||||
|
end
|
||||||
|
rescue ArgumentError, TypeError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def time_zone_aware?
|
||||||
|
@time_zone_aware
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,13 +0,0 @@
|
|||||||
module ValidatesTimeliness
|
|
||||||
module CoreExtensions
|
|
||||||
module Date
|
|
||||||
|
|
||||||
def to_dummy_time
|
|
||||||
::Time.send(ValidatesTimeliness.default_timezone, 2000, 1, 1, 0, 0, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Date.send(:include, ValidatesTimeliness::CoreExtensions::Date)
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
module ValidatesTimeliness
|
|
||||||
module CoreExtensions
|
|
||||||
module DateTime
|
|
||||||
|
|
||||||
def to_dummy_time
|
|
||||||
::Time.send(ValidatesTimeliness.default_timezone, 2000, 1, 1, hour, min, sec)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
DateTime.send(:include, ValidatesTimeliness::CoreExtensions::DateTime)
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
module ValidatesTimeliness
|
|
||||||
module CoreExtensions
|
|
||||||
module Time
|
|
||||||
|
|
||||||
def to_dummy_time
|
|
||||||
self.class.send(ValidatesTimeliness.default_timezone, 2000, 1, 1, hour, min, sec)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Time.send(:include, ValidatesTimeliness::CoreExtensions::Time)
|
|
||||||
13
lib/validates_timeliness/extensions.rb
Normal file
13
lib/validates_timeliness/extensions.rb
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module Extensions
|
||||||
|
autoload :TimelinessDateTimeSelect, 'validates_timeliness/extensions/date_time_select'
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.enable_date_time_select_extension!
|
||||||
|
::ActionView::Helpers::Tags::DateSelect.send(:prepend, ValidatesTimeliness::Extensions::TimelinessDateTimeSelect)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.enable_multiparameter_extension!
|
||||||
|
require 'validates_timeliness/extensions/multiparameter_handler'
|
||||||
|
end
|
||||||
|
end
|
||||||
50
lib/validates_timeliness/extensions/date_time_select.rb
Normal file
50
lib/validates_timeliness/extensions/date_time_select.rb
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module Extensions
|
||||||
|
module TimelinessDateTimeSelect
|
||||||
|
# Intercepts the date and time select helpers to reuse the values from
|
||||||
|
# the params rather than the parsed value. This allows invalid date/time
|
||||||
|
# values to be redisplayed instead of blanks to aid correction by the user.
|
||||||
|
# It's a minor usability improvement which is rarely an issue for the user.
|
||||||
|
attr_accessor :object_name, :method_name, :template_object, :options, :html_options
|
||||||
|
|
||||||
|
POSITION = {
|
||||||
|
:year => 1, :month => 2, :day => 3, :hour => 4, :min => 5, :sec => 6
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
class DateTimeValue
|
||||||
|
attr_accessor :year, :month, :day, :hour, :min, :sec
|
||||||
|
|
||||||
|
def initialize(year:, month:, day: nil, hour: nil, min: nil, sec: nil)
|
||||||
|
@year, @month, @day, @hour, @min, @sec = year, month, day, hour, min, sec
|
||||||
|
end
|
||||||
|
|
||||||
|
def change(options)
|
||||||
|
self.class.new(
|
||||||
|
year: options.fetch(:year, year),
|
||||||
|
month: options.fetch(:month, month),
|
||||||
|
day: options.fetch(:day, day),
|
||||||
|
hour: options.fetch(:hour, hour),
|
||||||
|
min: options.fetch(:min) { options[:hour] ? 0 : min },
|
||||||
|
sec: options.fetch(:sec) { options[:hour] || options[:min] ? 0 : sec }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Splat args to support Rails 5.0 which expects object, and 5.2 which doesn't
|
||||||
|
def value(*object)
|
||||||
|
return super unless @template_object.params[@object_name]
|
||||||
|
|
||||||
|
pairs = @template_object.params[@object_name].select {|k,v| k =~ /^#{@method_name}\(/ }
|
||||||
|
return super if pairs.empty?
|
||||||
|
|
||||||
|
values = {}
|
||||||
|
pairs.each_pair do |key, value|
|
||||||
|
position = key[/\((\d+)\w+\)/, 1]
|
||||||
|
values[POSITION.key(position.to_i)] = value.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
DateTimeValue.new(values)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module Extensions
|
||||||
|
class AcceptsMultiparameterTime < Module
|
||||||
|
|
||||||
|
def initialize(defaults: {})
|
||||||
|
|
||||||
|
define_method(:cast) do |value|
|
||||||
|
if value.is_a?(Hash)
|
||||||
|
value_from_multiparameter_assignment(value)
|
||||||
|
else
|
||||||
|
super(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
define_method(:assert_valid_value) do |value|
|
||||||
|
if value.is_a?(Hash)
|
||||||
|
value_from_multiparameter_assignment(value)
|
||||||
|
else
|
||||||
|
super(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
define_method(:value_from_multiparameter_assignment) do |values_hash|
|
||||||
|
defaults.each do |k, v|
|
||||||
|
values_hash[k] ||= v
|
||||||
|
end
|
||||||
|
return unless values_hash.values_at(1,2,3).all?{ |v| v.present? } &&
|
||||||
|
Date.valid_civil?(*values_hash.values_at(1,2,3))
|
||||||
|
|
||||||
|
values = values_hash.sort.map(&:last)
|
||||||
|
::Time.send(default_timezone, *values)
|
||||||
|
end
|
||||||
|
private :value_from_multiparameter_assignment
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveModel::Type::Date.class_eval do
|
||||||
|
include ValidatesTimeliness::Extensions::AcceptsMultiparameterTime.new
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveModel::Type::Time.class_eval do
|
||||||
|
include ValidatesTimeliness::Extensions::AcceptsMultiparameterTime.new(
|
||||||
|
defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveModel::Type::DateTime.class_eval do
|
||||||
|
include ValidatesTimeliness::Extensions::AcceptsMultiparameterTime.new(
|
||||||
|
defaults: { 4 => 0, 5 => 0 }
|
||||||
|
)
|
||||||
|
end
|
||||||
@ -1,308 +0,0 @@
|
|||||||
module ValidatesTimeliness
|
|
||||||
|
|
||||||
# A date and time format regular expression generator. Allows you to
|
|
||||||
# construct a date, time or datetime format using predefined tokens in
|
|
||||||
# a string. This makes it much easier to catalogue and customize the formats
|
|
||||||
# rather than dealing directly with regular expressions. The formats are then
|
|
||||||
# compiled into regular expressions for use validating date or time strings.
|
|
||||||
#
|
|
||||||
# Formats can be added or removed to customize the set of valid date or time
|
|
||||||
# string values.
|
|
||||||
#
|
|
||||||
class Formats
|
|
||||||
cattr_accessor :time_formats,
|
|
||||||
:date_formats,
|
|
||||||
:datetime_formats,
|
|
||||||
:time_expressions,
|
|
||||||
:date_expressions,
|
|
||||||
:datetime_expressions,
|
|
||||||
:format_tokens,
|
|
||||||
:format_proc_args
|
|
||||||
|
|
||||||
# Format tokens:
|
|
||||||
# y = year
|
|
||||||
# m = month
|
|
||||||
# d = day
|
|
||||||
# h = hour
|
|
||||||
# n = minute
|
|
||||||
# s = second
|
|
||||||
# u = micro-seconds
|
|
||||||
# ampm = meridian (am or pm) with or without dots (e.g. am, a.m, or a.m.)
|
|
||||||
# _ = optional space
|
|
||||||
# tz = Timezone abbreviation (e.g. UTC, GMT, PST, EST)
|
|
||||||
# zo = Timezone offset (e.g. +10:00, -08:00, +1000)
|
|
||||||
#
|
|
||||||
# All other characters are considered literal. You can embed regexp in the
|
|
||||||
# format but no gurantees that it will remain intact. If you avoid the use
|
|
||||||
# of any token characters and regexp dots or backslashes as special characters
|
|
||||||
# in the regexp, it may well work as expected. For special characters use
|
|
||||||
# POSIX character clsses for safety.
|
|
||||||
#
|
|
||||||
# Repeating tokens:
|
|
||||||
# x = 1 or 2 digits for unit (e.g. 'h' means an hour can be '9' or '09')
|
|
||||||
# xx = 2 digits exactly for unit (e.g. 'hh' means an hour can only be '09')
|
|
||||||
#
|
|
||||||
# Special Cases:
|
|
||||||
# yy = 2 or 4 digit year
|
|
||||||
# yyyyy = exactly 4 digit year
|
|
||||||
# mmm = month long name (e.g. 'Jul' or 'July')
|
|
||||||
# ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
|
|
||||||
# u = microseconds matches 1 to 6 digits
|
|
||||||
#
|
|
||||||
# Any other invalid combination of repeating tokens will be swallowed up
|
|
||||||
# by the next lowest length valid repeating token (e.g. yyy will be
|
|
||||||
# replaced with yy)
|
|
||||||
|
|
||||||
@@time_formats = [
|
|
||||||
'hh:nn:ss',
|
|
||||||
'hh-nn-ss',
|
|
||||||
'h:nn',
|
|
||||||
'h.nn',
|
|
||||||
'h nn',
|
|
||||||
'h-nn',
|
|
||||||
'h:nn_ampm',
|
|
||||||
'h.nn_ampm',
|
|
||||||
'h nn_ampm',
|
|
||||||
'h-nn_ampm',
|
|
||||||
'h_ampm'
|
|
||||||
]
|
|
||||||
|
|
||||||
@@date_formats = [
|
|
||||||
'yyyy-mm-dd',
|
|
||||||
'yyyy/mm/dd',
|
|
||||||
'yyyy.mm.dd',
|
|
||||||
'm/d/yy',
|
|
||||||
'd/m/yy',
|
|
||||||
'm\d\yy',
|
|
||||||
'd\m\yy',
|
|
||||||
'd-m-yy',
|
|
||||||
'd.m.yy',
|
|
||||||
'd mmm yy'
|
|
||||||
]
|
|
||||||
|
|
||||||
@@datetime_formats = [
|
|
||||||
'yyyy-mm-dd hh:nn:ss',
|
|
||||||
'yyyy-mm-dd h:nn',
|
|
||||||
'yyyy-mm-dd hh:nn:ss.u',
|
|
||||||
'm/d/yy h:nn:ss',
|
|
||||||
'm/d/yy h:nn_ampm',
|
|
||||||
'm/d/yy h:nn',
|
|
||||||
'd/m/yy hh:nn:ss',
|
|
||||||
'd/m/yy h:nn_ampm',
|
|
||||||
'd/m/yy h:nn',
|
|
||||||
'ddd, dd mmm yyyy hh:nn:ss (zo|tz)', # RFC 822
|
|
||||||
'ddd mmm d hh:nn:ss zo yyyy', # Ruby time string
|
|
||||||
'yyyy-mm-ddThh:nn:ss(?:Z|zo)' # iso 8601
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# All tokens available for format construction. The token array is made of
|
|
||||||
# token regexp, validation regexp and key for format proc mapping if any.
|
|
||||||
# If the token needs no format proc arg then the validation regexp should
|
|
||||||
# not have a capturing group, as all captured groups are passed to the
|
|
||||||
# format proc.
|
|
||||||
#
|
|
||||||
# The token regexp should only use a capture group if 'look-behind' anchor
|
|
||||||
# is required. The first capture group will be considered a literal and put
|
|
||||||
# into the validation regexp string as-is. This is a hack.
|
|
||||||
@@format_tokens = [
|
|
||||||
{ 'd' => [ /(\A|[^d])d{1}(?=[^d])/, '(\d{1,2})', :day ] }, #/
|
|
||||||
{ 'ddd' => [ /d{3,}/, '(\w{3,9})' ] },
|
|
||||||
{ 'dd' => [ /d{2,}/, '(\d{2})', :day ] },
|
|
||||||
{ 'mmm' => [ /m{3,}/, '(\w{3,9})', :month ] },
|
|
||||||
{ 'mm' => [ /m{2}/, '(\d{2})', :month ] },
|
|
||||||
{ 'm' => [ /(\A|[^ap])m{1}/, '(\d{1,2})', :month ] },
|
|
||||||
{ 'yyyy' => [ /y{4,}/, '(\d{4})', :year ] },
|
|
||||||
{ 'yy' => [ /y{2,}/, '(\d{2}|\d{4})', :year ] },
|
|
||||||
{ 'hh' => [ /h{2,}/, '(\d{2})', :hour ] },
|
|
||||||
{ 'h' => [ /h{1}/, '(\d{1,2})', :hour ] },
|
|
||||||
{ 'nn' => [ /n{2,}/, '(\d{2})', :min ] },
|
|
||||||
{ 'n' => [ /n{1}/, '(\d{1,2})', :min ] },
|
|
||||||
{ 'ss' => [ /s{2,}/, '(\d{2})', :sec ] },
|
|
||||||
{ 's' => [ /s{1}/, '(\d{1,2})', :sec ] },
|
|
||||||
{ 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] },
|
|
||||||
{ 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] },
|
|
||||||
{ 'zo' => [ /zo/, '(?:[+-]\d{2}:?\d{2})'] },
|
|
||||||
{ 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] },
|
|
||||||
{ '_' => [ /_/, '\s?' ] }
|
|
||||||
]
|
|
||||||
|
|
||||||
# Arguments whichs will be passed to the format proc if matched in the
|
|
||||||
# time string. The key must should the key from the format tokens. The array
|
|
||||||
# consists of the arry position of the arg, the arg name, and the code to
|
|
||||||
# place in the time array slot. The position can be nil which means the arg
|
|
||||||
# won't be placed in the array.
|
|
||||||
#
|
|
||||||
# The code can be used to manipulate the arg value if required, otherwise
|
|
||||||
# should just be the arg name.
|
|
||||||
#
|
|
||||||
@@format_proc_args = {
|
|
||||||
:year => [0, 'y', 'unambiguous_year(y)'],
|
|
||||||
:month => [1, 'm', 'month_index(m)'],
|
|
||||||
:day => [2, 'd', 'd'],
|
|
||||||
:hour => [3, 'h', 'full_hour(h,md)'],
|
|
||||||
:min => [4, 'n', 'n'],
|
|
||||||
:sec => [5, 's', 's'],
|
|
||||||
:usec => [6, 'u', 'microseconds(u)'],
|
|
||||||
:meridian => [nil, 'md', nil]
|
|
||||||
}
|
|
||||||
|
|
||||||
class << self
|
|
||||||
|
|
||||||
def compile_format_expressions
|
|
||||||
@@time_expressions = compile_formats(@@time_formats)
|
|
||||||
@@date_expressions = compile_formats(@@date_formats)
|
|
||||||
@@datetime_expressions = compile_formats(@@datetime_formats)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Loop through format expressions for type and call proc on matches. Allow
|
|
||||||
# pre or post match strings to exist if strict is false. Otherwise wrap
|
|
||||||
# regexp in start and end anchors.
|
|
||||||
# Returns 7 part time array.
|
|
||||||
def parse(string, type, strict=true)
|
|
||||||
return string unless string.is_a?(String)
|
|
||||||
|
|
||||||
matches = nil
|
|
||||||
exp, processor = expression_set(type, string).find do |regexp, proc|
|
|
||||||
full = /\A#{regexp}\Z/ if strict
|
|
||||||
full ||= case type
|
|
||||||
when :datetime then /\A#{regexp}\Z/
|
|
||||||
when :date then /\A#{regexp}/
|
|
||||||
else /#{regexp}\Z/
|
|
||||||
end
|
|
||||||
matches = full.match(string.strip)
|
|
||||||
end
|
|
||||||
processor.call(*matches[1..7]) if matches
|
|
||||||
end
|
|
||||||
|
|
||||||
# Delete formats of specified type. Error raised if format not found.
|
|
||||||
def remove_formats(type, *remove_formats)
|
|
||||||
remove_formats.each do |format|
|
|
||||||
unless self.send("#{type}_formats").delete(format)
|
|
||||||
raise "Format #{format} not found in #{type} formats"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
compile_format_expressions
|
|
||||||
end
|
|
||||||
|
|
||||||
# Adds new formats. Must specify format type and can specify a :before
|
|
||||||
# option to nominate which format the new formats should be inserted in
|
|
||||||
# front on to take higher precedence.
|
|
||||||
# Error is raised if format already exists or if :before format is not found.
|
|
||||||
def add_formats(type, *add_formats)
|
|
||||||
formats = self.send("#{type}_formats")
|
|
||||||
options = {}
|
|
||||||
options = add_formats.pop if add_formats.last.is_a?(Hash)
|
|
||||||
before = options[:before]
|
|
||||||
raise "Format for :before option #{format} was not found." if before && !formats.include?(before)
|
|
||||||
|
|
||||||
add_formats.each do |format|
|
|
||||||
raise "Format #{format} is already included in #{type} formats" if formats.include?(format)
|
|
||||||
|
|
||||||
index = before ? formats.index(before) : -1
|
|
||||||
formats.insert(index, format)
|
|
||||||
end
|
|
||||||
compile_format_expressions
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# Removes formats where the 1 or 2 digit month comes first, to eliminate
|
|
||||||
# formats which are ambiguous with the European style of day then month.
|
|
||||||
# The mmm token is ignored as its not ambigous.
|
|
||||||
def remove_us_formats
|
|
||||||
us_format_regexp = /\Am{1,2}[^m]/
|
|
||||||
date_formats.reject! { |format| us_format_regexp =~ format }
|
|
||||||
datetime_formats.reject! { |format| us_format_regexp =~ format }
|
|
||||||
compile_format_expressions
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Compile formats into validation regexps and format procs
|
|
||||||
def format_expression_generator(string_format)
|
|
||||||
regexp = string_format.dup
|
|
||||||
order = {}
|
|
||||||
regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes
|
|
||||||
|
|
||||||
format_tokens.each do |token|
|
|
||||||
token_name = token.keys.first
|
|
||||||
token_regexp, regexp_str, arg_key = *token.values.first
|
|
||||||
|
|
||||||
# hack for lack of look-behinds. If has a capture group then is
|
|
||||||
# considered an anchor to put straight back in the regexp string.
|
|
||||||
regexp.gsub!(token_regexp) {|m| "#{$1}" + regexp_str }
|
|
||||||
order[arg_key] = $~.begin(0) if $~ && !arg_key.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
return Regexp.new(regexp), format_proc(order)
|
|
||||||
rescue
|
|
||||||
raise "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}."
|
|
||||||
end
|
|
||||||
|
|
||||||
# Generates a proc which when executed maps the regexp capture groups to a
|
|
||||||
# proc argument based on order captured. A time array is built using the proc
|
|
||||||
# argument in the position indicated by the first element of the proc arg
|
|
||||||
# array.
|
|
||||||
#
|
|
||||||
# Examples:
|
|
||||||
#
|
|
||||||
# 'yyyy-mm-dd hh:nn' => lambda {|y,m,d,h,n| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } }
|
|
||||||
# 'dd/mm/yyyy h:nn_ampm' => lambda {|d,m,y,h,n,md| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|i| i.to_i } }
|
|
||||||
#
|
|
||||||
def format_proc(order)
|
|
||||||
arg_map = format_proc_args
|
|
||||||
args = order.invert.sort.map {|p| arg_map[p[1]][1] }
|
|
||||||
arr = [nil] * 7
|
|
||||||
order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? }
|
|
||||||
proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.to_i } }"
|
|
||||||
eval proc_string
|
|
||||||
end
|
|
||||||
|
|
||||||
def compile_formats(formats)
|
|
||||||
formats.map { |format| regexp, format_proc = format_expression_generator(format) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Pick expression set and combine date and datetimes for
|
|
||||||
# datetime attributes to allow date string as datetime
|
|
||||||
def expression_set(type, string)
|
|
||||||
case type
|
|
||||||
when :date
|
|
||||||
date_expressions
|
|
||||||
when :time
|
|
||||||
time_expressions
|
|
||||||
when :datetime
|
|
||||||
# gives a speed-up for date string as datetime attributes
|
|
||||||
if string.length < 11
|
|
||||||
date_expressions + datetime_expressions
|
|
||||||
else
|
|
||||||
datetime_expressions + date_expressions
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def full_hour(hour, meridian)
|
|
||||||
hour = hour.to_i
|
|
||||||
return hour if meridian.nil?
|
|
||||||
if meridian.delete('.').downcase == 'am'
|
|
||||||
hour == 12 ? 0 : hour
|
|
||||||
else
|
|
||||||
hour == 12 ? hour : hour + 12
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def unambiguous_year(year, threshold=30)
|
|
||||||
year = "#{year.to_i < threshold ? '20' : '19'}#{year}" if year.length == 2
|
|
||||||
year.to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
def month_index(month)
|
|
||||||
return month.to_i if month.to_i.nonzero?
|
|
||||||
Date::ABBR_MONTHNAMES.index(month.capitalize) || Date::MONTHNAMES.index(month.capitalize)
|
|
||||||
end
|
|
||||||
|
|
||||||
def microseconds(usec)
|
|
||||||
(".#{usec}".to_f * 1_000_000).to_i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
29
lib/validates_timeliness/helper_methods.rb
Normal file
29
lib/validates_timeliness/helper_methods.rb
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
module ActiveModel
|
||||||
|
module Validations
|
||||||
|
|
||||||
|
module HelperMethods
|
||||||
|
def validates_date(*attr_names)
|
||||||
|
timeliness_validation_for attr_names, :date
|
||||||
|
end
|
||||||
|
|
||||||
|
def validates_time(*attr_names)
|
||||||
|
timeliness_validation_for attr_names, :time
|
||||||
|
end
|
||||||
|
|
||||||
|
def validates_datetime(*attr_names)
|
||||||
|
timeliness_validation_for attr_names, :datetime
|
||||||
|
end
|
||||||
|
|
||||||
|
def validates_timeliness_of(*attr_names)
|
||||||
|
timeliness_validation_for attr_names
|
||||||
|
end
|
||||||
|
|
||||||
|
def timeliness_validation_for(attr_names, type=nil)
|
||||||
|
options = _merge_attributes(attr_names)
|
||||||
|
options.update(:type => type) if type
|
||||||
|
validates_with TimelinessValidator, options
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,12 +0,0 @@
|
|||||||
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}}"
|
|
||||||
between: "must be between {{earliest}} and {{latest}}"
|
|
||||||
71
lib/validates_timeliness/orm/active_model.rb
Normal file
71
lib/validates_timeliness/orm/active_model.rb
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module ORM
|
||||||
|
module ActiveModel
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
public
|
||||||
|
|
||||||
|
def define_attribute_methods(*attr_names)
|
||||||
|
super.tap { define_timeliness_methods }
|
||||||
|
end
|
||||||
|
|
||||||
|
def undefine_attribute_methods
|
||||||
|
super.tap { undefine_timeliness_attribute_methods }
|
||||||
|
end
|
||||||
|
|
||||||
|
def define_timeliness_methods(before_type_cast=false)
|
||||||
|
return if timeliness_validated_attributes.blank?
|
||||||
|
timeliness_validated_attributes.each do |attr_name|
|
||||||
|
define_attribute_timeliness_methods(attr_name, before_type_cast)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def generated_timeliness_methods
|
||||||
|
@generated_timeliness_methods ||= Module.new { |m|
|
||||||
|
extend Mutex_m
|
||||||
|
}.tap { |mod| include mod }
|
||||||
|
end
|
||||||
|
|
||||||
|
def undefine_timeliness_attribute_methods
|
||||||
|
generated_timeliness_methods.module_eval do
|
||||||
|
instance_methods.each { |m| undef_method(m) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def define_attribute_timeliness_methods(attr_name, before_type_cast=false)
|
||||||
|
define_timeliness_write_method(attr_name)
|
||||||
|
define_timeliness_before_type_cast_method(attr_name) if before_type_cast
|
||||||
|
end
|
||||||
|
|
||||||
|
def define_timeliness_write_method(attr_name)
|
||||||
|
generated_timeliness_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
||||||
|
def #{attr_name}=(value)
|
||||||
|
@timeliness_cache ||= {}
|
||||||
|
@timeliness_cache['#{attr_name}'] = value
|
||||||
|
|
||||||
|
@attributes['#{attr_name}'] = super
|
||||||
|
end
|
||||||
|
STR
|
||||||
|
end
|
||||||
|
|
||||||
|
def define_timeliness_before_type_cast_method(attr_name)
|
||||||
|
generated_timeliness_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
||||||
|
def #{attr_name}_before_type_cast
|
||||||
|
read_timeliness_attribute_before_type_cast('#{attr_name}')
|
||||||
|
end
|
||||||
|
STR
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_timeliness_attribute_before_type_cast(attr_name)
|
||||||
|
@timeliness_cache && @timeliness_cache[attr_name] || @attributes[attr_name]
|
||||||
|
end
|
||||||
|
|
||||||
|
def _clear_timeliness_cache
|
||||||
|
@timeliness_cache = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
17
lib/validates_timeliness/orm/active_record.rb
Normal file
17
lib/validates_timeliness/orm/active_record.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
module ORM
|
||||||
|
module ActiveRecord
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
def read_timeliness_attribute_before_type_cast(attr_name)
|
||||||
|
read_attribute_before_type_cast(attr_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveSupport.on_load(:active_record) do
|
||||||
|
include ValidatesTimeliness::AttributeMethods
|
||||||
|
include ValidatesTimeliness::ORM::ActiveRecord
|
||||||
|
end
|
||||||
23
lib/validates_timeliness/railtie.rb
Normal file
23
lib/validates_timeliness/railtie.rb
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
class Railtie < Rails::Railtie
|
||||||
|
initializer "validates_timeliness.initialize_active_record", :after => 'active_record.initialize_timezone' do
|
||||||
|
ActiveSupport.on_load(:active_record) do
|
||||||
|
ValidatesTimeliness.default_timezone = ActiveRecord::Base.default_timezone
|
||||||
|
ValidatesTimeliness.extend_orms << :active_record
|
||||||
|
ValidatesTimeliness.load_orms
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
initializer "validates_timeliness.initialize_restriction_errors" do
|
||||||
|
ValidatesTimeliness.ignore_restriction_errors = !Rails.env.test?
|
||||||
|
end
|
||||||
|
|
||||||
|
initializer "validates_timeliness.initialize_timeliness_ambiguous_date_format", :after => :load_config_initializers do
|
||||||
|
if Timeliness.respond_to?(:ambiguous_date_format) # i.e. v0.4+
|
||||||
|
# Set default for each new thread if you have changed the default using
|
||||||
|
# the format switching methods.
|
||||||
|
Timeliness.configuration.ambiguous_date_format = Timeliness::Definitions.current_date_format
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,158 +0,0 @@
|
|||||||
module Spec
|
|
||||||
module Rails
|
|
||||||
module Matchers
|
|
||||||
class ValidateTimeliness
|
|
||||||
|
|
||||||
VALIDITY_TEST_VALUES = {
|
|
||||||
:date => {:pass => '2000-01-01', :fail => '2000-01-32'},
|
|
||||||
:time => {:pass => '12:00', :fail => '25:00'},
|
|
||||||
:datetime => {:pass => '2000-01-01 00:00:00', :fail => '2000-01-32 00:00:00'}
|
|
||||||
}
|
|
||||||
|
|
||||||
OPTION_TEST_SETTINGS = {
|
|
||||||
:before => { :method => :-, :modify_on => :valid },
|
|
||||||
:after => { :method => :+, :modify_on => :valid },
|
|
||||||
:on_or_before => { :method => :+, :modify_on => :invalid },
|
|
||||||
:on_or_after => { :method => :-, :modify_on => :invalid }
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize(attribute, options)
|
|
||||||
@expected, @options = attribute, options
|
|
||||||
@validator = ValidatesTimeliness::Validator.new(options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def matches?(record)
|
|
||||||
@record = record
|
|
||||||
@type = @options[:type]
|
|
||||||
|
|
||||||
valid = test_validity
|
|
||||||
|
|
||||||
valid = test_option(:before) if @options[:before] && valid
|
|
||||||
valid = test_option(:after) if @options[:after] && valid
|
|
||||||
|
|
||||||
valid = test_option(:on_or_before) if @options[:on_or_before] && valid
|
|
||||||
valid = test_option(:on_or_after) if @options[:on_or_after] && valid
|
|
||||||
|
|
||||||
valid = test_between if @options[:between] && valid
|
|
||||||
|
|
||||||
return valid
|
|
||||||
end
|
|
||||||
|
|
||||||
def failure_message
|
|
||||||
"expected model to validate #{@type} attribute #{@expected.inspect} with #{@last_failure}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def negative_failure_message
|
|
||||||
"expected not to validate #{@type} attribute #{@expected.inspect}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def description
|
|
||||||
"have validated #{@type} attribute #{@expected.inspect}"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def test_validity
|
|
||||||
invalid_value = VALIDITY_TEST_VALUES[@type][:fail]
|
|
||||||
valid_value = parse_and_cast(VALIDITY_TEST_VALUES[@type][:pass])
|
|
||||||
error_matching(invalid_value, "invalid_#{@type}".to_sym) &&
|
|
||||||
no_error_matching(valid_value, "invalid_#{@type}".to_sym)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_option(option)
|
|
||||||
settings = OPTION_TEST_SETTINGS[option]
|
|
||||||
boundary = parse_and_cast(@options[option])
|
|
||||||
|
|
||||||
method = settings[:method]
|
|
||||||
|
|
||||||
valid_value, invalid_value = if settings[:modify_on] == :valid
|
|
||||||
[ boundary.send(method, 1), boundary ]
|
|
||||||
else
|
|
||||||
[ boundary, boundary.send(method, 1) ]
|
|
||||||
end
|
|
||||||
|
|
||||||
error_matching(invalid_value, option) &&
|
|
||||||
no_error_matching(valid_value, option)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_before
|
|
||||||
before = parse_and_cast(@options[:before])
|
|
||||||
|
|
||||||
error_matching(before - 1, :before) &&
|
|
||||||
no_error_matching(before, :before)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_between
|
|
||||||
between = parse_and_cast(@options[:between])
|
|
||||||
|
|
||||||
error_matching(between.first - 1, :between) &&
|
|
||||||
error_matching(between.last + 1, :between) &&
|
|
||||||
no_error_matching(between.first, :between) &&
|
|
||||||
no_error_matching(between.last, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_and_cast(value)
|
|
||||||
value = @validator.send(:restriction_value, value, @record)
|
|
||||||
@validator.send(:type_cast_value, value)
|
|
||||||
end
|
|
||||||
|
|
||||||
def error_matching(value, option)
|
|
||||||
match = error_message_for(option)
|
|
||||||
@record.send("#{@expected}=", value)
|
|
||||||
@record.valid?
|
|
||||||
errors = @record.errors.on(@expected)
|
|
||||||
pass = [ errors ].flatten.any? {|error| /#{match}/ === error }
|
|
||||||
@last_failure = "error matching '#{match}' when value is #{format_value(value)}" unless pass
|
|
||||||
pass
|
|
||||||
end
|
|
||||||
|
|
||||||
def no_error_matching(value, option)
|
|
||||||
pass = !error_matching(value, option)
|
|
||||||
unless pass
|
|
||||||
error = error_message_for(option)
|
|
||||||
@last_failure = "no error matching '#{error}' when value is #{format_value(value)}"
|
|
||||||
end
|
|
||||||
pass
|
|
||||||
end
|
|
||||||
|
|
||||||
def error_message_for(option)
|
|
||||||
msg = @validator.send(:error_messages)[option]
|
|
||||||
restriction = @validator.send(:restriction_value, @validator.configuration[option], @record)
|
|
||||||
|
|
||||||
if restriction
|
|
||||||
restriction = [restriction] unless restriction.is_a?(Array)
|
|
||||||
restriction.map! {|r| @validator.send(:type_cast_value, r) }
|
|
||||||
interpolate = @validator.send(:interpolation_values, option, restriction )
|
|
||||||
# get I18n message if defined and has interpolation keys in msg
|
|
||||||
if defined?(I18n) && !@validator.send(:custom_error_messages).include?(option)
|
|
||||||
msg = @record.errors.generate_message(@expected, option, interpolate)
|
|
||||||
else
|
|
||||||
msg = msg % interpolate
|
|
||||||
end
|
|
||||||
end
|
|
||||||
msg
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_value(value)
|
|
||||||
return value if value.is_a?(String)
|
|
||||||
value.strftime(ValidatesTimeliness::Validator.error_value_formats[@type])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_date(attribute, options={})
|
|
||||||
options[:type] = :date
|
|
||||||
ValidateTimeliness.new(attribute, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_time(attribute, options={})
|
|
||||||
options[:type] = :time
|
|
||||||
ValidateTimeliness.new(attribute, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_datetime(attribute, options={})
|
|
||||||
options[:type] = :datetime
|
|
||||||
ValidateTimeliness.new(attribute, options)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
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,163 +1,120 @@
|
|||||||
|
require 'active_model'
|
||||||
|
require 'active_model/validator'
|
||||||
|
|
||||||
module ValidatesTimeliness
|
module ValidatesTimeliness
|
||||||
|
class Validator < ActiveModel::EachValidator
|
||||||
|
attr_reader :type, :attributes, :converter
|
||||||
|
|
||||||
class Validator
|
RESTRICTIONS = {
|
||||||
cattr_accessor :ignore_restriction_errors
|
:is_at => :==,
|
||||||
cattr_accessor :error_value_formats
|
:before => :<,
|
||||||
|
:after => :>,
|
||||||
self.ignore_restriction_errors = false
|
|
||||||
self.error_value_formats = {
|
|
||||||
:time => '%H:%M:%S',
|
|
||||||
:date => '%Y-%m-%d',
|
|
||||||
:datetime => '%Y-%m-%d %H:%M:%S'
|
|
||||||
}
|
|
||||||
|
|
||||||
RESTRICTION_METHODS = {
|
|
||||||
:before => :<,
|
|
||||||
:after => :>,
|
|
||||||
:on_or_before => :<=,
|
:on_or_before => :<=,
|
||||||
:on_or_after => :>=,
|
:on_or_after => :>=,
|
||||||
:between => lambda {|v, r| (r.first..r.last).include?(v) }
|
}.freeze
|
||||||
}
|
|
||||||
|
|
||||||
attr_reader :configuration, :type
|
DEFAULT_ERROR_VALUE_FORMATS = {
|
||||||
|
:date => '%Y-%m-%d',
|
||||||
|
:time => '%H:%M:%S',
|
||||||
|
:datetime => '%Y-%m-%d %H:%M:%S'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
def initialize(configuration)
|
RESTRICTION_ERROR_MESSAGE = "Error occurred validating %s for %s restriction:\n%s"
|
||||||
defaults = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false }
|
|
||||||
@configuration = defaults.merge(configuration)
|
def self.kind
|
||||||
@type = @configuration.delete(:type)
|
:timeliness
|
||||||
end
|
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])
|
def initialize(options)
|
||||||
|
@type = options.delete(:type) || :datetime
|
||||||
|
@allow_nil, @allow_blank = options.delete(:allow_nil), options.delete(:allow_blank)
|
||||||
|
|
||||||
add_error(record, attr_name, :blank) and return if raw_value.blank?
|
if range = options.delete(:between)
|
||||||
|
raise ArgumentError, ":between must be a Range or an Array" unless range.is_a?(Range) || range.is_a?(Array)
|
||||||
add_error(record, attr_name, "invalid_#{type}".to_sym) and return unless value
|
options[:on_or_after] = range.first
|
||||||
|
if range.is_a?(Range) && range.exclude_end?
|
||||||
|
options[:before] = range.last
|
||||||
|
else
|
||||||
|
options[:on_or_before] = range.last
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@restrictions_to_check = RESTRICTIONS.keys & options.keys
|
||||||
|
|
||||||
|
super
|
||||||
|
|
||||||
|
setup_timeliness_validated_attributes(options[:class]) if options[:class]
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup_timeliness_validated_attributes(model)
|
||||||
|
if model.respond_to?(:timeliness_validated_attributes)
|
||||||
|
model.timeliness_validated_attributes ||= []
|
||||||
|
model.timeliness_validated_attributes |= attributes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_each(record, attr_name, value)
|
||||||
|
raw_value = attribute_raw_value(record, attr_name) || value
|
||||||
|
return if (@allow_nil && raw_value.nil?) || (@allow_blank && raw_value.blank?)
|
||||||
|
|
||||||
|
@converter = initialize_converter(record, attr_name)
|
||||||
|
|
||||||
|
value = @converter.parse(raw_value) if value.is_a?(String) || options[:format]
|
||||||
|
value = @converter.type_cast_value(value)
|
||||||
|
|
||||||
|
add_error(record, attr_name, :"invalid_#{@type}") and return if value.blank?
|
||||||
|
|
||||||
validate_restrictions(record, attr_name, value)
|
validate_restrictions(record, attr_name, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def raw_value(record, attr_name)
|
|
||||||
record.send("#{attr_name}_before_type_cast")
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_restrictions(record, attr_name, value)
|
def validate_restrictions(record, attr_name, value)
|
||||||
value = type_cast_value(value)
|
@restrictions_to_check.each do |restriction|
|
||||||
|
|
||||||
RESTRICTION_METHODS.each do |option, method|
|
|
||||||
next unless restriction = configuration[option]
|
|
||||||
begin
|
begin
|
||||||
restriction = restriction_value(restriction, record)
|
restriction_value = @converter.type_cast_value(@converter.evaluate(options[restriction], record))
|
||||||
next if restriction.nil?
|
unless value.send(RESTRICTIONS[restriction], restriction_value)
|
||||||
restriction = type_cast_value(restriction)
|
add_error(record, attr_name, restriction, restriction_value) and break
|
||||||
|
|
||||||
unless evaluate_restriction(restriction, value, method)
|
|
||||||
add_error(record, attr_name, option, interpolation_values(option, restriction))
|
|
||||||
end
|
end
|
||||||
rescue
|
rescue => e
|
||||||
unless self.class.ignore_restriction_errors
|
unless ValidatesTimeliness.ignore_restriction_errors
|
||||||
add_error(record, attr_name, "restriction '#{option}' value was invalid")
|
message = RESTRICTION_ERROR_MESSAGE % [ attr_name, restriction.inspect, e.message ]
|
||||||
|
add_error(record, attr_name, message) and break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def interpolation_values(option, restriction)
|
def add_error(record, attr_name, message, value=nil)
|
||||||
format = self.class.error_value_formats[type]
|
value = format_error_value(value) if value
|
||||||
restriction = [restriction] unless restriction.is_a?(Array)
|
message_options = { :message => options.fetch(:"#{message}_message", options[:message]), :restriction => value }
|
||||||
|
record.errors.add(attr_name, message, message_options)
|
||||||
if defined?(I18n)
|
|
||||||
message = custom_error_messages[option] || I18n.translate('activerecord.errors.messages')[option]
|
|
||||||
subs = message.scan(/\{\{([^\}]*)\}\}/)
|
|
||||||
interpolations = {}
|
|
||||||
subs.each_with_index {|s, i| interpolations[s[0].to_sym] = restriction[i].strftime(format) }
|
|
||||||
interpolations
|
|
||||||
else
|
|
||||||
restriction.map {|r| r.strftime(format) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def evaluate_restriction(restriction, value, comparator)
|
def format_error_value(value)
|
||||||
return true if restriction.nil?
|
format = I18n.t(@type, :default => DEFAULT_ERROR_VALUE_FORMATS[@type], :scope => 'validates_timeliness.error_value_formats')
|
||||||
|
value.strftime(format)
|
||||||
case comparator
|
|
||||||
when Symbol
|
|
||||||
value.send(comparator, restriction)
|
|
||||||
when Proc
|
|
||||||
comparator.call(value, restriction)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_error(record, attr_name, message, interpolate=nil)
|
|
||||||
if defined?(I18n)
|
|
||||||
# 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 || {})
|
|
||||||
else
|
|
||||||
message = error_messages[message] if message.is_a?(Symbol)
|
|
||||||
message = message % interpolate
|
|
||||||
record.errors.add(attr_name, message)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_messages
|
def attribute_raw_value(record, attr_name)
|
||||||
return @error_messages if defined?(@error_messages)
|
record.respond_to?(:read_timeliness_attribute_before_type_cast) &&
|
||||||
@error_messages = ValidatesTimeliness.default_error_messages.merge(custom_error_messages)
|
record.read_timeliness_attribute_before_type_cast(attr_name.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
def custom_error_messages
|
def time_zone_aware?(record, attr_name)
|
||||||
return @custom_error_messages if defined?(@custom_error_messages)
|
record.class.respond_to?(:skip_time_zone_conversion_for_attributes) &&
|
||||||
@custom_error_messages = configuration.inject({}) {|msgs, (k, v)|
|
!record.class.skip_time_zone_conversion_for_attributes.include?(attr_name.to_sym)
|
||||||
if md = /(.*)_message$/.match(k.to_s)
|
|
||||||
msgs[md[1].to_sym] = v
|
|
||||||
end
|
|
||||||
msgs
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def restriction_value(restriction, record)
|
def initialize_converter(record, attr_name)
|
||||||
case restriction
|
ValidatesTimeliness::Converter.new(
|
||||||
when Time, Date, DateTime
|
type: @type,
|
||||||
restriction
|
time_zone_aware: time_zone_aware?(record, attr_name),
|
||||||
when Symbol
|
format: options[:format],
|
||||||
restriction_value(record.send(restriction), record)
|
ignore_usec: options[:ignore_usec]
|
||||||
when Proc
|
)
|
||||||
restriction_value(restriction.call(record), record)
|
|
||||||
when Array
|
|
||||||
restriction.map {|r| restriction_value(r, record) }.sort
|
|
||||||
when Range
|
|
||||||
restriction_value([restriction.first, restriction.last], record)
|
|
||||||
else
|
|
||||||
record.class.parse_date_time(restriction, type, false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def type_cast_value(value)
|
|
||||||
if value.is_a?(Array)
|
|
||||||
value.map {|v| type_cast_value(v) }
|
|
||||||
else
|
|
||||||
case type
|
|
||||||
when :time
|
|
||||||
value.to_dummy_time
|
|
||||||
when :date
|
|
||||||
value.to_date
|
|
||||||
when :datetime
|
|
||||||
if value.is_a?(DateTime) || value.is_a?(Time)
|
|
||||||
value.to_time
|
|
||||||
else
|
|
||||||
value.to_time(ValidatesTimeliness.default_timezone)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Compatibility with ActiveModel validates method which matches option keys to their validator class
|
||||||
|
ActiveModel::Validations::TimelinessValidator = ValidatesTimeliness::Validator
|
||||||
|
|||||||
3
lib/validates_timeliness/version.rb
Normal file
3
lib/validates_timeliness/version.rb
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module ValidatesTimeliness
|
||||||
|
VERSION = '5.0.0.beta2'
|
||||||
|
end
|
||||||
@ -1,38 +0,0 @@
|
|||||||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
||||||
|
|
||||||
describe ValidatesTimeliness::ActionView::InstanceTag, :type => :helper do
|
|
||||||
|
|
||||||
before do
|
|
||||||
@person = Person.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should display invalid datetime as datetime_select values" do
|
|
||||||
@person.birth_date_and_time = "2008-02-30 12:00:22"
|
|
||||||
output = datetime_select(:person, :birth_date_and_time, :include_blank => true, :include_seconds => true)
|
|
||||||
|
|
||||||
output.should have_tag('select[id=person_birth_date_and_time_1i]') do
|
|
||||||
with_tag('option[selected=selected]', '2008')
|
|
||||||
end
|
|
||||||
output.should have_tag('select[id=person_birth_date_and_time_2i]') do
|
|
||||||
with_tag('option[selected=selected]', 'February')
|
|
||||||
end
|
|
||||||
output.should have_tag('select[id=person_birth_date_and_time_3i]') do
|
|
||||||
with_tag('option[selected=selected]', '30')
|
|
||||||
end
|
|
||||||
output.should have_tag('select[id=person_birth_date_and_time_4i]') do
|
|
||||||
with_tag('option[selected=selected]', '12')
|
|
||||||
end
|
|
||||||
output.should have_tag('select[id=person_birth_date_and_time_5i]') do
|
|
||||||
with_tag('option[selected=selected]', '00')
|
|
||||||
end
|
|
||||||
output.should have_tag('select[id=person_birth_date_and_time_6i]') do
|
|
||||||
with_tag('option[selected=selected]', '22')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should display datetime_select when datetime value is nil" do
|
|
||||||
@person.birth_date_and_time = nil
|
|
||||||
output = datetime_select(:person, :birth_date_and_time, :include_blank => true, :include_seconds => true)
|
|
||||||
output.should have_tag('select', 6)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,222 +0,0 @@
|
|||||||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
||||||
|
|
||||||
describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
|
|
||||||
include ValidatesTimeliness::ActiveRecord::AttributeMethods
|
|
||||||
include ValidatesTimeliness::ValidationMethods
|
|
||||||
|
|
||||||
before do
|
|
||||||
@person = Person.new
|
|
||||||
end
|
|
||||||
|
|
||||||
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 read_date_time_attribute when date attribute is retrieved" do
|
|
||||||
@person.should_receive(:read_date_time_attribute)
|
|
||||||
@person.birth_date = "2000-01-01"
|
|
||||||
@person.birth_date
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should call read_date_time_attribute when time attribute is retrieved" do
|
|
||||||
@person.should_receive(:read_date_time_attribute)
|
|
||||||
@person.birth_time = "12:00"
|
|
||||||
@person.birth_time
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should call rea_date_time_attribute when datetime attribute is retrieved" do
|
|
||||||
@person.should_receive(:read_date_time_attribute)
|
|
||||||
@person.birth_date_and_time = "2000-01-01 12:00"
|
|
||||||
@person.birth_date_and_time
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should call parser on write for datetime attribute" do
|
|
||||||
@person.class.should_receive(:parse_date_time).once
|
|
||||||
@person.birth_date_and_time = "2000-01-01 02:03:04"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should call parser on write for date attribute" do
|
|
||||||
@person.class.should_receive(:parse_date_time).once
|
|
||||||
@person.birth_date = "2000-01-01"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should call parser on write for time attribute" do
|
|
||||||
@person.class.should_receive(:parse_date_time).once
|
|
||||||
@person.birth_time = "12:00"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return raw string value for attribute_before_type_cast when written as string" do
|
|
||||||
time_string = "2000-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.birth_date_and_time_before_type_cast.should == time_string
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return Time object for attribute_before_type_cast when written as Time" do
|
|
||||||
@person.birth_date_and_time = Time.mktime(2000, 1, 1, 2, 3, 4)
|
|
||||||
@person.birth_date_and_time_before_type_cast.should be_kind_of(Time)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return Time object for datetime attribute read method when assigned Time object" do
|
|
||||||
@person.birth_date_and_time = Time.now
|
|
||||||
@person.birth_date_and_time.should be_kind_of(Time)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return Time object for datetime attribute read method when assigned string" do
|
|
||||||
@person.birth_date_and_time = "2000-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time.should be_kind_of(Time)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return Date object for date attribute read method when assigned Date object" do
|
|
||||||
@person.birth_date = Date.today
|
|
||||||
@person.birth_date.should be_kind_of(Date)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return Date object for date attribute read method when assigned string" do
|
|
||||||
@person.birth_date = '2000-01-01'
|
|
||||||
@person.birth_date.should be_kind_of(Date)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return nil when time is invalid" do
|
|
||||||
@person.birth_date_and_time = "2000-01-32 02:03:04"
|
|
||||||
@person.birth_date_and_time.should be_nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should not save invalid date value to database" do
|
|
||||||
time_string = "2000-01-32 02:03:04"
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save
|
|
||||||
@person.reload
|
|
||||||
@person.birth_date_and_time_before_type_cast.should be_nil
|
|
||||||
end
|
|
||||||
|
|
||||||
unless RAILS_VER < '2.1'
|
|
||||||
it "should return stored time string as Time with correct timezone" do
|
|
||||||
Time.zone = 'Melbourne'
|
|
||||||
time_string = "2000-06-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z %z').should == time_string + ' EST +1000'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return time object from database in correct timezone" do
|
|
||||||
Time.zone = 'Melbourne'
|
|
||||||
time_string = "2000-06-01 09:00:00"
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save
|
|
||||||
@person.reload
|
|
||||||
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z %z').should == time_string + ' EST +1000'
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "dirty attributes" do
|
|
||||||
|
|
||||||
it "should return true for attribute changed? when value updated" do
|
|
||||||
time_string = "2000-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.birth_date_and_time_changed?.should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should show changes when time attribute changed from nil to Time object" do
|
|
||||||
time_string = "2000-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
time = @person.birth_date_and_time
|
|
||||||
@person.changes.should == {"birth_date_and_time" => [nil, time]}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should show changes when time attribute changed from Time object to nil" do
|
|
||||||
time_string = "2020-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save false
|
|
||||||
@person.reload
|
|
||||||
time = @person.birth_date_and_time
|
|
||||||
@person.birth_date_and_time = nil
|
|
||||||
@person.changes.should == {"birth_date_and_time" => [time, nil]}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should show no changes when assigned same value as Time object" do
|
|
||||||
time_string = "2020-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save false
|
|
||||||
@person.reload
|
|
||||||
time = @person.birth_date_and_time
|
|
||||||
@person.birth_date_and_time = time
|
|
||||||
@person.changes.should == {}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should show no changes when assigned same value as time string" do
|
|
||||||
time_string = "2020-01-01 02:03:04"
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save false
|
|
||||||
@person.reload
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.changes.should == {}
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
else
|
|
||||||
|
|
||||||
it "should return time object from database in default timezone" do
|
|
||||||
ActiveRecord::Base.default_timezone = :utc
|
|
||||||
time_string = "2000-01-01 09:00:00"
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save
|
|
||||||
@person.reload
|
|
||||||
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z').should == time_string + ' GMT'
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return same time object on repeat reads on existing object" do
|
|
||||||
Time.zone = 'Melbourne' unless RAILS_VER < '2.1'
|
|
||||||
time_string = "2000-01-01 09:00:00"
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date_and_time = time_string
|
|
||||||
@person.save!
|
|
||||||
@person.reload
|
|
||||||
time = @person.birth_date_and_time
|
|
||||||
@person.birth_date_and_time.should == time
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return same date object on repeat reads on existing object" do
|
|
||||||
date_string = Date.today
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date = date_string
|
|
||||||
@person.save!
|
|
||||||
@person.reload
|
|
||||||
date = @person.birth_date
|
|
||||||
@person.birth_date.should == date
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return correct date value after new value assigned" do
|
|
||||||
today = Date.today
|
|
||||||
tomorrow = Date.today + 1.day
|
|
||||||
@person = Person.new
|
|
||||||
@person.birth_date = today
|
|
||||||
@person.birth_date.should == today
|
|
||||||
@person.birth_date = tomorrow
|
|
||||||
@person.birth_date.should == tomorrow
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should update date attribute on existing object" do
|
|
||||||
today = Date.today
|
|
||||||
tomorrow = Date.today + 1.day
|
|
||||||
@person = Person.create(:birth_date => today)
|
|
||||||
@person.birth_date = tomorrow
|
|
||||||
@person.save!
|
|
||||||
@person.reload
|
|
||||||
@person.birth_date.should == tomorrow
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
||||||
|
|
||||||
describe ValidatesTimeliness::ActiveRecord::MultiparameterAttributes do
|
|
||||||
def obj
|
|
||||||
@obj ||= Person.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should convert array for datetime type into datetime string" do
|
|
||||||
time_string = obj.time_array_to_string([2000,2,1,9,10,11], :datetime)
|
|
||||||
time_string.should == "2000-02-01 09:10:11"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should convert array for date type into date string" do
|
|
||||||
time_string = obj.time_array_to_string([2000,2,1], :date)
|
|
||||||
time_string.should == "2000-02-01"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should convert array for time type into time string" do
|
|
||||||
time_string = obj.time_array_to_string([2000,1,1,9,10,11], :time)
|
|
||||||
time_string.should == "09:10:11"
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "execute_callstack_for_multiparameter_attributes" do
|
|
||||||
before do
|
|
||||||
@callstack = {
|
|
||||||
'birth_date_and_time' => [2000,2,1,9,10,11],
|
|
||||||
'birth_date' => [2000,2,1,9,10,11],
|
|
||||||
'birth_time' => [2000,2,1,9,10,11]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should store datetime string for datetime column" do
|
|
||||||
obj.should_receive(:birth_date_and_time=).once.with("2000-02-01 09:10:11")
|
|
||||||
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should store date string for a date column" do
|
|
||||||
obj.should_receive(:birth_date=).once.with("2000-02-01")
|
|
||||||
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should store time string for a time column" do
|
|
||||||
obj.should_receive(:birth_time=).once.with("09:10:11")
|
|
||||||
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
||||||
|
|
||||||
describe ValidatesTimeliness::CoreExtensions::Date do
|
|
||||||
before do
|
|
||||||
@a_date = Date.new(2008, 7, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should make a date value into a dummy time value" do
|
|
||||||
@a_date.to_dummy_time.should == Time.utc(2000,1,1,0,0,0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe ValidatesTimeliness::CoreExtensions::Time do
|
|
||||||
before do
|
|
||||||
@a_time = Time.mktime(2008, 7, 1, 2, 3, 4)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should make a time value into a dummy time value" do
|
|
||||||
@a_time.to_dummy_time.should == Time.utc(2000,1,1,2,3,4)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe ValidatesTimeliness::CoreExtensions::DateTime do
|
|
||||||
before do
|
|
||||||
@a_datetime = DateTime.new(2008, 7, 1, 2, 3, 4)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should make a datetime value into a dummy time value" do
|
|
||||||
@a_datetime.to_dummy_time.should == Time.utc(2000,1,1,2,3,4)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,274 +0,0 @@
|
|||||||
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
||||||
|
|
||||||
describe ValidatesTimeliness::Formats do
|
|
||||||
attr_reader :formats
|
|
||||||
|
|
||||||
before do
|
|
||||||
@formats = ValidatesTimeliness::Formats
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "expression generator" do
|
|
||||||
it "should generate regexp for time" do
|
|
||||||
generate_regexp_str('hh:nn:ss').should == '/(\d{2}):(\d{2}):(\d{2})/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for time with meridian" do
|
|
||||||
generate_regexp_str('hh:nn:ss ampm').should == '/(\d{2}):(\d{2}):(\d{2}) ((?:[aApP])\.?[mM]\.?)/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for time with meridian and optional space between" do
|
|
||||||
generate_regexp_str('hh:nn:ss_ampm').should == '/(\d{2}):(\d{2}):(\d{2})\s?((?:[aApP])\.?[mM]\.?)/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for time with single or double digits" do
|
|
||||||
generate_regexp_str('h:n:s').should == '/(\d{1,2}):(\d{1,2}):(\d{1,2})/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for date" do
|
|
||||||
generate_regexp_str('yyyy-mm-dd').should == '/(\d{4})-(\d{2})-(\d{2})/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for date with slashes" do
|
|
||||||
generate_regexp_str('dd/mm/yyyy').should == '/(\d{2})\/(\d{2})\/(\d{4})/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for date with dots" do
|
|
||||||
generate_regexp_str('dd.mm.yyyy').should == '/(\d{2})\.(\d{2})\.(\d{4})/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for Ruby time string" do
|
|
||||||
expected = '/(\w{3,9}) (\w{3,9}) (\d{2}):(\d{2}):(\d{2}) (?:[+-]\d{2}:?\d{2}) (\d{4})/'
|
|
||||||
generate_regexp_str('ddd mmm hh:nn:ss zo yyyy').should == expected
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate regexp for iso8601 datetime" do
|
|
||||||
expected = '/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:Z|(?:[+-]\d{2}:?\d{2}))/'
|
|
||||||
generate_regexp_str('yyyy-mm-ddThh:nn:ss(?:Z|zo)').should == expected
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "format proc generator" do
|
|
||||||
it "should generate proc which outputs date array with values in correct order" do
|
|
||||||
generate_proc('yyyy-mm-dd').call('2000', '1', '2').should == [2000,1,2,0,0,0,0]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate proc which outputs date array from format with different order" do
|
|
||||||
generate_proc('dd/mm/yyyy').call('2', '1', '2000').should == [2000,1,2,0,0,0,0]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate proc which outputs time array" do
|
|
||||||
generate_proc('hh:nn:ss').call('01', '02', '03').should == [0,0,0,1,2,3,0]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate proc which outputs time array with meridian 'pm' adjusted hour" do
|
|
||||||
generate_proc('hh:nn:ss ampm').call('01', '02', '03', 'pm').should == [0,0,0,13,2,3,0]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate proc which outputs time array with meridian 'am' unadjusted hour" do
|
|
||||||
generate_proc('hh:nn:ss ampm').call('01', '02', '03', 'am').should == [0,0,0,1,2,3,0]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should generate proc which outputs time array with microseconds" do
|
|
||||||
generate_proc('hh:nn:ss.u').call('01', '02', '03', '99').should == [0,0,0,1,2,3,990000]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "validation regexps" do
|
|
||||||
|
|
||||||
describe "for time formats" do
|
|
||||||
format_tests = {
|
|
||||||
'hh:nn:ss' => {:pass => ['12:12:12', '01:01:01'], :fail => ['1:12:12', '12:1:12', '12:12:1', '12-12-12']},
|
|
||||||
'hh-nn-ss' => {:pass => ['12-12-12', '01-01-01'], :fail => ['1-12-12', '12-1-12', '12-12-1', '12:12:12']},
|
|
||||||
'h:nn' => {:pass => ['12:12', '1:01'], :fail => ['12:2', '12-12']},
|
|
||||||
'h.nn' => {:pass => ['2.12', '12.12'], :fail => ['2.1', '12:12']},
|
|
||||||
'h nn' => {:pass => ['2 12', '12 12'], :fail => ['2 1', '2.12', '12:12']},
|
|
||||||
'h-nn' => {:pass => ['2-12', '12-12'], :fail => ['2-1', '2.12', '12:12']},
|
|
||||||
'h:nn_ampm' => {:pass => ['2:12am', '2:12 pm', '2:12 AM', '2:12PM'], :fail => ['1:2am', '1:12 pm', '2.12am']},
|
|
||||||
'h.nn_ampm' => {:pass => ['2.12am', '2.12 pm'], :fail => ['1:2am', '1:12 pm', '2:12am']},
|
|
||||||
'h nn_ampm' => {:pass => ['2 12am', '2 12 pm'], :fail => ['1 2am', '1 12 pm', '2:12am']},
|
|
||||||
'h-nn_ampm' => {:pass => ['2-12am', '2-12 pm'], :fail => ['1-2am', '1-12 pm', '2:12am']},
|
|
||||||
'h_ampm' => {:pass => ['2am', '2 am', '12 pm'], :fail => ['1.am', '12 pm', '2:12am']},
|
|
||||||
}
|
|
||||||
format_tests.each do |format, values|
|
|
||||||
it "should correctly validate times in format '#{format}'" do
|
|
||||||
regexp = generate_regexp(format)
|
|
||||||
values[:pass].each {|value| value.should match(regexp)}
|
|
||||||
values[:fail].each {|value| value.should_not match(regexp)}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "for date formats" do
|
|
||||||
format_tests = {
|
|
||||||
'yyyy/mm/dd' => {:pass => ['2000/02/01'], :fail => ['2000\02\01', '2000/2/1', '00/02/01']},
|
|
||||||
'yyyy-mm-dd' => {:pass => ['2000-02-01'], :fail => ['2000\02\01', '2000-2-1', '00-02-01']},
|
|
||||||
'yyyy.mm.dd' => {:pass => ['2000.02.01'], :fail => ['2000\02\01', '2000.2.1', '00.02.01']},
|
|
||||||
'm/d/yy' => {:pass => ['2/1/01', '02/01/00', '02/01/2000'], :fail => ['2/1/0', '2.1.01']},
|
|
||||||
'd/m/yy' => {:pass => ['1/2/01', '01/02/00', '01/02/2000'], :fail => ['1/2/0', '1.2.01']},
|
|
||||||
'm\d\yy' => {:pass => ['2\1\01', '2\01\00', '02\01\2000'], :fail => ['2\1\0', '2/1/01']},
|
|
||||||
'd\m\yy' => {:pass => ['1\2\01', '1\02\00', '01\02\2000'], :fail => ['1\2\0', '1/2/01']},
|
|
||||||
'd-m-yy' => {:pass => ['1-2-01', '1-02-00', '01-02-2000'], :fail => ['1-2-0', '1/2/01']},
|
|
||||||
'd.m.yy' => {:pass => ['1.2.01', '1.02.00', '01.02.2000'], :fail => ['1.2.0', '1/2/01']},
|
|
||||||
'd mmm yy' => {:pass => ['1 Feb 00', '1 Feb 2000', '1 February 00', '01 February 2000'],
|
|
||||||
:fail => ['1 Fe 00', 'Feb 1 2000', '1 Feb 0']}
|
|
||||||
}
|
|
||||||
format_tests.each do |format, values|
|
|
||||||
it "should correctly validate dates in format '#{format}'" do
|
|
||||||
regexp = generate_regexp(format)
|
|
||||||
values[:pass].each {|value| value.should match(regexp)}
|
|
||||||
values[:fail].each {|value| value.should_not match(regexp)}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "for datetime formats" do
|
|
||||||
format_tests = {
|
|
||||||
'ddd mmm d hh:nn:ss zo yyyy' => {:pass => ['Sat Jul 19 12:00:00 +1000 2008'], :fail => []},
|
|
||||||
'yyyy-mm-ddThh:nn:ss(?:Z|zo)' => {:pass => ['2008-07-19T12:00:00+10:00', '2008-07-19T12:00:00Z'], :fail => ['2008-07-19T12:00:00Z+10:00']},
|
|
||||||
}
|
|
||||||
format_tests.each do |format, values|
|
|
||||||
it "should correctly validate datetimes in format '#{format}'" do
|
|
||||||
regexp = generate_regexp(format)
|
|
||||||
values[:pass].each {|value| value.should match(regexp)}
|
|
||||||
values[:fail].each {|value| value.should_not match(regexp)}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "extracting values" do
|
|
||||||
|
|
||||||
it "should return time array from date string" do
|
|
||||||
time_array = formats.parse('12:13:14', :time, true)
|
|
||||||
time_array.should == [0,0,0,12,13,14,0]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return date array from time string" do
|
|
||||||
time_array = formats.parse('2000-02-01', :date, true)
|
|
||||||
time_array.should == [2000,2,1,0,0,0,0]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return datetime array from string value" do
|
|
||||||
time_array = formats.parse('2000-02-01 12:13:14', :datetime, true)
|
|
||||||
time_array.should == [2000,2,1,12,13,14,0]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should parse date string when type is datetime" do
|
|
||||||
time_array = formats.parse('2000-02-01', :datetime, false)
|
|
||||||
time_array.should == [2000,2,1,0,0,0,0]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should ignore time when extracting date and strict is false" do
|
|
||||||
time_array = formats.parse('2000-02-01 12:12', :date, false)
|
|
||||||
time_array.should == [2000,2,1,0,0,0,0]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should ignore date when extracting time and strict is false" do
|
|
||||||
time_array = formats.parse('2000-02-01 12:12', :time, false)
|
|
||||||
time_array.should == [0,0,0,12,12,0,0]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "removing formats" do
|
|
||||||
before do
|
|
||||||
formats.compile_format_expressions
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should remove format from format array" do
|
|
||||||
formats.remove_formats(:time, 'h.nn_ampm')
|
|
||||||
formats.time_formats.should_not include("h o'clock")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should not match time after its format is removed" do
|
|
||||||
validate('2.12am', :time).should be_true
|
|
||||||
formats.remove_formats(:time, 'h.nn_ampm')
|
|
||||||
validate('2.12am', :time).should be_false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should raise error if format does not exist" do
|
|
||||||
lambda { formats.remove_formats(:time, "ss:hh:nn") }.should raise_error()
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
|
||||||
formats.time_formats << 'h.nn_ampm'
|
|
||||||
# reload class instead
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "adding formats" do
|
|
||||||
before do
|
|
||||||
formats.compile_format_expressions
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should add format to format array" do
|
|
||||||
formats.add_formats(:time, "h o'clock")
|
|
||||||
formats.time_formats.should include("h o'clock")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should match new format after its added" do
|
|
||||||
validate("12 o'clock", :time).should be_false
|
|
||||||
formats.add_formats(:time, "h o'clock")
|
|
||||||
validate("12 o'clock", :time).should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should add format before specified format and be higher precedence" do
|
|
||||||
formats.add_formats(:time, "ss:hh:nn", :before => 'hh:nn:ss')
|
|
||||||
validate("59:23:58", :time).should be_true
|
|
||||||
time_array = formats.parse('59:23:58', :time)
|
|
||||||
time_array.should == [0,0,0,23,58,59,0]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should raise error if format exists" do
|
|
||||||
lambda { formats.add_formats(:time, "hh:nn:ss") }.should raise_error()
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should raise error if format exists" do
|
|
||||||
lambda { formats.add_formats(:time, "ss:hh:nn", :before => 'nn:hh:ss') }.should raise_error()
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
|
||||||
formats.time_formats.delete("h o'clock")
|
|
||||||
formats.time_formats.delete("ss:hh:nn")
|
|
||||||
# reload class instead
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "removing US formats" do
|
|
||||||
it "should validate a date as European format when US formats removed" do
|
|
||||||
time_array = formats.parse('01/02/2000', :date)
|
|
||||||
time_array.should == [2000, 1, 2,0,0,0,0]
|
|
||||||
formats.remove_us_formats
|
|
||||||
time_array = formats.parse('01/02/2000', :date)
|
|
||||||
time_array.should == [2000, 2, 1,0,0,0,0]
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
|
||||||
# reload class
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate(time_string, type)
|
|
||||||
valid = false
|
|
||||||
formats.send("#{type}_expressions").each do |(regexp, processor)|
|
|
||||||
valid = true and break if /\A#{regexp}\Z/ =~ time_string
|
|
||||||
end
|
|
||||||
valid
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate_regexp(format)
|
|
||||||
# wrap in line start and end anchors to emulate extract values method
|
|
||||||
/\A#{formats.send(:format_expression_generator, format)[0]}\Z/
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate_regexp_str(format)
|
|
||||||
formats.send(:format_expression_generator, format)[0].inspect
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate_proc(format)
|
|
||||||
formats.send(:format_expression_generator, format)[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_format(type, format)
|
|
||||||
formats.send("#{type}_formats").delete(format)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# For use with the ginger gem to test plugin against multiple versions of Rails.
|
|
||||||
#
|
|
||||||
# To use ginger:
|
|
||||||
#
|
|
||||||
# sudo gem install freelancing-god-ginger --source=http://gems.github.com
|
|
||||||
#
|
|
||||||
# Then run
|
|
||||||
#
|
|
||||||
# ginger spec
|
|
||||||
#
|
|
||||||
Ginger.configure do |config|
|
|
||||||
rails_versions = ['2.0.2', '2.1.2', '2.2.2']
|
|
||||||
|
|
||||||
rails_versions.each do |v|
|
|
||||||
g = Ginger::Scenario.new
|
|
||||||
g['rails'] = v
|
|
||||||
config.scenarios << g.dup
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
class ApplicationController; end
|
|
||||||
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
class Person < ActiveRecord::Base
|
|
||||||
set_table_name 'people'
|
|
||||||
end
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
ActiveRecord::Schema.define(:version => 1) do
|
|
||||||
|
|
||||||
create_table "people", :force => true do |t|
|
|
||||||
t.column "name", :string
|
|
||||||
t.column "birth_date_and_time", :datetime
|
|
||||||
t.column "birth_date", :date
|
|
||||||
t.column "birth_time", :time
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# 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,218 +0,0 @@
|
|||||||
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',
|
|
||||||
:between => ['2000-01-01', '2000-01-03']
|
|
||||||
|
|
||||||
validates_time :birth_time,
|
|
||||||
:before => '23:00',
|
|
||||||
:after => '09:00',
|
|
||||||
:on_or_before => '22:00',
|
|
||||||
:on_or_after => '10:00',
|
|
||||||
:between => ['09:00', '17:00']
|
|
||||||
|
|
||||||
validates_datetime :birth_date_and_time,
|
|
||||||
:before => '2000-01-10 23:00',
|
|
||||||
:after => '2000-01-01 09:00',
|
|
||||||
:on_or_before => '2000-01-09 23:00',
|
|
||||||
:on_or_after => '2000-01-02 09:00',
|
|
||||||
:between => ['2000-01-01 09:00', '2000-01-01 17:00']
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
class CustomMessages < Person
|
|
||||||
validates_date :birth_date,
|
|
||||||
:invalid_date_message => 'is not really a date',
|
|
||||||
:before => '2000-01-10',
|
|
||||||
:before_message => 'is too late',
|
|
||||||
:after => '2000-01-01',
|
|
||||||
:after_message => 'is too early',
|
|
||||||
:on_or_before => '2000-01-09',
|
|
||||||
:on_or_before_message => 'is just too late',
|
|
||||||
:on_or_after => '2000-01-02',
|
|
||||||
:on_or_after_message => 'is just too early'
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "ValidateTimeliness matcher" do
|
|
||||||
attr_accessor :no_validation, :with_validation
|
|
||||||
|
|
||||||
@@attribute_for_type = { :date => :birth_date, :time => :birth_time, :datetime => :birth_date_and_time }
|
|
||||||
|
|
||||||
before do
|
|
||||||
@no_validation = NoValidation.new
|
|
||||||
@with_validation = WithValidation.new
|
|
||||||
end
|
|
||||||
|
|
||||||
[:date, :time, :datetime].each do |type|
|
|
||||||
|
|
||||||
it "should report that #{type} is validated" do
|
|
||||||
with_validation.should self.send("validate_#{type}", attribute_for_type(type))
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should report that #{type} is not validated" do
|
|
||||||
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with before option" do
|
|
||||||
test_values = {
|
|
||||||
:date => ['2000-01-10', '2000-01-11'],
|
|
||||||
:time => ['23:00', '22:59'],
|
|
||||||
:datetime => ['2000-01-10 23:00', '2000-01-10 22:59']
|
|
||||||
}
|
|
||||||
|
|
||||||
[:date, :time, :datetime].each do |type|
|
|
||||||
|
|
||||||
it "should report that #{type} is validated" do
|
|
||||||
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][0])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should report that #{type} is not validated when option value is incorrect" do
|
|
||||||
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][1])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should report that #{type} is not validated with option" do
|
|
||||||
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][0])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with after option" do
|
|
||||||
test_values = {
|
|
||||||
:date => ['2000-01-01', '2000-01-02'],
|
|
||||||
:time => ['09:00', '09:01'],
|
|
||||||
:datetime => ['2000-01-01 09:00', '2000-01-01 09:01']
|
|
||||||
}
|
|
||||||
|
|
||||||
[:date, :time, :datetime].each do |type|
|
|
||||||
|
|
||||||
it "should report that #{type} is validated" do
|
|
||||||
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][0])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should report that #{type} is not validated when option value is incorrect" do
|
|
||||||
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][1])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should report that #{type} is not validated with option" do
|
|
||||||
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][0])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with on_or_before option" do
|
|
||||||
test_values = {
|
|
||||||
:date => ['2000-01-09', '2000-01-08'],
|
|
||||||
:time => ['22:00', '21:59'],
|
|
||||||
:datetime => ['2000-01-09 23:00', '2000-01-09 22:59']
|
|
||||||
}
|
|
||||||
|
|
||||||
[:date, :time, :datetime].each do |type|
|
|
||||||
|
|
||||||
it "should report that #{type} is validated" do
|
|
||||||
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][0])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should report that #{type} is not validated when option value is incorrect" do
|
|
||||||
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][1])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should report that #{type} is not validated with option" do
|
|
||||||
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][0])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with on_or_after option" do
|
|
||||||
test_values = {
|
|
||||||
:date => ['2000-01-02', '2000-01-03'],
|
|
||||||
:time => ['10:00', '10:01'],
|
|
||||||
:datetime => ['2000-01-02 09:00', '2000-01-02 09:01']
|
|
||||||
}
|
|
||||||
|
|
||||||
[:date, :time, :datetime].each do |type|
|
|
||||||
|
|
||||||
it "should report that #{type} is validated" do
|
|
||||||
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][0])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should report that #{type} is not validated when option value is incorrect" do
|
|
||||||
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][1])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should report that #{type} is not validated with option" do
|
|
||||||
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][0])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "between option" do
|
|
||||||
test_values = {
|
|
||||||
:date => [ ['2000-01-01', '2000-01-03'], ['2000-01-01', '2000-01-04'] ],
|
|
||||||
:time => [ ['09:00', '17:00'], ['09:00', '17:01'] ],
|
|
||||||
:datetime => [ ['2000-01-01 09:00', '2000-01-01 17:00'], ['2000-01-01 09:00', '2000-01-01 17:01'] ]
|
|
||||||
}
|
|
||||||
|
|
||||||
[:date, :time, :datetime].each do |type|
|
|
||||||
|
|
||||||
it "should report that #{type} is validated" do
|
|
||||||
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][0])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should report that #{type} is not validated when option value is incorrect" do
|
|
||||||
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][1])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should report that #{type} is not validated with option" do
|
|
||||||
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][0])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "custom messages" do
|
|
||||||
|
|
||||||
before do
|
|
||||||
@person = CustomMessages.new
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should match error message for invalid" do
|
|
||||||
@person.should validate_date(:birth_date, :invalid_date_message => 'is not really a date')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should match error message for before option" do
|
|
||||||
@person.should validate_date(:birth_date, :before => '2000-01-10',
|
|
||||||
:invalid_date_message => 'is not really a date',
|
|
||||||
:before_message => 'is too late')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should match error message for after option" do
|
|
||||||
@person.should validate_date(:birth_date, :after => '2000-01-01',
|
|
||||||
:invalid_date_message => 'is not really a date',
|
|
||||||
:after_message => 'is too early')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should match error message for on_or_before option" do
|
|
||||||
@person.should validate_date(:birth_date, :on_or_before => '2000-01-09',
|
|
||||||
:invalid_date_message => 'is not really a date',
|
|
||||||
:on_or_before_message => 'is just too late')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should match error message for on_or_after option" do
|
|
||||||
@person.should validate_date(:birth_date, :on_or_after => '2000-01-02',
|
|
||||||
:invalid_date_message => 'is not really a date',
|
|
||||||
:on_or_after_message => 'is just too early')
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def attribute_for_type(type)
|
|
||||||
@@attribute_for_type[type.to_sym]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,54 +1,100 @@
|
|||||||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
require 'rspec'
|
||||||
$:.unshift(File.dirname(__FILE__))
|
|
||||||
$:.unshift(File.dirname(__FILE__) + '/resources')
|
|
||||||
|
|
||||||
ENV['RAILS_ENV'] = 'test'
|
require 'byebug'
|
||||||
|
require 'active_model'
|
||||||
require 'rubygems'
|
require 'active_model/validations'
|
||||||
require 'spec'
|
|
||||||
|
|
||||||
vendored_rails = File.dirname(__FILE__) + '/../../../../vendor/rails'
|
|
||||||
|
|
||||||
if vendored = File.exists?(vendored_rails)
|
|
||||||
Dir.glob(vendored_rails + "/**/lib").each { |dir| $:.unshift dir }
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
require 'ginger'
|
|
||||||
rescue LoadError
|
|
||||||
end
|
|
||||||
if ENV['VERSION']
|
|
||||||
gem 'rails', ENV['VERSION']
|
|
||||||
else
|
|
||||||
gem 'rails'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
RAILS_ROOT = File.dirname(__FILE__)
|
|
||||||
|
|
||||||
require 'rails/version'
|
|
||||||
require 'active_record'
|
require 'active_record'
|
||||||
require 'active_record/version'
|
|
||||||
require 'action_controller'
|
|
||||||
require 'action_view'
|
require 'action_view'
|
||||||
|
require 'timecop'
|
||||||
require 'spec/rails'
|
|
||||||
require 'time_travel/time_travel'
|
|
||||||
|
|
||||||
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'
|
require 'validates_timeliness'
|
||||||
|
require 'validates_timeliness/orm/active_model'
|
||||||
|
|
||||||
if RAILS_VER >= '2.1'
|
require 'rails/railtie'
|
||||||
Time.zone_default = ActiveSupport::TimeZone['UTC']
|
|
||||||
ActiveRecord::Base.time_zone_aware_attributes = true
|
require 'support/test_model'
|
||||||
|
require 'support/model_helpers'
|
||||||
|
require 'support/config_helper'
|
||||||
|
require 'support/tag_matcher'
|
||||||
|
|
||||||
|
ValidatesTimeliness.setup do |c|
|
||||||
|
c.extend_orms = [ :active_record ]
|
||||||
|
c.enable_date_time_select_extension!
|
||||||
|
c.enable_multiparameter_extension!
|
||||||
|
c.default_timezone = :utc
|
||||||
end
|
end
|
||||||
|
|
||||||
ActiveRecord::Migration.verbose = false
|
Time.zone = 'Australia/Melbourne'
|
||||||
|
|
||||||
|
LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/../lib/generators/validates_timeliness/templates/en.yml')
|
||||||
|
I18n.load_path.unshift(LOCALE_PATH)
|
||||||
|
I18n.available_locales = ['en', 'es']
|
||||||
|
|
||||||
|
# Extend TestModel as you would another ORM/ODM module
|
||||||
|
module TestModelShim
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
include ValidatesTimeliness::AttributeMethods
|
||||||
|
include ValidatesTimeliness::ORM::ActiveModel
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
# Hook into native time zone handling check, if any
|
||||||
|
def timeliness_attribute_timezone_aware?(attr_name)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Person
|
||||||
|
include TestModel
|
||||||
|
attribute :birth_date, :date
|
||||||
|
attribute :birth_time, :time
|
||||||
|
attribute :birth_datetime, :datetime
|
||||||
|
|
||||||
|
define_attribute_methods model_attributes.keys
|
||||||
|
end
|
||||||
|
|
||||||
|
class PersonWithShim < Person
|
||||||
|
include TestModelShim
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Base.default_timezone = :utc
|
||||||
|
ActiveRecord::Base.time_zone_aware_attributes = true
|
||||||
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
|
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
|
||||||
|
ActiveRecord::Base.time_zone_aware_types = [:datetime, :time]
|
||||||
|
ActiveRecord::Migration.verbose = false
|
||||||
|
ActiveRecord::Schema.define(:version => 1) do
|
||||||
|
create_table :employees, :force => true do |t|
|
||||||
|
t.string :first_name
|
||||||
|
t.string :last_name
|
||||||
|
t.date :birth_date
|
||||||
|
t.time :birth_time
|
||||||
|
t.datetime :birth_datetime
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
require 'sqlite_patch' if RAILS_VER < '2.1'
|
class Employee < ActiveRecord::Base
|
||||||
|
attr_accessor :redefined_birth_date_called
|
||||||
|
validates_date :birth_date, :allow_nil => true
|
||||||
|
validates_time :birth_time, :allow_nil => true
|
||||||
|
validates_datetime :birth_datetime, :allow_nil => true
|
||||||
|
|
||||||
require 'schema'
|
def birth_date=(value)
|
||||||
require 'person'
|
self.redefined_birth_date_called = true
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.configure do |c|
|
||||||
|
c.mock_with :rspec
|
||||||
|
c.include(TagMatcher)
|
||||||
|
c.include(ModelHelpers)
|
||||||
|
c.include(ConfigHelper)
|
||||||
|
c.before do
|
||||||
|
reset_validation_setup_for(Person)
|
||||||
|
reset_validation_setup_for(PersonWithShim)
|
||||||
|
end
|
||||||
|
|
||||||
|
c.filter_run_excluding :active_record => lambda {|version|
|
||||||
|
!(::ActiveRecord::VERSION::STRING.to_s =~ /^#{version.to_s}/)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|||||||
36
spec/support/config_helper.rb
Normal file
36
spec/support/config_helper.rb
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
module ConfigHelper
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
# Justin French tip
|
||||||
|
def with_config(preference_name, temporary_value)
|
||||||
|
old_value = ValidatesTimeliness.send(preference_name)
|
||||||
|
ValidatesTimeliness.send(:"#{preference_name}=", temporary_value)
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
ValidatesTimeliness.send(:"#{preference_name}=", old_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset_validation_setup_for(model_class)
|
||||||
|
model_class.reset_callbacks(:validate)
|
||||||
|
model_class._validators.clear
|
||||||
|
model_class.timeliness_validated_attributes = [] if model_class.respond_to?(:timeliness_validated_attributes)
|
||||||
|
model_class.undefine_attribute_methods
|
||||||
|
# This is a hack to avoid a disabled super method error message after an undef
|
||||||
|
model_class.instance_variable_set(:@generated_attribute_methods, nil)
|
||||||
|
model_class.instance_variable_set(:@generated_timeliness_methods, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def with_config(preference_name, temporary_value)
|
||||||
|
original_config_value = ValidatesTimeliness.send(preference_name)
|
||||||
|
|
||||||
|
before(:all) do
|
||||||
|
ValidatesTimeliness.send(:"#{preference_name}=", temporary_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
after(:all) do
|
||||||
|
ValidatesTimeliness.send(:"#{preference_name}=", original_config_value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
26
spec/support/model_helpers.rb
Normal file
26
spec/support/model_helpers.rb
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
module ModelHelpers
|
||||||
|
|
||||||
|
# Some test helpers from Rails source
|
||||||
|
def invalid!(attr_name, values, error = nil)
|
||||||
|
with_each_person_value(attr_name, values) do |record, value|
|
||||||
|
expect(record).to be_invalid
|
||||||
|
expect(record.errors[attr_name].size).to be >= 1
|
||||||
|
expect(record.errors[attr_name].first).to eq(error) if error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid!(attr_name, values)
|
||||||
|
with_each_person_value(attr_name, values) do |record, value|
|
||||||
|
expect(record).to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_each_person_value(attr_name, values)
|
||||||
|
record = Person.new
|
||||||
|
Array.wrap(values).each do |value|
|
||||||
|
record.send("#{attr_name}=", value)
|
||||||
|
yield record, value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
35
spec/support/tag_matcher.rb
Normal file
35
spec/support/tag_matcher.rb
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
require 'nokogiri'
|
||||||
|
|
||||||
|
module TagMatcher
|
||||||
|
extend RSpec::Matchers::DSL
|
||||||
|
|
||||||
|
matcher :have_tag do |selector|
|
||||||
|
match do |subject|
|
||||||
|
matches = doc(subject).search(selector)
|
||||||
|
|
||||||
|
if @inner_text
|
||||||
|
matches = matches.select { |element| element.inner_text == @inner_text }
|
||||||
|
end
|
||||||
|
|
||||||
|
matches.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
chain :with_inner_text do |inner_text|
|
||||||
|
@inner_text = inner_text
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def body(subject)
|
||||||
|
if subject.respond_to?(:body)
|
||||||
|
subject.body
|
||||||
|
else
|
||||||
|
subject.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def doc(subject)
|
||||||
|
@doc ||= Nokogiri::HTML(body(subject))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
61
spec/support/test_model.rb
Normal file
61
spec/support/test_model.rb
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
module TestModel
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
extend ActiveModel::Translation
|
||||||
|
include ActiveModel::Validations
|
||||||
|
include ActiveModel::AttributeMethods
|
||||||
|
|
||||||
|
included do
|
||||||
|
attribute_method_suffix "="
|
||||||
|
cattr_accessor :model_attributes
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def attribute(name, type)
|
||||||
|
self.model_attributes ||= {}
|
||||||
|
self.model_attributes[name] = type
|
||||||
|
end
|
||||||
|
|
||||||
|
def define_method_attribute=(attr_name)
|
||||||
|
generated_attribute_methods.module_eval("def #{attr_name}=(new_value); @attributes['#{attr_name}']=self.class.type_cast('#{attr_name}', new_value); end", __FILE__, __LINE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def define_method_attribute(attr_name)
|
||||||
|
generated_attribute_methods.module_eval("def #{attr_name}; @attributes['#{attr_name}']; end", __FILE__, __LINE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def type_cast(attr_name, value)
|
||||||
|
return value unless value.is_a?(String)
|
||||||
|
type_name = model_attributes[attr_name.to_sym]
|
||||||
|
type = ActiveModel::Type.lookup(type_name)
|
||||||
|
type.cast(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(attributes = nil)
|
||||||
|
@attributes = self.class.model_attributes.keys.inject({}) do |hash, column|
|
||||||
|
hash[column.to_s] = nil
|
||||||
|
hash
|
||||||
|
end
|
||||||
|
self.attributes = attributes unless attributes.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def attributes
|
||||||
|
@attributes
|
||||||
|
end
|
||||||
|
|
||||||
|
def attributes=(new_attributes={})
|
||||||
|
new_attributes.each do |key, value|
|
||||||
|
send "#{key}=", value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(method_id, *args, &block)
|
||||||
|
if !matched_attribute_method(method_id.to_s).nil?
|
||||||
|
self.class.define_attribute_methods self.class.model_attributes.keys
|
||||||
|
send(method_id, *args, &block)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
Copyright (c) 2008 Peter Yandell
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
require 'time'
|
|
||||||
|
|
||||||
module TimeTravel
|
|
||||||
module TimeExtensions
|
|
||||||
|
|
||||||
def self.included(base)
|
|
||||||
base.extend(ClassMethods)
|
|
||||||
base.class_eval do
|
|
||||||
class << self
|
|
||||||
alias_method :immutable_now, :now
|
|
||||||
alias_method :now, :mutable_now
|
|
||||||
end
|
|
||||||
end
|
|
||||||
base.now = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
|
|
||||||
@@now = nil
|
|
||||||
|
|
||||||
def now=(time)
|
|
||||||
time = Time.parse(time) if time.instance_of?(String)
|
|
||||||
@@now = time
|
|
||||||
end
|
|
||||||
|
|
||||||
def mutable_now #:nodoc:
|
|
||||||
@@now || immutable_now
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
require 'time_travel/time_extensions'
|
|
||||||
|
|
||||||
Time.send(:include, TimeTravel::TimeExtensions)
|
|
||||||
|
|
||||||
def at_time(time)
|
|
||||||
Time.now = time
|
|
||||||
begin
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
Time.now = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
69
spec/validates_timeliness/attribute_methods_spec.rb
Normal file
69
spec/validates_timeliness/attribute_methods_spec.rb
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
RSpec.describe ValidatesTimeliness::AttributeMethods do
|
||||||
|
it 'should define read_timeliness_attribute_before_type_cast instance method' do
|
||||||
|
expect(PersonWithShim.new).to respond_to(:read_timeliness_attribute_before_type_cast)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".timeliness_validated_attributes" do
|
||||||
|
it 'should return attributes validated with plugin validator' do
|
||||||
|
PersonWithShim.timeliness_validated_attributes = []
|
||||||
|
PersonWithShim.validates_date :birth_date
|
||||||
|
PersonWithShim.validates_time :birth_time
|
||||||
|
PersonWithShim.validates_datetime :birth_datetime
|
||||||
|
|
||||||
|
expect(PersonWithShim.timeliness_validated_attributes).to eq([ :birth_date, :birth_time, :birth_datetime ])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "attribute write method" do
|
||||||
|
class PersonWithCache
|
||||||
|
include TestModel
|
||||||
|
include TestModelShim
|
||||||
|
attribute :birth_date, :date
|
||||||
|
attribute :birth_time, :time
|
||||||
|
attribute :birth_datetime, :datetime
|
||||||
|
validates_date :birth_date
|
||||||
|
validates_time :birth_time
|
||||||
|
validates_datetime :birth_datetime
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should cache attribute raw value' do
|
||||||
|
r = PersonWithCache.new
|
||||||
|
r.birth_datetime = date_string = '2010-01-01'
|
||||||
|
expect(r.read_timeliness_attribute_before_type_cast('birth_datetime')).to eq(date_string)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not overwrite user defined methods' do
|
||||||
|
e = Employee.new
|
||||||
|
e.birth_date = '2010-01-01'
|
||||||
|
expect(e.redefined_birth_date_called).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with plugin parser" do
|
||||||
|
with_config(:use_plugin_parser, true)
|
||||||
|
|
||||||
|
class PersonWithParser
|
||||||
|
include TestModel
|
||||||
|
include TestModelShim
|
||||||
|
attribute :birth_date, :date
|
||||||
|
attribute :birth_time, :time
|
||||||
|
attribute :birth_datetime, :datetime
|
||||||
|
validates_date :birth_date
|
||||||
|
validates_time :birth_time
|
||||||
|
validates_datetime :birth_datetime
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse a string value' do
|
||||||
|
expect(Timeliness::Parser).to receive(:parse)
|
||||||
|
r = PersonWithParser.new
|
||||||
|
r.birth_date = '2010-01-01'
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "before_type_cast method" do
|
||||||
|
it 'should not be defined if ORM does not support it' do
|
||||||
|
expect(PersonWithShim.new).not_to respond_to(:birth_datetime_before_type_cast)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
247
spec/validates_timeliness/converter_spec.rb
Normal file
247
spec/validates_timeliness/converter_spec.rb
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
RSpec.describe ValidatesTimeliness::Converter do
|
||||||
|
subject(:converter) { described_class.new(type: type, time_zone_aware: time_zone_aware, ignore_usec: ignore_usec) }
|
||||||
|
|
||||||
|
let(:options) { Hash.new }
|
||||||
|
let(:type) { :date }
|
||||||
|
let(:time_zone_aware) { false }
|
||||||
|
let(:ignore_usec) { false }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Timecop.freeze(Time.mktime(2010, 1, 1, 0, 0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
delegate :type_cast_value, :evaluate, :parse, :dummy_time, to: :converter
|
||||||
|
|
||||||
|
describe "#type_cast_value" do
|
||||||
|
describe "for date type" do
|
||||||
|
let(:type) { :date }
|
||||||
|
|
||||||
|
it "should return same value for date value" do
|
||||||
|
expect(type_cast_value(Date.new(2010, 1, 1))).to eq(Date.new(2010, 1, 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return date part of time value" do
|
||||||
|
expect(type_cast_value(Time.mktime(2010, 1, 1, 0, 0, 0))).to eq(Date.new(2010, 1, 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return date part of datetime value" do
|
||||||
|
expect(type_cast_value(DateTime.new(2010, 1, 1, 0, 0, 0))).to eq(Date.new(2010, 1, 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return nil for invalid value types' do
|
||||||
|
expect(type_cast_value(12)).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for time type" do
|
||||||
|
let(:type) { :time }
|
||||||
|
|
||||||
|
it "should return same value for time value matching dummy date part" do
|
||||||
|
expect(type_cast_value(Time.utc(2000, 1, 1, 0, 0, 0))).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return dummy time value with same time part for time value with different date" do
|
||||||
|
expect(type_cast_value(Time.utc(2010, 1, 1, 0, 0, 0))).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return dummy time only for date value" do
|
||||||
|
expect(type_cast_value(Date.new(2010, 1, 1))).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return dummy date with time part for datetime value" do
|
||||||
|
expect(type_cast_value(DateTime.civil_from_format(:utc, 2010, 1, 1, 12, 34, 56))).to eq(Time.utc(2000, 1, 1, 12, 34, 56))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return nil for invalid value types' do
|
||||||
|
expect(type_cast_value(12)).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for datetime type" do
|
||||||
|
let(:type) { :datetime }
|
||||||
|
let(:time_zone_aware) { true }
|
||||||
|
|
||||||
|
it "should return Date as Time value" do
|
||||||
|
expect(type_cast_value(Date.new(2010, 1, 1))).to eq(Time.local(2010, 1, 1, 0, 0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return same Time value" do
|
||||||
|
value = Time.utc(2010, 1, 1, 12, 34, 56)
|
||||||
|
expect(type_cast_value(Time.utc(2010, 1, 1, 12, 34, 56))).to eq(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return as Time with same component values" do
|
||||||
|
expect(type_cast_value(DateTime.civil_from_format(:utc, 2010, 1, 1, 12, 34, 56))).to eq(Time.utc(2010, 1, 1, 12, 34, 56))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return same Time in correct zone if timezone aware" do
|
||||||
|
value = Time.utc(2010, 1, 1, 12, 34, 56)
|
||||||
|
result = type_cast_value(value)
|
||||||
|
expect(result).to eq(Time.zone.local(2010, 1, 1, 23, 34, 56))
|
||||||
|
expect(result.zone).to eq('AEDT')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return nil for invalid value types' do
|
||||||
|
expect(type_cast_value(12)).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "ignore_usec option" do
|
||||||
|
let(:type) { :datetime }
|
||||||
|
let(:ignore_usec) { true }
|
||||||
|
|
||||||
|
it "should ignore usec on time values when evaluated" do
|
||||||
|
value = Time.utc(2010, 1, 1, 12, 34, 56, 10000)
|
||||||
|
expect(type_cast_value(value)).to eq(Time.utc(2010, 1, 1, 12, 34, 56))
|
||||||
|
end
|
||||||
|
|
||||||
|
context do
|
||||||
|
let(:time_zone_aware) { true }
|
||||||
|
|
||||||
|
it "should ignore usec and return time in correct zone if timezone aware" do
|
||||||
|
value = Time.utc(2010, 1, 1, 12, 34, 56, 10000)
|
||||||
|
result = type_cast_value(value)
|
||||||
|
expect(result).to eq(Time.zone.local(2010, 1, 1, 23, 34, 56))
|
||||||
|
expect(result.zone).to eq('AEDT')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#dummy_time" do
|
||||||
|
it 'should return Time with dummy date values but same time components' do
|
||||||
|
expect(dummy_time(Time.utc(2010, 11, 22, 12, 34, 56))).to eq(Time.utc(2000, 1, 1, 12, 34, 56))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return same value for Time which already has dummy date values' do
|
||||||
|
expect(dummy_time(Time.utc(2000, 1, 1, 12, 34, 56))).to eq(Time.utc(2000, 1, 1, 12, 34, 56))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return time component values shifted to current zone if timezone aware' do
|
||||||
|
expect(dummy_time(Time.utc(2000, 1, 1, 12, 34, 56))).to eq(Time.zone.local(2000, 1, 1, 23, 34, 56))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return base dummy time value for Date value' do
|
||||||
|
expect(dummy_time(Date.new(2010, 11, 22))).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with custom dummy date" do
|
||||||
|
it 'should return dummy time with custom dummy date' do
|
||||||
|
with_config(:dummy_date_for_time_type, [2010, 1, 1] ) do
|
||||||
|
expect(dummy_time(Time.utc(1999, 11, 22, 12, 34, 56))).to eq(Time.utc(2010, 1, 1, 12, 34, 56))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#evaluate" do
|
||||||
|
let(:person) { Person.new }
|
||||||
|
|
||||||
|
it 'should return Date object as is' do
|
||||||
|
value = Date.new(2010,1,1)
|
||||||
|
expect(evaluate(value, person)).to eq(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return Time object as is' do
|
||||||
|
value = Time.mktime(2010,1,1)
|
||||||
|
expect(evaluate(value, person)).to eq(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return DateTime object as is' do
|
||||||
|
value = DateTime.new(2010,1,1,0,0,0)
|
||||||
|
expect(evaluate(value, person)).to eq(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return Time value returned from proc with 0 arity' do
|
||||||
|
value = Time.mktime(2010,1,1)
|
||||||
|
expect(evaluate(lambda { value }, person)).to eq(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return Time value returned by record attribute call in proc arity of 1' do
|
||||||
|
value = Time.mktime(2010,1,1)
|
||||||
|
person.birth_time = value
|
||||||
|
expect(evaluate(lambda {|r| r.birth_time }, person)).to eq(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return Time value for attribute method symbol which returns Time' do
|
||||||
|
value = Time.mktime(2010,1,1)
|
||||||
|
person.birth_datetime = value
|
||||||
|
expect(evaluate(:birth_datetime, person)).to eq(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return Time value is default zone from string time value' do
|
||||||
|
value = '2010-01-01 12:00:00'
|
||||||
|
expect(evaluate(value, person)).to eq(Time.utc(2010,1,1,12,0,0))
|
||||||
|
end
|
||||||
|
|
||||||
|
context do
|
||||||
|
let(:converter) { described_class.new(type: :date, time_zone_aware: true) }
|
||||||
|
|
||||||
|
it 'should return Time value is current zone from string time value if timezone aware' do
|
||||||
|
value = '2010-01-01 12:00:00'
|
||||||
|
expect(evaluate(value, person)).to eq(Time.zone.local(2010,1,1,12,0,0))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return Time value in default zone from proc which returns string time' do
|
||||||
|
value = '2010-11-12 13:00:00'
|
||||||
|
expect(evaluate(lambda { value }, person)).to eq(Time.utc(2010,11,12,13,0,0))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return Time value for attribute method symbol which returns string time value' do
|
||||||
|
value = '13:00:00'
|
||||||
|
person.birth_time = value
|
||||||
|
expect(evaluate(:birth_time, person)).to eq(Time.utc(2000,1,1,13,0,0))
|
||||||
|
end
|
||||||
|
|
||||||
|
context "restriction shorthand" do
|
||||||
|
before do
|
||||||
|
Timecop.freeze(Time.mktime(2010, 1, 1, 0, 0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should evaluate :now as current time' do
|
||||||
|
expect(evaluate(:now, person)).to eq(Time.now)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should evaluate :today as current time' do
|
||||||
|
expect(evaluate(:today, person)).to eq(Date.today)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not use shorthand if symbol if is record method' do
|
||||||
|
time = 1.day.from_now
|
||||||
|
allow(person).to receive(:now).and_return(time)
|
||||||
|
expect(evaluate(:now, person)).to eq(time)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#parse" do
|
||||||
|
context "use_plugin_parser setting is true" do
|
||||||
|
with_config(:use_plugin_parser, true)
|
||||||
|
|
||||||
|
it 'should use timeliness' do
|
||||||
|
expect(Timeliness::Parser).to receive(:parse)
|
||||||
|
parse('2000-01-01')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "use_plugin_parser setting is false" do
|
||||||
|
with_config(:use_plugin_parser, false)
|
||||||
|
|
||||||
|
it 'should use Time.zone.parse attribute is timezone aware' do
|
||||||
|
expect(Timeliness::Parser).to_not receive(:parse)
|
||||||
|
parse('2000-01-01')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should use value#to_time if use_plugin_parser setting is false and attribute is not timezone aware' do
|
||||||
|
value = '2000-01-01'
|
||||||
|
expect(value).to receive(:to_time)
|
||||||
|
parse(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return nil if value is nil' do
|
||||||
|
expect(parse(nil)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
162
spec/validates_timeliness/extensions/date_time_select_spec.rb
Normal file
162
spec/validates_timeliness/extensions/date_time_select_spec.rb
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
RSpec.describe 'ValidatesTimeliness::Extensions::DateTimeSelect' do
|
||||||
|
include ActionView::Helpers::DateHelper
|
||||||
|
attr_reader :person, :params
|
||||||
|
|
||||||
|
with_config(:use_plugin_parser, true)
|
||||||
|
|
||||||
|
before do
|
||||||
|
@person = Person.new
|
||||||
|
@params = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "datetime_select" do
|
||||||
|
it "should use param values when attribute is nil" do
|
||||||
|
@params["person"] = {
|
||||||
|
"birth_datetime(1i)" => '2009',
|
||||||
|
"birth_datetime(2i)" => '2',
|
||||||
|
"birth_datetime(3i)" => '29',
|
||||||
|
"birth_datetime(4i)" => '12',
|
||||||
|
"birth_datetime(5i)" => '13',
|
||||||
|
"birth_datetime(6i)" => '14',
|
||||||
|
}
|
||||||
|
person.birth_datetime = nil
|
||||||
|
@output = datetime_select(:person, :birth_datetime, include_blank: true, include_seconds: true)
|
||||||
|
should_have_datetime_selected(:birth_datetime, year: 2009, month: 'February', day: 29, hour: 12, min: 13, sec: 14)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should override object values and use params if present" do
|
||||||
|
@params["person"] = {
|
||||||
|
"birth_datetime(1i)" => '2009',
|
||||||
|
"birth_datetime(2i)" => '2',
|
||||||
|
"birth_datetime(3i)" => '29',
|
||||||
|
"birth_datetime(4i)" => '12',
|
||||||
|
"birth_datetime(5i)" => '13',
|
||||||
|
"birth_datetime(6i)" => '14',
|
||||||
|
}
|
||||||
|
person.birth_datetime = "2010-01-01 15:16:17"
|
||||||
|
@output = datetime_select(:person, :birth_datetime, include_blank: true, include_seconds: true)
|
||||||
|
should_have_datetime_selected(:birth_datetime, year: 2009, month: 'February', day: 29, hour: 12, min: 13, sec: 14)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should use attribute values from object if no params" do
|
||||||
|
person.birth_datetime = "2009-01-02 12:13:14"
|
||||||
|
@output = datetime_select(:person, :birth_datetime, include_blank: true, include_seconds: true)
|
||||||
|
should_have_datetime_selected(:birth_datetime, year: 2009, month: 'January', day: 2, hour: 12, min: 13, sec: 14)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should use attribute values if params does not contain attribute params" do
|
||||||
|
person.birth_datetime = "2009-01-02 12:13:14"
|
||||||
|
@params["person"] = { }
|
||||||
|
@output = datetime_select(:person, :birth_datetime, include_blank: true, include_seconds: true)
|
||||||
|
should_have_datetime_selected(:birth_datetime, year: 2009, month: 'January', day: 2, hour: 12, min: 13, sec: 14)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not select values when attribute value is nil and has no param values" do
|
||||||
|
person.birth_datetime = nil
|
||||||
|
@output = datetime_select(:person, :birth_datetime, include_blank: true, include_seconds: true)
|
||||||
|
should_not_have_datetime_selected(:birth_datetime, :year, :month, :day, :hour, :min, :sec)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "date_select" do
|
||||||
|
it "should use param values when attribute is nil" do
|
||||||
|
@params["person"] = {
|
||||||
|
"birth_date(1i)" => '2009',
|
||||||
|
"birth_date(2i)" => '2',
|
||||||
|
"birth_date(3i)" => '29',
|
||||||
|
}
|
||||||
|
person.birth_date = nil
|
||||||
|
@output = date_select(:person, :birth_date, include_blank: true)
|
||||||
|
should_have_datetime_selected(:birth_date, year: 2009, month: 'February', day: 29)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should override object values and use params if present" do
|
||||||
|
@params["person"] = {
|
||||||
|
"birth_date(1i)" => '2009',
|
||||||
|
"birth_date(2i)" => '2',
|
||||||
|
"birth_date(3i)" => '29',
|
||||||
|
}
|
||||||
|
person.birth_date = "2009-03-01"
|
||||||
|
@output = date_select(:person, :birth_date, include_blank: true)
|
||||||
|
should_have_datetime_selected(:birth_date, year: 2009, month: 'February', day: 29)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should select attribute values from object if no params" do
|
||||||
|
person.birth_date = "2009-01-02"
|
||||||
|
@output = date_select(:person, :birth_date, include_blank: true)
|
||||||
|
should_have_datetime_selected(:birth_date, year: 2009, month: 'January', day: 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should select attribute values if params does not contain attribute params" do
|
||||||
|
person.birth_date = "2009-01-02"
|
||||||
|
@params["person"] = { }
|
||||||
|
@output = date_select(:person, :birth_date, include_blank: true)
|
||||||
|
should_have_datetime_selected(:birth_date, year: 2009, month: 'January', day: 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not select values when attribute value is nil and has no param values" do
|
||||||
|
person.birth_date = nil
|
||||||
|
@output = date_select(:person, :birth_date, include_blank: true)
|
||||||
|
should_not_have_datetime_selected(:birth_time, :year, :month, :day)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should allow the day part to be discarded" do
|
||||||
|
@params["person"] = {
|
||||||
|
"birth_date(1i)" => '2009',
|
||||||
|
"birth_date(2i)" => '2',
|
||||||
|
}
|
||||||
|
|
||||||
|
@output = date_select(:person, :birth_date, include_blank: true, discard_day: true)
|
||||||
|
should_have_datetime_selected(:birth_date, year: 2009, month: 'February')
|
||||||
|
should_not_have_datetime_selected(:birth_time, :day)
|
||||||
|
expect(@output).to have_tag("input[id=person_birth_date_3i][type=hidden][value='1']")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "time_select" do
|
||||||
|
before do
|
||||||
|
Timecop.freeze Time.mktime(2009,1,1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should use param values when attribute is nil" do
|
||||||
|
@params["person"] = {
|
||||||
|
"birth_time(1i)" => '2000',
|
||||||
|
"birth_time(2i)" => '1',
|
||||||
|
"birth_time(3i)" => '1',
|
||||||
|
"birth_time(4i)" => '12',
|
||||||
|
"birth_time(5i)" => '13',
|
||||||
|
"birth_time(6i)" => '14',
|
||||||
|
}
|
||||||
|
person.birth_time = nil
|
||||||
|
@output = time_select(:person, :birth_time, include_blank: true, include_seconds: true)
|
||||||
|
should_have_datetime_selected(:birth_time, hour: 12, min: 13, sec: 14)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should select attribute values from object if no params" do
|
||||||
|
person.birth_time = "2000-01-01 12:13:14"
|
||||||
|
@output = time_select(:person, :birth_time, include_blank: true, include_seconds: true)
|
||||||
|
should_have_datetime_selected(:birth_time, hour: 12, min: 13, sec: 14)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not select values when attribute value is nil and has no param values" do
|
||||||
|
person.birth_time = nil
|
||||||
|
@output = time_select(:person, :birth_time, include_blank: true, include_seconds: true)
|
||||||
|
should_not_have_datetime_selected(:birth_time, :hour, :min, :sec)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def should_have_datetime_selected(field, datetime_hash)
|
||||||
|
datetime_hash.each do |key, value|
|
||||||
|
index = {year: 1, month: 2, day: 3, hour: 4, min: 5, sec: 6}[key]
|
||||||
|
expect(@output).to have_tag("select[id=person_#{field}_#{index}i] option[selected=selected]", value.to_s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def should_not_have_datetime_selected(field, *attributes)
|
||||||
|
attributes.each do |attribute|
|
||||||
|
index = {year: 1, month: 2, day: 3, hour: 4, min: 5, sec: 6}[attribute]
|
||||||
|
expect(@output).not_to have_tag("select[id=person_#{attribute}_#{index}i] option[selected=selected]")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
RSpec.describe 'ValidatesTimeliness::Extensions::MultiparameterHandler' do
|
||||||
|
|
||||||
|
context "time column" do
|
||||||
|
it 'should be nil invalid date portion' do
|
||||||
|
employee = record_with_multiparameter_attribute(:birth_datetime, [2000, 2, 31, 12, 0, 0])
|
||||||
|
expect(employee.birth_datetime).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should assign a Time value for valid datetimes' do
|
||||||
|
employee = record_with_multiparameter_attribute(:birth_datetime, [2000, 2, 28, 12, 0, 0])
|
||||||
|
expect(employee.birth_datetime).to eq Time.zone.local(2000, 2, 28, 12, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should be nil for incomplete date portion' do
|
||||||
|
employee = record_with_multiparameter_attribute(:birth_datetime, [2000, nil, nil])
|
||||||
|
expect(employee.birth_datetime).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "date column" do
|
||||||
|
it 'should assign nil for invalid date' do
|
||||||
|
employee = record_with_multiparameter_attribute(:birth_date, [2000, 2, 31])
|
||||||
|
expect(employee.birth_date).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should assign a Date value for valid date' do
|
||||||
|
employee = record_with_multiparameter_attribute(:birth_date, [2000, 2, 28])
|
||||||
|
expect(employee.birth_date).to eq Date.new(2000, 2, 28)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should assign hash values for incomplete date' do
|
||||||
|
employee = record_with_multiparameter_attribute(:birth_date, [2000, nil, nil])
|
||||||
|
expect(employee.birth_date).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def record_with_multiparameter_attribute(name, values)
|
||||||
|
hash = {}
|
||||||
|
values.each_with_index {|value, index| hash["#{name}(#{index+1}i)"] = value.to_s }
|
||||||
|
Employee.new(hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
28
spec/validates_timeliness/helper_methods_spec.rb
Normal file
28
spec/validates_timeliness/helper_methods_spec.rb
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
RSpec.describe ValidatesTimeliness, 'HelperMethods' do
|
||||||
|
let(:record) { Person.new }
|
||||||
|
|
||||||
|
it 'should define class validation methods' do
|
||||||
|
expect(Person).to respond_to(:validates_date)
|
||||||
|
expect(Person).to respond_to(:validates_time)
|
||||||
|
expect(Person).to respond_to(:validates_datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should define instance validation methods' do
|
||||||
|
expect(record).to respond_to(:validates_date)
|
||||||
|
expect(record).to respond_to(:validates_time)
|
||||||
|
expect(record).to respond_to(:validates_datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should validate instance using class validation defined' do
|
||||||
|
Person.validates_date :birth_date
|
||||||
|
record.valid?
|
||||||
|
|
||||||
|
expect(record.errors[:birth_date]).not_to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should validate instance using instance valiation method' do
|
||||||
|
record.validates_date :birth_date
|
||||||
|
|
||||||
|
expect(record.errors[:birth_date]).not_to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
190
spec/validates_timeliness/orm/active_record_spec.rb
Normal file
190
spec/validates_timeliness/orm/active_record_spec.rb
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
RSpec.describe ValidatesTimeliness, 'ActiveRecord' do
|
||||||
|
context "validation methods" do
|
||||||
|
let(:record) { Employee.new }
|
||||||
|
|
||||||
|
it 'should be defined for the class' do
|
||||||
|
expect(ActiveRecord::Base).to respond_to(:validates_date)
|
||||||
|
expect(ActiveRecord::Base).to respond_to(:validates_time)
|
||||||
|
expect(ActiveRecord::Base).to respond_to(:validates_datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should defines for the instance' do
|
||||||
|
expect(record).to respond_to(:validates_date)
|
||||||
|
expect(record).to respond_to(:validates_time)
|
||||||
|
expect(record).to respond_to(:validates_datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should validate a valid value string" do
|
||||||
|
record.birth_date = '2012-01-01'
|
||||||
|
|
||||||
|
record.valid?
|
||||||
|
expect(record.errors[:birth_date]).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should validate a invalid value string" do
|
||||||
|
record.birth_date = 'not a date'
|
||||||
|
|
||||||
|
record.valid?
|
||||||
|
expect(record.birth_date_before_type_cast).to eq 'not a date'
|
||||||
|
expect(record.errors[:birth_date]).not_to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should validate a nil value" do
|
||||||
|
record.birth_date = nil
|
||||||
|
|
||||||
|
record.valid?
|
||||||
|
expect(record.errors[:birth_date]).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'attribute timezone awareness' do
|
||||||
|
let(:klass) {
|
||||||
|
Class.new(ActiveRecord::Base) do
|
||||||
|
self.table_name = 'employees'
|
||||||
|
attr_accessor :some_date
|
||||||
|
attr_accessor :some_time
|
||||||
|
attr_accessor :some_datetime
|
||||||
|
validates_date :some_date
|
||||||
|
validates_time :some_time
|
||||||
|
validates_datetime :some_datetime
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
context "attribute write method" do
|
||||||
|
class EmployeeWithCache < ActiveRecord::Base
|
||||||
|
self.table_name = 'employees'
|
||||||
|
validates_date :birth_date, :allow_blank => true
|
||||||
|
validates_time :birth_time, :allow_blank => true
|
||||||
|
validates_datetime :birth_datetime, :allow_blank => true
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with plugin parser" do
|
||||||
|
with_config(:use_plugin_parser, true)
|
||||||
|
let(:record) { EmployeeWithParser.new }
|
||||||
|
|
||||||
|
class EmployeeWithParser < ActiveRecord::Base
|
||||||
|
self.table_name = 'employees'
|
||||||
|
validates_date :birth_date, :allow_blank => true
|
||||||
|
validates_time :birth_time, :allow_blank => true
|
||||||
|
validates_datetime :birth_datetime, :allow_blank => true
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Timeliness::Parser).to receive(:parse).and_call_original
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for a date column" do
|
||||||
|
it 'should parse a string value' do
|
||||||
|
record.birth_date = '2010-01-01'
|
||||||
|
expect(record.birth_date).to eq(Date.new(2010, 1, 1))
|
||||||
|
|
||||||
|
expect(Timeliness::Parser).to have_received(:parse)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse a invalid string value as nil' do
|
||||||
|
record.birth_date = 'not valid'
|
||||||
|
expect(record.birth_date).to be_nil
|
||||||
|
|
||||||
|
expect(Timeliness::Parser).to have_received(:parse)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should store a Date value after parsing string' do
|
||||||
|
record.birth_date = '2010-01-01'
|
||||||
|
|
||||||
|
expect(record.birth_date).to be_kind_of(Date)
|
||||||
|
expect(record.birth_date).to eq Date.new(2010, 1, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for a time column" do
|
||||||
|
around do |example|
|
||||||
|
time_zone_aware_types = ActiveRecord::Base.time_zone_aware_types.dup
|
||||||
|
example.call
|
||||||
|
ActiveRecord::Base.time_zone_aware_types = time_zone_aware_types
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'timezone aware' do
|
||||||
|
with_config(:default_timezone, 'Australia/Melbourne')
|
||||||
|
|
||||||
|
before do
|
||||||
|
unless ActiveRecord::Base.time_zone_aware_types.include?(:time)
|
||||||
|
ActiveRecord::Base.time_zone_aware_types.push(:time)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse a string value' do
|
||||||
|
record.birth_time = '12:30'
|
||||||
|
|
||||||
|
expect(record.birth_time).to eq('12:30'.in_time_zone)
|
||||||
|
expect(Timeliness::Parser).to have_received(:parse)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse a invalid string value as nil' do
|
||||||
|
record.birth_time = 'not valid'
|
||||||
|
|
||||||
|
expect(record.birth_time).to be_nil
|
||||||
|
expect(Timeliness::Parser).to have_received(:parse)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should store a Time value after parsing string' do
|
||||||
|
record.birth_time = '12:30'
|
||||||
|
|
||||||
|
expect(record.birth_time).to eq('12:30'.in_time_zone)
|
||||||
|
expect(record.birth_time.utc_offset).to eq '12:30'.in_time_zone.utc_offset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
skip 'not timezone aware' do
|
||||||
|
before do
|
||||||
|
ActiveRecord::Base.time_zone_aware_types.delete(:time)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse a string value' do
|
||||||
|
record.birth_time = '12:30'
|
||||||
|
|
||||||
|
expect(record.birth_time).to eq(Time.utc(2000,1,1,12,30))
|
||||||
|
expect(Timeliness::Parser).to have_received(:parse)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse a invalid string value as nil' do
|
||||||
|
record.birth_time = 'not valid'
|
||||||
|
|
||||||
|
expect(record.birth_time).to be_nil
|
||||||
|
expect(Timeliness::Parser).to have_received(:parse)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should store a Time value in utc' do
|
||||||
|
record.birth_time = '12:30'
|
||||||
|
|
||||||
|
expect(record.birth_time.utc_offset).to eq Time.now.utc.utc_offset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for a datetime column" do
|
||||||
|
with_config(:default_timezone, 'Australia/Melbourne')
|
||||||
|
|
||||||
|
it 'should parse a string value into Time value' do
|
||||||
|
record.birth_datetime = '2010-01-01 12:00'
|
||||||
|
|
||||||
|
expect(record.birth_datetime).to eq Time.zone.local(2010,1,1,12,00)
|
||||||
|
expect(Timeliness::Parser).to have_received(:parse)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse a invalid string value as nil' do
|
||||||
|
record.birth_datetime = 'not valid'
|
||||||
|
|
||||||
|
expect(record.birth_datetime).to be_nil
|
||||||
|
expect(Timeliness::Parser).to have_received(:parse)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse string as current timezone' do
|
||||||
|
record.birth_datetime = '2010-06-01 12:00'
|
||||||
|
|
||||||
|
expect(record.birth_datetime.utc_offset).to eq Time.zone.utc_offset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
22
spec/validates_timeliness/railtie_spec.rb
Normal file
22
spec/validates_timeliness/railtie_spec.rb
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
require 'validates_timeliness/railtie'
|
||||||
|
|
||||||
|
RSpec.describe ValidatesTimeliness::Railtie do
|
||||||
|
context "intializers" do
|
||||||
|
context "validates_timeliness.initialize_timeliness_ambiguous_date_format" do
|
||||||
|
it 'should set the timeliness default ambiguous date format from the current format' do
|
||||||
|
expect(Timeliness.configuration.ambiguous_date_format).to eq :us
|
||||||
|
ValidatesTimeliness.parser.use_euro_formats
|
||||||
|
|
||||||
|
initializer("validates_timeliness.initialize_timeliness_ambiguous_date_format").run
|
||||||
|
|
||||||
|
expect(Timeliness.configuration.ambiguous_date_format).to eq :euro
|
||||||
|
end
|
||||||
|
end if Timeliness.respond_to?(:ambiguous_date_format)
|
||||||
|
|
||||||
|
def initializer(name)
|
||||||
|
ValidatesTimeliness::Railtie.initializers.find { |i|
|
||||||
|
i.name == name
|
||||||
|
} || raise("Initializer #{name} not found")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
55
spec/validates_timeliness/validator/after_spec.rb
Normal file
55
spec/validates_timeliness/validator/after_spec.rb
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
RSpec.describe ValidatesTimeliness::Validator, ":after option" do
|
||||||
|
describe "for date type" do
|
||||||
|
before do
|
||||||
|
Person.validates_date :birth_date, :after => Date.new(2010, 1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for same date value" do
|
||||||
|
invalid!(:birth_date, Date.new(2010, 1, 1), 'must be after 2010-01-01')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for date before restriction" do
|
||||||
|
invalid!(:birth_date, Date.new(2009, 12, 31), 'must be after 2010-01-01')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for date after restriction" do
|
||||||
|
valid!(:birth_date, Date.new(2010, 1, 2))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for time type" do
|
||||||
|
before do
|
||||||
|
Person.validates_time :birth_time, :after => Time.mktime(2000, 1, 1, 12, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for same time as restriction" do
|
||||||
|
invalid!(:birth_time, Time.local(2000, 1, 1, 12, 0, 0), 'must be after 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for time before restriction" do
|
||||||
|
invalid!(:birth_time, Time.local(2000, 1, 1, 11, 59, 59), 'must be after 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for time after restriction" do
|
||||||
|
valid!(:birth_time, Time.local(2000, 1, 1, 12, 00, 01))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for datetime type" do
|
||||||
|
before do
|
||||||
|
Person.validates_datetime :birth_datetime, :after => DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for same datetime as restriction" do
|
||||||
|
invalid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 0), 'must be after 2010-01-01 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for datetime is before restriction" do
|
||||||
|
invalid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 11, 59, 59), 'must be after 2010-01-01 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for datetime is after restriction" do
|
||||||
|
valid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 1))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
55
spec/validates_timeliness/validator/before_spec.rb
Normal file
55
spec/validates_timeliness/validator/before_spec.rb
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
RSpec.describe ValidatesTimeliness::Validator, ":before option" do
|
||||||
|
describe "for date type" do
|
||||||
|
before do
|
||||||
|
Person.validates_date :birth_date, :before => Date.new(2010, 1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for date after restriction" do
|
||||||
|
invalid!(:birth_date, Date.new(2010, 1, 2), 'must be before 2010-01-01')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for same date value" do
|
||||||
|
invalid!(:birth_date, Date.new(2010, 1, 1), 'must be before 2010-01-01')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for date before restriction" do
|
||||||
|
valid!(:birth_date, Date.new(2009, 12, 31))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for time type" do
|
||||||
|
before do
|
||||||
|
Person.validates_time :birth_time, :before => Time.mktime(2000, 1, 1, 12, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for time after restriction" do
|
||||||
|
invalid!(:birth_time, Time.local(2000, 1, 1, 12, 00, 01), 'must be before 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for same time as restriction" do
|
||||||
|
invalid!(:birth_time, Time.local(2000, 1, 1, 12, 0, 0), 'must be before 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for time before restriction" do
|
||||||
|
valid!(:birth_time, Time.local(2000, 1, 1, 11, 59, 59))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for datetime type" do
|
||||||
|
before do
|
||||||
|
Person.validates_datetime :birth_datetime, :before => DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for datetime after restriction" do
|
||||||
|
invalid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 1), 'must be before 2010-01-01 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for same datetime as restriction" do
|
||||||
|
invalid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 0), 'must be before 2010-01-01 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for datetime before restriction" do
|
||||||
|
valid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 11, 59, 59))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
59
spec/validates_timeliness/validator/is_at_spec.rb
Normal file
59
spec/validates_timeliness/validator/is_at_spec.rb
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
RSpec.describe ValidatesTimeliness::Validator, ":is_at option" do
|
||||||
|
before do
|
||||||
|
Timecop.freeze(Time.local(2010, 1, 1, 0, 0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for date type" do
|
||||||
|
before do
|
||||||
|
Person.validates_date :birth_date, :is_at => Date.new(2010, 1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for date before restriction" do
|
||||||
|
invalid!(:birth_date, Date.new(2009, 12, 31), 'must be at 2010-01-01')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for date after restriction" do
|
||||||
|
invalid!(:birth_date, Date.new(2010, 1, 2), 'must be at 2010-01-01')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for same date value" do
|
||||||
|
valid!(:birth_date, Date.new(2010, 1, 1))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for time type" do
|
||||||
|
before do
|
||||||
|
Person.validates_time :birth_time, :is_at => Time.mktime(2000, 1, 1, 12, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for time before restriction" do
|
||||||
|
invalid!(:birth_time, Time.local(2000, 1, 1, 11, 59, 59), 'must be at 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for time after restriction" do
|
||||||
|
invalid!(:birth_time, Time.local(2000, 1, 1, 12, 00, 01), 'must be at 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for same time as restriction" do
|
||||||
|
valid!(:birth_time, Time.local(2000, 1, 1, 12, 0, 0))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for datetime type" do
|
||||||
|
before do
|
||||||
|
Person.validates_datetime :birth_datetime, :is_at => DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for datetime before restriction" do
|
||||||
|
invalid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 11, 59, 59), 'must be at 2010-01-01 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for datetime after restriction" do
|
||||||
|
invalid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 1), 'must be at 2010-01-01 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for same datetime as restriction" do
|
||||||
|
valid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 0))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
55
spec/validates_timeliness/validator/on_or_after_spec.rb
Normal file
55
spec/validates_timeliness/validator/on_or_after_spec.rb
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
RSpec.describe ValidatesTimeliness::Validator, ":on_or_after option" do
|
||||||
|
describe "for date type" do
|
||||||
|
before do
|
||||||
|
Person.validates_date :birth_date, :on_or_after => Date.new(2010, 1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for date before restriction" do
|
||||||
|
invalid!(:birth_date, Date.new(2009, 12, 31), 'must be on or after 2010-01-01')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for same date value" do
|
||||||
|
valid!(:birth_date, Date.new(2010, 1, 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for date after restriction" do
|
||||||
|
valid!(:birth_date, Date.new(2010, 1, 2))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for time type" do
|
||||||
|
before do
|
||||||
|
Person.validates_time :birth_time, :on_or_after => Time.mktime(2000, 1, 1, 12, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for time before restriction" do
|
||||||
|
invalid!(:birth_time, Time.local(2000, 1, 1, 11, 59, 59), 'must be on or after 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for time after restriction" do
|
||||||
|
valid!(:birth_time, Time.local(2000, 1, 1, 12, 00, 01))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for same time as restriction" do
|
||||||
|
valid!(:birth_time, Time.local(2000, 1, 1, 12, 0, 0))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for datetime type" do
|
||||||
|
before do
|
||||||
|
Person.validates_datetime :birth_datetime, :on_or_after => DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for datetime before restriction" do
|
||||||
|
invalid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 11, 59, 59), 'must be on or after 2010-01-01 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for same datetime as restriction" do
|
||||||
|
valid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for datetime after restriction" do
|
||||||
|
valid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 1))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
55
spec/validates_timeliness/validator/on_or_before_spec.rb
Normal file
55
spec/validates_timeliness/validator/on_or_before_spec.rb
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
RSpec.describe ValidatesTimeliness::Validator, ":on_or_before option" do
|
||||||
|
describe "for date type" do
|
||||||
|
before do
|
||||||
|
Person.validates_date :birth_date, :on_or_before => Date.new(2010, 1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for date after restriction" do
|
||||||
|
invalid!(:birth_date, Date.new(2010, 1, 2), 'must be on or before 2010-01-01')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for date before restriction" do
|
||||||
|
valid!(:birth_date, Date.new(2009, 12, 31))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for same date value" do
|
||||||
|
valid!(:birth_date, Date.new(2010, 1, 1))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for time type" do
|
||||||
|
before do
|
||||||
|
Person.validates_time :birth_time, :on_or_before => Time.mktime(2000, 1, 1, 12, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for time after restriction" do
|
||||||
|
invalid!(:birth_time, Time.local(2000, 1, 1, 12, 00, 01), 'must be on or before 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for time before restriction" do
|
||||||
|
valid!(:birth_time, Time.local(2000, 1, 1, 11, 59, 59))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for same time as restriction" do
|
||||||
|
valid!(:birth_time, Time.local(2000, 1, 1, 12, 0, 0))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for datetime type" do
|
||||||
|
before do
|
||||||
|
Person.validates_datetime :birth_datetime, :on_or_before => DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for datetime after restriction" do
|
||||||
|
invalid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 1), 'must be on or before 2010-01-01 12:00:00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid for same datetime as restriction" do
|
||||||
|
valid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 12, 0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid for datetime before restriction" do
|
||||||
|
valid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 11, 59, 59))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
258
spec/validates_timeliness/validator_spec.rb
Normal file
258
spec/validates_timeliness/validator_spec.rb
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
RSpec.describe ValidatesTimeliness::Validator do
|
||||||
|
before do
|
||||||
|
Timecop.freeze(Time.local(2010, 1, 1, 0, 0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Model.validates with :timeliness option" do
|
||||||
|
it 'should use plugin validator class' do
|
||||||
|
Person.validates :birth_date, :timeliness => {:is_at => Date.new(2010,1,1), :type => :date}
|
||||||
|
expect(Person.validators.select { |v| v.is_a?(ActiveModel::Validations::TimelinessValidator) }.size).to eq(1)
|
||||||
|
invalid!(:birth_date, Date.new(2010,1,2))
|
||||||
|
valid!(:birth_date, Date.new(2010,1,1))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should use default to :datetime type' do
|
||||||
|
Person.validates :birth_datetime, :timeliness => {:is_at => Time.mktime(2010,1,1)}
|
||||||
|
expect(Person.validators.first.type).to eq(:datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should add attribute to timeliness attributes set' do
|
||||||
|
expect(PersonWithShim.timeliness_validated_attributes).not_to include(:birth_time)
|
||||||
|
|
||||||
|
PersonWithShim.validates :birth_time, :timeliness => {:is_at => "12:30"}
|
||||||
|
|
||||||
|
expect(PersonWithShim.timeliness_validated_attributes).to include(:birth_time)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not be valid for value which not valid date or time value' do
|
||||||
|
Person.validates_date :birth_date
|
||||||
|
invalid!(:birth_date, "Not a date", 'is not a valid date')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not be valid attribute is type cast to nil but raw value is non-nil invalid value' do
|
||||||
|
Person.validates_date :birth_date, :allow_nil => true
|
||||||
|
record = Person.new
|
||||||
|
allow(record).to receive(:birth_date).and_return(nil)
|
||||||
|
allow(record).to receive(:read_timeliness_attribute_before_type_cast).and_return("Not a date")
|
||||||
|
expect(record).not_to be_valid
|
||||||
|
expect(record.errors[:birth_date].first).to eq('is not a valid date')
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ":allow_nil option" do
|
||||||
|
it 'should not allow nil by default' do
|
||||||
|
Person.validates_date :birth_date
|
||||||
|
invalid!(:birth_date, [nil], 'is not a valid date')
|
||||||
|
valid!(:birth_date, Date.today)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should allow nil when true' do
|
||||||
|
Person.validates_date :birth_date, :allow_nil => true
|
||||||
|
valid!(:birth_date, [nil])
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with raw value cache" do
|
||||||
|
it "should not be valid with an invalid format" do
|
||||||
|
PersonWithShim.validates_date :birth_date, :allow_nil => true
|
||||||
|
|
||||||
|
p = PersonWithShim.new
|
||||||
|
p.birth_date = 'bogus'
|
||||||
|
|
||||||
|
expect(p).not_to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ":allow_blank option" do
|
||||||
|
it 'should not allow blank by default' do
|
||||||
|
Person.validates_date :birth_date
|
||||||
|
invalid!(:birth_date, '', 'is not a valid date')
|
||||||
|
valid!(:birth_date, Date.today)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should allow blank when true' do
|
||||||
|
Person.validates_date :birth_date, :allow_blank => true
|
||||||
|
valid!(:birth_date, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with raw value cache" do
|
||||||
|
it "should not be valid with an invalid format" do
|
||||||
|
PersonWithShim.validates_date :birth_date, :allow_blank => true
|
||||||
|
|
||||||
|
p = PersonWithShim.new
|
||||||
|
p.birth_date = 'bogus'
|
||||||
|
|
||||||
|
expect(p).not_to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ':message options' do
|
||||||
|
it 'should allow message option too' do
|
||||||
|
Person.validates_date :birth_date, on_or_after: :today, message: 'cannot be in past'
|
||||||
|
invalid!(:birth_date, Date.today - 5.days, 'cannot be in past')
|
||||||
|
valid!(:birth_date, Date.today)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should first allow the defined message' do
|
||||||
|
Person.validates_date :birth_date, on_or_after: :today, on_or_after_message: 'cannot be in past', message: 'dummy message'
|
||||||
|
invalid!(:birth_date, Date.today - 5.days, 'cannot be in past')
|
||||||
|
valid!(:birth_date, Date.today)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ":between option" do
|
||||||
|
describe "array value" do
|
||||||
|
it 'should be split option into :on_or_after and :on_or_before values' do
|
||||||
|
on_or_after, on_or_before = Date.new(2010,1,1), Date.new(2010,1,2)
|
||||||
|
Person.validates_date :birth_date, :between => [on_or_after, on_or_before]
|
||||||
|
expect(Person.validators.first.options[:on_or_after]).to eq(on_or_after)
|
||||||
|
expect(Person.validators.first.options[:on_or_before]).to eq(on_or_before)
|
||||||
|
invalid!(:birth_date, on_or_after - 1, "must be on or after 2010-01-01")
|
||||||
|
invalid!(:birth_date, on_or_before + 1, "must be on or before 2010-01-02")
|
||||||
|
valid!(:birth_date, on_or_after)
|
||||||
|
valid!(:birth_date, on_or_before)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "range value" do
|
||||||
|
it 'should be split option into :on_or_after and :on_or_before values' do
|
||||||
|
on_or_after, on_or_before = Date.new(2010,1,1), Date.new(2010,1,2)
|
||||||
|
Person.validates_date :birth_date, :between => on_or_after..on_or_before
|
||||||
|
expect(Person.validators.first.options[:on_or_after]).to eq(on_or_after)
|
||||||
|
expect(Person.validators.first.options[:on_or_before]).to eq(on_or_before)
|
||||||
|
invalid!(:birth_date, on_or_after - 1, "must be on or after 2010-01-01")
|
||||||
|
invalid!(:birth_date, on_or_before + 1, "must be on or before 2010-01-02")
|
||||||
|
valid!(:birth_date, on_or_after)
|
||||||
|
valid!(:birth_date, on_or_before)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "range with excluded end value" do
|
||||||
|
it 'should be split option into :on_or_after and :before values' do
|
||||||
|
on_or_after, before = Date.new(2010,1,1), Date.new(2010,1,3)
|
||||||
|
Person.validates_date :birth_date, :between => on_or_after...before
|
||||||
|
expect(Person.validators.first.options[:on_or_after]).to eq(on_or_after)
|
||||||
|
expect(Person.validators.first.options[:before]).to eq(before)
|
||||||
|
invalid!(:birth_date, on_or_after - 1, "must be on or after 2010-01-01")
|
||||||
|
invalid!(:birth_date, before, "must be before 2010-01-03")
|
||||||
|
valid!(:birth_date, on_or_after)
|
||||||
|
valid!(:birth_date, before - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ":ignore_usec option" do
|
||||||
|
it "should not be valid when usec values don't match and option is false" do
|
||||||
|
Person.validates_datetime :birth_datetime, :on_or_before => Time.utc(2010,1,2,3,4,5), :ignore_usec => false
|
||||||
|
invalid!(:birth_datetime, Time.utc(2010,1,2,3,4,5,10000))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid when usec values dont't match and option is true" do
|
||||||
|
Person.validates_datetime :birth_datetime, :on_or_before => Time.utc(2010,1,2,3,4,5), :ignore_usec => true
|
||||||
|
valid!(:birth_datetime, Time.utc(2010,1,2,3,4,5,10000))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ":format option" do
|
||||||
|
class PersonWithFormatOption
|
||||||
|
include TestModel
|
||||||
|
include TestModelShim
|
||||||
|
attribute :birth_date, :date
|
||||||
|
attribute :birth_time, :time
|
||||||
|
attribute :birth_datetime, :datetime
|
||||||
|
validates_date :birth_date, :format => 'dd-mm-yyyy'
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:person) { PersonWithFormatOption.new }
|
||||||
|
|
||||||
|
with_config(:use_plugin_parser, true)
|
||||||
|
|
||||||
|
it "should be valid when value matches format" do
|
||||||
|
person.birth_date = '11-12-1913'
|
||||||
|
person.valid?
|
||||||
|
expect(person.errors[:birth_date]).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid when value does not match format" do
|
||||||
|
person.birth_date = '1913-12-11'
|
||||||
|
person.valid?
|
||||||
|
expect(person.errors[:birth_date]).to include('is not a valid date')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "restriction value errors" do
|
||||||
|
let(:person) { Person.new(:birth_date => Date.today) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Person.validates_time :birth_date, :is_at => lambda { raise }, :before => lambda { raise }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be added when ignore_restriction_errors is false" do
|
||||||
|
with_config(:ignore_restriction_errors, false) do
|
||||||
|
person.valid?
|
||||||
|
expect(person.errors[:birth_date].first).to match("Error occurred validating birth_date")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be added when ignore_restriction_errors is true" do
|
||||||
|
with_config(:ignore_restriction_errors, true) do
|
||||||
|
person.valid?
|
||||||
|
expect(person.errors[:birth_date]).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should exit on first error' do
|
||||||
|
with_config(:ignore_restriction_errors, false) do
|
||||||
|
person.valid?
|
||||||
|
expect(person.errors[:birth_date].size).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#format_error_value" do
|
||||||
|
describe "default" do
|
||||||
|
it 'should format date error value as yyyy-mm-dd' do
|
||||||
|
validator = ValidatesTimeliness::Validator.new(:attributes => [:birth_date], :type => :date)
|
||||||
|
expect(validator.format_error_value(Date.new(2010,1,1))).to eq('2010-01-01')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should format time error value as hh:nn:ss' do
|
||||||
|
validator = ValidatesTimeliness::Validator.new(:attributes => [:birth_time], :type => :time)
|
||||||
|
expect(validator.format_error_value(Time.mktime(2010,1,1,12,34,56))).to eq('12:34:56')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should format datetime error value as yyyy-mm-dd hh:nn:ss' do
|
||||||
|
validator = ValidatesTimeliness::Validator.new(:attributes => [:birth_datetime], :type => :datetime)
|
||||||
|
expect(validator.format_error_value(Time.mktime(2010,1,1,12,34,56))).to eq('2010-01-01 12:34:56')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with missing translation" do
|
||||||
|
before :all do
|
||||||
|
I18n.locale = :es
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should use the default format for the type' do
|
||||||
|
validator = ValidatesTimeliness::Validator.new(:attributes => [:birth_date], :type => :date)
|
||||||
|
expect(validator.format_error_value(Date.new(2010,1,1))).to eq('2010-01-01')
|
||||||
|
end
|
||||||
|
|
||||||
|
after :all do
|
||||||
|
I18n.locale = :en
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "custom error message" do
|
||||||
|
it 'should be used for invalid type' do
|
||||||
|
Person.validates_date :birth_date, :invalid_date_message => 'custom invalid message'
|
||||||
|
invalid!(:birth_date, 'asdf', 'custom invalid message')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should be used for invalid restriction' do
|
||||||
|
Person.validates_date :birth_date, :before => Time.now, :before_message => 'custom before message'
|
||||||
|
invalid!(:birth_date, Time.now, 'custom before message')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
41
spec/validates_timeliness_spec.rb
Normal file
41
spec/validates_timeliness_spec.rb
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
RSpec.describe ValidatesTimeliness do
|
||||||
|
|
||||||
|
it 'should alias use_euro_formats to remove_us_formats on Timeliness gem' do
|
||||||
|
expect(Timeliness).to respond_to(:remove_us_formats)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should alias to date_for_time_type to dummy_date_for_time_type on Timeliness gem' do
|
||||||
|
expect(Timeliness).to respond_to(:dummy_date_for_time_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "config" do
|
||||||
|
it 'should delegate default_timezone to Timeliness gem' do
|
||||||
|
expect(Timeliness).to receive(:default_timezone=)
|
||||||
|
ValidatesTimeliness.default_timezone = :utc
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should delegate dummy_date_for_time_type to Timeliness gem' do
|
||||||
|
expect(Timeliness).to receive(:dummy_date_for_time_type)
|
||||||
|
expect(Timeliness).to receive(:dummy_date_for_time_type=)
|
||||||
|
array = ValidatesTimeliness.dummy_date_for_time_type
|
||||||
|
ValidatesTimeliness.dummy_date_for_time_type = array
|
||||||
|
end
|
||||||
|
|
||||||
|
context "parser" do
|
||||||
|
it 'should delegate add_formats to Timeliness gem' do
|
||||||
|
expect(Timeliness).to receive(:add_formats)
|
||||||
|
ValidatesTimeliness.parser.add_formats
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should delegate remove_formats to Timeliness gem' do
|
||||||
|
expect(Timeliness).to receive(:remove_formats)
|
||||||
|
ValidatesTimeliness.parser.remove_formats
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should delegate remove_us_formats to Timeliness gem' do
|
||||||
|
expect(Timeliness).to receive(:remove_us_formats)
|
||||||
|
ValidatesTimeliness.parser.remove_us_formats
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,61 +0,0 @@
|
|||||||
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,509 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
it "should return array of Time objects when restriction is array of Time objects" do
|
|
||||||
time1, time2 = Time.now, 1.day.ago
|
|
||||||
restriction_value([time1, time2], :datetime).should == [time2, time1]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return array of Time objects when restriction is array of strings" do
|
|
||||||
time1, time2 = "2000-01-02", "2000-01-01"
|
|
||||||
restriction_value([time1, time2], :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return array of Time objects when restriction is Range of Time objects" do
|
|
||||||
time1, time2 = Time.now, 1.day.ago
|
|
||||||
restriction_value(time1..time2, :datetime).should == [time2, time1]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return array of Time objects when restriction is Range of time strings" do
|
|
||||||
time1, time2 = "2000-01-02", "2000-01-01"
|
|
||||||
restriction_value(time1..time2, :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
|
|
||||||
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 between restriction" do
|
|
||||||
|
|
||||||
describe "for datetime type" do
|
|
||||||
before do
|
|
||||||
configure_validator(:between => [1.day.ago.at_midnight, 1.day.from_now.at_midnight])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when value is before earlist :between restriction" do
|
|
||||||
validate_with(:birth_date_and_time, 2.days.ago)
|
|
||||||
should_have_error(:birth_date_and_time, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when value is after latest :between restriction" do
|
|
||||||
validate_with(:birth_date_and_time, 2.days.from_now)
|
|
||||||
should_have_error(:birth_date_and_time, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid when value is equal to earliest :between restriction" do
|
|
||||||
validate_with(:birth_date_and_time, 1.day.ago.at_midnight)
|
|
||||||
should_have_no_error(:birth_date_and_time, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid when value is equal to latest :between restriction" do
|
|
||||||
validate_with(:birth_date_and_time, 1.day.from_now.at_midnight)
|
|
||||||
should_have_no_error(:birth_date_and_time, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should allow a range for between restriction" do
|
|
||||||
configure_validator(:type => :datetime, :between => (1.day.ago.at_midnight)..(1.day.from_now.at_midnight))
|
|
||||||
validate_with(:birth_date_and_time, 1.day.from_now.at_midnight)
|
|
||||||
should_have_no_error(:birth_date_and_time, :between)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "for date type" do
|
|
||||||
before do
|
|
||||||
configure_validator(:type => :date, :between => [1.day.ago.to_date, 1.day.from_now.to_date])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when value is before earlist :between restriction" do
|
|
||||||
validate_with(:birth_date, 2.days.ago.to_date)
|
|
||||||
should_have_error(:birth_date, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when value is after latest :between restriction" do
|
|
||||||
validate_with(:birth_date, 2.days.from_now.to_date)
|
|
||||||
should_have_error(:birth_date, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid when value is equal to earliest :between restriction" do
|
|
||||||
validate_with(:birth_date, 1.day.ago.to_date)
|
|
||||||
should_have_no_error(:birth_date, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid when value is equal to latest :between restriction" do
|
|
||||||
validate_with(:birth_date, 1.day.from_now.to_date)
|
|
||||||
should_have_no_error(:birth_date, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should allow a range for between restriction" do
|
|
||||||
configure_validator(:type => :date, :between => (1.day.ago.to_date)..(1.day.from_now.to_date))
|
|
||||||
validate_with(:birth_date, 1.day.from_now.to_date)
|
|
||||||
should_have_no_error(:birth_date, :between)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "for time type" do
|
|
||||||
before do
|
|
||||||
configure_validator(:type => :time, :between => ["09:00", "17:00"])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when value is before earlist :between restriction" do
|
|
||||||
validate_with(:birth_time, "08:59")
|
|
||||||
should_have_error(:birth_time, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have error when value is after latest :between restriction" do
|
|
||||||
validate_with(:birth_time, "17:01")
|
|
||||||
should_have_error(:birth_time, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid when value is equal to earliest :between restriction" do
|
|
||||||
validate_with(:birth_time, "09:00")
|
|
||||||
should_have_no_error(:birth_time, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be valid when value is equal to latest :between restriction" do
|
|
||||||
validate_with(:birth_time, "17:00")
|
|
||||||
should_have_no_error(:birth_time, :between)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should allow a range for between restriction" do
|
|
||||||
configure_validator(:type => :time, :between => "09:00".."17:00")
|
|
||||||
validate_with(:birth_time, "17:00")
|
|
||||||
should_have_no_error(:birth_time, :between)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "instance with 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 "custom_error_messages" do
|
|
||||||
it "should return hash of custom error messages from configuration with _message truncated from keys" do
|
|
||||||
configure_validator(:type => :date, :invalid_date_message => 'thats no date')
|
|
||||||
validator.send(:custom_error_messages)[:invalid_date].should == 'thats no date'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return empty hash if no custom error messages in configuration" do
|
|
||||||
configure_validator(:type => :date)
|
|
||||||
validator.send(:custom_error_messages).should be_empty
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "interpolation_values" do
|
|
||||||
if defined?(I18n)
|
|
||||||
it "should return hash of interpolation keys with restriction values" do
|
|
||||||
before = '1900-01-01'
|
|
||||||
configure_validator(:type => :date, :before => before)
|
|
||||||
validator.send(:interpolation_values, :before, before.to_date).should == {:restriction => before}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return empty hash if no interpolation keys are in message" do
|
|
||||||
before = '1900-01-01'
|
|
||||||
configure_validator(:type => :date, :before => before, :before_message => 'too late')
|
|
||||||
validator.send(:interpolation_values, :before, before.to_date).should be_empty
|
|
||||||
end
|
|
||||||
else
|
|
||||||
it "should return array of interpolation values" do
|
|
||||||
before = '1900-01-01'
|
|
||||||
configure_validator(:type => :date, :before => before)
|
|
||||||
validator.send(:interpolation_values, :before, before.to_date).should == [before]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "restriction errors" do
|
|
||||||
before :each do
|
|
||||||
configure_validator(:type => :date, :before => lambda { raise })
|
|
||||||
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.sub(/ (\%s|\{\{\w*\}\}).*/, ''); h }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,31 +1,21 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
|
$:.push File.expand_path("../lib", __FILE__)
|
||||||
|
require "validates_timeliness/version"
|
||||||
|
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = %q{validates_timeliness}
|
s.name = "validates_timeliness"
|
||||||
s.version = "1.1.3"
|
s.version = ValidatesTimeliness::VERSION
|
||||||
|
s.authors = ["Adam Meehan"]
|
||||||
|
s.summary = %q{Date and time validation plugin for Rails which allows custom formats}
|
||||||
|
s.description = %q{Adds validation methods to ActiveModel for validating dates and times. Works with multiple ORMS.}
|
||||||
|
s.email = %q{adam.meehan@gmail.com}
|
||||||
|
s.homepage = %q{http://github.com/adzap/validates_timeliness}
|
||||||
|
s.license = "MIT"
|
||||||
|
|
||||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
s.require_paths = ["lib"]
|
||||||
s.authors = ["Adam Meehan"]
|
s.files = `git ls-files`.split("\n") - %w{ .gitignore .rspec Gemfile Gemfile.lock autotest/discover.rb Appraisals Travis.yml } - Dir['gemsfiles/*']
|
||||||
s.autorequire = %q{validates_timeliness}
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||||
s.date = %q{2009-01-13}
|
s.extra_rdoc_files = ["README.rdoc", "CHANGELOG.rdoc", "LICENSE"]
|
||||||
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
|
s.add_runtime_dependency(%q<timeliness>, [">= 0.3.10", "< 1"])
|
||||||
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
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user