validates_timeliness 1.1.5 → 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ = 1.1.6 [2009-03-19]
2
+ - Rail 2.3 support
3
+ - Added :with_date and :with_time options. They allow an attribute to be combined with another attribute or value to make a datetime value for validation against the temporal restrictions
4
+ - Added :equal_to option
5
+ - Option key validation
6
+ - Better behaviour with other plugins using alias_method_chain on read_attribute and define_attribute_methods
7
+ - Added option to enable datetime_select extension for future use to optionally enable. Enabled by default until version 2.
8
+ - Added :ignore_usec option for datetime restrictions to be compared without microsecond
9
+ - some refactoring
10
+
1
11
  = 1.1.5 [2009-01-21]
2
12
  - Fixed regex for 'yy' format token which wasn't greedy enough for date formats ending with year when a datetime string parsed as date with a 4 digit year
3
13
 
data/README.rdoc CHANGED
@@ -33,12 +33,16 @@ think should be a valid date or time string.
33
33
 
34
34
  As plugin (from master)
35
35
 
36
- ./script/plugin git://github.com/adzap/validates_timeliness.git
36
+ ./script/plugin install git://github.com/adzap/validates_timeliness.git
37
37
 
38
38
  As gem
39
39
 
40
40
  sudo gem install validates_timeliness
41
41
 
42
+ # in environment.rb
43
+
44
+ config.gem 'validates_timeliness'
45
+
42
46
 
43
47
  == USAGE:
44
48
 
@@ -51,50 +55,54 @@ validation method
51
55
  end
52
56
 
53
57
  The list of validation methods available are as follows:
54
-
55
- * validates_date - validate value as date
56
-
57
- * validates_time - validate value as time only i.e. '12:20pm'
58
-
59
- * validates_datetime - validate value as a full date and time
58
+ validates_date - validate value as date
59
+ validates_time - validate value as time only i.e. '12:20pm'
60
+ validates_datetime - validate value as a full date and time
60
61
 
61
62
  The validation methods take the usual options plus some specific ones to restrict
62
63
  the valid range of dates or times allowed
63
64
 
64
- Temporal options (or restrictions):
65
- :before - Attribute must be before this value to be valid
66
- :on_or_before - Attribute must be equal to or before this value to be valid
67
- :after - Attribute must be after this value to be valid
68
- :on_or_after - Attribute must be equal to or after this value to be valid
69
- :between - Attribute must be between the values to be valid
70
-
71
- Regular validation options:
72
- :allow_nil - Allow a nil value to be valid
73
- :allow_blank - Allows a nil or empty string value to be valid
74
- :if - Execute validation when :if evaluates true
75
- :unless - Execute validation when :unless evaluates false
76
-
77
- Message options: - Use these to override the default error messages
78
- :invalid_date_message
79
- :invalid_time_message
80
- :invalid_datetime_message
81
- :before_message
82
- :on_or_before_message
83
- :after_message
84
- :on_or_after_message
85
- :between_message
86
-
87
- The temporal restrictions can take 4 different value types:
88
-
89
- * String value
90
- * Date, Time, or DateTime object value
91
- * Proc or lambda object
92
- * A symbol matching the method name in the model
93
- * Between option takes an array of two values or a range
65
+ Temporal options (or restrictions):
66
+ :equal_to - Attribute must be equal to value to be valid
67
+ :before - Attribute must be before this value to be valid
68
+ :on_or_before - Attribute must be equal to or before this value to be valid
69
+ :after - Attribute must be after this value to be valid
70
+ :on_or_after - Attribute must be equal to or after this value to be valid
71
+ :between - Attribute must be between the values to be valid. Takes an array of two values or a range
72
+
73
+ Regular validation options:
74
+ :allow_nil - Allow a nil value to be valid
75
+ :allow_blank - Allows a nil or empty string value to be valid
76
+ :if - Execute validation when :if evaluates true
77
+ :unless - Execute validation when :unless evaluates false
78
+
79
+ Special options:
80
+ :with_time - Validate a date attribute value combined with a time value against any temporal restrictions
81
+ :with_date - Validate a time attribute value combined with a date value against any temporal restrictions
82
+ :ignore_usec - Ignores microsecond value on datetime restrictions
83
+
84
+ Message options: - Use these to override the default error messages
85
+ :invalid_date_message
86
+ :invalid_time_message
87
+ :invalid_datetime_message
88
+ :equal_to_message
89
+ :before_message
90
+ :on_or_before_message
91
+ :after_message
92
+ :on_or_after_message
93
+ :between_message
94
+
95
+ The temporal restrictions, with_date and with_time can take 4 different value types:
96
+ * String value
97
+ * Date, Time, or DateTime object value
98
+ * Proc or lambda object which may take an optional parameter being the record object
99
+ * A symbol matching the method name in the model
94
100
 
95
101
  When an attribute value is compared to temporal restrictions, they are compared as
96
102
  the same type as the validation method type. So using validates_date means all
97
- values are compared as dates.
103
+ values are compared as dates. This is except in the case of with_time and with_date
104
+ options which effectively force the value to validated as a datetime against the
105
+ temporal options.
98
106
 
99
107
  == EXAMPLES:
100
108
 
@@ -107,6 +115,8 @@ values are compared as dates.
107
115
  :allow_nil => true
108
116
 
109
117
  validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now }
118
+
119
+ validates_date :entry_date, :with_time => '17:00', :on_or_before => :competition_closing
110
120
 
111
121
 
112
122
  === DATE/TIME FORMATS:
