validates_timeliness 3.0.15 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +5 -5
  2. data/.github/dependabot.yml +7 -0
  3. data/.github/workflows/ci.yml +36 -0
  4. data/CHANGELOG.md +337 -0
  5. data/LICENSE +1 -1
  6. data/README.md +319 -0
  7. data/Rakefile +0 -10
  8. data/gemfiles/rails_6_0.gemfile +13 -0
  9. data/gemfiles/rails_6_1.gemfile +14 -0
  10. data/gemfiles/rails_edge.gemfile +13 -0
  11. data/lib/generators/validates_timeliness/templates/validates_timeliness.rb +2 -2
  12. data/lib/validates_timeliness/attribute_methods.rb +35 -78
  13. data/lib/validates_timeliness/{conversion.rb → converter.rb} +28 -14
  14. data/lib/validates_timeliness/extensions/date_time_select.rb +23 -34
  15. data/lib/validates_timeliness/extensions/multiparameter_handler.rb +38 -63
  16. data/lib/validates_timeliness/extensions.rb +3 -4
  17. data/lib/validates_timeliness/helper_methods.rb +8 -2
  18. data/lib/validates_timeliness/orm/active_model.rb +71 -0
  19. data/lib/validates_timeliness/orm/active_record.rb +3 -39
  20. data/lib/validates_timeliness/railtie.rb +8 -0
  21. data/lib/validates_timeliness/validator.rb +28 -17
  22. data/lib/validates_timeliness/version.rb +1 -1
  23. data/lib/validates_timeliness.rb +4 -4
  24. data/spec/spec_helper.rb +13 -11
  25. data/spec/support/model_helpers.rb +5 -6
  26. data/spec/support/tag_matcher.rb +35 -0
  27. data/spec/support/test_model.rb +6 -5
  28. data/spec/validates_timeliness/attribute_methods_spec.rb +8 -25
  29. data/spec/validates_timeliness/converter_spec.rb +247 -0
  30. data/spec/validates_timeliness/extensions/date_time_select_spec.rb +37 -34
  31. data/spec/validates_timeliness/extensions/multiparameter_handler_spec.rb +12 -13
  32. data/spec/validates_timeliness/helper_methods_spec.rb +9 -11
  33. data/spec/validates_timeliness/orm/active_record_spec.rb +85 -139
  34. data/spec/validates_timeliness/railtie_spec.rb +22 -0
  35. data/spec/validates_timeliness/validator/after_spec.rb +4 -6
  36. data/spec/validates_timeliness/validator/before_spec.rb +4 -6
  37. data/spec/validates_timeliness/validator/is_at_spec.rb +9 -7
  38. data/spec/validates_timeliness/validator/on_or_after_spec.rb +4 -6
  39. data/spec/validates_timeliness/validator/on_or_before_spec.rb +4 -6
  40. data/spec/validates_timeliness/validator_spec.rb +45 -29
  41. data/spec/validates_timeliness_spec.rb +9 -11
  42. data/validates_timeliness.gemspec +16 -4
  43. metadata +61 -31
  44. data/CHANGELOG.rdoc +0 -188
  45. data/README.rdoc +0 -299
  46. data/gemfiles/mongoid_2_1.gemfile +0 -16
  47. data/gemfiles/mongoid_2_2.gemfile +0 -16
  48. data/gemfiles/mongoid_2_3.gemfile +0 -16
  49. data/gemfiles/mongoid_2_4.gemfile +0 -16
  50. data/gemfiles/rails_3_0.gemfile +0 -15
  51. data/gemfiles/rails_3_1.gemfile +0 -15
  52. data/gemfiles/rails_3_2.gemfile +0 -15
  53. data/lib/validates_timeliness/orm/mongoid.rb +0 -49
  54. data/spec/validates_timeliness/conversion_spec.rb +0 -234
  55. data/spec/validates_timeliness/orm/mongoid_spec.rb +0 -223
@@ -1,60 +1,49 @@
1
1
  module ValidatesTimeliness
2
2
  module Extensions
3
- module DateTimeSelect
4
- extend ActiveSupport::Concern
5
-
3
+ module TimelinessDateTimeSelect
6
4
  # Intercepts the date and time select helpers to reuse the values from
7
5
  # the params rather than the parsed value. This allows invalid date/time
8
6
  # values to be redisplayed instead of blanks to aid correction by the user.
