├── .document
├── .gemtest
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .rspec
├── Gemfile
├── LICENSE
├── Rakefile
├── Readme.md
├── config
└── locales
│ ├── af.yml
│ ├── ar.yml
│ ├── ca.yml
│ ├── de.yml
│ ├── en.yml
│ ├── es.yml
│ ├── fr.yml
│ ├── it.yml
│ ├── ja.yml
│ ├── nl.yml
│ ├── pl.yml
│ ├── pt-BR.yml
│ ├── ru.yml
│ ├── tr.yml
│ └── zh-CN.yml
├── date_validator.gemspec
├── lib
├── active_model
│ └── validations
│ │ └── date_validator.rb
├── date_validator.rb
└── date_validator
│ ├── engine.rb
│ └── version.rb
└── test
├── date_validator_test.rb
└── test_helper.rb
/.document:
--------------------------------------------------------------------------------
1 | README.rdoc
2 | lib/**/*.rb
3 | bin/*
4 | features/**/*.feature
5 | LICENSE
6 |
--------------------------------------------------------------------------------
/.gemtest:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codegram/date_validator/2ec1621d82231ba64f7dd7450d0c0018d7b4994d/.gemtest
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: "Tests"
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - "*"
10 |
11 | env:
12 | CI: "true"
13 |
14 | jobs:
15 | main:
16 | name: Tests
17 | runs-on: ubuntu-latest
18 | strategy:
19 | matrix:
20 | ruby: [2.2, 2.3, 2.4, 2.5, 2.6, 2.7]
21 | activemodel: [3.2, 4.0, 4.1, 4.2, 5.0, 5.1, 5.2, 6.0, 6.1]
22 | include:
23 | - ruby: 3.0
24 | activemodel: 6.0
25 | - ruby: 3.0
26 | activemodel: 6.1
27 | exclude:
28 | - ruby: 2.2
29 | activemodel: 6.0
30 | - ruby: 2.3
31 | activemodel: 6.0
32 | - ruby: 2.4
33 | activemodel: 6.0
34 | - ruby: 2.2
35 | activemodel: 6.1
36 | - ruby: 2.3
37 | activemodel: 6.1
38 | - ruby: 2.4
39 | activemodel: 6.1
40 | fail-fast: true
41 | env:
42 | ACTIVE_MODEL_VERSION: ${{ matrix.activemodel }}
43 | steps:
44 | - uses: actions/checkout@v2.0.0
45 | with:
46 | fetch-depth: 1
47 | - uses: ruby/setup-ruby@master
48 | with:
49 | ruby-version: ${{ matrix.ruby }}
50 | - run: bundle install --jobs 4 --retry 3
51 | name: Install Ruby deps
52 | - run: bundle exec rake
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sw?
2 | .DS_Store
3 | coverage
4 | rdoc
5 | pkg
6 | *.rbc
7 | doc/*
8 | .yardoc/*
9 | Gemfile.lock
10 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --colour
2 | --format documentation
3 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gemspec
4 |
5 | active_model_opts =
6 | case version = ENV['ACTIVE_MODEL_VERSION'] || "master"
7 | when 'master' then { github: 'rails/rails' }
8 | when 'default' then '~> 3'
9 | else "~> #{version}"
10 | end
11 |
12 | gem 'activemodel', active_model_opts
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Codegram
2 |
3 | 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:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | 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.
8 |
9 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler'
2 | Bundler::GemHelper.install_tasks
3 |
4 | require 'rake/testtask'
5 | Rake::TestTask.new do |t|
6 | t.libs << "test"
7 | t.test_files = FileList['test/**/*_test.rb']
8 | t.verbose = true
9 | end
10 |
11 | begin
12 | require 'yard'
13 | YARD::Rake::YardocTask.new(:docs) do |t|
14 | t.files = ['lib/**/*.rb']
15 | t.options = ['-m', 'markdown', '--no-private', '-r', 'Readme.md', '--title', 'Date Validator documentation']
16 | end
17 |
18 | site = 'doc'
19 | source_branch = 'master'
20 | deploy_branch = 'gh-pages'
21 |
22 | desc "generate and deploy documentation website to github pages"
23 | multitask :pages do
24 | puts ">>> Deploying #{deploy_branch} branch to Github Pages <<<"
25 | require 'git'
26 | repo = Git.open('.')
27 | puts "\n>>> Checking out #{deploy_branch} branch <<<\n"
28 | repo.branch("#{deploy_branch}").checkout
29 | (Dir["*"] - [site]).each { |f| rm_rf(f) }
30 | Dir["#{site}/*"].each {|f| mv(f, "./")}
31 | rm_rf(site)
32 | puts "\n>>> Moving generated site files <<<\n"
33 | Dir["**/*"].each {|f| repo.add(f) }
34 | repo.status.deleted.each {|f, s| repo.remove(f)}
35 | puts "\n>>> Commiting: Site updated at #{Time.now.utc} <<<\n"
36 | message = ENV["MESSAGE"] || "Site updated at #{Time.now.utc}"
37 | repo.commit(message)
38 | puts "\n>>> Pushing generated site to #{deploy_branch} branch <<<\n"
39 | repo.push
40 | puts "\n>>> Github Pages deploy complete <<<\n"
41 | repo.branch("#{source_branch}").checkout
42 | end
43 |
44 | task doc: [:docs]
45 | rescue LoadError
46 | end
47 |
48 | task default: :test
49 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # date_validator [](https://travis-ci.org/codegram/date_validator)
2 |
3 |
4 | A simple date validator for Rails. Should be compatible with all latest Rubies (>2.2, includes Ruby 3.0).
5 |
6 |
7 | ```shell
8 | $ gem install date_validator
9 | ```
10 |
11 | And I mean simple. In your model:
12 |
13 | ```ruby
14 | validates :expiration_date, date: true
15 | ```
16 |
17 | or with some options, such as:
18 |
19 | ```ruby
20 | validates :expiration_date,
21 | date: { after: Proc.new { Time.now },
22 | before: Proc.new { Time.now + 1.year } }
23 | # Using Proc.new prevents production cache issues
24 | ```
25 |
26 | If you want to check the date against another attribute, you can pass it
27 | a Symbol instead of a block:
28 |
29 | ```ruby
30 | # Ensure the expiration date is after the packaging date
31 | validates :expiration_date,
32 | date: { after: :packaging_date }
33 | ```
34 |
35 | or access attributes via the object being validated directly (the input to the Proc):
36 |
37 | ```ruby
38 | validates :due_date,
39 | date: { after_or_equal_to: Proc.new { |obj| obj.created_at.to_date }
40 | # The object being validated is available in the Proc
41 | ```
42 |
43 | For now the available options you can use are `:after`, `:before`,
44 | `:after_or_equal_to`, `:before_or_equal_to` and `:equal_to`.
45 |
46 | If you want to specify a custom message, you can do so in the options hash:
47 |
48 | ```ruby
49 | validates :start_date,
50 | date: { after: Proc.new { Date.today }, message: 'must be after today' },
51 | on: :create
52 | ```
53 |
54 | Pretty much self-explanatory! :)
55 |
56 | If you want to make sure an attribute is before/after another attribute, use:
57 |
58 | ```ruby
59 | validates :start_date, date: { before: :end_date }
60 | ```
61 |
62 | If you want to allow an empty date, use:
63 |
64 | ```ruby
65 | validates :optional_date, date: { allow_blank: true }
66 | ```
67 | ## Note on Patches/Pull Requests
68 |
69 | * Fork the project.
70 | * Make your feature addition or bug fix.
71 | * Add tests for it. This is important so I don't break it in a
72 | future version unintentionally.
73 | * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
74 | * Send us a pull request. Bonus points for topic branches.
75 |
76 | ## Copyright
77 |
78 | Copyright (c) 2013 Codegram. See LICENSE for details.
79 |
--------------------------------------------------------------------------------
/config/locales/af.yml:
--------------------------------------------------------------------------------
1 | # Sample localization file for Afrikaans. Add more files in this directory for other locales.
2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale for starting points.
3 | af:
4 | errors:
5 | messages:
6 | not_a_date: "is nie 'n datum nie"
7 | date_after: "moet na %{date} wees"
8 | date_after_or_equal_to: "moet na of op %{date} wees"
9 | date_before: "moet voor %{date} wees"
10 | date_before_or_equal_to: "moet voor of op %{date} wees"
11 | date_equal_to: "moet gelyk aan %{date} wees"
12 | cannot_be_in_the_future: "kan nie in die toekoms wees nie"
13 |
--------------------------------------------------------------------------------
/config/locales/ar.yml:
--------------------------------------------------------------------------------
1 | ar:
2 | errors:
3 | messages:
4 | not_a_date: "ليس بتاريخ"
5 | date_after: "يجب أن يكون بعد %{date}"
6 | date_after_or_equal_to: "يجب أن يكون مساوي أو بعد %{date}"
7 | date_before: "يجب أن يكون قبل %{date}"
8 | date_before_or_equal_to: "يجب أن يكون مساوي أو قبل %{date}"
9 | date_equal_to: "يجب أن يساوي %{date}"
10 |
--------------------------------------------------------------------------------
/config/locales/ca.yml:
--------------------------------------------------------------------------------
1 | ca:
2 | errors:
3 | messages:
4 | not_a_date: "no és una data"
5 | date_after: "ha de ser posterior a %{date}"
6 | date_after_or_equal_to: "ha de ser posterior o igual a %{date}"
7 | date_before: "ha de ser abans de %{date}"
8 | date_before_or_equal_to: "ha de ser abans de o igual a %{date}"
9 | date_equal_to: "ha de ser igual a %{date}"
10 |
--------------------------------------------------------------------------------
/config/locales/de.yml:
--------------------------------------------------------------------------------
1 | de:
2 | errors:
3 | messages:
4 | not_a_date: "ist kein Datum"
5 | date_after: "muss nach %{date} sein"
6 | date_after_or_equal_to: "muss nach oder gleich %{date} sein"
7 | date_before: "muss vor %{date} sein"
8 | date_before_or_equal_to: "muss vor oder gleich %{date} sein"
9 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | errors:
3 | messages:
4 | not_a_date: "is not a date"
5 | date_after: "must be after %{date}"
6 | date_after_or_equal_to: "must be after or equal to %{date}"
7 | date_before: "must be before %{date}"
8 | date_before_or_equal_to: "must be before or equal to %{date}"
9 | date_equal_to: "must be equal to %{date}"
10 |
--------------------------------------------------------------------------------
/config/locales/es.yml:
--------------------------------------------------------------------------------
1 | es:
2 | errors:
3 | messages:
4 | not_a_date: "no es una fecha"
5 | date_after: "tiene que ser posterior a %{date}"
6 | date_after_or_equal_to: "tiene que ser posterior o igual a %{date}"
7 | date_before: "tiene que ser antes de %{date}"
8 | date_before_or_equal_to: "tiene que ser antes o igual a %{date}"
9 | date_equal_to: "tiene que ser igual a %{date}"
10 |
--------------------------------------------------------------------------------
/config/locales/fr.yml:
--------------------------------------------------------------------------------
1 | # Sample localization file for English. Add more files in this directory for other locales.
2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 |
4 | fr:
5 | errors:
6 | messages:
7 | not_a_date: "n'est pas une date"
8 | date_after: "doit être après %{date}"
9 | date_after_or_equal_to: "doit être supérieur ou égal à %{date}"
10 | date_before: "doit être avant %{date}"
11 | date_before_or_equal_to: "doit être inférieur ou égal à %{date}"
12 |
--------------------------------------------------------------------------------
/config/locales/it.yml:
--------------------------------------------------------------------------------
1 | it:
2 | errors:
3 | messages:
4 | not_a_date: "non è una data"
5 | date_after: "deve essere successiva a %{date}"
6 | date_after_or_equal_to: "deve essere successiva o uguale a %{date}"
7 | date_before: "deve essere antecedente a %{date}"
8 | date_before_or_equal_to: "deve essere antecedente o uguale a %{date}"
9 | date_equal_to: "deve essere uguale a %{date}"
10 |
--------------------------------------------------------------------------------
/config/locales/ja.yml:
--------------------------------------------------------------------------------
1 | ja:
2 | errors:
3 | messages:
4 | not_a_date: "は日付ではありません"
5 | date_after: "は%{date}より後に設定してください"
6 | date_after_or_equal_to: "は%{date}以後に設定してください"
7 | date_before: "は%{date}より前に設定してください"
8 | date_before_or_equal_to: "は%{date}以前に設定してください"
9 | date_equal_to: "は%{date}にしてください"
10 |
--------------------------------------------------------------------------------
/config/locales/nl.yml:
--------------------------------------------------------------------------------
1 | nl:
2 | errors:
3 | messages:
4 | not_a_date: "is geen datum"
5 | date_after: "moet na %{date} liggen"
6 | date_after_or_equal_to: "moet gelijk zijn aan of na %{date} liggen"
7 | date_before: "moet voor %{date} liggen"
8 | date_before_or_equal_to: "moet gelijk zijn of voor %{date} liggen"
9 |
--------------------------------------------------------------------------------
/config/locales/pl.yml:
--------------------------------------------------------------------------------
1 | pl:
2 | errors:
3 | messages:
4 | not_a_date: "nie jest datą"
5 | date_after: "musi być po %{date}"
6 | date_after_or_equal_to: "musi być po lub równa %{date}"
7 | date_before: "musi być przed %{date}"
8 | date_before_or_equal_to: "musi być przed lub równa %{date}"
9 |
--------------------------------------------------------------------------------
/config/locales/pt-BR.yml:
--------------------------------------------------------------------------------
1 | pt-BR:
2 | errors:
3 | messages:
4 | not_a_date: "não é uma data válida"
5 | date_after: "deve ser após %{date}"
6 | date_after_or_equal_to: "deve ser após ou igual a %{date}"
7 | date_before: "deve ser antes de %{date}"
8 | date_before_or_equal_to: "deve ser antes ou igual a %{date}"
9 |
--------------------------------------------------------------------------------
/config/locales/ru.yml:
--------------------------------------------------------------------------------
1 | ru:
2 | errors:
3 | messages:
4 | not_a_date: "не является датой"
5 | date_after: "должна быть позднее чем %{date}"
6 | date_after_or_equal_to: "должна равняться или быть позднее чем %{date}"
7 | date_before: "должна быть ранее чем %{date}"
8 | date_before_or_equal_to: "должна равняться или быть ранее чем %{date}"
9 |
--------------------------------------------------------------------------------
/config/locales/tr.yml:
--------------------------------------------------------------------------------
1 | tr:
2 | errors:
3 | messages:
4 | not_a_date: "geçerli tarih değil"
5 | date_after: "%{date} den ileri bir tarih olmalı"
6 | date_after_or_equal_to: "%{date} ile aynı veya ileri bir tarih olmalı"
7 | date_before: "%{date} den öncesi bir tarih olmalı"
8 | date_before_or_equal_to: "%{date} ile aynı veya öncesi bir tarih olmalı"
9 | date_equal_to: "%{date} ile aynı tarihte olmalı"
10 |
--------------------------------------------------------------------------------
/config/locales/zh-CN.yml:
--------------------------------------------------------------------------------
1 | zh-CN:
2 | errors:
3 | messages:
4 | not_a_date: "不是一个日期"
5 | date_after: "必须晚于 %{date}"
6 | date_after_or_equal_to: "必须早于或等于 %{date}"
7 | date_before: "必须早于 %{date}"
8 | date_before_or_equal_to: "必须晚于或等于 %{date}"
9 | date_equal_to: "必须等于 %{date}"
10 |
--------------------------------------------------------------------------------
/date_validator.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path("../lib", __FILE__)
3 | require "date_validator/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "date_validator"
7 | s.version = DateValidator::VERSION
8 | s.authors = ["Oriol Gual", "Josep M. Bach", "Josep Jaume Rey"]
9 | s.email = ["info@codegram.com"]
10 | s.homepage = "http://github.com/codegram/date_validator"
11 | s.license = "MIT"
12 | s.summary = %q{A simple, ORM agnostic, Ruby >=2.2 compatible date validator for Rails 3+, based on ActiveModel.}
13 | s.description = %q{A simple, ORM agnostic, Ruby >=2.2 compatible date validator for Rails 3+, based on ActiveModel. Currently supporting :after, :before, :after_or_equal_to and :before_or_equal_to options.}
14 |
15 | s.rubyforge_project = "date_validator"
16 |
17 | s.add_runtime_dependency 'activemodel', '>= 3'
18 | s.add_runtime_dependency 'activesupport', '>= 3'
19 |
20 | s.add_development_dependency 'minitest'
21 | s.add_development_dependency 'rake', '>= 12.3.3'
22 | s.add_development_dependency 'tzinfo'
23 |
24 | s.required_ruby_version = Gem::Requirement.new(">= 2.2".freeze)
25 |
26 | s.files = `git ls-files`.split("\n")
27 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
29 | s.require_paths = ["lib"]
30 | end
31 |
--------------------------------------------------------------------------------
/lib/active_model/validations/date_validator.rb:
--------------------------------------------------------------------------------
1 | require 'active_model/validations'
2 | require 'active_support/core_ext/date_time/conversions'
3 | require 'active_support/core_ext/hash/conversions'
4 |
5 | # ActiveModel Rails module.
6 | module ActiveModel
7 |
8 | # ActiveModel::Validations Rails module. Contains all the default validators.
9 | module Validations
10 |
11 | # Date Validator. Inherits from ActiveModel::EachValidator.
12 | #
13 | # Responds to the regular validator API methods `#check_validity` and
14 | # `#validate_each`.
15 | class DateValidator < ActiveModel::EachValidator
16 |
17 | # Implemented checks and their associated operators.
18 | CHECKS = { after: :>, after_or_equal_to: :>=,
19 | before: :<, before_or_equal_to: :<=,
20 | equal_to: :==
21 | }.freeze
22 |
23 | # Call `#initialize` on the superclass, adding a default
24 | # `allow_nil: false` option.
25 | def initialize(options)
26 | super(options.reverse_merge(allow_nil: false))
27 | end
28 |
29 | # Validates the arguments passed to the validator.
30 | #
31 | # They must be either any kind of Time, a Proc, or a Symbol.
32 | def check_validity!
33 | keys = CHECKS.keys
34 | options.slice(*keys).each do |option, value|
35 | next if is_time?(value) || value.is_a?(Proc) || value.is_a?(Symbol) || (defined?(ActiveSupport::TimeWithZone) and value.is_a? ActiveSupport::TimeWithZone)
36 | raise ArgumentError, ":#{option} must be a time, a date, a time_with_zone, a symbol or a proc"
37 | end
38 | end
39 |
40 | # Overridden because standard allow_nil and allow_blank checks don't work with
41 | # string expressions that cannot be type cast to dates. We have to validate
42 | # the pre-type cast values.
43 | def validate(record)
44 | attributes.each do |attribute|
45 | value = record.read_attribute_for_validation(attribute)
46 | validate_each(record, attribute, value)
47 | end
48 | end
49 |
50 | # The actual validator method. It is called when ActiveRecord iterates
51 | # over all the validators.
52 | def validate_each(record, attr_name, value)
53 | before_type_cast = :"#{attr_name}_before_type_cast"
54 |
55 | value_before_type_cast = if record.respond_to?(before_type_cast)
56 | record.send(before_type_cast)
57 | else
58 | nil
59 | end
60 |
61 | if value_before_type_cast.present? && value.nil?
62 | record.errors.add(attr_name, :not_a_date, **options)
63 | return
64 | end
65 |
66 | return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
67 |
68 | unless value
69 | record.errors.add(attr_name, :not_a_date, **options)
70 | return
71 | end
72 |
73 | unless is_time?(value)
74 | record.errors.add(attr_name, :not_a_date, **options)
75 | return
76 | end
77 |
78 | options.slice(*CHECKS.keys).each do |option, option_value|
79 | option_value = option_value.call(record) if option_value.is_a?(Proc)
80 | option_value = record.send(option_value) if option_value.is_a?(Symbol)
81 |
82 | original_value = value
83 | original_option_value = option_value
84 |
85 | # To enable to_i conversion, these types must be converted to Datetimes
86 | if defined?(ActiveSupport::TimeWithZone)
87 | option_value = option_value.to_datetime if option_value.is_a?(ActiveSupport::TimeWithZone)
88 | value = value.to_datetime if value.is_a?(ActiveSupport::TimeWithZone)
89 | end
90 |
91 | if defined?(Date)
92 | option_value = option_value.to_datetime if option_value.is_a?(Date)
93 | value = value.to_datetime if value.is_a?(Date)
94 | end
95 |
96 | unless is_time?(option_value) && value.to_i.send(CHECKS[option], option_value.to_i)
97 | record.errors.add(attr_name, :"date_#{option}", **options.merge(
98 | value: original_value,
99 | date: (I18n.localize(original_option_value) rescue original_option_value)
100 | ))
101 | end
102 | end
103 | end
104 |
105 | private
106 |
107 | def is_time?(object)
108 | object.is_a?(Time) || (defined?(Date) and object.is_a?(Date)) || (defined?(ActiveSupport::TimeWithZone) and object.is_a?(ActiveSupport::TimeWithZone))
109 | end
110 | end
111 |
112 | module HelperMethods
113 | # Validates whether the value of the specified attribute is a validate Date
114 | #
115 | # class Person < ActiveRecord::Base
116 | # validates_date_of :payment_date, after: :packaging_date
117 | # validates_date_of :expiration_date, before: Proc.new { Time.now }
118 | # end
119 | #
120 | # Configuration options:
121 | # * :after - check that a Date is after the specified one.
122 | # * :before - check that a Date is before the specified one.
123 | # * :after_or_equal_to - check that a Date is after or equal to the specified one.
124 | # * :before_or_equal_to - check that a Date is before or equal to the specified one.
125 | # * :equal_to - check that a Date is equal to the specified one.
126 | def validates_date_of(*attr_names)
127 | validates_with DateValidator, _merge_attributes(attr_names)
128 | end
129 | end
130 | end
131 | end
132 |
--------------------------------------------------------------------------------
/lib/date_validator.rb:
--------------------------------------------------------------------------------
1 | require 'active_model/validations/date_validator'
2 | require 'active_support/i18n'
3 | require 'date_validator/engine' if defined?(Rails)
4 |
5 | # A simple date validator for Rails 3+.
6 | #
7 | # @example
8 | # validates :expiration_date,
9 | # date: { after: Proc.new { Time.now },
10 | # before: Proc.new { Time.now + 1.year } }
11 | # # Using Proc.new prevents production cache issues
12 | #
13 | module DateValidator
14 | end
15 |
--------------------------------------------------------------------------------
/lib/date_validator/engine.rb:
--------------------------------------------------------------------------------
1 | module DateValidator
2 | class Engine < Rails::Engine
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lib/date_validator/version.rb:
--------------------------------------------------------------------------------
1 | module DateValidator
2 | # The version number.
3 | VERSION = "0.12.0"
4 | end
5 |
--------------------------------------------------------------------------------
/test/date_validator_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module ActiveModel
4 | module Validations
5 |
6 | describe DateValidator do
7 |
8 | before do
9 | TestRecord.reset_callbacks(:validate)
10 | end
11 |
12 | it "checks validity of the arguments" do
13 | [3, "foo", 1..6].each do |wrong_argument|
14 | proc {
15 | TestRecord.validates(:expiration_date, date: { before: wrong_argument })
16 | }.must_raise(ArgumentError, ":before must be a time, a date, a time_with_zone, a symbol or a proc")
17 | end
18 | end
19 |
20 | it "complains when no options are provided" do
21 | I18n.backend.reload!
22 | TestRecord.validates :expiration_date,
23 | date: { before: Time.now }
24 |
25 | model = TestRecord.new(nil)
26 | model.valid?.must_equal false
27 | model.errors[:expiration_date].must_equal(["is not a date"])
28 | end
29 |
30 | it "works with helper methods" do
31 | time = Time.now
32 | TestRecord.validates_date_of :expiration_date, before: time
33 | model = TestRecord.new(time + 20000)
34 | model.valid?.must_equal false
35 | end
36 |
37 | [:valid,:invalid].each do |must_be|
38 | _context = must_be == :valid ? 'when value validates correctly' : 'when value does not match validation requirements'
39 |
40 | describe _context do
41 | [:after, :before, :after_or_equal_to, :before_or_equal_to, :equal_to].each do |check|
42 | now = Time.now.to_datetime
43 |
44 | model_date = case check
45 | when :after then must_be == :valid ? now + 21000 : now - 1
46 | when :before then must_be == :valid ? now - 21000 : now + 1
47 | when :after_or_equal_to then must_be == :valid ? now : now - 21000
48 | when :before_or_equal_to then must_be == :valid ? now : now + 21000
49 | when :equal_to then must_be == :valid ? now : now + 21000
50 | end
51 |
52 | it "ensures that an attribute is #{must_be} when #{must_be == :valid ? 'respecting' : 'offending' } the #{check} check" do
53 | TestRecord.validates :expiration_date,
54 | date: {:"#{check}" => now}
55 |
56 | model = TestRecord.new(model_date)
57 | must_be == :valid ? model.valid?.must_equal(true) : model.valid?.must_equal(false)
58 | end
59 |
60 | if _context == 'when value does not match validation requirements'
61 | it "yields a default error message indicating that value must be #{check} validation requirements" do
62 | TestRecord.validates :expiration_date,
63 | date: {:"#{check}" => now}
64 |
65 | model = TestRecord.new(model_date)
66 | model.valid?.must_equal false
67 | model.errors[:expiration_date].must_equal(["must be " + check.to_s.gsub('_',' ') + " #{I18n.localize(now)}"])
68 | end
69 | end
70 | end
71 |
72 | if _context == 'when value does not match validation requirements'
73 | now = Time.now.to_datetime
74 |
75 | it "allows for a custom validation message" do
76 | TestRecord.validates :expiration_date,
77 | date: { before_or_equal_to: now,
78 | message: 'must be after Christmas' }
79 |
80 | model = TestRecord.new(now + 21000)
81 | model.valid?.must_equal false
82 | model.errors[:expiration_date].must_equal(["must be after Christmas"])
83 | end
84 |
85 | it "allows custom validation message to be handled by I18n" do
86 | custom_message = 'Custom Date Message'
87 |
88 | I18n.backend.eager_load! if I18n.backend.respond_to?(:eager_load!)
89 | I18n.backend.store_translations('en', { errors: { messages: { not_a_date: custom_message }}})
90 |
91 | TestRecord.validates :expiration_date, date: true
92 |
93 | model = TestRecord.new(nil)
94 | model.valid?.must_equal false
95 | model.errors[:expiration_date].must_equal([custom_message])
96 | end
97 | end
98 |
99 | end
100 | end
101 |
102 | extra_types = [:proc, :symbol]
103 | extra_types.push(:date) if defined?(Date) and defined?(DateTime)
104 | extra_types.push(:time_with_zone) if defined?(ActiveSupport::TimeWithZone)
105 |
106 | extra_types.each do |type|
107 | it "accepts a #{type} as an argument to a check" do
108 | case type
109 | when :proc then
110 | TestRecord.validates(:expiration_date, date: { after: Proc.new {Time.now + 21000} }).must_be_kind_of Hash
111 | when :symbol then
112 | TestRecord.send(:define_method, :min_date, lambda { Time.now + 21000 })
113 | TestRecord.validates(:expiration_date, date: { after: :min_date }).must_be_kind_of Hash
114 | when :date then
115 | TestRecord.validates(:expiration_date, date: { after: Time.now.to_date }).must_be_kind_of Hash
116 | when :time_with_zone then
117 | Time.zone = "Hawaii"
118 | TestRecord.validates(:expiration_date, date: { before: Time.zone.parse((Time.now + 21000).to_s, Time.now) }).must_be_kind_of Hash
119 | end
120 | end
121 | end
122 |
123 | it "gracefully handles an unexpected result from a proc argument evaluation" do
124 | TestRecord.validates :expiration_date,
125 | date: { after: Proc.new { nil } }
126 |
127 | TestRecord.new(Time.now).valid?.must_equal false
128 | end
129 |
130 | it "gracefully handles an unexpected result from a symbol argument evaluation" do
131 | TestRecord.send(:define_method, :min_date, lambda { nil })
132 | TestRecord.validates :expiration_date,
133 | date: { after: :min_date }
134 |
135 | TestRecord.new(Time.now).valid?.must_equal false
136 | end
137 |
138 | describe "with type cast attributes" do
139 | before do
140 | TestRecord.send(:define_method, :expiration_date_before_type_cast, lambda { 'last year' })
141 | end
142 |
143 | it "should detect invalid date expressions when nil is allowed" do
144 | TestRecord.validates(:expiration_date, date: true, allow_nil: true)
145 | TestRecord.new(nil).valid?.must_equal false
146 | end
147 |
148 | it "should detect invalid date expressions when blank is allowed" do
149 | TestRecord.validates(:expiration_date, date: true, allow_blank: true)
150 | TestRecord.new(nil).valid?.must_equal false
151 | end
152 | end
153 |
154 | describe 'with garbage input' do
155 | it 'is invalid' do
156 | TestRecord.validates(:expiration_date, date: true, allow_nil: true)
157 | TestRecord.new('not a date').valid?.must_equal false
158 | end
159 | end
160 | end
161 |
162 | end
163 | end
164 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | begin
2 | require 'simplecov'
3 | SimpleCov.start do
4 | add_group "Lib", 'lib'
5 | end
6 | rescue LoadError
7 | end
8 |
9 | begin; require 'turn'; rescue LoadError; end
10 |
11 | gem 'minitest'
12 | require 'minitest/autorun'
13 |
14 | require 'active_model'
15 | require 'active_support/core_ext/time/zones'
16 | require 'date_validator'
17 |
18 | I18n.load_path += Dir[File.expand_path(File.join(File.dirname(__FILE__), '../config/locales', '*.yml')).to_s]
19 |
20 | class TestRecord
21 | include ActiveModel::Validations
22 | attr_accessor :expiration_date
23 |
24 | def initialize(expiration_date)
25 | @expiration_date = expiration_date
26 | end
27 | end
28 |
--------------------------------------------------------------------------------