@@ -120,44 +130,43 @@ be happy to know that is exactly the format you can use to define your own if
120
130
  you want. No complex regular expressions or duck punching (monkey patching) the
121
131
  plugin is needed.
122
132
 
123
- Time formats:
124
- hh:nn:ss
125
- hh-nn-ss
126
- h:nn
127
- h.nn
128
- h nn
129
- h-nn
130
- h:nn_ampm
131
- h.nn_ampm
132
- h nn_ampm
133
- h-nn_ampm
134
- h_ampm
135
-
136
- NOTE: Any time format without a meridian token (the 'ampm' token) is considered
137
- in 24 hour time.
138
-
139
- Date formats:
140
- yyyy/mm/dd
141
- yyyy-mm-dd
142
- yyyy.mm.dd
143
- m/d/yy OR d/m/yy
144
- m\d\yy OR d\m\yy
145
- d-m-yy
146
- d.m.yy
147
- d mmm yy
148
-
149
- NOTE: To use non-US date formats see US/EURO FORMATS section
150
-
151
- Datetime formats:
152
- m/d/yy h:nn:ss OR d/m/yy hh:nn:ss
153
- m/d/yy h:nn OR d/m/yy h:nn
154
- m/d/yy h:nn_ampm OR d/m/yy h:nn_ampm
155
- yyyy-mm-dd hh:nn:ss
156
- yyyy-mm-dd h:nn
157
- ddd mmm d hh:nn:ss zo yyyy # Ruby time string
158
- yyyy-mm-ddThh:nn:ss(?:Z|zo) # ISO 8601
159
-
160
- NOTE: To use non-US date formats see US/EURO FORMATS section
133
+ ==== Time formats:
134
+ hh:nn:ss
135
+ hh-nn-ss
136
+ h:nn
137
+ h.nn
138
+ h nn
139
+ h-nn
140
+ h:nn_ampm
141
+ h.nn_ampm
142
+ h nn_ampm
143
+ h-nn_ampm
144
+ h_ampm
145
+
146
+ NOTE: Any time format without a meridian token (the 'ampm' token) is considered in 24 hour time.
147
+
148
+ ==== Date formats:
149
+ yyyy/mm/dd
150
+ yyyy-mm-dd
151
+ yyyy.mm.dd
152
+ m/d/yy OR d/m/yy
153
+ m\d\yy OR d\m\yy
154
+ d-m-yy
155
+ d.m.yy
156
+ d mmm yy
157
+
158
+ NOTE: To use non-US date formats see US/EURO FORMATS section
159
+
160
+ ==== Datetime formats:
161
+ m/d/yy h:nn:ss OR d/m/yy hh:nn:ss
162
+ m/d/yy h:nn OR d/m/yy h:nn
163
+ m/d/yy h:nn_ampm OR d/m/yy h:nn_ampm
164
+ yyyy-mm-dd hh:nn:ss
165
+ yyyy-mm-dd h:nn
166
+ ddd mmm d hh:nn:ss zo yyyy # Ruby time string
167
+ yyyy-mm-ddThh:nn:ss(?:Z|zo) # ISO 8601
168
+
169
+ NOTE: To use non-US date formats see US/EURO FORMATS section
161
170
 
162
171
  Here is what each format token means:
163
172
 
@@ -219,7 +228,7 @@ Done! That format is no longer considered valid. Easy!
219
228
  Ok, now I hear you say "Well I have format that I want to use but you don't have it".
220
229
  Ahh, then add it yourself. Again stick this in an initializer file
221
230
 
222
- ValidatesTimeliness::Formats.add_formats(:time, "d o'clock")
231
+ ValidatesTimeliness::Formats.add_formats(:time, "d o'clock")
223
232
 
224
233
  Now "10 o'clock" will be a valid value. So easy, no more whingeing!
225
234
 
@@ -234,7 +243,7 @@ with an existing format, will mean your format is ignored. If you need to make
234
243
  your new format higher precedence than an existing format, you can include the
235
244
  before option like so
236
245
 
237
- ValidatesTimeliness::Formats.add_formats(:time, 'ss:nn:hh', :before => 'hh:nn:ss')
246
+ ValidatesTimeliness::Formats.add_formats(:time, 'ss:nn:hh', :before => 'hh:nn:ss')
238
247
 
239
248
  Now a time of '59:30:23' will be interpreted as 11:30:59 pm. This option saves
240
249
  you adding a new one and deleting an old one to get it to work.
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require 'date'
5
5
  require 'spec/rake/spectask'
6
6
 
7
7
  GEM = "validates_timeliness"
8
- GEM_VERSION = "1.1.5"
8
+ GEM_VERSION = "1.1.6"
9
9
  AUTHOR = "Adam Meehan"
10
10
  EMAIL = "adam.meehan@gmail.com"
11
11
  HOMEPAGE = "http://github.com/adzap/validates_timeliness"
@@ -24,9 +24,6 @@ spec = Gem::Specification.new do |s|
24
24
  s.email = EMAIL
25
25
  s.homepage = HOMEPAGE
26
26
 
27
- # Uncomment this to add a dependency
28
- # s.add_dependency "foo"
29
-
30
27
  s.require_path = 'lib'
31
28
  s.autorequire = GEM
32
29
  s.files = %w(LICENSE README.rdoc Rakefile TODO CHANGELOG) + Dir.glob("{lib,spec}/**/*")
data/TODO CHANGED
@@ -1,4 +1,3 @@
1
1
  - :format option
2
- - :with_date and :with_time options
3
2
  - valid formats could come from locale file
4
3
  - add replace_formats instead add_formats :before
@@ -1,4 +1,9 @@
1
1
  module ValidatesTimeliness
