validates_date_time 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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