9
7
  # It's a minor usability improvement which is rarely an issue for the user.
8
+ attr_accessor :object_name, :method_name, :template_object, :options, :html_options
10
9
 
11
- included do
12
- alias_method_chain :datetime_selector, :timeliness
13
- alias_method_chain :value, :timeliness
14
- end
10
+ POSITION = {
11
+ :year => 1, :month => 2, :day => 3, :hour => 4, :min => 5, :sec => 6
12
+ }.freeze
15
13
 
16
- class TimelinessDateTime
14
+ class DateTimeValue
17
15
  attr_accessor :year, :month, :day, :hour, :min, :sec
18
16
 
19
- def initialize(year, month, day, hour, min, sec)
17
+ def initialize(year:, month:, day: nil, hour: nil, min: nil, sec: nil)
20
18
  @year, @month, @day, @hour, @min, @sec = year, month, day, hour, min, sec
21
19
  end
22
20
 
23
- # adapted from activesupport/lib/active_support/core_ext/date_time/calculations.rb, line 36 (3.0.7)
24
21
  def change(options)
25
- TimelinessDateTime.new(
26
- options[:year] || year,
27
- options[:month] || month,
28
- options[:day] || day,
29
- options[:hour] || hour,
30
- options[:min] || (options[:hour] ? 0 : min),
31
- options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec)
22
+ self.class.new(
23
+ year: options.fetch(:year, year),
24
+ month: options.fetch(:month, month),
25
+ day: options.fetch(:day, day),
26
+ hour: options.fetch(:hour, hour),
27
+ min: options.fetch(:min) { options[:hour] ? 0 : min },
28
+ sec: options.fetch(:sec) { options[:hour] || options[:min] ? 0 : sec }
32
29
  )
33
30
  end
34
31
  end
35
32
 
36
- def datetime_selector_with_timeliness(*args)
37
- @timeliness_date_or_time_tag = true
38
- datetime_selector_without_timeliness(*args)
39
- end
40
-
41
- def value_with_timeliness(object)
42
- unless @timeliness_date_or_time_tag && @template_object.params[@object_name]
43
- return value_without_timeliness(object)
44
- end
45
-
46
- @template_object.params[@object_name]
33
+ # Splat args to support Rails 5.0 which expects object, and 5.2 which doesn't
34
+ def value(*object)
35
+ return super unless @template_object.params[@object_name]
47
36
 
48
37
  pairs = @template_object.params[@object_name].select {|k,v| k =~ /^#{@method_name}\(/ }
49
- return value_without_timeliness(object) if pairs.empty?
38
+ return super if pairs.empty?
50
39
 
51
- values = [nil] * 6
52
- pairs.map do |(param, value)|
53
- position = param.scan(/\((\d+)\w+\)/).first.first
54
- values[position.to_i-1] = value.to_i
40
+ values = {}
41
+ pairs.each_pair do |key, value|
42
+ position = key[/\((\d+)\w+\)/, 1]
43
+ values[POSITION.key(position.to_i)] = value.to_i
55
44
  end
56
45
 
57
- TimelinessDateTime.new(*values)
46
+ DateTimeValue.new(**values)
58
47
  end
59
48
  end
60
49
  end
@@ -1,80 +1,55 @@
1
1
  module ValidatesTimeliness
2
2
  module Extensions
3
- module MultiparameterHandler
4
- extend ActiveSupport::Concern
3
+ class AcceptsMultiparameterTime < Module
5
4
 
6
- # Stricter handling of date and time values from multiparameter
7
- # assignment from the date/time select view helpers
5
+ def initialize(defaults: {})
8
6
 
9
- included do
10
- alias_method_chain :instantiate_time_object, :timeliness
11
- alias_method :execute_callstack_for_multiparameter_attributes, :execute_callstack_for_multiparameter_attributes_with_timeliness
12
- alias_method :read_value_from_parameter, :read_value_from_parameter_with_timeliness
13
- end
14
-
15
- private
16
-
17
- def invalid_multiparameter_date_or_time_as_string(values)
18
- value = [values[0], *values[1..2].map {|s| s.to_s.rjust(2,"0")} ].join("-")
19
- value += ' ' + values[3..5].map {|s| s.to_s.rjust(2, "0") }.join(":") unless values[3..5].empty?
20
- value
21
- end
22
-
23
- def instantiate_time_object_with_timeliness(name, values)
24
- validate_multiparameter_date_values(values) {
25
- instantiate_time_object_without_timeliness(name, values)
26
- }
27
- end
28
-
29
- def instantiate_date_object(name, values)
30
- validate_multiparameter_date_values(values) {
31
- Date.new(*values)
32
- }
33
- end
34
-
35
- # Yield if date values are valid
36
- def validate_multiparameter_date_values(values)
37
- if values[0..2].all?{ |v| v.present? } && Date.valid_civil?(*values[0..2])
38
- yield
39
- else
40
- invalid_multiparameter_date_or_time_as_string(values)
7
+ define_method(:cast) do |value|
8
+ if value.is_a?(Hash)
9
+ value_from_multiparameter_assignment(value)
10
+ else
11
+ super(value)
12
+ end
41
13
  end
42
- end
43
-
44
- def read_value_from_parameter_with_timeliness(name, values_from_param)
45
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
46
- values = values_from_param.is_a?(Hash) ? values_from_param.to_a.sort_by(&:first).map(&:last) : values_from_param
47
14
 
48
- if values.empty? || values.all?{ |v| v.nil? }
49
- nil
50
- elsif klass == Time
51
- instantiate_time_object(name, values)
52
- elsif klass == Date
53
- instantiate_date_object(name, values)
54
- else
55
- if respond_to?(:read_other_parameter_value)
56
- read_date_parameter_value(name, values_from_param)
15
+ define_method(:assert_valid_value) do |value|
16
+ if value.is_a?(Hash)
17
+ value_from_multiparameter_assignment(value)
57
18
  else
58
- klass.new(*values)
19
+ super(value)
59
20
  end
60
21
  end
61
- end
62
22
 
63
- def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
64
- errors = []
65
- callstack.each do |name, values_with_empty_parameters|
66
- begin
67
- send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
68
- rescue => ex
69
- values = values_with_empty_parameters.is_a?(Hash) ? values_with_empty_parameters.values : values_with_empty_parameters
70
- errors << ActiveRecord::AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
23
+ define_method(:value_from_multiparameter_assignment) do |values_hash|
24
+ defaults.each do |k, v|
25
+ values_hash[k] ||= v
71
26
  end
27
+ return unless values_hash.values_at(1,2,3).all?{ |v| v.present? } &&
28
+ Date.valid_civil?(*values_hash.values_at(1,2,3))
29
+
30
+ values = values_hash.sort.map(&:last)
31
+ ::Time.send(default_timezone, *values)
72
32
  end
73
- unless errors.empty?
74
- raise ActiveRecord::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
75
- end
33
+ private :value_from_multiparameter_assignment
34
+
76
35
  end
77
36
 
78
37
  end
79
38
  end
80
39
  end
40
+
41
+ ActiveModel::Type::Date.class_eval do
42
+ include ValidatesTimeliness::Extensions::AcceptsMultiparameterTime.new
43
+ end
44
+
45
+ ActiveModel::Type::Time.class_eval do
46
+ include ValidatesTimeliness::Extensions::AcceptsMultiparameterTime.new(
47
+ defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
48
+ )
49
+ end
50
+
51
+ ActiveModel::Type::DateTime.class_eval do
52
+ include ValidatesTimeliness::Extensions::AcceptsMultiparameterTime.new(
53
+ defaults: { 4 => 0, 5 => 0 }
54
+ )
55
+ end
@@ -1,14 +1,13 @@
1
1
  module ValidatesTimeliness
2
2
  module Extensions
3
- autoload :DateTimeSelect, 'validates_timeliness/extensions/date_time_select'
4
- autoload :MultiparameterHandler, 'validates_timeliness/extensions/multiparameter_handler'
3
+ autoload :TimelinessDateTimeSelect, 'validates_timeliness/extensions/date_time_select'
5
4
  end
6
5
 
7
6
  def self.enable_date_time_select_extension!
8
- ::ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::Extensions::DateTimeSelect)
7
+ ::ActionView::Helpers::Tags::DateSelect.send(:prepend, ValidatesTimeliness::Extensions::TimelinessDateTimeSelect)
9
8
  end
10
9
 
11
10
  def self.enable_multiparameter_extension!
12
- ::ActiveRecord::Base.send(:include, ValidatesTimeliness::Extensions::MultiparameterHandler)
11
+ require 'validates_timeliness/extensions/multiparameter_handler'
13
12
  end
14
13
  end
@@ -14,8 +14,14 @@ module ActiveModel
14
14
  timeliness_validation_for attr_names, :datetime
15
15
  end
16
16
 
17
- def timeliness_validation_for(attr_names, type)
18
- validates_with TimelinessValidator, _merge_attributes(attr_names).merge(:type => type)
17
+ def validates_timeliness_of(*attr_names)
18
+ timeliness_validation_for attr_names
19
+ end
20
+
21
+ def timeliness_validation_for(attr_names, type=nil)
22
+ options = _merge_attributes(attr_names)
23
+ options.update(:type => type) if type
24
+ validates_with TimelinessValidator, options
19
25
  end
20
26
  end
21
27
 
@@ -0,0 +1,71 @@
1
+ module ValidatesTimeliness
2
+ module ORM
3
+ module ActiveModel
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ public
8
+
9
+ def define_attribute_methods(*attr_names)
10
+ super.tap { define_timeliness_methods }
11
+ end
12
+
13
+ def undefine_attribute_methods
14
+ super.tap { undefine_timeliness_attribute_methods }
15
+ end
16
+
17
+ def define_timeliness_methods(before_type_cast=false)
18
+ return if timeliness_validated_attributes.blank?
19
+ timeliness_validated_attributes.each do |attr_name|
20
+ define_attribute_timeliness_methods(attr_name, before_type_cast)
21
+ end
22
+ end
23
+
24
+ def generated_timeliness_methods
25
+ @generated_timeliness_methods ||= Module.new { |m|
26
+ extend Mutex_m
27
+ }.tap { |mod| include mod }
28
+ end
29
+
30
+ def undefine_timeliness_attribute_methods
31
+ generated_timeliness_methods.module_eval do
32
+ instance_methods.each { |m| undef_method(m) }
33
+ end
34
+ end
35
+
36
+ def define_attribute_timeliness_methods(attr_name, before_type_cast=false)
37
+ define_timeliness_write_method(attr_name)
38
+ define_timeliness_before_type_cast_method(attr_name) if before_type_cast
39
+ end
40
+
41
+ def define_timeliness_write_method(attr_name)
42
+ generated_timeliness_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
43
+ def #{attr_name}=(value)
44
+ @timeliness_cache ||= {}
45
+ @timeliness_cache['#{attr_name}'] = value
46
+
47
+ @attributes['#{attr_name}'] = super
48
+ end
49
+ STR
50
+ end
51
+
52
+ def define_timeliness_before_type_cast_method(attr_name)
53
+ generated_timeliness_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
54
+ def #{attr_name}_before_type_cast
55
+ read_timeliness_attribute_before_type_cast('#{attr_name}')
56
+ end
57
+ STR
58
+ end
59
+ end
60
+
61
+ def read_timeliness_attribute_before_type_cast(attr_name)
62
+ @timeliness_cache && @timeliness_cache[attr_name] || @attributes[attr_name]
63
+ end
64
+
65
+ def _clear_timeliness_cache
66
+ @timeliness_cache = {}
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -3,51 +3,15 @@ module ValidatesTimeliness
3
3
  module ActiveRecord
4
4
  extend ActiveSupport::Concern
5
5
 
6
- module ClassMethods
7
- public
8
-
9
- def timeliness_attribute_timezone_aware?(attr_name)
10
- create_time_zone_conversion_attribute?(attr_name, timeliness_column_for_attribute(attr_name))
11
- end
12
-
13
- def timeliness_attribute_type(attr_name)
14
- timeliness_column_for_attribute(attr_name).type
15
- end
16
-
17
- def timeliness_column_for_attribute(attr_name)
18
- columns_hash.fetch(attr_name.to_s) do |attr_name|
19
- validation_type = _validators[attr_name.to_sym].find {|v| v.kind == :timeliness }.type
20
- ::ActiveRecord::ConnectionAdapters::Column.new(attr_name, nil, validation_type.to_s)
21
- end
22
- end
23
-
24
- def define_attribute_methods
25
- super.tap do |attribute_methods_generated|
26
- define_timeliness_methods true
27
- end
28
- end
29
-
30
- protected
31
-
32
- def timeliness_type_cast_code(attr_name, var_name)
33
- type = timeliness_attribute_type(attr_name)
34
-
35
- method_body = super
36
- method_body << "\n#{var_name} = #{var_name}.to_date if #{var_name}" if type == :date
37
- method_body
38
- end
39
- end
40
-
41
- def reload(*args)
42
- _clear_timeliness_cache
43
- super
6
+ def read_timeliness_attribute_before_type_cast(attr_name)
7
+ read_attribute_before_type_cast(attr_name)
44
8
  end
45
9
 
46
10
  end
47
11
  end
48
12
  end
49
13
 
50
- class ActiveRecord::Base
14
+ ActiveSupport.on_load(:active_record) do
51
15
  include ValidatesTimeliness::AttributeMethods
52
16
  include ValidatesTimeliness::ORM::ActiveRecord
53
17
  end
@@ -11,5 +11,13 @@ module ValidatesTimeliness
11
11
  initializer "validates_timeliness.initialize_restriction_errors" do
12
12
  ValidatesTimeliness.ignore_restriction_errors = !Rails.env.test?
13
13
  end
14
+
15
+ initializer "validates_timeliness.initialize_timeliness_ambiguous_date_format", :after => :load_config_initializers do
16
+ if Timeliness.respond_to?(:ambiguous_date_format) # i.e. v0.4+
17
+ # Set default for each new thread if you have changed the default using
18
+ # the format switching methods.
19
+ Timeliness.configuration.ambiguous_date_format = Timeliness::Definitions.current_date_format
20
+ end
21
+ end
14
22
  end
15
23
  end
@@ -3,9 +3,7 @@ require 'active_model/validator'
3
3
 
4
4
  module ValidatesTimeliness
5
5
  class Validator < ActiveModel::EachValidator
6
- include Conversion
7
-
8
- attr_reader :type
6
+ attr_reader :type, :attributes, :converter
9
7
 