2
+
3
+ def self.enable_datetime_select_invalid_value_extension!
4
+ ::ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::ActionView::InstanceTag)
5
+ end
6
+
2
7
  module ActionView
3
8
 
4
9
  # Intercepts the date and time select helpers to allow the
@@ -41,5 +46,3 @@ module ValidatesTimeliness
41
46
 
42
47
  end
43
48
  end
44
-
45
- ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::ActionView::InstanceTag)
@@ -18,29 +18,27 @@ module ValidatesTimeliness
18
18
 
19
19
  def self.included(base)
20
20
  base.extend ClassMethods
21
+ base.class_eval do
22
+ alias_method_chain :read_attribute, :timeliness
23
+ class << self
24
+ alias_method_chain :define_attribute_methods, :timeliness
25
+ end
26
+ end
21
27
  end
22
28
 
23
29
  # Adds check for cached date/time attributes which have been type cast already
24
30
  # and value can be used from cache. This prevents the raw date/time value from
25
31
  # being type cast using default Rails type casting when writing values
26
32
  # to the database.
27
- def read_attribute(attr_name)
33
+ def read_attribute_with_timeliness(attr_name)
28
34
  attr_name = attr_name.to_s
29
35
  if !(value = @attributes[attr_name]).nil?
30
- if column = column_for_attribute(attr_name)
31
- if unserializable_attribute?(attr_name, column)
32
- unserialize_attribute(attr_name)
33
- elsif [:date, :time, :datetime].include?(column.type) && @attributes_cache.has_key?(attr_name)
34
- @attributes_cache[attr_name]
35
- else
36
- column.type_cast(value)
37
- end
38
- else
39
- value
36
+ column = column_for_attribute(attr_name)
37
+ if column && [:date, :time, :datetime].include?(column.type) && @attributes_cache.has_key?(attr_name)
38
+ return @attributes_cache[attr_name]
40
39
  end
41
- else
42
- nil
43
40
  end
41
+ read_attribute_without_timeliness(attr_name)
44
42
  end
45
43
 
46
44
  # If Rails dirty attributes is enabled then the value is added to
@@ -48,19 +46,20 @@ module ValidatesTimeliness
48
46
  # implementation as it chains the write_attribute method which deletes
49
47
  # the attribute from the cache.
50
48
  def write_date_time_attribute(attr_name, value, type, time_zone_aware)
51
- old = read_attribute(attr_name) if defined?(::ActiveRecord::Dirty)
52
49
  new = self.class.parse_date_time(value, type)
53
50
 
54
- unless type == :date || new.nil?
55
- new = new.to_time rescue new
51
+ if new && type != :date
52
+ new = new.to_time
53
+ new = new.in_time_zone if time_zone_aware
56
54
  end
57
55
 
58
- new = new.in_time_zone if new && time_zone_aware
59
- @attributes_cache[attr_name] = new
60
-
61
- if defined?(::ActiveRecord::Dirty) && !changed_attributes.include?(attr_name) && old != new
62
- changed_attributes[attr_name] = (old.duplicable? ? old.clone : old)
56
+ if defined?(::ActiveRecord::Dirty) && !changed_attributes.include?(attr_name)
57
+ old = read_attribute(attr_name)
58
+ if old != new
59
+ changed_attributes[attr_name] = (old.duplicable? ? old.clone : old)
60
+ end
63
61
  end
62
+ @attributes_cache[attr_name] = new
64
63
  @attributes[attr_name] = value
65
64
  end
66
65
 
@@ -74,7 +73,7 @@ module ValidatesTimeliness
74
73
 
75
74
  if @attributes_cache.has_key?(attr_name)
76
75
  time = read_attribute_before_type_cast(attr_name)
77
- time = self.class.parse_date_time(date, type)
76
+ time = self.class.parse_date_time(time, type)
78
77
  else
79
78
  time = read_attribute(attr_name)
80
79
  @attributes[attr_name] = time && time_zone_aware ? time.in_time_zone : time
@@ -84,19 +83,15 @@ module ValidatesTimeliness
84
83
 
85
84
  module ClassMethods
86
85
 
87
- # Override AR method to define attribute reader and writer method for
88
- # date, time and datetime attributes to use plugin parser.
89
- def define_attribute_methods
86
+ # Define attribute reader and writer method for date, time and
87
+ # datetime attributes to use plugin parser.
88
+ def define_attribute_methods_with_timeliness
90
89
  return if generated_methods?
91
90
  columns_hash.each do |name, column|
92
91
  unless instance_method_already_implemented?(name)
93
- if self.serialized_attributes[name]
94
- define_read_method_for_serialized_attribute(name)
95
- elsif [:date, :time, :datetime].include?(column.type)
92
+ if [:date, :time, :datetime].include?(column.type)
96
93
  time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
97
94
  define_read_method_for_dates_and_times(name, column.type, time_zone_aware)
98
- else
99
- define_read_method(name.to_sym, name, column)
100
95
  end
101
96
  end
102
97
 
@@ -104,15 +99,10 @@ module ValidatesTimeliness
104
99
  if [:date, :time, :datetime].include?(column.type)
105
100
  time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
106
101
  define_write_method_for_dates_and_times(name, column.type, time_zone_aware)
107
- else
108
- define_write_method(name.to_sym)
109
102
  end
110
103
  end
111
-
112
- unless instance_method_already_implemented?("#{name}?")
113
- define_question_method(name)
114
- end
115
104
  end
105
+ define_attribute_methods_without_timeliness
116
106
  end
117
107
 
118
108
  # Define write method for date, time and datetime columns
