Last active
September 8, 2015 21:26
-
-
Save johnathanludwig/9983091 to your computer and use it in GitHub Desktop.
A custom validator for date ranges. Read the comments for directions on how to use it. Place this file in your initializers directory. I prefer this structure: config/initializers/extensions/active_model/validations/date_range.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ActiveModel | |
module Validations | |
class DateRangeValidator < ActiveModel::EachValidator | |
def validate_each(record, attribute, value) | |
return if value.blank? | |
pass = true | |
if comparison_date = options[:before] | |
if record.send(comparison_date).present? | |
pass = value < record.send(comparison_date) | |
expected = 'before' | |
end | |
elsif comparison_date = options[:on_or_before] | |
if record.send(comparison_date).present? | |
pass = value <= record.send(comparison_date) | |
expected = 'on or before' | |
end | |
elsif comparison_date = options[:after] | |
if record.send(comparison_date).present? | |
pass = value > record.send(comparison_date) | |
expected = 'after' | |
end | |
elsif comparison_date = options[:on_or_after] | |
if record.send(comparison_date).present? | |
pass = value >= record.send(comparison_date) | |
expected = 'on or after' | |
end | |
end | |
record.errors[attribute] << (options[:message] || "must be #{expected} #{comparison_date.to_s.gsub('_', ' ')}") unless pass | |
end | |
end | |
module HelperMethods | |
# Validates that the specified date is valid based on the comparison date. | |
# | |
# class Person < ActiveRecord::Base | |
# validates_date_range_of :start_date, before: :end_date | |
# end | |
# | |
# In this case, the start_date must be before the end_date. | |
# If either date is not present, no error will be added. | |
# Use <tt>validates_presence_of</tt> if you need to require them. | |
# | |
# Other options are: | |
# validates_date_range_of :start_date, on_or_before: :end_date | |
# validates_date_range_of :end_date, after: :start_date | |
# validates_date_range_of :end_date, on_or_after: :start_date | |
def validates_date_range_of(*attr_names) | |
validates_with DateRangeValidator, _merge_attributes(attr_names) | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require File.expand_path(File.dirname(__FILE__) + "/../test_helper") | |
# we use the shoulda gem for testing. | |
class TestClass | |
include ActiveModel::Validations | |
include ActiveModel::Conversion | |
extend ActiveModel::Naming | |
attr_accessor :start_date, :end_date | |
def initialize(attributes={}) | |
attributes && attributes.each do |name, value| | |
send("#{name}=", value) if respond_to? name.to_sym | |
end | |
end | |
end | |
class DateRangeTest < ActiveSupport::TestCase | |
context 'DateRange' do | |
should 'not add error if main field is null' do | |
TestClass.validates_date_range_of :start_date, before: :end_date | |
t = TestClass.new(start_date: nil, end_date: Date.yesterday) | |
t.valid? | |
assert_does_not_contain t.errors.full_messages, /must be before end date/ | |
end | |
context 'validates before' do | |
should 'not add error if comparison field is null' do | |
TestClass.validates_date_range_of :start_date, before: :end_date | |
t = TestClass.new(start_date: Date.yesterday, end_date: nil) | |
t.valid? | |
assert_does_not_contain t.errors.full_messages, /must be before end date/ | |
end | |
should 'not add error if value is before comparison field' do | |
TestClass.validates_date_range_of :start_date, before: :end_date | |
t = TestClass.new(start_date: Date.yesterday, end_date: Date.today) | |
t.valid? | |
assert_does_not_contain t.errors.full_messages, /must be before end date/ | |
end | |
should 'add error if end_date is before start date' do | |
TestClass.validates_date_range_of :start_date, before: :end_date | |
t = TestClass.new(start_date: Date.today, end_date: Date.yesterday) | |
t.valid? | |
assert_contains t.errors.full_messages, /must be before end date/ | |
end | |
end | |
context 'validates on_or_before' do | |
should 'not add error if comparison field is null' do | |
TestClass.validates_date_range_of :start_date, on_or_before: :end_date | |
t = TestClass.new(start_date: Date.yesterday, end_date: nil) | |
t.valid? | |
assert_does_not_contain t.errors.full_messages, /must be on or before end date/ | |
end | |
should 'not add error if value is on same day as comparison field' do | |
TestClass.validates_date_range_of :start_date, on_or_before: :end_date | |
t = TestClass.new(start_date: Date.today, end_date: Date.today) | |
t.valid? | |
assert_does_not_contain t.errors.full_messages, /must be on or before end date/ | |
end | |
should 'add error if end_date is before start date' do | |
TestClass.validates_date_range_of :start_date, on_or_before: :end_date | |
t = TestClass.new(start_date: Date.today, end_date: Date.yesterday) | |
t.valid? | |
assert_contains t.errors.full_messages, /must be on or before end date/ | |
end | |
end | |
context 'validates after' do | |
should 'not add error if comparison field is null' do | |
TestClass.validates_date_range_of :end_date, after: :start_date | |
t = TestClass.new(start_date: Date.yesterday, end_date: nil) | |
t.valid? | |
assert_does_not_contain t.errors.full_messages, /must be after start date/ | |
end | |
should 'not add error if value is after comparison field' do | |
TestClass.validates_date_range_of :end_date, after: :start_date | |
t = TestClass.new(start_date: Date.yesterday, end_date: Date.today) | |
t.valid? | |
assert_does_not_contain t.errors.full_messages, /must be after start date/ | |
end | |
should 'add error if end_date is after start date' do | |
TestClass.validates_date_range_of :end_date, after: :start_date | |
t = TestClass.new(start_date: Date.today, end_date: Date.yesterday) | |
t.valid? | |
assert_contains t.errors.full_messages, /must be after start date/ | |
end | |
end | |
context 'validates on_or_after' do | |
should 'not add error if comparison field is null' do | |
TestClass.validates_date_range_of :end_date, on_or_after: :start_date | |
t = TestClass.new(start_date: Date.yesterday, end_date: nil) | |
t.valid? | |
assert_does_not_contain t.errors.full_messages, /must be on or after start date/ | |
end | |
should 'not add error if value is on same day as comparison field' do | |
TestClass.validates_date_range_of :end_date, on_or_after: :start_date | |
t = TestClass.new(start_date: Date.today, end_date: Date.today) | |
t.valid? | |
assert_does_not_contain t.errors.full_messages, /must be on or after start date/ | |
end | |
should 'add error if end_date is after start date' do | |
TestClass.validates_date_range_of :end_date, on_or_after: :start_date | |
t = TestClass.new(start_date: Date.today, end_date: Date.yesterday) | |
t.valid? | |
assert_contains t.errors.full_messages, /must be on or after start date/ | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment