validates_timeliness 4.1.1 → 6.0.0.beta2

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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +7 -0
  3. data/.github/workflows/ci.yml +41 -0
  4. data/CHANGELOG.rdoc +14 -6
  5. data/LICENSE +1 -1
  6. data/README.md +321 -0
  7. data/Rakefile +0 -10
  8. data/gemfiles/rails_6_0.gemfile +14 -0
  9. data/gemfiles/rails_6_1.gemfile +14 -0
  10. data/gemfiles/rails_edge.gemfile +14 -0
  11. data/lib/validates_timeliness/attribute_methods.rb +34 -73
  12. data/lib/validates_timeliness/{conversion.rb → converter.rb} +26 -14
  13. data/lib/validates_timeliness/extensions/date_time_select.rb +23 -27
  14. data/lib/validates_timeliness/extensions/multiparameter_handler.rb +47 -66
  15. data/lib/validates_timeliness/extensions.rb +2 -2
  16. data/lib/validates_timeliness/orm/active_model.rb +53 -2
  17. data/lib/validates_timeliness/orm/active_record.rb +2 -84
  18. data/lib/validates_timeliness/railtie.rb +1 -1
  19. data/lib/validates_timeliness/validator.rb +21 -18
  20. data/lib/validates_timeliness/version.rb +1 -1
  21. data/lib/validates_timeliness.rb +3 -3
  22. data/spec/spec_helper.rb +2 -0
  23. data/spec/support/model_helpers.rb +1 -2
  24. data/spec/support/test_model.rb +6 -4
  25. data/spec/validates_timeliness/attribute_methods_spec.rb +0 -15
  26. data/spec/validates_timeliness/{conversion_spec.rb → converter_spec.rb} +64 -49
  27. data/spec/validates_timeliness/extensions/date_time_select_spec.rb +27 -27
  28. data/spec/validates_timeliness/extensions/multiparameter_handler_spec.rb +10 -10
  29. data/spec/validates_timeliness/orm/active_record_spec.rb +72 -136
  30. data/validates_timeliness.gemspec +4 -3
  31. metadata +38 -16
  32. data/.travis.yml +0 -24
  33. data/README.rdoc +0 -300
  34. data/gemfiles/rails_4_2.gemfile +0 -14
@@ -1,10 +1,18 @@
1
1
  module ValidatesTimeliness
2
- module Conversion
2
+ class Converter
3
+ attr_reader :type, :format, :ignore_usec
3
4
 
4
- def type_cast_value(value, type)
5
+ def initialize(type:, format: nil, ignore_usec: false, time_zone_aware: false)
6
+ @type = type
7
+ @format = format
8
+ @ignore_usec = ignore_usec
9
+ @time_zone_aware = time_zone_aware
10
+ end
11
+
12
+ def type_cast_value(value)
5
13
  return nil if value.nil? || !value.respond_to?(:to_time)
6
14
 
7
- value = value.in_time_zone if value.acts_like?(:time) && @timezone_aware
15
+ value = value.in_time_zone if value.acts_like?(:time) && time_zone_aware?
8
16
  value = case type
9
17
  when :time
10
18
  dummy_time(value)
@@ -15,8 +23,8 @@ module ValidatesTimeliness
15
23
  else
16
24
  value
17
25
  end
18
- if options[:ignore_usec] && value.is_a?(Time)
19
- Timeliness::Parser.make_time(Array(value).reverse[4..9], (:current if @timezone_aware))
26
+ if ignore_usec && value.is_a?(Time)
27
+ Timeliness::Parser.make_time(Array(value).reverse[4..9], (:current if time_zone_aware?))
20
28
  else
21
29
  value
22
30
  end
@@ -24,30 +32,30 @@ module ValidatesTimeliness
24
32
 
25
33
  def dummy_time(value)
26
34
  time = if value.acts_like?(:time)
27
- value = value.in_time_zone if @timezone_aware
35
+ value = value.in_time_zone if time_zone_aware?
28
36
  [value.hour, value.min, value.sec]
29
37
  else
30
38
  [0,0,0]
31
39
  end
32
40
  values = ValidatesTimeliness.dummy_date_for_time_type + time
33
- Timeliness::Parser.make_time(values, (:current if @timezone_aware))
41
+ Timeliness::Parser.make_time(values, (:current if time_zone_aware?))
34
42
  end
35
43
 
36
- def evaluate_option_value(value, record)
44
+ def evaluate(value, scope=nil)
37
45
  case value
38
46
  when Time, Date
39
47
  value
40
48
  when String
41
49
  parse(value)
42
50
  when Symbol
43
- if !record.respond_to?(value) && restriction_shorthand?(value)
51
+ if !scope.respond_to?(value) && restriction_shorthand?(value)
44
52
  ValidatesTimeliness.restriction_shorthand_symbols[value].call
45
53
  else
46
- evaluate_option_value(record.send(value), record)
54
+ evaluate(scope.send(value))
47
55
  end
48
56
  when Proc
49
- result = value.arity > 0 ? value.call(record) : value.call
50
- evaluate_option_value(result, record)
57
+ result = value.arity > 0 ? value.call(scope) : value.call
58
+ evaluate(result, scope)
51
59
  else
52
60
  value
53
61
  end
@@ -59,14 +67,18 @@ module ValidatesTimeliness
59
67
 
60
68
  def parse(value)
61
69
  return nil if value.nil?
70
+
62
71
  if ValidatesTimeliness.use_plugin_parser
63
- Timeliness::Parser.parse(value, @type, :zone => (:current if @timezone_aware), :format => options[:format], :strict => false)
72
+ Timeliness::Parser.parse(value, type, zone: (:current if time_zone_aware?), format: format, strict: false)
64
73
  else
65
- @timezone_aware ? Time.zone.parse(value) : value.to_time(ValidatesTimeliness.default_timezone)
74
+ time_zone_aware? ? Time.zone.parse(value) : value.to_time(ValidatesTimeliness.default_timezone)
66
75
  end
67
76
  rescue ArgumentError, TypeError
68
77
  nil
69
78
  end
70
79
 
80
+ def time_zone_aware?
81
+ @time_zone_aware
82
+ end
71
83
  end
72
84
  end
@@ -1,54 +1,50 @@
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 :value, :timeliness
13
- end
10
+ POSITION = {
11
+ :year => 1, :month => 2, :day => 3, :hour => 4, :min => 5, :sec => 6
12
+ }.freeze
14
13
 
15
- class TimelinessDateTime
14
+ class DateTimeValue
16
15
  attr_accessor :year, :month, :day, :hour, :min, :sec
17
16
 
18
- def initialize(year, month, day, hour, min, sec)
17
+ def initialize(year:, month:, day: nil, hour: nil, min: nil, sec: nil)
19
18
  @year, @month, @day, @hour, @min, @sec = year, month, day, hour, min, sec
20
19
  end
21
20
 
22
- # adapted from activesupport/lib/active_support/core_ext/date_time/calculations.rb, line 36 (3.0.7)
23
21
  def change(options)
24
- TimelinessDateTime.new(
25
- options[:year] || year,
26
- options[:month] || month,
27
- options[:day] || day,
28
- options[:hour] || hour,
29
- options[:min] || (options[:hour] ? 0 : min),
30
- 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 }
31
29
  )
32
30
  end
33
31
  end
34
32
 
35
- def value_with_timeliness(object)
36
- return value_without_timeliness(object) unless @template_object.params[@object_name]
37
-
38
- @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]
39
36
 
40
37
  pairs = @template_object.params[@object_name].select {|k,v| k =~ /^#{@method_name}\(/ }
41
- return value_without_timeliness(object) if pairs.empty?
38
+ return super if pairs.empty?
42
39
 
43
- values = [nil] * 6
44
- pairs.map do |(param, value)|
45
- position = param.scan(/\((\d+)\w+\)/).first.first
46
- 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
47
44
  end
48
45
 
49
- TimelinessDateTime.new(*values)
46
+ DateTimeValue.new(**values)
50
47
  end
51
-
52
48
  end
53
49
  end
54
50
  end
@@ -1,74 +1,55 @@
1
- ActiveRecord::AttributeAssignment::MultiparameterAttribute.class_eval do
2
- private
1
+ module ValidatesTimeliness
2
+ module Extensions
3
+ class AcceptsMultiparameterTime < Module
4
+
5
+ def initialize(defaults: {})
6
+
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
13
+ end
14
+
15
+ define_method(:assert_valid_value) do |value|
16
+ if value.is_a?(Hash)
17
+ value_from_multiparameter_assignment(value)
18
+ else
19
+ super(value)
20
+ end
21
+ end
22
+
23
+ define_method(:value_from_multiparameter_assignment) do |values_hash|
24
+ defaults.each do |k, v|
25
+ values_hash[k] ||= v
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)
32
+ end
33
+ private :value_from_multiparameter_assignment
3
34
 
4
- # Yield if date values are valid
5
- def validate_multiparameter_date_values(set_values)
6
- if set_values[0..2].all?{ |v| v.present? } && Date.valid_civil?(*set_values[0..2])
7
- yield
8
- else
9
- invalid_multiparameter_date_or_time_as_string(set_values)
10
- end
11
- end
12
-
13
- def invalid_multiparameter_date_or_time_as_string(values)
14
- value = [values[0], *values[1..2].map {|s| s.to_s.rjust(2,"0")} ].join("-")
15
- value += ' ' + values[3..5].map {|s| s.to_s.rjust(2, "0") }.join(":") unless values[3..5].empty?
16
- value
17
- end
18
-
19
- def instantiate_time_object(set_values)
20
- raise if set_values.any?(&:nil?)
21
-
22
- validate_multiparameter_date_values(set_values) {
23
- set_values = set_values.map {|v| v.is_a?(String) ? v.strip : v }
24
-
25
- if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type_or_column)
26
- Time.zone.local(*set_values)
27
- else
28
- Time.send(object.class.default_timezone, *set_values)
29
- end
30
- }
31
- rescue
32
- invalid_multiparameter_date_or_time_as_string(set_values)
33
- end
34
-
35
- def read_time
36
- # If column is a :time (and not :date or :timestamp) there is no need to validate if
37
- # there are year/month/day fields
38
- if cast_type_or_column.type == :time
39
- # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
40
- { 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
41
- values[key] ||= value
42
35
  end
43
- end
44
-
45
- max_position = extract_max_param(6)
46
- set_values = values.values_at(*(1..max_position))
47
36
 
48
- instantiate_time_object(set_values)
49
- end
50
-
51
- def read_date
52
- set_values = values.values_at(1,2,3).map {|v| v.is_a?(String) ? v.strip : v }
53
-
54
- if set_values.any? { |v| v.is_a?(String) }
55
- Timeliness.parse(set_values.join('-'), :date).try(:to_date) or raise TypeError
56
- else
57
- Date.new(*set_values)
58
37
  end
59
- rescue TypeError, ArgumentError, NoMethodError => ex # if Date.new raises an exception on an invalid date
60
- # Date.new with nil values throws NoMethodError
61
- raise ex if ex.is_a?(NoMethodError) && ex.message !~ /undefined method `div' for/
62
- invalid_multiparameter_date_or_time_as_string(set_values)
63
- end
64
-
65
- # Cast type is v4.2 and column before
66
- def cast_type_or_column
67
- @cast_type || @column
68
38
  end
39
+ end
69
40
 
70
- def timezone_conversion_attribute?
71
- object.class.send(:create_time_zone_conversion_attribute?, name, column)
72
- end
41
+ ActiveModel::Type::Date.class_eval do
42
+ include ValidatesTimeliness::Extensions::AcceptsMultiparameterTime.new
43
+ end
73
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
+ )
74
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,10 +1,10 @@
1
1
  module ValidatesTimeliness
2
2
  module Extensions
3
- autoload :DateTimeSelect, 'validates_timeliness/extensions/date_time_select'
3
+ autoload :TimelinessDateTimeSelect, 'validates_timeliness/extensions/date_time_select'
4
4
  end
5
5
 
6
6
  def self.enable_date_time_select_extension!
7
- ::ActionView::Helpers::Tags::DateSelect.send(:include, ValidatesTimeliness::Extensions::DateTimeSelect)
7
+ ::ActionView::Helpers::Tags::DateSelect.send(:prepend, ValidatesTimeliness::Extensions::TimelinessDateTimeSelect)
8
8
  end
9
9
 
10
10
  def self.enable_multiparameter_extension!
@@ -7,14 +7,65 @@ module ValidatesTimeliness
7
7
  public
8
8
 
9
9
  def define_attribute_methods(*attr_names)
10
- super.tap { define_timeliness_methods}
10
+ super.tap { define_timeliness_methods }
11
11
  end
12
12
 
13
13
  def undefine_attribute_methods
14
14
  super.tap { undefine_timeliness_attribute_methods }
15
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
16
59
  end
17
-
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
+
18
69
  end
19
70
  end
20
71
  end
@@ -3,97 +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
- if ::ActiveModel.version >= Gem::Version.new('4.2')
18
- def timeliness_column_for_attribute(attr_name)
19
- columns_hash.fetch(attr_name.to_s) do |key|
20
- validation_type = _validators[key.to_sym].find {|v| v.kind == :timeliness }.type.to_s
21
- ::ActiveRecord::ConnectionAdapters::Column.new(key, nil, lookup_cast_type(validation_type), validation_type)
22
- end
23
- end
24
-
25
- def lookup_cast_type(sql_type)
26
- case sql_type
27
- when 'datetime' then ::ActiveRecord::Type::DateTime.new
28
- when 'date' then ::ActiveRecord::Type::Date.new
29
- when 'time' then ::ActiveRecord::Type::Time.new
30
- end
31
- end
32
- else
33
- def timeliness_column_for_attribute(attr_name)
34
- columns_hash.fetch(attr_name.to_s) do |key|
35
- validation_type = _validators[key.to_sym].find {|v| v.kind == :timeliness }.type.to_s
36
- ::ActiveRecord::ConnectionAdapters::Column.new(key, nil, validation_type)
37
- end
38
- end
39
- end
40
-
41
- def define_attribute_methods
42
- super.tap {
43
- generated_timeliness_methods.synchronize do
44
- return if @timeliness_methods_generated
45
- define_timeliness_methods true
46
- @timeliness_methods_generated = true
47
- end
48
- }
49
- end
50
-
51
- def undefine_attribute_methods
52
- super.tap {
53
- generated_timeliness_methods.synchronize do
54
- return unless @timeliness_methods_generated
55
- undefine_timeliness_attribute_methods
56
- @timeliness_methods_generated = false
57
- end
58
- }
59
- end
60
- # Override to overwrite methods in ActiveRecord attribute method module because in AR 4+
61
- # there is curious code which calls the method directly from the generated methods module
62
- # via bind inside method_missing. This means our method in the formerly custom timeliness
63
- # methods module was never reached.
64
- def generated_timeliness_methods
65
- generated_attribute_methods
66
- end
67
- end
68
-
69
- def write_timeliness_attribute(attr_name, value)
70
- @timeliness_cache ||= {}
71
- @timeliness_cache[attr_name] = value
72
-
73
- if ValidatesTimeliness.use_plugin_parser
74
- type = self.class.timeliness_attribute_type(attr_name)
75
- timezone = :current if self.class.timeliness_attribute_timezone_aware?(attr_name)
76
- value = Timeliness::Parser.parse(value, type, :zone => timezone)
77
- value = value.to_date if value && type == :date
78
- end
79
-
80
- write_attribute(attr_name, value)
81
- end
82
-
83
6
  def read_timeliness_attribute_before_type_cast(attr_name)
84
- @timeliness_cache && @timeliness_cache[attr_name] || read_attribute_before_type_cast(attr_name)
85
- end
86
-
87
- def reload(*args)
88
- _clear_timeliness_cache
89
- super
7
+ read_attribute_before_type_cast(attr_name)
90
8
  end
91
9
 
92
10
  end
93
11
  end
94
12
  end
95
13
 
96
- class ActiveRecord::Base
14
+ ActiveSupport.on_load(:active_record) do
97
15
  include ValidatesTimeliness::AttributeMethods
98
16
  include ValidatesTimeliness::ORM::ActiveRecord
99
17
  end
@@ -12,7 +12,7 @@ module ValidatesTimeliness
12
12
  ValidatesTimeliness.ignore_restriction_errors = !Rails.env.test?
13
13
  end
14
14
 
15
- initializer "validates_timeliness.initialize_timeliness_ambiguous_date_format", :after => 'load_config_initializers' do
15
+ initializer "validates_timeliness.initialize_timeliness_ambiguous_date_format", :after => :load_config_initializers do
16
16
  if Timeliness.respond_to?(:ambiguous_date_format) # i.e. v0.4+
17
17
  # Set default for each new thread if you have changed the default using
18
18
  # the format switching methods.
@@ -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, :attributes
6
+ attr_reader :type, :attributes, :converter
9
7
 
10
8
  RESTRICTIONS = {
11
9
  :is_at => :==,
@@ -55,18 +53,14 @@ module ValidatesTimeliness
55
53
  end
56
54
  end
57
55
 
58
- # Rails 4.0 compatibility for old #setup method with class as arg
59
- if Gem::Version.new(ActiveModel::VERSION::STRING) <= Gem::Version.new('4.1')
60
- alias_method(:setup, :setup_timeliness_validated_attributes)
61
- end
62
-
63
56
  def validate_each(record, attr_name, value)
64
57
  raw_value = attribute_raw_value(record, attr_name) || value
65
58
  return if (@allow_nil && raw_value.nil?) || (@allow_blank && raw_value.blank?)
66
59
 
67
- @timezone_aware = timezone_aware?(record, attr_name)
68
- value = parse(raw_value) if value.is_a?(String) || options[:format]
69
- 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)
70
64
 
71
65
  add_error(record, attr_name, :"invalid_#{@type}") and return if value.blank?
72
66
 
@@ -76,7 +70,7 @@ module ValidatesTimeliness
76
70
  def validate_restrictions(record, attr_name, value)
77
71
  @restrictions_to_check.each do |restriction|
78
72
  begin
79
- restriction_value = type_cast_value(evaluate_option_value(options[restriction], record), @type)
73
+ restriction_value = @converter.type_cast_value(@converter.evaluate(options[restriction], record))
80
74
  unless value.send(RESTRICTIONS[restriction], restriction_value)
81
75
  add_error(record, attr_name, restriction, restriction_value) and break
82
76
  end
@@ -91,12 +85,12 @@ module ValidatesTimeliness
91
85
 
92
86
  def add_error(record, attr_name, message, value=nil)
93
87
  value = format_error_value(value) if value
94
- message_options = { :message => options.fetch(:"#{message}_message", options[:message]), :restriction => value }
95
- 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)
96
90
  end
97
91
 
98
92
  def format_error_value(value)
99
- 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')
100
94
  value.strftime(format)
101
95
  end
102
96
 
@@ -105,9 +99,18 @@ module ValidatesTimeliness
105
99
  record.read_timeliness_attribute_before_type_cast(attr_name.to_s)
106
100
  end
107
101
 
108
- def timezone_aware?(record, attr_name)
109
- record.class.respond_to?(:timeliness_attribute_timezone_aware?) &&
110
- record.class.timeliness_attribute_timezone_aware?(attr_name)
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)
105
+ end
106
+
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
+ )
111
114
  end
112
115
 
113
116
  end
@@ -1,3 +1,3 @@
1
1
  module ValidatesTimeliness
2
- VERSION = '4.1.1'
2
+ VERSION = '6.0.0.beta2'
3
3
  end
@@ -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,5 +1,6 @@
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'
@@ -59,6 +60,7 @@ end
59
60
  ActiveRecord::Base.default_timezone = :utc
60
61
  ActiveRecord::Base.time_zone_aware_attributes = true
61
62
  ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
63
+ ActiveRecord::Base.time_zone_aware_types = [:datetime, :time]
62
64
  ActiveRecord::Migration.verbose = false
63
65
  ActiveRecord::Schema.define(:version => 1) do
64
66
  create_table :employees, :force => true do |t|
@@ -17,8 +17,7 @@ module ModelHelpers
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
@@ -15,17 +15,19 @@ module TestModel
15
15
  self.model_attributes[name] = type
16
16
  end
17
17
 
18
- def define_method_attribute=(attr_name)
18
+ def define_method_attribute=(attr_name, owner: nil)
19
19
  generated_attribute_methods.module_eval("def #{attr_name}=(new_value); @attributes['#{attr_name}']=self.class.type_cast('#{attr_name}', new_value); end", __FILE__, __LINE__)
20
20
  end
21
21
 
22
- def define_method_attribute(attr_name)
22
+ def define_method_attribute(attr_name, owner: nil)
23
23
  generated_attribute_methods.module_eval("def #{attr_name}; @attributes['#{attr_name}']; end", __FILE__, __LINE__)
24
24
  end
25
25
 
26
26
  def type_cast(attr_name, value)
27
27
  return value unless value.is_a?(String)
28
- value.send("to_#{model_attributes[attr_name.to_sym]}") rescue nil
28
+ type_name = model_attributes[attr_name.to_sym]
29
+ type = ActiveModel::Type.lookup(type_name)
30
+ type.cast(value)
29
31
  end
30
32
  end
31
33
 
@@ -48,7 +50,7 @@ module TestModel
48
50
  end
49
51
 
50
52
  def method_missing(method_id, *args, &block)
51
- if match_attribute_method?(method_id.to_s)
53
+ if !matched_attribute_method(method_id.to_s).nil?
52
54
  self.class.define_attribute_methods self.class.model_attributes.keys
53
55
  send(method_id, *args, &block)
54
56
  else
@@ -38,21 +38,6 @@ RSpec.describe ValidatesTimeliness::AttributeMethods do
38
38
  expect(e.redefined_birth_date_called).to be_truthy
39
39
  end
40
40
 
41
- it 'should be undefined if model class has dynamic attribute methods reset' do
42
- # Force method definitions
43
- PersonWithShim.validates_date :birth_date
44
- r = PersonWithShim.new
45
- r.birth_date = Time.now
46
-
47
- write_method = :birth_date=
48
-
49
- expect(PersonWithShim.send(:generated_timeliness_methods).instance_methods).to include(write_method)
50
-
51
- PersonWithShim.undefine_attribute_methods
52
-
53
- expect(PersonWithShim.send(:generated_timeliness_methods).instance_methods).not_to include(write_method)
54
- end
55
-
56
41
  context "with plugin parser" do
57
42
  with_config(:use_plugin_parser, true)
58
43