@@ -1,6 +1,10 @@
1
1
  module ValidatesTimeliness
2
- module ActiveRecord
3
2
 
3
+ def self.enable_multiparameter_attributes_extension!
4
+ ::ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::MultiparameterAttributes)
5
+ end
6
+
7
+ module ActiveRecord
4
8
  module MultiparameterAttributes
5
9
 
6
10
  def self.included(base)
@@ -38,13 +42,13 @@ module ValidatesTimeliness
38
42
  values = values.map(&:to_s)
39
43
 
40
44
  case type
41
- when :date
42
- extract_date_from_multiparameter_attributes(values)
43
- when :time
44
- extract_time_from_multiparameter_attributes(values)
45
- when :datetime
46
- date_values, time_values = values.slice!(0, 3), values
47
- extract_date_from_multiparameter_attributes(date_values) + " " + extract_time_from_multiparameter_attributes(time_values)
45
+ when :date
46
+ extract_date_from_multiparameter_attributes(values)
47
+ when :time
48
+ extract_time_from_multiparameter_attributes(values)
49
+ when :datetime
50
+ date_values, time_values = values.slice!(0, 3), values
51
+ extract_date_from_multiparameter_attributes(date_values) + " " + extract_time_from_multiparameter_attributes(time_values)
48
52
  end
49
53
  end
50
54
 
@@ -60,5 +64,3 @@ module ValidatesTimeliness
60
64
 
61
65
  end
62
66
  end
63
-
64
- ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::MultiparameterAttributes)
@@ -168,9 +168,9 @@ module ValidatesTimeliness
168
168
  exp, processor = expression_set(type, string).find do |regexp, proc|
169
169
  full = /\A#{regexp}\Z/ if strict
170
170
  full ||= case type
171
- when :datetime then /\A#{regexp}\Z/
172
- when :date then /\A#{regexp}/
173
- else /#{regexp}\Z/
171
+ when :date then /\A#{regexp}/
172
+ when :time then /#{regexp}\Z/
173
+ when :datetime then /\A#{regexp}\Z/
174
174
  end
175
175
  matches = full.match(string.strip)
176
176
  end
@@ -268,17 +268,17 @@ module ValidatesTimeliness
268
268
  # datetime attributes to allow date string as datetime
269
269
  def expression_set(type, string)
270
270
  case type
271
- when :date
272
- date_expressions
273
- when :time
274
- time_expressions
275
- when :datetime
276
- # gives a speed-up for date string as datetime attributes
277
- if string.length < 11
278
- date_expressions + datetime_expressions
279
- else
280
- datetime_expressions + date_expressions
281
- end
271
+ when :date
272
+ date_expressions
273
+ when :time
274
+ time_expressions
275
+ when :datetime
276
+ # gives a speed-up for date string as datetime attributes
277
+ if string.length < 11
278
+ date_expressions + datetime_expressions
279
+ else
280
+ datetime_expressions + date_expressions
281
+ end
282
282
  end
283
283
  end
284
284
 
@@ -316,3 +316,5 @@ module ValidatesTimeliness
316
316
  end
317
317
  end
318
318
  end
319
+
320
+ ValidatesTimeliness::Formats.compile_format_expressions
@@ -5,6 +5,7 @@ en:
5
5
  invalid_date: "is not a valid date"
6
6
  invalid_time: "is not a valid time"
7
7
  invalid_datetime: "is not a valid datetime"
8
+ equal_to: "must be equal to {{restriction}}"
8
9
  before: "must be before {{restriction}}"
9
10
  on_or_before: "must be on or before {{restriction}}"
10
11
  after: "must be after {{restriction}}"
@@ -92,8 +92,8 @@ module Spec
92
92
  end
93
93
 
94
94
  def parse_and_cast(value)
95
- value = @validator.send(:restriction_value, value, @record)
96
- @validator.send(:type_cast_value, value)
95
+ value = @validator.class.send(:evaluate_option_value, value, @type, @record)
96
+ @validator.class.send(:type_cast_value, value, @type)
97
97
  end
98
98
 
99
99
  def error_matching(value, option)
@@ -117,11 +117,11 @@ module Spec
117
117
 
118
118
  def error_message_for(option)
119
119
  msg = @validator.send(:error_messages)[option]
120
- restriction = @validator.send(:restriction_value, @validator.configuration[option], @record)
120
+ restriction = @validator.class.send(:evaluate_option_value, @validator.configuration[option], @type, @record)
121
121
 
122
122
  if restriction
123
123
  restriction = [restriction] unless restriction.is_a?(Array)
124
- restriction.map! {|r| @validator.send(:type_cast_value, r) }
124
+ restriction.map! {|r| @validator.class.send(:type_cast_value, r, @type) }
125
125
  interpolate = @validator.send(:interpolation_values, option, restriction )
126
126
  # get I18n message if defined and has interpolation keys in msg
127
127
  if defined?(I18n) && !@validator.send(:custom_error_messages).include?(option)
@@ -49,13 +49,13 @@ module ValidatesTimeliness
49
49
  private
50
50
 
51
51
  def validates_timeliness_of(attr_names, configuration)
52
- validator = ValidatesTimeliness::Validator.new(configuration)
52
+ validator = ValidatesTimeliness::Validator.new(configuration.symbolize_keys)
53
53
 
54
54
  # bypass handling of allow_nil and allow_blank to validate raw value
55
55
  configuration.delete(:allow_nil)
56
56
  configuration.delete(:allow_blank)
57
57
  validates_each(attr_names, configuration) do |record, attr_name, value|