10
8
  RESTRICTIONS = {
11
9
  :is_at => :==,
@@ -42,13 +40,16 @@ module ValidatesTimeliness
42
40
  end
43
41
 
44
42
  @restrictions_to_check = RESTRICTIONS.keys & options.keys
43
+
45
44
  super
45
+
46
+ setup_timeliness_validated_attributes(options[:class]) if options[:class]
46
47
  end
47
48
 
48
- def setup(model)
49
+ def setup_timeliness_validated_attributes(model)
49
50
  if model.respond_to?(:timeliness_validated_attributes)
50
51
  model.timeliness_validated_attributes ||= []
51
- model.timeliness_validated_attributes |= @attributes
52
+ model.timeliness_validated_attributes |= attributes
52
53
  end
53
54
  end
54
55
 
@@ -56,9 +57,10 @@ module ValidatesTimeliness
56
57
  raw_value = attribute_raw_value(record, attr_name) || value
57
58
  return if (@allow_nil && raw_value.nil?) || (@allow_blank && raw_value.blank?)
58
59
 
59
- @timezone_aware = timezone_aware?(record, attr_name)
60
- value = parse(raw_value) if value.is_a?(String) || options[:format]
61
- value = type_cast_value(value, @type)
60
+ @converter = initialize_converter(record, attr_name)
61
+
62
+ value = @converter.parse(raw_value) if value.is_a?(String) || options[:format]
63
+ value = @converter.type_cast_value(value)
62
64
 
63
65
  add_error(record, attr_name, :"invalid_#{@type}") and return if value.blank?
64
66
 
@@ -68,7 +70,7 @@ module ValidatesTimeliness
68
70
  def validate_restrictions(record, attr_name, value)
69
71
  @restrictions_to_check.each do |restriction|
70
72
  begin
71
- restriction_value = type_cast_value(evaluate_option_value(options[restriction], record), @type)
73
+ restriction_value = @converter.type_cast_value(@converter.evaluate(options[restriction], record))
72
74
  unless value.send(RESTRICTIONS[restriction], restriction_value)
73
75
  add_error(record, attr_name, restriction, restriction_value) and break
74
76
  end
@@ -83,23 +85,32 @@ module ValidatesTimeliness
83
85
 
84
86
  def add_error(record, attr_name, message, value=nil)
85
87
  value = format_error_value(value) if value
86
- message_options = { :message => options[:"#{message}_message"], :restriction => value }
87
- record.errors.add(attr_name, message, message_options)
88
+ message_options = { message: options.fetch(:"#{message}_message", options[:message]), restriction: value }
89
+ record.errors.add(attr_name, message, **message_options)
88
90
  end
89
91
 
90
92
  def format_error_value(value)
91
- format = I18n.t(@type, :default => DEFAULT_ERROR_VALUE_FORMATS[@type], :scope => 'validates_timeliness.error_value_formats')
93
+ format = I18n.t(@type, default: DEFAULT_ERROR_VALUE_FORMATS[@type], scope: 'validates_timeliness.error_value_formats')
92
94
  value.strftime(format)
93
95
  end
94
96
 
95
97
  def attribute_raw_value(record, attr_name)
96
- record.respond_to?(:_timeliness_raw_value_for) &&
97
- record._timeliness_raw_value_for(attr_name.to_s)
98
+ record.respond_to?(:read_timeliness_attribute_before_type_cast) &&
99
+ record.read_timeliness_attribute_before_type_cast(attr_name.to_s)
100
+ end
101
+
102
+ def time_zone_aware?(record, attr_name)
103
+ record.class.respond_to?(:skip_time_zone_conversion_for_attributes) &&
104
+ !record.class.skip_time_zone_conversion_for_attributes.include?(attr_name.to_sym)
98
105
  end
99
106
 
100
- def timezone_aware?(record, attr_name)
101
- record.class.respond_to?(:timeliness_attribute_timezone_aware?) &&
102
- record.class.timeliness_attribute_timezone_aware?(attr_name)
107
+ def initialize_converter(record, attr_name)
108
+ ValidatesTimeliness::Converter.new(
109
+ type: @type,
110
+ time_zone_aware: time_zone_aware?(record, attr_name),
111
+ format: options[:format],
112
+ ignore_usec: options[:ignore_usec]
113
+ )
103
114
  end
104
115
 
105
116
  end
@@ -1,3 +1,3 @@
1
1
  module ValidatesTimeliness
2
- VERSION = '3.0.15'
2
+ VERSION = '6.0.0'
3
3
  end
@@ -28,7 +28,7 @@ module ValidatesTimeliness
28
28
  attr_accessor :extend_orms, :ignore_restriction_errors, :restriction_shorthand_symbols, :use_plugin_parser
29
29
  end
30
30
 
31
- # Extend ORM/ODMs for full support (:active_record, :mongoid).
31
+ # Extend ORM/ODMs for full support (:active_record).
32
32
  self.extend_orms = []
33
33
 
34
34
  # Ignore errors when restriction options are evaluated
@@ -36,8 +36,8 @@ module ValidatesTimeliness
36
36
 
37
37
  # Shorthand time and date symbols for restrictions
38
38
  self.restriction_shorthand_symbols = {
39
- :now => lambda { Time.current },
40
- :today => lambda { Date.current }
39
+ now: proc { Time.current },
40
+ today: proc { Date.current }
41
41
  }
42
42
 
43
43
  # Use the plugin date/time parser which is stricter and extensible
@@ -62,7 +62,7 @@ module ValidatesTimeliness
62
62
  def self.parser; Timeliness end
63
63
  end
64
64
 
65
- require 'validates_timeliness/conversion'
65
+ require 'validates_timeliness/converter'
66
66
  require 'validates_timeliness/validator'
67
67
  require 'validates_timeliness/helper_methods'
68
68
  require 'validates_timeliness/attribute_methods'
data/spec/spec_helper.rb CHANGED
@@ -1,17 +1,21 @@
1
1
  require 'rspec'
2
2
 
3
+ require 'byebug'
3
4
  require 'active_model'
4
5
  require 'active_model/validations'
5
6
  require 'active_record'
6
7
  require 'action_view'
7
- require 'timecop'
8
- require 'rspec_tag_matchers'
8
+ require 'active_support/testing/time_helpers'
9
9
 
10
10
  require 'validates_timeliness'
11
+ require 'validates_timeliness/orm/active_model'
12
+
13
+ require 'rails/railtie'
11
14
 
12
15
  require 'support/test_model'
13
16
  require 'support/model_helpers'
14
17
  require 'support/config_helper'
18
+ require 'support/tag_matcher'
15
19
 
16
20
  ValidatesTimeliness.setup do |c|
17
21
  c.extend_orms = [ :active_record ]
@@ -24,19 +28,15 @@ Time.zone = 'Australia/Melbourne'
24
28
 
25
29
  LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/../lib/generators/validates_timeliness/templates/en.yml')
26
30
  I18n.load_path.unshift(LOCALE_PATH)
31
+ I18n.available_locales = ['en', 'es']
27
32
 
28
33
  # Extend TestModel as you would another ORM/ODM module
29
34
  module TestModelShim
30
35
  extend ActiveSupport::Concern
31
36
  include ValidatesTimeliness::AttributeMethods
37
+ include ValidatesTimeliness::ORM::ActiveModel
32
38
 
33
39
  module ClassMethods
34
- # Hook method for attribute method generation
35
- def define_attribute_methods(attr_names)
36
- super
37
- define_timeliness_methods
38
- end
39
-
40
40
  # Hook into native time zone handling check, if any
41
41
  def timeliness_attribute_timezone_aware?(attr_name)
42
42
  false
@@ -60,6 +60,7 @@ end
60
60
  ActiveRecord::Base.default_timezone = :utc
61
61
  ActiveRecord::Base.time_zone_aware_attributes = true
62
62
  ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
63
+ ActiveRecord::Base.time_zone_aware_types = [:datetime, :time]
63
64
  ActiveRecord::Migration.verbose = false
64
65
  ActiveRecord::Schema.define(:version => 1) do
65
66
  create_table :employees, :force => true do |t|
@@ -74,8 +75,8 @@ end
74
75
  class Employee < ActiveRecord::Base
75
76
  attr_accessor :redefined_birth_date_called
76
77
  validates_date :birth_date, :allow_nil => true
77
- validates_date :birth_time, :allow_nil => true
78
- validates_date :birth_datetime, :allow_nil => true
78
+ validates_time :birth_time, :allow_nil => true
79
+ validates_datetime :birth_datetime, :allow_nil => true
79
80
 
80
81
  def birth_date=(value)
81
82
  self.redefined_birth_date_called = true
@@ -85,9 +86,10 @@ end
85
86
 
86
87
  RSpec.configure do |c|
87
88
  c.mock_with :rspec
88
- c.include(RspecTagMatchers)
89
+ c.include(TagMatcher)
89
90
  c.include(ModelHelpers)
90
91
  c.include(ConfigHelper)
92
+ c.include(ActiveSupport::Testing::TimeHelpers)
91
93
  c.before do
92
94
  reset_validation_setup_for(Person)
93
95
  reset_validation_setup_for(PersonWithShim)
@@ -3,22 +3,21 @@ module ModelHelpers
3
3
  # Some test helpers from Rails source
4
4
  def invalid!(attr_name, values, error = nil)
5
5
  with_each_person_value(attr_name, values) do |record, value|
6
- record.should be_invalid
7
- record.errors[attr_name].size.should >= 1
8
- record.errors[attr_name].first.should == error if error
6
+ expect(record).to be_invalid
7
+ expect(record.errors[attr_name].size).to be >= 1
8
+ expect(record.errors[attr_name].first).to eq(error) if error
9
9
  end
10
10
  end
11
11
 
12
12
  def valid!(attr_name, values)
13
13
  with_each_person_value(attr_name, values) do |record, value|
14
- record.should be_valid
14
+ expect(record).to be_valid
15
15
  end
16
16
  end
17
17
 
18
18
  def with_each_person_value(attr_name, values)
19
19
  record = Person.new
20
- values = [values] unless values.is_a?(Array)
21
- values.each do |value|
20
+ Array.wrap(values).each do |value|
22
21
  record.send("#{attr_name}=", value)
23
22
  yield record, value
24
23
  end
@@ -0,0 +1,35 @@
1
+ require 'nokogiri'
2
+
3
+ module TagMatcher
4
+ extend RSpec::Matchers::DSL
5
+
6
+ matcher :have_tag do |selector|
7
+ match do |subject|
8
+ matches = doc(subject).search(selector)
9
+
10
+ if @inner_text
11
+ matches = matches.select { |element| element.inner_text == @inner_text }
12
+ end
13
+
14
+ matches.any?
15
+ end
16
+
17
+ chain :with_inner_text do |inner_text|
18
+ @inner_text = inner_text
19
+ end
20
+
21
+ private
22
+
23
+ def body(subject)
24
+ if subject.respond_to?(:body)
25
+ subject.body
26
+ else
27
+ subject.to_s
28
+ end
29
+ end
30
+
31
+ def doc(subject)
32
+ @doc ||= Nokogiri::HTML(body(subject))
33
+ end
34
+ end
35
+ end