validates_date_time 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,61 @@
1
+ [5 March 2009]
2
+
3
+ * Add support for I18N [Roman Shterenzon]
4
+
5
+ [18 February 2009]
6
+
7
+ * Fix errors caused by assigning values that don't respond to #strip.
8
+
9
+ [26 May 2008]
10
+
11
+ * Fix incorrect validation of 24 hour times including meridian, eg: 18:20 AM.
12
+
13
+ [2 May 2008]
14
+
15
+ * Permenently remove old ActiveRecord::Validations::DateTime namespace.
16
+
17
+ [19 March 2008]
18
+
19
+ * Fix README typo.
20
+
21
+ [11 February 2008]
22
+
23
+ * Make :after add an error if the date is the same as the restriction.
24
+
25
+ [7 January 2008]
26
+
27
+ * Add support for ISO8601 formats.
28
+
29
+ [14 December 2007]
30
+
31
+ * Allow 1 through 6 digits for microseconds.
32
+
33
+ [11 December 2007]
34
+
35
+ * Parse microseconds from time and datetime strings.
36
+
37
+ [28 September 2007]
38
+
39
+ * Preserve ActiveRecord::Validations::DateTime.us_date_format for compatibility.
40
+
41
+ [27 September 2007]
42
+
43
+ * Improve the handling of multiparameter attributes.
44
+
45
+ * BACKWARDS INCOMPATIBLE: Change namespace from ActiveRecord::Validations::DateTime to ValidatesDateTime.
46
+
47
+ [19 September 2007]
48
+
49
+ * Almost a complete rewrite! API unchanged. Let me know if there are any issues.
50
+
51
+ [2 Sep 2007]
52
+
53
+ * Allow date and time formats like "February 5, 2006" and "September 01, 2007 06:10". [Mark A. Lane]
54
+
55
+ [7 Jun 07]
56
+
57
+ * Datetime restrictions should be parsed as datetimes [Adam Meehan]
58
+
59
+ [Earlier]
60
+
61
+ * Remove unused colon separated date format (dd:mm:yy) from parsing
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006 Jonathan Viney
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,94 @@
1
+ validates_date_time
2
+ ===================
3
+ This plugin adds the ability to do stricter date and time checking with ActiveRecord.
4
+
5
+ Install
6
+ =======
7
+ ./script/plugin install git://github.com/nickstenning/validates_date_time.git
8
+
9
+ Instructions
10
+ ============
11
+ The validators can be used to parse strings into Date and Time objects as well as restrict
12
+ an attribute based on other dates or times.
13
+ class Person < ActiveRecord::Base
14
+ validates_date :date_of_birth
15
+ validates_time :time_of_birth
16
+ validates_date_time :date_and_time_of_birth
17
+ end
18
+
19
+ Use `:allow_nil` to allow the value to be blank.
20
+ class Person < ActiveRecord::Base
21
+ validates_date :date_of_birth, :allow_nil => true
22
+ end
23
+
24
+ Supported formats
25
+ =================
26
+ The default for the plugin is to expect dates in day/month/year format. If you are in the
27
+ US, you will want to change the default to month/day/year by placing the following in config/environment.rb
28
+ ValidatesDateTime.us_date_format = true
29
+
30
+ Date format examples:
31
+ - 2006-01-01
32
+ - 1 Jan 06
33
+ - 1 Jan 2006
34
+ - 10/1/06
35
+ - 1/1/2006
36
+
37
+ Time format examples:
38
+ - 1pm
39
+ - 10:11
40
+ - 12:30pm
41
+ - 8am
42
+
43
+ Datetime format examples:
44
+ - 1 Jan 2006 2pm
45
+ - 31/1/06 8:30am
46
+
47
+ Examples
48
+ ========
49
+ If an attribute value can not be parsed correctly, an error is added:
50
+ p = Person.new
51
+ p.date_of_birth = "1 Jan 2006"
52
+ p.time_of_birth = "5am"
53
+ p.save # true
54
+
55
+ p.date_of_birth = "30 Feb 2006"
56
+ p.save # false, 30 feb is invalid for obvious reasons
57
+
58
+ p.date_of_birth = "java is better than ruby"
59
+ p.save # false
60
+
61
+ In the final example, as I'm sure you are aware, the record failed to save not only
62
+ because "java is better than ruby" is an invalid date, but more importantly, because the statement is blatantly false. ;)
63
+
64
+ Restricting date and time ranges
65
+ ================================
66
+ Using the `:before` and `:after` options you can restrict a date or time value based on other attribute values
67
+ and predefined values. You can pass as many value to :before or :after as you like.
68
+ class Person
69
+ validates_date :date_of_birth, :before => [:date_of_death, Proc.new { 1.day.from_now_to_date}], :after => '1 Jan 1900'
70
+ validates_date :date_of_death, :before => Proc.new { 1.day.from_now.to_date }
71
+ end
72
+
73
+ p = Person.new
74
+ p.date_of_birth = '1800-01-01'
75
+ p.save # false
76
+ p.errors[:date_of_birth] # must be after 1 Jan 1900
77
+
78
+ p.date_of_death = Date.new(2010, 1, 1)
79
+ p.save # false
80
+ p.errors[:date_of_death] # must be before <1 day from now>
81
+
82
+ p.date_of_birth = '1960-03-02'
83
+ p.date_of_death = '2003-06-07'
84
+ p.save # true
85
+
86
+ You can customise the error messages for dates or times that fall outside the required range. The boundary date will be substituted in for %s. Eg:
87
+ class Person
88
+ validates_date :date_of_birth, :after => Date.new(1900, 1, 1), :before => Proc.new { 1.day.from_now.to_date }, :before_message => 'Ensure it is before %s', :after_message => 'Ensure it is after %s'
89
+ end
90
+
91
+ Author
92
+ ======
93
+ If you find this plugin useful, please consider a donation to [show your support](http://www.paypal.com/cgi-bin/webscr?cmd=_send-money)!
94
+ Suggestions, comments, problems are all welcome. You'll find me at jonathan.viney@gmail.com
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gemspec|
8
+ gemspec.name = "validates_date_time"
9
+ gemspec.summary = "A Rails plugin adds the ability to validate dates and times with ActiveRecord."
10
+ gemspec.description = "A Rails plugin that adds an ActiveRecord validation helper to do range and start/end date checking in."
11
+ gemspec.email = ["jonathan.viney@gmail.com", "nick@whiteink.com"]
12
+ gemspec.homepage = "http://github.com/nickstenning/validates_date_time"
13
+ gemspec.authors = ["Jonathan Viney", "Nick Stenning"]
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: gem install jeweler"
17
+ end
18
+
19
+
20
+ desc 'Default: run unit tests.'
21
+ task :default => :test
22
+
23
+ desc 'Test the validates_date_time plugin.'
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.pattern = 'test/**/*_test.rb'
27
+ t.verbose = true
28
+ end
29
+
30
+ desc 'Generate documentation for the validates_date_time plugin.'
31
+ Rake::RDocTask.new(:rdoc) do |rdoc|
32
+ rdoc.rdoc_dir = 'rdoc'
33
+ rdoc.title = 'validates_date_time'
34
+ rdoc.options << '--line-numbers' << '--inline-source'
35
+ rdoc.rdoc_files.include('README')
36
+ rdoc.rdoc_files.include('lib/**/*.rb')
37
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/lib/validates_date_time'
@@ -0,0 +1,47 @@
1
+ module ValidatesDateTime
2
+ module MultiparameterAttributes
3
+ def self.included(base)
4
+ base.alias_method_chain :execute_callstack_for_multiparameter_attributes, :temporal_error_handling
5
+ end
6
+
7
+ def execute_callstack_for_multiparameter_attributes_with_temporal_error_handling(callstack)
8
+ errors = []
9
+ callstack.each do |name, values|
10
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
11
+
12
+ if values.empty?
13
+ send("#{name}=", nil)
14
+ else
15
+ column = column_for_attribute(name)
16
+
17
+ if [:date, :time, :datetime].include?(column.type)
18
+ values = values.map(&:to_s)
19
+
20
+ result = case column.type
21
+ when :date
22
+ extract_date_from_multiparameter_attributes(values)
23
+ when :time
24
+ extract_time_from_multiparameter_attributes(values)
25
+ when :datetime
26
+ date_values, time_values = values.slice!(0, 3), values
27
+ extract_date_from_multiparameter_attributes(date_values) + " " + extract_time_from_multiparameter_attributes(time_values)
28
+ end
29
+
30
+ send("#{name}=", result)
31
+ end
32
+ end
33
+ end
34
+ unless errors.empty?
35
+ raise ActiveRecord::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
36
+ end
37
+ end
38
+
39
+ def extract_date_from_multiparameter_attributes(values)
40
+ [values[0], *values.slice(1, 2).map { |s| s.rjust(2, "0") }].join("-")
41
+ end
42
+
43
+ def extract_time_from_multiparameter_attributes(values)
44
+ values.last(3).map { |s| s.rjust(2, "0") }.join(":")
45
+ end
46
+ end
47
+ end
data/lib/parser.rb ADDED
@@ -0,0 +1,130 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class Column
4
+ class << self
5
+ def string_to_date(value)
6
+ return if value.blank?
7
+ return value if value.is_a?(Date)
8
+ return value.to_date if value.is_a?(Time) || value.is_a?(DateTime)
9
+
10
+ year, month, day = case value.to_s.strip
11
+ # 22/1/06, 22\1\06 or 22.1.06
12
+ when /\A(\d{1,2})[\\\/\.-](\d{1,2})[\\\/\.-](\d{2}|\d{4})\Z/
13
+ ValidatesDateTime.us_date_format ? [$3, $1, $2] : [$3, $2, $1]
14
+ # 22 Feb 06 or 1 jun 2001
15
+ when /\A(\d{1,2}) (\w{3,9}) (\d{2}|\d{4})\Z/
16
+ [$3, $2, $1]
17
+ # July 1 2005
18
+ when /\A(\w{3,9}) (\d{1,2})\,? (\d{2}|\d{4})\Z/
19
+ [$3, $1, $2]
20
+ # 2006-01-01
21
+ when /\A(\d{4})-(\d{2})-(\d{2})\Z/
22
+ [$1, $2, $3]
23
+ # 2006-01-01T10:10:10+13:00
24
+ when /\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\Z/
25
+ [$1, $2, $3]
26
+ # Not a valid date string
27
+ else
28
+ return nil
29
+ end
30
+
31
+ Date.new(unambiguous_year(year), month_index(month), day.to_i) rescue nil
32
+ end
33
+
34
+ def string_to_dummy_time(value)
35
+ return if value.blank?
36
+ return value if value.is_a?(Time) || value.is_a?(DateTime)
37
+ return value.to_time(ActiveRecord::Base.default_timezone) if value.is_a?(Date)
38
+
39
+ hour, minute, second, microsecond = case value.to_s.strip
40
+ # 12 hours with minute and second
41
+ when /\A(\d{1,2})[\. :](\d{2})[\. :](\d{2})\s?(am|pm)\Z/i
42
+ [full_hour($1, $4), $2, $3]
43
+ # 12 hour with minute: 7.30pm, 11:20am, 2 20PM
44
+ when /\A(\d{1,2})[\. :](\d{2})\s?(am|pm)\Z/i
45
+ return nil unless $1.to_i <= 12
46
+ [full_hour($1, $3), $2]
47
+ # 12 hour without minute: 2pm, 11Am, 7 pm
48
+ when /\A(\d{1,2})\s?(am|pm)\Z/i
49
+ [full_hour($1, $2)]
50
+ # 24 hour: 22:30, 03.10, 12 30
51
+ when /\A(\d{2})[\. :](\d{2})([\. :](\d{2})(\.(\d{1,6}))?)?\Z/
52
+ [$1, $2, $4, $6]
53
+ # 2006-01-01T10:10:10+13:00
54
+ when /\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\Z/
55
+ [$4, $5, $6]
56
+ # Not a valid time string
57
+ else
58
+ return nil
59
+ end
60
+
61
+ Time.send(ActiveRecord::Base.default_timezone, 2000, 1, 1, hour.to_i, minute.to_i, second.to_i, microsecond.to_i) rescue nil
62
+ end
63
+
64
+ def string_to_time(value)
65
+ return if value.blank?
66
+ return value if value.is_a?(Time) || value.is_a?(DateTime)
67
+ return value.to_time(ActiveRecord::Base.default_timezone) if value.is_a?(Date)
68
+
69
+ value = value.to_s.strip
70
+
71
+ if value =~ /\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\Z/
72
+ time_array = [$1, $2, $3, $4, $5, $6].map!(&:to_i)
73
+ else
74
+ # The basic approach is to attempt to parse a date from the front of the string, splitting on spaces.
75
+ # Once a date has been parsed, a time is extracted from the rest of the string.
76
+ split_index = date = nil
77
+ loop do
78
+ split_index = value.index(' ', split_index ? split_index + 1 : 0)
79
+
80
+ if split_index.nil? or date = string_to_date(value.first(split_index))
81
+ break
82
+ end
83
+ end
84
+
85
+ return if date.nil?
86
+
87
+ time = string_to_dummy_time(value.last(value.size - split_index))
88
+ return if time.nil?
89
+
90
+ time_array = [date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec]
91
+ end
92
+
93
+ # From schema_definitions.rb
94
+ begin
95
+ Time.send(ActiveRecord::Base.default_timezone, *time_array)
96
+ rescue
97
+ zone_offset = if Base.default_timezone == :local then DateTime.now.offset else 0 end
98
+ # Append zero calendar reform start to account for dates skipped by calendar reform
99
+ DateTime.new(*time_array[0..5] << zone_offset << 0) rescue nil
100
+ end
101
+ end
102
+
103
+ def full_hour(hour, meridian)
104
+ hour = hour.to_i
105
+ if meridian.strip.downcase == 'am'
106
+ hour == 12 ? 0 : hour
107
+ else
108
+ hour == 12 ? hour : hour + 12
109
+ end
110
+ end
111
+
112
+ def month_index(month)
113
+ return month.to_i if month.to_i.nonzero?
114
+ Date::ABBR_MONTHNAMES.index(month.capitalize) || Date::MONTHNAMES.index(month.capitalize)
115
+ end
116
+
117
+ # Extract a 4-digit year from a 2-digit year.
118
+ # If the number is less than 20, assume year 20#{number}
119
+ # otherwise use 19#{number}. Ignore if already 4 digits.
120
+ #
121
+ # Eg:
122
+ # 10 => 2010, 60 => 1960, 00 => 2000, 1963 => 1963
123
+ def unambiguous_year(year)
124
+ year = "#{year.to_i < 20 ? '20' : '19'}#{year}" if year.length == 2
125
+ year.to_i
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,150 @@
1
+ require File.dirname(__FILE__) + '/parser'
2
+ require File.dirname(__FILE__) + '/multiparameter_attributes'
3
+
4
+ module ValidatesDateTime
5
+ def self.included(base)
6
+ base.class_eval do
7
+ extend ClassMethods
8
+ include MultiparameterAttributes
9
+ end
10
+ end
11
+
12
+ mattr_accessor :us_date_format
13
+ self.us_date_format = false
14
+
15
+ DEFAULT_TEMPORAL_VALIDATION_OPTIONS = {
16
+ :before_message => "must be before %s",
17
+ :after_message => "must be after %s",
18
+ :on => :save
19
+ }.freeze
20
+
21
+ class ValidatesDateTimeRestriction < Struct.new(:raw_value, :parse_method)
22
+ def value(record)
23
+ @last_value = case raw_value
24
+ when Symbol
25
+ record.send(raw_value)
26
+ when Proc
27
+ raw_value.call(record)
28
+ else
29
+ raw_value
30
+ end
31
+
32
+ @last_value = parse(@last_value)
33
+ end
34
+
35
+ def parse(string)
36
+ ActiveRecord::ConnectionAdapters::Column.send("string_to_#{parse_method}", string)
37
+ end
38
+
39
+ def last_value
40
+ @last_value
41
+ end
42
+
43
+ def to_s
44
+ if raw_value.is_a?(Symbol)
45
+ raw_value.to_s.humanize
46
+ else
47
+ @last_value.to_s
48
+ end
49
+ end
50
+ end
51
+
52
+
53
+ module ClassMethods
54
+ def validates_date(*args)
55
+ options = temporal_validation_options({ :message => "is an invalid date" }, args)
56
+ attr_names = args
57
+
58
+ # We must remove this from the configuration that is passed to validates_each because
59
+ # we want to have our own definition of nil that uses the before_type_cast value
60
+ allow_nil = options.delete(:allow_nil)
61
+
62
+ prepare_restrictions(options, :date)
63
+
64
+ validates_each(attr_names, options) do |record, attr_name, value|
65
+ raw_value = record.send("#{attr_name}_before_type_cast")
66
+
67
+ # If value that is unable to be parsed, and a blank value where allow_nil is not set are both invalid
68
+ if (!raw_value.blank? and !value) || (raw_value.blank? and !allow_nil)
69
+ record.errors.add(attr_name, options[:message])
70
+ elsif value
71
+ validate_before_and_after_restrictions(record, attr_name, value, options)
72
+ end
73
+ end
74
+ end
75
+
76
+ def validates_time(*args)
77
+ options = temporal_validation_options({ :message => "is an invalid time" }, args)
78
+ attr_names = args
79
+
80
+ allow_nil = options.delete(:allow_nil)
81
+ prepare_restrictions(options, :dummy_time)
82
+
83
+ validates_each(attr_names, options) do |record, attr_name, value|
84
+ raw_value = record.send("#{attr_name}_before_type_cast")
85
+
86
+ if (!raw_value.blank? and !value) || (raw_value.blank? and !allow_nil)
87
+ record.errors.add(attr_name, options[:message])
88
+ elsif value
89
+ validate_before_and_after_restrictions(record, attr_name, value, options)
90
+ end
91
+ end
92
+ end
93
+
94
+ def validates_date_time(*args)
95
+ options = temporal_validation_options({ :message => "is an invalid date time" }, args)
96
+ attr_names = args
97
+
98
+ allow_nil = options.delete(:allow_nil)
99
+ prepare_restrictions(options, :time)
100
+
101
+ validates_each(attr_names, options) do |record, attr_name, value|
102
+ raw_value = record.send("#{attr_name}_before_type_cast")
103
+
104
+ if (!raw_value.blank? and !value) || (raw_value.blank? and !allow_nil)
105
+ record.errors.add(attr_name, options[:message])
106
+ elsif value
107
+ validate_before_and_after_restrictions(record, attr_name, value, options)
108
+ end
109
+ end
110
+ end
111
+
112
+ private
113
+ def validate_before_and_after_restrictions(record, attr_name, value, options)
114
+ if options[:before]
115
+ options[:before].each do |r|
116
+ if r.value(record) and value >= r.last_value
117
+ record.errors.add(attr_name, :before, :value => r, :default => options[:before_message] % r)
118
+ break
119
+ end
120
+ end
121
+ end
122
+
123
+ if options[:after]
124
+ options[:after].each do |r|
125
+ if r.value(record) and value <= r.last_value
126
+ record.errors.add(attr_name, :after, :value => r, :default => options[:after_message] % r)
127
+ break
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ def prepare_restrictions(options, parse_method)
134
+ options[:before] = [*options[:before]].compact.map { |r| ValidatesDateTimeRestriction.new(r, parse_method) }
135
+ options[:after] = [*options[:after]].compact.map { |r| ValidatesDateTimeRestriction.new(r, parse_method) }
136
+ end
137
+
138
+ def temporal_validation_options(options, args)
139
+ returning options do
140
+ options.reverse_merge!(DEFAULT_TEMPORAL_VALIDATION_OPTIONS)
141
+ options.update(args.pop) if args.last.is_a?(Hash)
142
+ options.assert_valid_keys :message, :before_message, :after_message, :before, :after, :if, :on, :allow_nil
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ class ActiveRecord::Base
149
+ include ValidatesDateTime
150
+ end
@@ -0,0 +1,59 @@
1
+ require 'test/unit'
2
+
3
+ begin
4
+ require File.dirname(__FILE__) + '/../../../../config/boot'
5
+ Rails::Initializer.run
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ gem 'activerecord', '=2.2.2'
9
+ require 'activerecord'
10
+ end
11
+
12
+ # Search for fixtures first
13
+ fixture_path = File.dirname(__FILE__) + '/fixtures/'
14
+ ActiveSupport::Dependencies.load_paths.insert(0, fixture_path)
15
+
16
+ require 'active_record/fixtures'
17
+
18
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/validates_date_time')
19
+
20
+ ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
21
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/debug.log')
22
+ ActiveRecord::Base.establish_connection(ENV['DB'] || 'mysql')
23
+
24
+ load(File.dirname(__FILE__) + '/schema.rb')
25
+
26
+ Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + '/fixtures/'
27
+
28
+ class Test::Unit::TestCase #:nodoc:
29
+ self.use_transactional_fixtures = true
30
+ self.use_instantiated_fixtures = false
31
+
32
+ fixtures :people
33
+
34
+ def p
35
+ people(:jonathan)
36
+ end
37
+
38
+ def assert_update_and_equal(expected, attributes = {})
39
+ assert p.update_attributes!(attributes), "#{attributes.inspect} should be valid"
40
+ assert_equal expected, p.send(attributes.keys.first).to_s
41
+ end
42
+
43
+ def assert_update_and_match(expected, attributes = {})
44
+ assert p.update_attributes(attributes), "#{attributes.inspect} should be valid"
45
+ assert_match expected, p.send(attributes.keys.first).to_s
46
+ end
47
+
48
+ def assert_invalid_and_errors_match(expected, attributes = {})
49
+ assert !p.update_attributes(attributes)
50
+ assert_match expected, p.errors.full_messages.join
51
+ end
52
+
53
+ def with_us_date_format(&block)
54
+ ValidatesDateTime.us_date_format = true
55
+ yield
56
+ ensure
57
+ ValidatesDateTime.us_date_format = false
58
+ end
59
+ end
data/test/database.yml ADDED
@@ -0,0 +1,6 @@
1
+ mysql:
2
+ :adapter: mysql
3
+ :host: localhost
4
+ :username: rails
5
+ :password:
6
+ :database: rails_plugin_test
data/test/date_test.rb ADDED
@@ -0,0 +1,133 @@
1
+ require File.dirname(__FILE__) + '/abstract_unit'
2
+
3
+ class DateTest < Test::Unit::TestCase
4
+ def test_valid_when_nil
5
+ assert p.update_attributes!(:date_of_birth => nil, :date_of_death => nil)
6
+ end
7
+
8
+ def test_date_required
9
+ assert_invalid_and_errors_match /invalid/, :required_date => ""
10
+ end
11
+
12
+ # Test 1/1/06 format
13
+ def test_first_format
14
+ { '1/1/01' => '2001-01-01', '29/10/2005' => '2005-10-29', '8\12\63' => '1963-12-08',
15
+ '07/06/2006' => '2006-06-07', '11\1\06' => '2006-01-11', '10.6.05' => '2005-06-10' }.each do |value, result|
16
+ assert_update_and_equal result, :date_of_birth => value
17
+ end
18
+ end
19
+
20
+ # Test 1 Jan 06 and 1 January 06 formats
21
+ def test_second_format
22
+ { '19 Mar 60' => '1960-03-19', '22 dec 1985' => '1985-12-22',
23
+ '24 August 00' => '2000-08-24', '25 December 1960' => '1960-12-25'}.each do |value, result|
24
+ assert_update_and_equal result, :date_of_birth => value
25
+ end
26
+ end
27
+
28
+ # Test February 4 2006 formats
29
+ def test_third_format
30
+ { 'february 4 06' => '2006-02-04', 'DECember 25 1850' => '1850-12-25', 'February 5, 2006' => '2006-02-05' }.each do |value, result|
31
+ assert_update_and_equal result, :date_of_birth => value
32
+ end
33
+ end
34
+
35
+ def test_iso_format
36
+ { '2006-01-01' => '2006-01-01', '1900-04-22' => '1900-04-22',
37
+ '2008-03-04T20:33:41' => '2008-03-04' }.each do |value, result|
38
+ assert_update_and_equal result, :date_of_birth => value
39
+ end
40
+ end
41
+
42
+ def test_format_without_validation
43
+ assert_equal Date.new(2005, 12, 11).to_s, Person.new(:date_of_birth => "11/12/05").date_of_birth.to_s
44
+ end
45
+
46
+ def test_invalid_formats
47
+ ['aksjhdaksjhd', 'meow', 'chocolate',
48
+ '221 jan 05', '21 JAN 001', '1 Jaw 00', '1 Febrarary 2003', '30/2/06',
49
+ '1/2/3/4', '11/22/33', '10/10/990', '189 /1 /9', '12\ f m', 100].each do |value|
50
+ assert_invalid_and_errors_match /invalid/, :date_of_birth => value
51
+ end
52
+ end
53
+
54
+ def test_date_objects
55
+ assert_update_and_equal '1963-04-05', :date_of_birth => Date.new(1963, 4, 5)
56
+ end
57
+
58
+ def test_before_and_after
59
+ p.update_attributes!(:date_of_death => '1950-01-01')
60
+ assert_invalid_and_errors_match /before/, :date_of_death => (Date.today + 1).to_s
61
+ assert_invalid_and_errors_match /before/, :date_of_death => Date.new(2030, 1, 1)
62
+
63
+ p.update_attributes!(:date_of_birth => '1950-01-01', :date_of_death => nil)
64
+ assert_invalid_and_errors_match /after/, :date_of_death => '1950-01-01'
65
+ assert p.update_attributes!(:date_of_death => Date.new(1951, 1, 1))
66
+ end
67
+
68
+ def test_before_and_after_with_custom_message
69
+ assert_invalid_and_errors_match /avant/, :date_of_arrival => 2.years.from_now, :date_of_departure => 2.years.ago
70
+ assert_invalid_and_errors_match /apres/, :date_of_arrival => '1792-03-03'
71
+ end
72
+
73
+ def test_before_and_after_with_i18n
74
+ original_load_path = I18n.load_path
75
+
76
+ I18n.load_path = [File.join(File.dirname(__FILE__),'fixtures/en.yml')]
77
+ I18n.reload!
78
+
79
+ assert_invalid_and_errors_match /i18n_before/, :date_of_death => (Date.today + 1).to_s
80
+ assert_invalid_and_errors_match /i18n_after/, :date_of_birth => (Date.today + 1).to_s
81
+ ensure
82
+ I18n.load_path = original_load_path
83
+ I18n.reload!
84
+ end
85
+
86
+ def test_dates_with_unknown_year
87
+ assert p.update_attributes!(:date_of_birth => '9999-12-11')
88
+ assert p.update_attributes!(:date_of_birth => Date.new(9999, 1, 1))
89
+ end
90
+
91
+ def test_us_date_format
92
+ with_us_date_format do
93
+ {'1/31/06' => '2006-01-31', '28 Feb 01' => '2001-02-28',
94
+ '10/10/80' => '1980-10-10', 'July 4 1960' => '1960-07-04',
95
+ '2006-03-20' => '2006-03-20'}.each do |value, result|
96
+ assert_update_and_equal result, :date_of_birth => value
97
+ end
98
+ end
99
+ end
100
+
101
+ def test_blank
102
+ assert p.update_attributes!(:date_of_birth => " ")
103
+ assert_nil p.date_of_birth
104
+ end
105
+
106
+ def test_conversion_of_restriction_result
107
+ assert_invalid_and_errors_match /Date of birth/, :date_of_death => Date.new(2001, 1, 1), :date_of_birth => Date.new(2005, 1, 1)
108
+ end
109
+
110
+ def test_multi_parameter_attribute_assignment_with_valid_date
111
+ assert_nothing_raised do
112
+ p.update_attributes!('date_of_birth(1i)' => '2006', 'date_of_birth(2i)' => '2', 'date_of_birth(3i)' => '10')
113
+ end
114
+
115
+ assert_equal Date.new(2006, 2, 10), p.date_of_birth
116
+ end
117
+
118
+ def test_multi_parameter_attribute_assignment_with_invalid_date
119
+ assert_nothing_raised do
120
+ assert !p.update_attributes('date_of_birth(1i)' => '2006', 'date_of_birth(2i)' => '2', 'date_of_birth(3i)' => '30')
121
+ end
122
+
123
+ assert p.errors[:date_of_birth]
124
+ end
125
+
126
+ def test_incomplete_multi_parameter_attribute_assignment
127
+ assert_nothing_raised do
128
+ assert !p.update_attributes('date_of_birth(1i)' => '2006', 'date_of_birth(2i)' => '1', 'date_of_birth(3i)' => '')
129
+ end
130
+
131
+ assert p.errors[:date_of_birth]
132
+ end
133
+ end
@@ -0,0 +1,77 @@
1
+ require File.dirname(__FILE__) + '/abstract_unit'
2
+
3
+ class DateTimeTest < Test::Unit::TestCase
4
+ def test_various_formats
5
+ formats = {
6
+ '2006-01-01 01:01:01' => /Jan 01 01:01:01 [\+-]?[\w ]+ 2006/,
7
+ '2/2/06 7pm' => /Feb 02 19:00:00 [\+-]?[\w ]+ 2006/,
8
+ '10 AUG 04 6.23am' => /Aug 10 06:23:00 [\+-]?[\w ]+ 2004/,
9
+ '6 June 1981 10 10' => /Jun 06 10:10:00 [\+-]?[\w ]+ 1981/,
10
+ 'September 01, 2007 06:10' => /Sep 01 06:10:00 [\+-]?[\w ]+ 2007/,
11
+ '2007-02-23T12:03:28' => /Feb 23 12:03:28 [\+-]?[\w ]+ 2007/
12
+ }
13
+
14
+ formats.each do |value, result|
15
+ assert_update_and_match result, :date_and_time_of_birth => value
16
+ end
17
+
18
+ with_us_date_format do
19
+ formats.each do |value, result|
20
+ assert_update_and_match result, :date_and_time_of_birth => value
21
+ end
22
+ end
23
+ end
24
+
25
+ def test_date_time_with_microseconds
26
+ assert_update_and_match /Mar 20 09:22:50 [\+-]?[\w ]+ 2007/, :date_and_time_of_birth => "20 Mar 07 09:22:50.987654"
27
+ assert_equal 987654, p.date_and_time_of_birth.usec
28
+ end
29
+
30
+ def test_invalid_formats
31
+ ['29 Feb 06 1am', '1 Jan 06', '7pm', 6].each do |value|
32
+ assert_invalid_and_errors_match /date time/, :date_and_time_of_birth => value
33
+ end
34
+ end
35
+
36
+ def test_before_and_after_restrictions_parsed_as_date_times
37
+ assert_invalid_and_errors_match /before/, :date_and_time_of_birth => '2008-01-02 00:00:00'
38
+ assert p.update_attributes!(:date_and_time_of_birth => '2008-01-01 01:01:00')
39
+
40
+ assert_invalid_and_errors_match /after/, :date_and_time_of_birth => '1981-01-01 01:00am'
41
+ assert p.update_attributes!(:date_and_time_of_birth => '1981-01-01 01:02am')
42
+ end
43
+
44
+ def test_multi_parameter_attribute_assignment_with_valid_date_times
45
+ assert_nothing_raised do
46
+ p.update_attributes!('date_and_time_of_birth(1i)' => '2006', 'date_and_time_of_birth(2i)' => '2', 'date_and_time_of_birth(3i)' => '20',
47
+ 'date_and_time_of_birth(4i)' => '23', 'date_and_time_of_birth(5i)' => '10', 'date_and_time_of_birth(6i)' => '40')
48
+ end
49
+
50
+ assert_equal Time.local(2006, 2, 20, 23, 10, 40), p.date_and_time_of_birth
51
+
52
+ # Without second parameter
53
+ assert_nothing_raised do
54
+ p.update_attributes!('date_and_time_of_birth(1i)' => '2004', 'date_and_time_of_birth(2i)' => '3', 'date_and_time_of_birth(3i)' => '14',
55
+ 'date_and_time_of_birth(4i)' => '22', 'date_and_time_of_birth(5i)' => '20')
56
+ end
57
+
58
+ assert_equal Time.local(2004, 3, 14, 22, 20), p.date_and_time_of_birth
59
+ end
60
+
61
+ def test_multi_parameter_attribute_assignment_with_invalid_date_time
62
+ assert_nothing_raised do
63
+ assert !p.update_attributes('date_and_time_of_birth(1i)' => '2006', 'date_and_time_of_birth(2i)' => '2', 'time_of_birth(3i)' => '10',
64
+ 'date_and_time_of_birth(4i)' => '30', 'date_and_time_of_birth(5i)' => '88', 'date_and_time_of_birth(6i)' => '100')
65
+ end
66
+
67
+ assert p.errors[:date_and_time_of_birth]
68
+ end
69
+
70
+ def test_incomplete_multi_parameter_attribute_assignment
71
+ assert_nothing_raised do
72
+ assert !p.update_attributes('date_and_time_of_birth(1i)' => '2006', 'date_and_time_of_birth(2i)' => '1')
73
+ end
74
+
75
+ assert p.errors[:date_and_time_of_birth]
76
+ end
77
+ end
@@ -0,0 +1,12 @@
1
+ en:
2
+ activerecord:
3
+ errors:
4
+ models:
5
+ person:
6
+ attributes:
7
+ date_of_birth:
8
+ after: 'i18n_after {{value}}'
9
+ before: 'i18n_before {{value}}'
10
+ date_of_death:
11
+ after: 'i18n_after {{value}}'
12
+ before: 'i18n_before {{value}}'
@@ -0,0 +1,4 @@
1
+ jonathan:
2
+ id: 1
3
+ required_date: 2006-01-01
4
+
@@ -0,0 +1,13 @@
1
+ class Person < ActiveRecord::Base
2
+ validates_date :date_of_birth, :allow_nil => true
3
+ validates_date :date_of_death, :allow_nil => true, :before => Proc.new { 1.day.from_now }, :after => :date_of_birth
4
+
5
+ validates_date :date_of_arrival, :allow_nil => true, :before => :date_of_departure, :after => '1 Jan 1800', :before_message => "avant %s", :after_message => "apres %s"
6
+
7
+ validates_time :time_of_birth, :allow_nil => true, :before => [Proc.new { Time.now }]
8
+ validates_time :time_of_death, :allow_nil => true, :after => [:time_of_birth, '7pm'], :before => [Proc.new { 10.years.from_now }]
9
+
10
+ validates_date_time :date_and_time_of_birth, :allow_nil => true, :before => '2008-01-01 01:01:01', :after => '1981-01-01 01:01am'
11
+
12
+ validates_date :required_date
13
+ end
data/test/schema.rb ADDED
@@ -0,0 +1,16 @@
1
+ ActiveRecord::Schema.define :version => 0 do
2
+ create_table :people, :force => true do |t|
3
+ t.column :date_of_birth, :date
4
+ t.column :date_of_death, :date
5
+
6
+ t.column :date_of_arrival, :date
7
+ t.column :date_of_departure, :date
8
+
9
+ t.column :time_of_birth, :time
10
+ t.column :time_of_death, :time
11
+
12
+ t.column :date_and_time_of_birth, :datetime
13
+
14
+ t.column :required_date, :date
15
+ end
16
+ end
data/test/time_test.rb ADDED
@@ -0,0 +1,103 @@
1
+ require File.dirname(__FILE__) + '/abstract_unit'
2
+
3
+ class TimeTest < Test::Unit::TestCase
4
+ def test_valid_when_nil
5
+ assert p.update_attributes!(:time_of_birth => nil, :time_of_death => nil, :time_of_death => nil)
6
+ end
7
+
8
+ def test_with_seconds
9
+ { '03:45:22' => /03:45:22/, '09:10:27' => /09:10:27/ }.each do |value, result|
10
+ assert_update_and_match result, :time_of_birth => value
11
+ end
12
+ end
13
+
14
+ def test_12_hour_with_minute
15
+ { '7.20pm' => /19:20:00/, ' 1:33 AM' => /01:33:00/, '11 28am' => /11:28:00/ }.each do |value, result|
16
+ assert_update_and_match result, :time_of_birth => value
17
+ end
18
+ end
19
+
20
+ def test_12_hour_without_minute
21
+ { '11 am' => /11:00:00/, '7PM ' => /19:00:00/, ' 1Am' => /01:00:00/, '12pm' => /12:00:00/, '12.00pm' => /12:00:00/, '12am' => /00:00:00/ }.each do |value, result|
22
+ assert_update_and_match result, :time_of_birth => value
23
+ end
24
+ end
25
+
26
+ def test_24_hour
27
+ { '22:00' => /22:00:00/, '10 23' => /10:23:00/, '01 01' => /01:01:00/ }.each do |value, result|
28
+ assert_update_and_match result, :time_of_birth => value
29
+ end
30
+ end
31
+
32
+ def test_24_hour_with_microseconds
33
+ assert_update_and_match /12:23:56/, :time_of_birth => "12:23:56.169732"
34
+ assert_equal 169732, p.time_of_birth.usec
35
+
36
+ assert_update_and_match /12:23:56/, :time_of_birth => "12:23:56.15"
37
+ assert_equal 15, p.time_of_birth.usec
38
+ end
39
+
40
+ def test_iso8601
41
+ assert_update_and_match /12:03:28/, :time_of_birth => "2008-02-23T12:03:28"
42
+ end
43
+
44
+ def test_24_hour_with_invalid_microseconds
45
+ assert_invalid_and_errors_match /invalid/, :time_of_birth => "12:23:11.1234567"
46
+ end
47
+
48
+ def test_time_objects
49
+ { Time.gm(2006, 2, 2, 22, 30) => /22:30:00/, '2pm' => /14:00:00/, Time.gm(2006, 2, 2, 1, 3) => /01:03:00/ }.each do |value, result|
50
+ assert_update_and_match result, :time_of_birth => value
51
+ end
52
+ end
53
+
54
+ def test_invalid_formats
55
+ ['1 PPM', 'lunchtime', '8..30', 'chocolate', '29am', '18:20 AM', '18:20 PM', 2].each do |value|
56
+ assert !p.update_attributes(:time_of_birth => value)
57
+ end
58
+ assert_match /time/, p.errors[:time_of_birth]
59
+ end
60
+
61
+ def test_after
62
+ assert_invalid_and_errors_match /must be after/, :time_of_death => '6pm'
63
+
64
+ assert p.update_attributes!(:time_of_death => '8pm')
65
+ assert p.update_attributes!(:time_of_death => nil, :time_of_birth => Time.gm(2001, 1, 1, 9))
66
+
67
+ assert_invalid_and_errors_match /must be after/, :time_of_death => '7am'
68
+ end
69
+
70
+ def test_before
71
+ assert_invalid_and_errors_match /must be before/, :time_of_birth => Time.now + 1.day
72
+ assert p.update_attributes!(:time_of_birth => Time.now - 1)
73
+ end
74
+
75
+ def test_blank
76
+ assert p.update_attributes!(:time_of_birth => " ")
77
+ assert_nil p.time_of_birth
78
+ end
79
+
80
+ def test_multi_parameter_attribute_assignment_with_valid_time
81
+ assert_nothing_raised do
82
+ p.update_attributes!('time_of_birth(1i)' => '3', 'time_of_birth(2i)' => '2', 'time_of_birth(3i)' => '10')
83
+ end
84
+
85
+ assert_equal Time.local(2000, 1, 1, 3, 2, 10), p.time_of_birth
86
+ end
87
+
88
+ def test_multi_parameter_attribute_assignment_with_invalid_time
89
+ assert_nothing_raised do
90
+ assert !p.update_attributes('time_of_birth(1i)' => '23', 'time_of_birth(2i)' => '2', 'time_of_birth(3i)' => '77')
91
+ end
92
+
93
+ assert p.errors[:time_of_birth]
94
+ end
95
+
96
+ def test_incomplete_multi_parameter_attribute_assignment
97
+ assert_nothing_raised do
98
+ assert !p.update_attributes('time_of_birth(1i)' => '10')
99
+ end
100
+
101
+ assert p.errors[:time_of_birth]
102
+ end
103
+ end
@@ -0,0 +1,63 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{validates_date_time}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jonathan Viney", "Nick Stenning"]
12
+ s.date = %q{2010-04-07}
13
+ s.description = %q{A Rails plugin that adds an ActiveRecord validation helper to do range and start/end date checking in.}
14
+ s.email = ["jonathan.viney@gmail.com", "nick@whiteink.com"]
15
+ s.extra_rdoc_files = [
16
+ "README.markdown"
17
+ ]
18
+ s.files = [
19
+ "CHANGELOG",
20
+ "MIT-LICENSE",
21
+ "README.markdown",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "init.rb",
25
+ "lib/multiparameter_attributes.rb",
26
+ "lib/parser.rb",
27
+ "lib/validates_date_time.rb",
28
+ "test/abstract_unit.rb",
29
+ "test/database.yml",
30
+ "test/date_test.rb",
31
+ "test/date_time_test.rb",
32
+ "test/fixtures/en.yml",
33
+ "test/fixtures/people.yml",
34
+ "test/fixtures/person.rb",
35
+ "test/schema.rb",
36
+ "test/time_test.rb",
37
+ "validates_date_time.gemspec"
38
+ ]
39
+ s.homepage = %q{http://github.com/nickstenning/validates_date_time}
40
+ s.rdoc_options = ["--charset=UTF-8"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = %q{1.3.6}
43
+ s.summary = %q{A Rails plugin adds the ability to validate dates and times with ActiveRecord.}
44
+ s.test_files = [
45
+ "test/abstract_unit.rb",
46
+ "test/date_test.rb",
47
+ "test/date_time_test.rb",
48
+ "test/fixtures/person.rb",
49
+ "test/schema.rb",
50
+ "test/time_test.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
58
+ else
59
+ end
60
+ else
61
+ end
62
+ end
63
+
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: validates_date_time
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Jonathan Viney
13
+ - Nick Stenning
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-04-07 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: A Rails plugin that adds an ActiveRecord validation helper to do range and start/end date checking in.
23
+ email:
24
+ - jonathan.viney@gmail.com
25
+ - nick@whiteink.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README.markdown
32
+ files:
33
+ - CHANGELOG
34
+ - MIT-LICENSE
35
+ - README.markdown
36
+ - Rakefile
37
+ - VERSION
38
+ - init.rb
39
+ - lib/multiparameter_attributes.rb
40
+ - lib/parser.rb
41
+ - lib/validates_date_time.rb
42
+ - test/abstract_unit.rb
43
+ - test/database.yml
44
+ - test/date_test.rb
45
+ - test/date_time_test.rb
46
+ - test/fixtures/en.yml
47
+ - test/fixtures/people.yml
48
+ - test/fixtures/person.rb
49
+ - test/schema.rb
50
+ - test/time_test.rb
51
+ - validates_date_time.gemspec
52
+ has_rdoc: true
53
+ homepage: http://github.com/nickstenning/validates_date_time
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options:
58
+ - --charset=UTF-8
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ requirements: []
76
+
77
+ rubyforge_project:
78
+ rubygems_version: 1.3.6
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: A Rails plugin adds the ability to validate dates and times with ActiveRecord.
82
+ test_files:
83
+ - test/abstract_unit.rb
84
+ - test/date_test.rb
85
+ - test/date_time_test.rb
86
+ - test/fixtures/person.rb
87
+ - test/schema.rb
88
+ - test/time_test.rb