58
- validator.call(record, attr_name)
58
+ validator.call(record, attr_name, value)
59
59
  end
60
60
  end
61
61
 
@@ -12,6 +12,7 @@ module ValidatesTimeliness
12
12
  }
13
13
 
14
14
  RESTRICTION_METHODS = {
15
+ :equal_to => :==,
15
16
  :before => :<,
16
17
  :after => :>,
17
18
  :on_or_before => :<=,
@@ -19,18 +20,24 @@ module ValidatesTimeliness
19
20
  :between => lambda {|v, r| (r.first..r.last).include?(v) }
20
21
  }
21
22
 
23
+ VALID_OPTIONS = [
24
+ :on, :if, :unless, :allow_nil, :empty, :allow_blank, :blank,
25
+ :with_time, :with_date, :ignore_usec,
26
+ :invalid_time_message, :invalid_date_message, :invalid_datetime_message
27
+ ] + RESTRICTION_METHODS.keys.map {|option| [option, "#{option}_message".to_sym] }.flatten
28
+
22
29
  attr_reader :configuration, :type
23
30
 
24
31
  def initialize(configuration)
25
- defaults = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false }
32
+ defaults = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false, :ignore_usec => false }
26
33
  @configuration = defaults.merge(configuration)
27
34
  @type = @configuration.delete(:type)
35
+ validate_options(@configuration)
28
36
  end
29
37
 
30
- def call(record, attr_name)
31
- value = record.send(attr_name)
38
+ def call(record, attr_name, value)
32
39
  value = record.class.parse_date_time(value, type, false) if value.is_a?(String)
33
- raw_value = raw_value(record, attr_name)
40
+ raw_value = raw_value(record, attr_name) || value
34
41
 
35
42
  return if (raw_value.nil? && configuration[:allow_nil]) || (raw_value.blank? && configuration[:allow_blank])
36
43
 
@@ -44,18 +51,25 @@ module ValidatesTimeliness
44
51
  private
45
52
 
46
53
  def raw_value(record, attr_name)
47
- record.send("#{attr_name}_before_type_cast")
54
+ record.send("#{attr_name}_before_type_cast") rescue nil
48
55
  end
49
56
 
50
57
  def validate_restrictions(record, attr_name, value)
51
- value = type_cast_value(value)
52
-
58
+ value = if @configuration[:with_time] || @configuration[:with_date]
59
+ restriction_type = :datetime
60
+ combine_date_and_time(value, record)
61
+ else
62
+ restriction_type = type
63
+ self.class.type_cast_value(value, type, @configuration[:ignore_usec])
64
+ end
65
+ return if value.nil?
66
+
53
67
  RESTRICTION_METHODS.each do |option, method|
54
68
  next unless restriction = configuration[option]
55
69
  begin
56
- restriction = restriction_value(restriction, record)
70
+ restriction = self.class.evaluate_option_value(restriction, restriction_type, record)
57
71
  next if restriction.nil?
58
- restriction = type_cast_value(restriction)
72
+ restriction = self.class.type_cast_value(restriction, restriction_type, @configuration[:ignore_usec])
59
73
 
60
74
  unless evaluate_restriction(restriction, value, method)
61
75
  add_error(record, attr_name, option, interpolation_values(option, restriction))
@@ -87,10 +101,10 @@ module ValidatesTimeliness
87
101
  return true if restriction.nil?
88
102
 
89
103
  case comparator
90
- when Symbol
91
- value.send(comparator, restriction)
92
- when Proc
93
- comparator.call(value, restriction)
104
+ when Symbol
105
+ value.send(comparator, restriction)
106
+ when Proc
107
+ comparator.call(value, restriction)
94
108
  end
95
109
  end
96
110
 
@@ -107,13 +121,11 @@ module ValidatesTimeliness
107
121
  end
108
122
 
109
123
  def error_messages
110
- return @error_messages if defined?(@error_messages)
111
- @error_messages = ValidatesTimeliness.default_error_messages.merge(custom_error_messages)
124
+ @error_messages ||= ValidatesTimeliness.default_error_messages.merge(custom_error_messages)
112
125
  end
113
126
 
114
127
  def custom_error_messages
