mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-25 15:22:58 +00:00
Compare commits
38 Commits
activemode
...
rails-61
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af3e6ff774 | ||
|
|
f25ab5671c | ||
|
|
a0adda3eb2 | ||
|
|
709d2c87e2 | ||
|
|
16221ac092 | ||
|
|
c0c42edd3f | ||
|
|
f8b91e9cea | ||
|
|
7fa4d85ee3 | ||
|
|
70307293c6 | ||
|
|
f42e905cd1 | ||
|
|
35eb50aa40 | ||
|
|
3134072f01 | ||
|
|
797ba48036 | ||
|
|
f8e6480f58 | ||
|
|
3835b2b161 | ||
|
|
00d038bc6f | ||
|
|
a3d0182b5e | ||
|
|
46296f914f | ||
|
|
4447361743 | ||
|
|
4523138c3c | ||
|
|
72807b87a7 | ||
|
|
9daf12c4a1 | ||
|
|
2a80683683 | ||
|
|
3a2882be75 | ||
|
|
bf1c808846 | ||
|
|
d0fcf754ec | ||
|
|
7233ff66dd | ||
|
|
f0ba09f278 | ||
|
|
80ee285b3a | ||
|
|
cf20576253 | ||
|
|
f39fbb0ad2 | ||
|
|
67106b0212 | ||
|
|
bc6d4fe5fc | ||
|
|
70335cd0ba | ||
|
|
af406cdedc | ||
|
|
837de0d212 | ||
|
|
116930d633 | ||
|
|
f025d58073 |
40
.travis.yml
40
.travis.yml
@@ -1,3 +1,5 @@
|
||||
dist: focal
|
||||
os: linux
|
||||
language: ruby
|
||||
before_install: gem install bundler
|
||||
cache: bundler
|
||||
@@ -6,9 +8,45 @@ gemfile:
|
||||
- gemfiles/rails_5_0.gemfile
|
||||
- gemfiles/rails_5_1.gemfile
|
||||
- gemfiles/rails_5_2.gemfile
|
||||
- gemfiles/rails_6_0.gemfile
|
||||
- gemfiles/rails_6_1.gemfile
|
||||
- gemfiles/rails_edge.gemfile
|
||||
|
||||
rvm:
|
||||
- "2.5.1"
|
||||
- "2.5.8"
|
||||
- "2.6.6"
|
||||
- "2.7.2"
|
||||
- "3.0.0"
|
||||
- ruby-head
|
||||
|
||||
jobs:
|
||||
allow_failures:
|
||||
- rvm: ruby-head
|
||||
- gemfile: gemfiles/rails_edge.gemfile
|
||||
exclude:
|
||||
- rvm: 2.5.8
|
||||
gemfile: gemfiles/rails_edge.gemfile
|
||||
- rvm: 2.6.6
|
||||
gemfile: gemfiles/rails_edge.gemfile
|
||||
- rvm: 2.7.2
|
||||
gemfile: gemfiles/rails_5_0.gemfile
|
||||
- rvm: 2.7.2
|
||||
gemfile: gemfiles/rails_5_1.gemfile
|
||||
- rvm: 2.7.2
|
||||
gemfile: gemfiles/rails_5_2.gemfile
|
||||
- rvm: 3.0.0
|
||||
gemfile: gemfiles/rails_5_0.gemfile
|
||||
- rvm: 3.0.0
|
||||
gemfile: gemfiles/rails_5_1.gemfile
|
||||
- rvm: 3.0.0
|
||||
gemfile: gemfiles/rails_5_2.gemfile
|
||||
- rvm: ruby-head
|
||||
gemfile: gemfiles/rails_5_0.gemfile
|
||||
- rvm: ruby-head
|
||||
gemfile: gemfiles/rails_5_1.gemfile
|
||||
- rvm: ruby-head
|
||||
gemfile: gemfiles/rails_5_2.gemfile
|
||||
fast_finish: true
|
||||
|
||||
script: 'bundle exec rspec'
|
||||
|
||||
|
||||
15
Appraisals
15
Appraisals
@@ -1,5 +1,6 @@
|
||||
appraise "rails_5_0" do
|
||||
gem "rails", "~> 5.0.0"
|
||||
gem 'sqlite3', '~> 1.3.6'
|
||||
end
|
||||
|
||||
appraise "rails_5_1" do
|
||||
@@ -8,4 +9,16 @@ end
|
||||
|
||||
appraise "rails_5_2" do
|
||||
gem "rails", "~> 5.2.0"
|
||||
end
|
||||
end
|
||||
|
||||
appraise "rails_6_0" do
|
||||
gem "rails", "~> 6.0.0"
|
||||
end
|
||||
|
||||
appraise "rails_6_1" do
|
||||
gem "rails", "~> 6.1.0"
|
||||
end
|
||||
|
||||
appraise "rails_edge" do
|
||||
gem "rails", git: "https://github.com/rails/rails.git", branch: "main"
|
||||
end
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
= [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'.
|
||||
* Add Ruby 3 compatibility
|
||||
* Add Rails 6.1 compatibility
|
||||
|
||||
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
|
||||
|
||||
|
||||
8
Gemfile
8
Gemfile
@@ -2,15 +2,11 @@ source 'http://rubygems.org'
|
||||
|
||||
gemspec
|
||||
|
||||
gem 'rails', '~> 5.0.0'
|
||||
gem 'rails', '~> 6.1.0'
|
||||
gem 'rspec'
|
||||
gem 'rspec-rails', '~> 3.7'
|
||||
gem 'sqlite3'
|
||||
gem 'timecop'
|
||||
gem 'byebug'
|
||||
gem 'appraisal'
|
||||
gem 'sqlite3'
|
||||
gem 'nokogiri', '~> 1.8'
|
||||
|
||||
group :active_record do
|
||||
gem 'sqlite3-ruby', :require => 'sqlite3'
|
||||
end
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2008-2010 Adam Meehan
|
||||
Copyright (c) 2008-2021 Adam Meehan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
353
README.md
Normal file
353
README.md
Normal file
@@ -0,0 +1,353 @@
|
||||
# ValidatesTimeliness [](https://travis-ci.org/adzap/validates_timeliness)
|
||||
|
||||
* Source: https://github.com/adzap/validates_timeliness
|
||||
* Issues: https://github.com/adzap/validates_timeliness/issues
|
||||
|
||||
|
||||
## Description
|
||||
|
||||
Complete validation of dates, times and datetimes for Rails 5.x, 6.x, and
|
||||
ActiveModel.
|
||||
|
||||
If you a looking for the old version for Rails 4.x go here
|
||||
[https://github.com/adzap/validates_timeliness/tree/4-0-stable].
|
||||
|
||||
## Features
|
||||
|
||||
* Adds validation for dates, times and datetimes to ActiveModel
|
||||
* Handles timezones and type casting of values for you
|
||||
* Only Rails date/time validation plugin offering complete validation (See
|
||||
ORM/ODM support)
|
||||
* Uses extensible date/time parser (Using [timeliness
|
||||
gem](https://github.com/adzap/timeliness). See Plugin Parser)
|
||||
* Adds extensions to fix Rails date/time select issues (See Extensions)
|
||||
* Supports I18n for the error messages. For multi-language support try
|
||||
[timeliness-i18n gem](https://github.com/pedrofurtado/timeliness-i18n).
|
||||
* Supports all the Rubies (that any sane person would be using in
|
||||
production).
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
# in Gemfile
|
||||
gem 'validates_timeliness', '~> 5.0.0.beta1'
|
||||
|
||||
# Run bundler
|
||||
$ bundle install
|
||||
```
|
||||
|
||||
Then run
|
||||
|
||||
```sh
|
||||
$ rails generate validates_timeliness:install
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Examples
|
||||
|
||||
```ruby
|
||||
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
|
||||
|
||||
```ruby
|
||||
class Person < ActiveRecord::Base
|
||||
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
|
||||
```
|
||||
|
||||
or even on a specific record, per ActiveModel API.
|
||||
|
||||
```ruby
|
||||
@person.validates_date :date_of_birth, on_or_before: lambda { Date.current }
|
||||
```
|
||||
|
||||
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 valid range of dates or times allowed
|
||||
|
||||
Temporal options (or restrictions):
|
||||
|
||||
```
|
||||
:is_at - Attribute must be equal to value to be valid
|
||||
:before - Attribute must be before this value to be valid
|
||||
:on_or_before - Attribute must be equal to or before this value to be valid
|
||||
:after - Attribute must be after this value to be valid
|
||||
:on_or_after - Attribute must be equal to or after this value to be valid
|
||||
:between - Attribute must be between the values to be valid. Range or Array of 2 values.
|
||||
```
|
||||
|
||||
Regular validation options:
|
||||
|
||||
```
|
||||
:allow_nil - Allow a nil value to be valid
|
||||
:allow_blank - Allows a nil or empty string value to be valid
|
||||
:if - Execute validation when :if evaluates true
|
||||
:unless - Execute validation when :unless evaluates false
|
||||
: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
|
||||
|
||||
```ruby
|
||||
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](https://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:
|
||||
|
||||
```yaml
|
||||
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:
|
||||
|
||||
```ruby
|
||||
:invalid_date_message
|
||||
:invalid_time_message
|
||||
:invalid_datetime_message
|
||||
:is_at_message
|
||||
:before_message
|
||||
:on_or_before_message
|
||||
:after_message
|
||||
:on_or_after_message
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
It is highly recommended you use the I18n system for error messages.
|
||||
|
||||
### Plugin Parser
|
||||
|
||||
The plugin uses the [timeliness gem](https://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:
|
||||
|
||||
```ruby
|
||||
# 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](https://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. `lambda { Time.current }`. 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:
|
||||
|
||||
```ruby
|
||||
validates_date :birth_date, on_or_before: :today
|
||||
```
|
||||
|
||||
The `:today` symbol is evaluated as `lambda { Date.current }`. The `:now` and
|
||||
`:today` symbols are pre-configured. Configure your own like so:
|
||||
|
||||
```ruby
|
||||
# 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.
|
||||
|
||||
```ruby
|
||||
# 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
|
||||
|
||||
```ruby
|
||||
# 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 option value itself may be invalid. This will add an error to the
|
||||
model such as 'Error occurred validating birth_date for :before restriction'.
|
||||
These can be annoying in development or production as you most likely just
|
||||
want to skip the option if no valid value was returned. By default these
|
||||
errors are displayed in Rails test mode.
|
||||
|
||||
To turn them on/off:
|
||||
|
||||
```ruby
|
||||
# in the setup block
|
||||
config.ignore_restriction_errors = true
|
||||
```
|
||||
|
||||
## Extensions
|
||||
|
||||
### Strict Parsing for Select Helpers
|
||||
|
||||
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:
|
||||
|
||||
```ruby
|
||||
# in the setup block
|
||||
config.enable_multiparameter_extension!
|
||||
```
|
||||
|
||||
### Display Invalid Values in Select Helpers
|
||||
|
||||
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.
|
||||
|
||||
To activate it, uncomment this line in the initializer:
|
||||
|
||||
```ruby
|
||||
# in the setup block
|
||||
config.enable_date_time_select_extension!
|
||||
```
|
||||
|
||||
## Contributors
|
||||
|
||||
To see the generous people who have contributed code, take a look at the
|
||||
[contributors
|
||||
list](https://github.com/adzap/validates_timeliness/contributors).
|
||||
|
||||
## Maintainers
|
||||
|
||||
* [Adam Meehan](https://github.com/adzap)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2008 Adam Meehan, released under the MIT license
|
||||
299
README.rdoc
299
README.rdoc
@@ -1,299 +0,0 @@
|
||||
= 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
|
||||
* Issues: http://github.com/adzap/validates_timeliness/issues
|
||||
|
||||
== Description
|
||||
|
||||
Complete validation of dates, times and datetimes for Rails 5.0.x and ActiveModel.
|
||||
|
||||
If you a looking for the old version for Rails 4.x go here [https://github.com/adzap/validates_timeliness/tree/4-0-stable].
|
||||
|
||||
|
||||
== Features
|
||||
|
||||
* Adds validation for dates, times and datetimes to ActiveModel
|
||||
|
||||
* Handles timezones and type casting of values for you
|
||||
|
||||
* Only Rails date/time validation plugin offering complete validation (See ORM/ODM support)
|
||||
|
||||
* Uses extensible date/time parser (Using {timeliness gem}[http://github.com/adzap/timeliness]. See Plugin Parser)
|
||||
|
||||
* Adds extensions to fix Rails date/time select issues (See Extensions)
|
||||
|
||||
* Supports I18n for the error messages. For multi-language support try {timeliness-i18n gem}[https://github.com/pedrofurtado/timeliness-i18n].
|
||||
|
||||
* Supports all the Rubies (that any sane person would be using in production).
|
||||
|
||||
|
||||
== Installation
|
||||
|
||||
# in Gemfile
|
||||
gem 'validates_timeliness', '~> 5.0.0.pre'
|
||||
|
||||
# Run bundler
|
||||
$ bundle install
|
||||
|
||||
Then run
|
||||
|
||||
$ rails generate validates_timeliness:install
|
||||
|
||||
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.
|
||||
|
||||
|
||||
== Examples
|
||||
|
||||
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
|
||||
|
||||
class Person < ActiveRecord::Base
|
||||
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
|
||||
|
||||
or even on a specific record, per ActiveModel API.
|
||||
|
||||
@person.validates_date :date_of_birth, :on_or_before => lambda { Date.current }
|
||||
|
||||
|
||||
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 valid range of dates or times allowed
|
||||
|
||||
Temporal options (or restrictions):
|
||||
:is_at - Attribute must be equal to value to be valid
|
||||
:before - Attribute must be before this value to be valid
|
||||
:on_or_before - Attribute must be equal to or before this value to be valid
|
||||
:after - Attribute must be after this value to be valid
|
||||
:on_or_after - Attribute must be equal to or after this value to be valid
|
||||
:between - Attribute must be between the values to be valid. Range or Array of 2 values.
|
||||
|
||||
Regular validation options:
|
||||
:allow_nil - Allow a nil value to be valid
|
||||
:allow_blank - Allows a nil or empty string value to be valid
|
||||
:if - Execute validation when :if evaluates true
|
||||
:unless - Execute validation when :unless evaluates false
|
||||
: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:
|
||||
|
||||
:invalid_date_message
|
||||
:invalid_time_message
|
||||
:invalid_datetime_message
|
||||
:is_at_message
|
||||
:before_message
|
||||
:on_or_before_message
|
||||
:after_message
|
||||
:on_or_after_message
|
||||
|
||||
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.
|
||||
|
||||
It is highly recommended you use the I18n system for error messages.
|
||||
|
||||
|
||||
=== 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
|
||||
option value itself may be invalid. This will add an error to the model such as
|
||||
'Error occurred validating birth_date for :before restriction'. These can be annoying
|
||||
in development or production as you most likely just want to skip the option if no
|
||||
valid value was returned. By default these errors are displayed in Rails test mode.
|
||||
|
||||
To turn them on/off:
|
||||
|
||||
# in the setup block
|
||||
config.ignore_restriction_errors = true
|
||||
|
||||
|
||||
== Extensions
|
||||
|
||||
=== Strict Parsing for Select Helpers
|
||||
|
||||
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!
|
||||
|
||||
|
||||
=== Display Invalid Values in Select Helpers
|
||||
|
||||
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.
|
||||
|
||||
To activate it, uncomment this line in the initializer:
|
||||
|
||||
# in the setup block
|
||||
config.enable_date_time_select_extension!
|
||||
|
||||
|
||||
== 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
|
||||
3
Rakefile
3
Rakefile
@@ -19,10 +19,11 @@ end
|
||||
|
||||
desc 'Generate documentation for plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.main = 'README.md'
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'ValidatesTimeliness'
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('README.md')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
|
||||
|
||||
@@ -5,14 +5,10 @@ source "http://rubygems.org"
|
||||
gem "rails", "~> 5.0.0"
|
||||
gem "rspec"
|
||||
gem "rspec-rails", "~> 3.7"
|
||||
gem "sqlite3", "~> 1.3.6"
|
||||
gem "timecop"
|
||||
gem "byebug"
|
||||
gem "appraisal"
|
||||
gem "sqlite3"
|
||||
gem "nokogiri", "~> 1.8"
|
||||
|
||||
group :active_record do
|
||||
gem "sqlite3-ruby", require: "sqlite3"
|
||||
end
|
||||
|
||||
gemspec path: "../"
|
||||
|
||||
@@ -5,14 +5,10 @@ source "http://rubygems.org"
|
||||
gem "rails", "~> 5.1.0"
|
||||
gem "rspec"
|
||||
gem "rspec-rails", "~> 3.7"
|
||||
gem "sqlite3"
|
||||
gem "timecop"
|
||||
gem "byebug"
|
||||
gem "appraisal"
|
||||
gem "sqlite3"
|
||||
gem "nokogiri", "~> 1.8"
|
||||
|
||||
group :active_record do
|
||||
gem "sqlite3-ruby", require: "sqlite3"
|
||||
end
|
||||
|
||||
gemspec path: "../"
|
||||
|
||||
@@ -5,14 +5,10 @@ source "http://rubygems.org"
|
||||
gem "rails", "~> 5.2.0"
|
||||
gem "rspec"
|
||||
gem "rspec-rails", "~> 3.7"
|
||||
gem "sqlite3"
|
||||
gem "timecop"
|
||||
gem "byebug"
|
||||
gem "appraisal"
|
||||
gem "sqlite3"
|
||||
gem "nokogiri", "~> 1.8"
|
||||
|
||||
group :active_record do
|
||||
gem "sqlite3-ruby", require: "sqlite3"
|
||||
end
|
||||
|
||||
gemspec path: "../"
|
||||
|
||||
14
gemfiles/rails_6_0.gemfile
Normal file
14
gemfiles/rails_6_0.gemfile
Normal file
@@ -0,0 +1,14 @@
|
||||
# This file was generated by Appraisal
|
||||
|
||||
source "http://rubygems.org"
|
||||
|
||||
gem "rails", "~> 6.0.0"
|
||||
gem "rspec"
|
||||
gem "rspec-rails", "~> 3.7"
|
||||
gem "sqlite3"
|
||||
gem "timecop"
|
||||
gem "byebug"
|
||||
gem "appraisal"
|
||||
gem "nokogiri", "~> 1.8"
|
||||
|
||||
gemspec path: "../"
|
||||
14
gemfiles/rails_6_1.gemfile
Normal file
14
gemfiles/rails_6_1.gemfile
Normal file
@@ -0,0 +1,14 @@
|
||||
# This file was generated by Appraisal
|
||||
|
||||
source "http://rubygems.org"
|
||||
|
||||
gem "rails", "~> 6.1.0"
|
||||
gem "rspec"
|
||||
gem "rspec-rails", "~> 3.7"
|
||||
gem "sqlite3"
|
||||
gem "timecop"
|
||||
gem "byebug"
|
||||
gem "appraisal"
|
||||
gem "nokogiri", "~> 1.8"
|
||||
|
||||
gemspec path: "../"
|
||||
14
gemfiles/rails_edge.gemfile
Normal file
14
gemfiles/rails_edge.gemfile
Normal file
@@ -0,0 +1,14 @@
|
||||
# This file was generated by Appraisal
|
||||
|
||||
source "http://rubygems.org"
|
||||
|
||||
gem "rails", git: "https://github.com/rails/rails.git", branch: "main"
|
||||
gem "rspec"
|
||||
gem "rspec-rails", "~> 3.7"
|
||||
gem "sqlite3"
|
||||
gem "timecop"
|
||||
gem "byebug"
|
||||
gem "appraisal"
|
||||
gem "nokogiri", "~> 1.8"
|
||||
|
||||
gemspec path: "../"
|
||||
@@ -36,8 +36,8 @@ module ValidatesTimeliness
|
||||
|
||||
# Shorthand time and date symbols for restrictions
|
||||
self.restriction_shorthand_symbols = {
|
||||
:now => lambda { Time.current },
|
||||
:today => lambda { Date.current }
|
||||
now: proc { Time.current },
|
||||
today: proc { Date.current }
|
||||
}
|
||||
|
||||
# Use the plugin date/time parser which is stricter and extensible
|
||||
|
||||
@@ -9,36 +9,36 @@ module ValidatesTimeliness
|
||||
end
|
||||
end
|
||||
|
||||
ActiveModel::Type::Date.class_eval do
|
||||
# Module.new do |m|
|
||||
def cast_value(value)
|
||||
return super unless ValidatesTimeliness.use_plugin_parser
|
||||
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
|
||||
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.tap { |mod| include mod }
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
ActiveModel::Type::Time.class_eval do
|
||||
ActiveModel::Type::Time.prepend Module.new {
|
||||
def user_input_in_time_zone(value)
|
||||
if value.is_a?(String) && ValidatesTimeliness.use_plugin_parser
|
||||
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
|
||||
end
|
||||
}
|
||||
|
||||
ActiveModel::Type::DateTime.class_eval do
|
||||
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)
|
||||
@@ -46,4 +46,4 @@ ActiveModel::Type::DateTime.class_eval do
|
||||
value.in_time_zone
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
@@ -43,7 +43,7 @@ module ValidatesTimeliness
|
||||
values[POSITION.key(position.to_i)] = value.to_i
|
||||
end
|
||||
|
||||
DateTimeValue.new(values)
|
||||
DateTimeValue.new(**values)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,7 +11,7 @@ module ValidatesTimeliness
|
||||
end
|
||||
end
|
||||
|
||||
class ActiveRecord::Base
|
||||
ActiveSupport.on_load(:active_record) do
|
||||
include ValidatesTimeliness::AttributeMethods
|
||||
include ValidatesTimeliness::ORM::ActiveRecord
|
||||
end
|
||||
|
||||
@@ -11,5 +11,13 @@ module ValidatesTimeliness
|
||||
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
|
||||
|
||||
@@ -86,7 +86,7 @@ module ValidatesTimeliness
|
||||
def add_error(record, attr_name, message, value=nil)
|
||||
value = format_error_value(value) if value
|
||||
message_options = { :message => options.fetch(:"#{message}_message", options[:message]), :restriction => value }
|
||||
record.errors.add(attr_name, message, message_options)
|
||||
record.errors.add(attr_name, message, **message_options)
|
||||
end
|
||||
|
||||
def format_error_value(value)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module ValidatesTimeliness
|
||||
VERSION = '5.0.0-pre'
|
||||
VERSION = '5.0.0.beta2'
|
||||
end
|
||||
|
||||
@@ -10,6 +10,8 @@ require 'timecop'
|
||||
require 'validates_timeliness'
|
||||
require 'validates_timeliness/orm/active_model'
|
||||
|
||||
require 'rails/railtie'
|
||||
|
||||
require 'support/test_model'
|
||||
require 'support/model_helpers'
|
||||
require 'support/config_helper'
|
||||
|
||||
@@ -17,8 +17,7 @@ module ModelHelpers
|
||||
|
||||
def with_each_person_value(attr_name, values)
|
||||
record = Person.new
|
||||
values = [values] unless values.is_a?(Array)
|
||||
values.each do |value|
|
||||
Array.wrap(values).each do |value|
|
||||
record.send("#{attr_name}=", value)
|
||||
yield record, value
|
||||
end
|
||||
|
||||
@@ -15,11 +15,11 @@ module TestModel
|
||||
self.model_attributes[name] = type
|
||||
end
|
||||
|
||||
def define_method_attribute=(attr_name)
|
||||
def define_method_attribute=(attr_name, owner: nil)
|
||||
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)
|
||||
def define_method_attribute(attr_name, owner: nil)
|
||||
generated_attribute_methods.module_eval("def #{attr_name}; @attributes['#{attr_name}']; end", __FILE__, __LINE__)
|
||||
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
|
||||
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
|
||||
s.require_paths = ["lib"]
|
||||
s.files = `git ls-files`.split("\n") - %w{ .gitignore .rspec Gemfile Gemfile.lock autotest/discover.rb Appraisals Travis.yml } - Dir['gemsfiles/*']
|
||||
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||
s.extra_rdoc_files = ["README.rdoc", "CHANGELOG.rdoc", "LICENSE"]
|
||||
s.extra_rdoc_files = ["README.md", "CHANGELOG.rdoc", "LICENSE"]
|
||||
|
||||
s.add_runtime_dependency(%q<timeliness>, ["~> 0.3.8"])
|
||||
s.add_runtime_dependency(%q<timeliness>, [">= 0.3.10", "< 1"])
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user