115
- return @custom_error_messages if defined?(@custom_error_messages)
116
- @custom_error_messages = configuration.inject({}) {|msgs, (k, v)|
128
+ @custom_error_messages ||= configuration.inject({}) {|msgs, (k, v)|
117
129
  if md = /(.*)_message$/.match(k.to_s)
118
130
  msgs[md[1].to_sym] = v
119
131
  end
@@ -121,42 +133,72 @@ module ValidatesTimeliness
121
133
  }
122
134
  end
123
135
 
124
- def restriction_value(restriction, record)
125
- case restriction
136
+ def combine_date_and_time(value, record)
137
+ if type == :date
138
+ date = value
139
+ time = @configuration[:with_time]
140
+ else
141
+ date = @configuration[:with_date]
142
+ time = value
143
+ end
144
+ date, time = self.class.evaluate_option_value(date, :date, record), self.class.evaluate_option_value(time, :time, record)
145
+ return if date.nil? || time.nil?
146
+ record.class.send(:make_time, [date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
147
+ end
148
+
149
+ def validate_options(options)
150
+ invalid_for_type = ([:time, :date, :datetime] - [@type]).map {|k| "invalid_#{k}_message".to_sym }
151
+ invalid_for_type << :with_date unless @type == :time
152
+ invalid_for_type << :with_time unless @type == :date
153
+ options.assert_valid_keys(VALID_OPTIONS - invalid_for_type)
154
+ end
155
+
156
+ # class methods
157
+ class << self
158
+
159
+ def evaluate_option_value(value, type, record)
160
+ case value
126
161
  when Time, Date, DateTime
127
- restriction
162
+ value
128
163
  when Symbol
129
- restriction_value(record.send(restriction), record)
164
+ evaluate_option_value(record.send(value), type, record)
130
165
  when Proc
131
- restriction_value(restriction.call(record), record)
166
+ evaluate_option_value(value.call(record), type, record)
132
167
  when Array
133
- restriction.map {|r| restriction_value(r, record) }.sort
168
+ value.map {|r| evaluate_option_value(r, type, record) }.sort
134
169
  when Range
135
- restriction_value([restriction.first, restriction.last], record)
170
+ evaluate_option_value([value.first, value.last], type, record)
136
171
  else
137
- record.class.parse_date_time(restriction, type, false)
172
+ record.class.parse_date_time(value, type, false)
173
+ end
138
174
  end
139
- end
140
-
141
- def type_cast_value(value)
142
- if value.is_a?(Array)
143
- value.map {|v| type_cast_value(v) }
144
- else
145
- case type
146
- when :time
147
- value.to_dummy_time
148
- when :date
149
- value.to_date
150
- when :datetime
151
- if value.is_a?(DateTime) || value.is_a?(Time)
152
- value.to_time
175
+
176
+ def type_cast_value(value, type, ignore_usec=false)
177
+ if value.is_a?(Array)
178
+ value.map {|v| type_cast_value(v, type, ignore_usec) }
179
+ else
180
+ value = case type
181
+ when :time
182
+ value.to_dummy_time
183
+ when :date
184
+ value.to_date
185
+ when :datetime
186
+ if value.is_a?(DateTime) || value.is_a?(Time)
187
+ value.to_time
188
+ else
189
+ value.to_time(ValidatesTimeliness.default_timezone)
190
+ end
153
191
  else
154
- value.to_time(ValidatesTimeliness.default_timezone)
192
+ nil
193
+ end
194
+ if ignore_usec && value.is_a?(Time)
195
+ ::ActiveRecord::Base.send(:make_time, Array(value).reverse[4..9])
196
+ else
197
+ value
155
198
  end
156
- else
157
- nil
158
199
  end
159
200
  end
201
+
160
202
  end
161
203
 
162
204
  end
@@ -21,14 +21,20 @@ module ValidatesTimeliness
21
21
 
22
22
  class << self
23
23
 
24
- def load_error_messages_with_i18n
25
- I18n.load_path += [ LOCALE_PATH ]
24
+ def enable_datetime_select_extension!
25
+ enable_datetime_select_invalid_value_extension!
26
+ enable_multiparameter_attributes_extension!
26
27
  end
27
28
 
28
- def load_error_messages_without_i18n
29
- messages = YAML::load(IO.read(LOCALE_PATH))
30
- errors = messages['en']['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h }
31
- ::ActiveRecord::Errors.default_error_messages.update(errors)
29
+ def load_error_messages
30
+ if defined?(I18n)
31
+ I18n.load_path += [ LOCALE_PATH ]
32
+ I18n.reload!
33
+ else
34
+ messages = YAML::load(IO.read(LOCALE_PATH))
35
+ errors = messages['en']['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h }
36
+ ::ActiveRecord::Errors.default_error_messages.update(errors)
37
+ end
32
38
  end
33
39
 
34
40
  def default_error_messages
@@ -39,28 +45,13 @@ module ValidatesTimeliness
39
45
  end
40
46
  end
41
47
 
42
- def setup_for_rails_2_0
43
- load_error_messages_without_i18n
44
- end
45
-
46
- def setup_for_rails_2_1
47
- load_error_messages_without_i18n
48
- end
49
-
50
- def setup_for_rails_2_2
51
- load_error_messages_with_i18n
52
- end
53
-
54
48
  def setup_for_rails
55
49
  major, minor = Rails::VERSION::MAJOR, Rails::VERSION::MINOR
56
50
  self.default_timezone = ::ActiveRecord::Base.default_timezone
57
- self.send("setup_for_rails_#{major}_#{minor}")
58
- rescue
59
- puts "Rails version #{major}.#{minor}.x not explicitly supported by validates_timeliness plugin. You may encounter some problems."
51
+ self.enable_datetime_select_extension!
52
+ self.load_error_messages
60
53
  end
61
54
  end
62
55
  end
63
56
 
64
57
  ValidatesTimeliness.setup_for_rails
65
-
66
- ValidatesTimeliness::Formats.compile_format_expressions
@@ -1,9 +1,6 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
4
- include ValidatesTimeliness::ActiveRecord::AttributeMethods
5
- include ValidatesTimeliness::ValidationMethods
6
-
7
4
  before do
8
5
  @person = Person.new
9
6
  end
@@ -35,7 +32,7 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
35
32
  @person.birth_time
36
33
  end
37
34
 
38
- it "should call rea_date_time_attribute when datetime attribute is retrieved" do
35
+ it "should call read_date_time_attribute when datetime attribute is retrieved" do
39
36
  @person.should_receive(:read_date_time_attribute)
40
37
  @person.birth_date_and_time = "2000-01-01 12:00"
41
38
  @person.birth_date_and_time
@@ -72,6 +69,11 @@ describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
72
69
  @person.birth_date_and_time.should be_kind_of(Time)
73
70
  end
74
71
 
72
+ it "should return Time object for datetime attribute read method when assigned Date object" do
73
+ @person.birth_date_and_time = Date.today
74
+ @person.birth_date_and_time.should be_kind_of(Time)
75
+ end
76
+
75
77
  it "should return Time object for datetime attribute read method when assigned string" do
76
78
  @person.birth_date_and_time = "2000-01-01 02:03:04"
77
79
  @person.birth_date_and_time.should be_kind_of(Time)
@@ -9,7 +9,7 @@
9
9
  # ginger spec
10
10
  #
11
11
  Ginger.configure do |config|
12
- rails_versions = ['2.0.2', '2.1.2', '2.2.2']
12
+ rails_versions = ['2.0.2', '2.1.2', '2.2.2', '2.3.2']
13
13
 
14
14
  rails_versions.each do |v|
15
15
  g = Ginger::Scenario.new
data/spec/spec_helper.rb CHANGED
@@ -2,7 +2,7 @@ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
2
  $:.unshift(File.dirname(__FILE__))
3
3
  $:.unshift(File.dirname(__FILE__) + '/resources')
4
4
 
5
- ENV['RAILS_ENV'] = 'test'
5
+ RAILS_ENV = ENV['RAILS_ENV'] = 'test'
6
6
 
7
7
  require 'rubygems'
8
8
  require 'spec'
@@ -30,6 +30,7 @@ require 'active_record'
30
30
  require 'active_record/version'
31
31
  require 'action_controller'
32
32
  require 'action_view'
33
+ require 'action_mailer'
33
34
 
34
35
  require 'spec/rails'
35
36
  require 'time_travel/time_travel'
@@ -16,55 +16,71 @@ describe ValidatesTimeliness::Validator do
16
16
  @person = Person.new
17
17
  end
18
18
 
19
- describe "restriction_value" do
19
+ describe "option keys validation" do
20
+ before do
21
+ keys = ValidatesTimeliness::Validator::VALID_OPTIONS - [:invalid_date_message, :invalid_time_message, :with_date, :with_time]
22
+ @valid_options = keys.inject({}) {|hash, opt| hash[opt] = nil; hash }
23
+ end
24
+
25
+ it "should raise error if invalid option key passed" do
26
+ @valid_options.update(:invalid_key => 'will not open lock')
27
+ lambda { Person.validates_datetime(@valid_options) }.should raise_error(ArgumentError)
28
+ end
29
+
30
+ it "should not raise error if option keys are valid" do
31
+ lambda { Person.validates_datetime(@valid_options) }.should_not raise_error(ArgumentError)
32
+ end
33
+ end
34
+
35
+ describe "evaluate_option_value" do
20
36
  it "should return Time object when restriction is Time object" do
21
- restriction_value(Time.now, :datetime).should be_kind_of(Time)
37
+ evaluate_option_value(Time.now, :datetime).should be_kind_of(Time)
22
38
  end
23
39
 
24
40
  it "should return Time object when restriction is string" do
25
- restriction_value("2007-01-01 12:00", :datetime).should be_kind_of(Time)
41
+ evaluate_option_value("2007-01-01 12:00", :datetime).should be_kind_of(Time)
26
42
  end
27
43
 
28
44
  it "should return Time object when restriction is method and method returns Time object" do
29
45
  person.stub!(:datetime_attr).and_return(Time.now)
30
- restriction_value(:datetime_attr, :datetime).should be_kind_of(Time)
46
+ evaluate_option_value(:datetime_attr, :datetime).should be_kind_of(Time)
31
47
  end
32
48
 
33
49
  it "should return Time object when restriction is method and method returns string" do
34
50
  person.stub!(:datetime_attr).and_return("2007-01-01 12:00")
35
- restriction_value(:datetime_attr, :datetime).should be_kind_of(Time)
51
+ evaluate_option_value(:datetime_attr, :datetime).should be_kind_of(Time)
36
52
  end
37
53
 
38
54
  it "should return Time object when restriction is proc which returns Time object" do
39
- restriction_value(lambda { Time.now }, :datetime).should be_kind_of(Time)
55
+ evaluate_option_value(lambda { Time.now }, :datetime).should be_kind_of(Time)
40
56
  end
41
57
 
42
58
  it "should return Time object when restriction is proc which returns string" do
43
- restriction_value(lambda {"2007-01-01 12:00"}, :datetime).should be_kind_of(Time)
59
+ evaluate_option_value(lambda {"2007-01-01 12:00"}, :datetime).should be_kind_of(Time)
44
60
  end
45
61
 
46
62
  it "should return array of Time objects when restriction is array of Time objects" do
47
63
  time1, time2 = Time.now, 1.day.ago
48
- restriction_value([time1, time2], :datetime).should == [time2, time1]
64
+ evaluate_option_value([time1, time2], :datetime).should == [time2, time1]
49
65
  end
50
66
 
51
67
  it "should return array of Time objects when restriction is array of strings" do
52
68
  time1, time2 = "2000-01-02", "2000-01-01"
53
- restriction_value([time1, time2], :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
69
+ evaluate_option_value([time1, time2], :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
54
70
  end
55
71
 
56
72
  it "should return array of Time objects when restriction is Range of Time objects" do
57
73
  time1, time2 = Time.now, 1.day.ago
58
- restriction_value(time1..time2, :datetime).should == [time2, time1]
74
+ evaluate_option_value(time1..time2, :datetime).should == [time2, time1]
59
75
  end
60
76
 
61
77
  it "should return array of Time objects when restriction is Range of time strings" do
62
78
  time1, time2 = "2000-01-02", "2000-01-01"
63
- restriction_value(time1..time2, :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
79
+ evaluate_option_value(time1..time2, :datetime).should == [Person.parse_date_time(time2, :datetime), Person.parse_date_time(time1, :datetime)]
64
80
  end
65
- def restriction_value(restriction, type)
81
+ def evaluate_option_value(restriction, type)
66
82
  configure_validator(:type => type)
67
- validator.send(:restriction_value, restriction, person)
83
+ validator.class.send(:evaluate_option_value, restriction, type, person)
68
84
  end
69
85
  end
70
86
 
@@ -330,6 +346,100 @@ describe ValidatesTimeliness::Validator do
330
346
  end
331
347
  end
332
348
 
349
+ describe "instance with :equal_to restriction" do
350
+
351
+ describe "for datetime type" do
352
+ before do
353
+ configure_validator(:equal_to => Time.now)
354
+ end
355
+
356
+ it "should have error when value not equal to :equal_to restriction" do
357
+ validate_with(:birth_date_and_time, Time.now + 1)
358
+ should_have_error(:birth_date_and_time, :equal_to)
359
+ end
360
+
361
+ it "should have error when value is equal to :equal_to restriction for all values except microscond, and microsecond is not ignored" do
362
+ configure_validator(:equal_to => Time.utc(2000, 1, 1, 0, 0, 0, 0), :ignore_usec => false)
363
+ validate_with(:birth_date_and_time, Time.utc(2000, 1, 1, 0, 0, 0, 500))
364
+ should_have_error(:birth_date_and_time, :equal_to)
365
+ end
366
+
367
+ it "should be valid when value is equal to :equal_to restriction for all values except microscond, and microsecond is ignored" do
368
+ configure_validator(:equal_to => Time.utc(2000, 1, 1, 0, 0, 0, 0), :ignore_usec => true)
369
+ validate_with(:birth_date_and_time, Time.utc(2000, 1, 1, 0, 0, 0, 500))
370
+ should_have_no_error(:birth_date_and_time, :equal_to)
371
+ end
372
+
373
+ it "should be valid when value is equal to :equal_to restriction" do
374
+ validate_with(:birth_date_and_time, Time.now)
375
+ should_have_no_error(:birth_date_and_time, :equal_to)
376
+ end
377
+ end
378
+
379
+ describe "for date type" do
380
+ before do
381
+ configure_validator(:type => :date, :equal_to => Date.today)
382
+ end
383
+
384
+ it "should have error when value is not equal to :equal_to restriction" do
385
+ validate_with(:birth_date, Date.today + 1)
386
+ should_have_error(:birth_date, :equal_to)
387
+ end
388
+
389
+ it "should be valid when value is equal to :equal_to restriction" do
390
+ validate_with(:birth_date, Date.today)
391
+ should_have_no_error(:birth_date, :equal_to)
392
+ end
393
+ end
394
+
395
+ describe "for time type" do
396
+ before do
397
+ configure_validator(:type => :time, :equal_to => "09:00:00")
398
+ end
399
+
400
+ it "should have error when value is not equal to :equal_to restriction" do
401
+ validate_with(:birth_time, "09:00:01")
402
+ should_have_error(:birth_time, :equal_to)
403
+ end
404
+
405
+ it "should be valid when value is equal to :equal_to restriction" do
406
+ validate_with(:birth_time, "09:00:00")
407
+ should_have_no_error(:birth_time, :equal_to)
408
+ end
409
+ end
410
+ end
411
+
412
+ describe "instance with :with_time option" do
413
+
414
+ it "should validate date attribute as datetime combining value of :with_time against restrictions " do
415
+ configure_validator(:type => :date, :with_time => '12:31', :on_or_before => Time.mktime(2000,1,1,12,30))
416
+ validate_with(:birth_date, "2000-01-01")
417
+ should_have_error(:birth_date, :on_or_before)
418
+ end
419
+
420
+ it "should skip restriction validation if :with_time value is nil" do
421
+ configure_validator(:type => :date, :with_time => nil, :on_or_before => Time.mktime(2000,1,1,12,30))
422
+ validate_with(:birth_date, "2000-01-01")
423
+ should_have_no_error(:birth_date, :on_or_before)
424
+ end
425
+
426
+ end
427
+
428
+ describe "instance with :with_date option" do
429
+
430
+ it "should validate time attribute as datetime combining value of :with_date against restrictions " do
431
+ configure_validator(:type => :time, :with_date => '2009-01-01', :on_or_before => Time.mktime(2000,1,1,12,30))
432
+ validate_with(:birth_date, "12:30")
433
+ should_have_error(:birth_date, :on_or_before)
434
+ end
435
+
436
+ it "should skip restriction validation if :with_date value is nil" do
437
+ configure_validator(:type => :time, :with_date => nil, :on_or_before => Time.mktime(2000,1,1,12,30))
438
+ validate_with(:birth_date, "12:30")
439
+ should_have_no_error(:birth_date, :on_or_before)
440
+ end
441
+ end
442
+
333
443
  describe "instance with mixed value and restriction types" do
334
444
 
335
445
  it "should validate datetime attribute with Date restriction" do
@@ -483,7 +593,7 @@ describe ValidatesTimeliness::Validator do
483
593
 
484
594
  def validate_with(attr_name, value)
485
595
  person.send("#{attr_name}=", value)
486
- validator.call(person, attr_name)
596
+ validator.call(person, attr_name, value)
487
597
  end
488
598
 
489
599
  def should_have_error(attr_name, error)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: validates_timeliness
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 1.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Meehan
@@ -9,7 +9,7 @@ autorequire: validates_timeliness
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-21 00:00:00 +11:00
12
+ date: 2009-03-19 00:00:00 +11:00
13
13
  default_executable:
14
14
  dependencies: []
15
15