validates_timeliness 4.1.1 → 5.0.0.alpha1
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.
- checksums.yaml +4 -4
- data/.travis.yml +8 -12
- data/CHANGELOG.rdoc +7 -11
- data/README.rdoc +4 -5
- data/gemfiles/rails_5_0.gemfile +18 -0
- data/gemfiles/rails_5_1.gemfile +18 -0
- data/gemfiles/rails_5_2.gemfile +18 -0
- data/lib/validates_timeliness.rb +1 -1
- data/lib/validates_timeliness/attribute_methods.rb +32 -71
- data/lib/validates_timeliness/{conversion.rb → converter.rb} +26 -14
- data/lib/validates_timeliness/extensions.rb +2 -2
- data/lib/validates_timeliness/extensions/date_time_select.rb +23 -27
- data/lib/validates_timeliness/extensions/multiparameter_handler.rb +47 -66
- data/lib/validates_timeliness/orm/active_model.rb +53 -2
- data/lib/validates_timeliness/orm/active_record.rb +1 -83
- data/lib/validates_timeliness/railtie.rb +0 -8
- data/lib/validates_timeliness/validator.rb +18 -15
- data/lib/validates_timeliness/version.rb +1 -1
- data/spec/spec_helper.rb +2 -2
- data/spec/support/test_model.rb +4 -2
- data/spec/validates_timeliness/attribute_methods_spec.rb +0 -15
- data/spec/validates_timeliness/{conversion_spec.rb → converter_spec.rb} +64 -49
- data/spec/validates_timeliness/extensions/date_time_select_spec.rb +27 -27
- data/spec/validates_timeliness/extensions/multiparameter_handler_spec.rb +10 -10
- data/spec/validates_timeliness/orm/active_record_spec.rb +72 -136
- data/validates_timeliness.gemspec +1 -1
- metadata +15 -21
- data/gemfiles/rails_4_2.gemfile +0 -14
- data/spec/validates_timeliness/railtie_spec.rb +0 -22
@@ -1,74 +1,55 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
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
|
@@ -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,90 +3,8 @@ 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
|
-
|
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
|
@@ -11,13 +11,5 @@ 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
|
22
14
|
end
|
23
15
|
end
|
@@ -3,9 +3,7 @@ require 'active_model/validator'
|
|
3
3
|
|
4
4
|
module ValidatesTimeliness
|
5
5
|
class Validator < ActiveModel::EachValidator
|
6
|
-
|
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
|
-
@
|
68
|
-
|
69
|
-
value =
|
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(
|
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
|
@@ -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
|
109
|
-
record.class.respond_to?(:
|
110
|
-
record.class.
|
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
|
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'
|
@@ -9,8 +10,6 @@ require 'timecop'
|
|
9
10
|
require 'validates_timeliness'
|
10
11
|
require 'validates_timeliness/orm/active_model'
|
11
12
|
|
12
|
-
require 'rails/railtie'
|
13
|
-
|
14
13
|
require 'support/test_model'
|
15
14
|
require 'support/model_helpers'
|
16
15
|
require 'support/config_helper'
|
@@ -59,6 +58,7 @@ end
|
|
59
58
|
ActiveRecord::Base.default_timezone = :utc
|
60
59
|
ActiveRecord::Base.time_zone_aware_attributes = true
|
61
60
|
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
|
61
|
+
ActiveRecord::Base.time_zone_aware_types = [:datetime, :time]
|
62
62
|
ActiveRecord::Migration.verbose = false
|
63
63
|
ActiveRecord::Schema.define(:version => 1) do
|
64
64
|
create_table :employees, :force => true do |t|
|
data/spec/support/test_model.rb
CHANGED
@@ -25,7 +25,9 @@ module TestModel
|
|
25
25
|
|
26
26
|
def type_cast(attr_name, value)
|
27
27
|
return value unless value.is_a?(String)
|
28
|
-
|
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
|
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
|
|
@@ -1,94 +1,109 @@
|
|
1
|
-
RSpec.describe ValidatesTimeliness::
|
2
|
-
|
1
|
+
RSpec.describe ValidatesTimeliness::Converter do
|
2
|
+
subject(:converter) { described_class.new(type: type, time_zone_aware: time_zone_aware, ignore_usec: ignore_usec) }
|
3
3
|
|
4
4
|
let(:options) { Hash.new }
|
5
|
+
let(:type) { :date }
|
6
|
+
let(:time_zone_aware) { false }
|
7
|
+
let(:ignore_usec) { false }
|
5
8
|
|
6
9
|
before do
|
7
10
|
Timecop.freeze(Time.mktime(2010, 1, 1, 0, 0, 0))
|
8
11
|
end
|
9
12
|
|
13
|
+
delegate :type_cast_value, :evaluate, :parse, :dummy_time, to: :converter
|
14
|
+
|
10
15
|
describe "#type_cast_value" do
|
11
16
|
describe "for date type" do
|
17
|
+
let(:type) { :date }
|
18
|
+
|
12
19
|
it "should return same value for date value" do
|
13
|
-
expect(type_cast_value(Date.new(2010, 1, 1)
|
20
|
+
expect(type_cast_value(Date.new(2010, 1, 1))).to eq(Date.new(2010, 1, 1))
|
14
21
|
end
|
15
22
|
|
16
23
|
it "should return date part of time value" do
|
17
|
-
expect(type_cast_value(Time.mktime(2010, 1, 1, 0, 0, 0)
|
24
|
+
expect(type_cast_value(Time.mktime(2010, 1, 1, 0, 0, 0))).to eq(Date.new(2010, 1, 1))
|
18
25
|
end
|
19
26
|
|
20
27
|
it "should return date part of datetime value" do
|
21
|
-
expect(type_cast_value(DateTime.new(2010, 1, 1, 0, 0, 0)
|
28
|
+
expect(type_cast_value(DateTime.new(2010, 1, 1, 0, 0, 0))).to eq(Date.new(2010, 1, 1))
|
22
29
|
end
|
23
30
|
|
24
31
|
it 'should return nil for invalid value types' do
|
25
|
-
expect(type_cast_value(12
|
32
|
+
expect(type_cast_value(12)).to eq(nil)
|
26
33
|
end
|
27
34
|
end
|
28
35
|
|
29
36
|
describe "for time type" do
|
37
|
+
let(:type) { :time }
|
38
|
+
|
30
39
|
it "should return same value for time value matching dummy date part" do
|
31
|
-
expect(type_cast_value(Time.utc(2000, 1, 1, 0, 0, 0)
|
40
|
+
expect(type_cast_value(Time.utc(2000, 1, 1, 0, 0, 0))).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
|
32
41
|
end
|
33
42
|
|
34
43
|
it "should return dummy time value with same time part for time value with different date" do
|
35
|
-
expect(type_cast_value(Time.utc(2010, 1, 1, 0, 0, 0)
|
44
|
+
expect(type_cast_value(Time.utc(2010, 1, 1, 0, 0, 0))).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
|
36
45
|
end
|
37
46
|
|
38
47
|
it "should return dummy time only for date value" do
|
39
|
-
expect(type_cast_value(Date.new(2010, 1, 1)
|
48
|
+
expect(type_cast_value(Date.new(2010, 1, 1))).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
|
40
49
|
end
|
41
50
|
|
42
51
|
it "should return dummy date with time part for datetime value" do
|
43
|
-
expect(type_cast_value(DateTime.civil_from_format(:utc, 2010, 1, 1, 12, 34, 56)
|
52
|
+
expect(type_cast_value(DateTime.civil_from_format(:utc, 2010, 1, 1, 12, 34, 56))).to eq(Time.utc(2000, 1, 1, 12, 34, 56))
|
44
53
|
end
|
45
54
|
|
46
55
|
it 'should return nil for invalid value types' do
|
47
|
-
expect(type_cast_value(12
|
56
|
+
expect(type_cast_value(12)).to eq(nil)
|
48
57
|
end
|
49
58
|
end
|
50
59
|
|
51
60
|
describe "for datetime type" do
|
61
|
+
let(:type) { :datetime }
|
62
|
+
let(:time_zone_aware) { true }
|
63
|
+
|
52
64
|
it "should return Date as Time value" do
|
53
|
-
expect(type_cast_value(Date.new(2010, 1, 1)
|
65
|
+
expect(type_cast_value(Date.new(2010, 1, 1))).to eq(Time.local(2010, 1, 1, 0, 0, 0))
|
54
66
|
end
|
55
67
|
|
56
68
|
it "should return same Time value" do
|
57
69
|
value = Time.utc(2010, 1, 1, 12, 34, 56)
|
58
|
-
expect(type_cast_value(Time.utc(2010, 1, 1, 12, 34, 56)
|
70
|
+
expect(type_cast_value(Time.utc(2010, 1, 1, 12, 34, 56))).to eq(value)
|
59
71
|
end
|
60
72
|
|
61
73
|
it "should return as Time with same component values" do
|
62
|
-
expect(type_cast_value(DateTime.civil_from_format(:utc, 2010, 1, 1, 12, 34, 56)
|
74
|
+
expect(type_cast_value(DateTime.civil_from_format(:utc, 2010, 1, 1, 12, 34, 56))).to eq(Time.utc(2010, 1, 1, 12, 34, 56))
|
63
75
|
end
|
64
76
|
|
65
77
|
it "should return same Time in correct zone if timezone aware" do
|
66
|
-
@timezone_aware = true
|
67
78
|
value = Time.utc(2010, 1, 1, 12, 34, 56)
|
68
|
-
result = type_cast_value(value
|
79
|
+
result = type_cast_value(value)
|
69
80
|
expect(result).to eq(Time.zone.local(2010, 1, 1, 23, 34, 56))
|
70
81
|
expect(result.zone).to eq('AEDT')
|
71
82
|
end
|
72
83
|
|
73
84
|
it 'should return nil for invalid value types' do
|
74
|
-
expect(type_cast_value(12
|
85
|
+
expect(type_cast_value(12)).to eq(nil)
|
75
86
|
end
|
76
87
|
end
|
77
88
|
|
78
89
|
describe "ignore_usec option" do
|
79
|
-
let(:
|
90
|
+
let(:type) { :datetime }
|
91
|
+
let(:ignore_usec) { true }
|
80
92
|
|
81
93
|
it "should ignore usec on time values when evaluated" do
|
82
94
|
value = Time.utc(2010, 1, 1, 12, 34, 56, 10000)
|
83
|
-
expect(type_cast_value(value
|
95
|
+
expect(type_cast_value(value)).to eq(Time.utc(2010, 1, 1, 12, 34, 56))
|
84
96
|
end
|
85
97
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
98
|
+
context do
|
99
|
+
let(:time_zone_aware) { true }
|
100
|
+
|
101
|
+
it "should ignore usec and return time in correct zone if timezone aware" do
|
102
|
+
value = Time.utc(2010, 1, 1, 12, 34, 56, 10000)
|
103
|
+
result = type_cast_value(value)
|
104
|
+
expect(result).to eq(Time.zone.local(2010, 1, 1, 23, 34, 56))
|
105
|
+
expect(result.zone).to eq('AEDT')
|
106
|
+
end
|
92
107
|
end
|
93
108
|
end
|
94
109
|
end
|
@@ -103,7 +118,6 @@ RSpec.describe ValidatesTimeliness::Conversion do
|
|
103
118
|
end
|
104
119
|
|
105
120
|
it 'should return time component values shifted to current zone if timezone aware' do
|
106
|
-
@timezone_aware = true
|
107
121
|
expect(dummy_time(Time.utc(2000, 1, 1, 12, 34, 56))).to eq(Time.zone.local(2000, 1, 1, 23, 34, 56))
|
108
122
|
end
|
109
123
|
|
@@ -120,61 +134,64 @@ RSpec.describe ValidatesTimeliness::Conversion do
|
|
120
134
|
end
|
121
135
|
end
|
122
136
|
|
123
|
-
describe "#
|
137
|
+
describe "#evaluate" do
|
124
138
|
let(:person) { Person.new }
|
125
139
|
|
126
140
|
it 'should return Date object as is' do
|
127
141
|
value = Date.new(2010,1,1)
|
128
|
-
expect(
|
142
|
+
expect(evaluate(value, person)).to eq(value)
|
129
143
|
end
|
130
144
|
|
131
145
|
it 'should return Time object as is' do
|
132
146
|
value = Time.mktime(2010,1,1)
|
133
|
-
expect(
|
147
|
+
expect(evaluate(value, person)).to eq(value)
|
134
148
|
end
|
135
149
|
|
136
150
|
it 'should return DateTime object as is' do
|
137
151
|
value = DateTime.new(2010,1,1,0,0,0)
|
138
|
-
expect(
|
152
|
+
expect(evaluate(value, person)).to eq(value)
|
139
153
|
end
|
140
154
|
|
141
155
|
it 'should return Time value returned from proc with 0 arity' do
|
142
156
|
value = Time.mktime(2010,1,1)
|
143
|
-
expect(
|
157
|
+
expect(evaluate(lambda { value }, person)).to eq(value)
|
144
158
|
end
|
145
159
|
|
146
160
|
it 'should return Time value returned by record attribute call in proc arity of 1' do
|
147
161
|
value = Time.mktime(2010,1,1)
|
148
162
|
person.birth_time = value
|
149
|
-
expect(
|
163
|
+
expect(evaluate(lambda {|r| r.birth_time }, person)).to eq(value)
|
150
164
|
end
|
151
165
|
|
152
166
|
it 'should return Time value for attribute method symbol which returns Time' do
|
153
167
|
value = Time.mktime(2010,1,1)
|
154
|
-
person.
|
155
|
-
expect(
|
168
|
+
person.birth_datetime = value
|
169
|
+
expect(evaluate(:birth_datetime, person)).to eq(value)
|
156
170
|
end
|
157
171
|
|
158
172
|
it 'should return Time value is default zone from string time value' do
|
159
173
|
value = '2010-01-01 12:00:00'
|
160
|
-
expect(
|
174
|
+
expect(evaluate(value, person)).to eq(Time.utc(2010,1,1,12,0,0))
|
161
175
|
end
|
162
176
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
177
|
+
context do
|
178
|
+
let(:converter) { described_class.new(type: :date, time_zone_aware: true) }
|
179
|
+
|
180
|
+
it 'should return Time value is current zone from string time value if timezone aware' do
|
181
|
+
value = '2010-01-01 12:00:00'
|
182
|
+
expect(evaluate(value, person)).to eq(Time.zone.local(2010,1,1,12,0,0))
|
183
|
+
end
|
167
184
|
end
|
168
185
|
|
169
186
|
it 'should return Time value in default zone from proc which returns string time' do
|
170
|
-
value = '2010-
|
171
|
-
expect(
|
187
|
+
value = '2010-11-12 13:00:00'
|
188
|
+
expect(evaluate(lambda { value }, person)).to eq(Time.utc(2010,11,12,13,0,0))
|
172
189
|
end
|
173
190
|
|
174
191
|
it 'should return Time value for attribute method symbol which returns string time value' do
|
175
|
-
value = '
|
192
|
+
value = '13:00:00'
|
176
193
|
person.birth_time = value
|
177
|
-
expect(
|
194
|
+
expect(evaluate(:birth_time, person)).to eq(Time.utc(2000,1,1,13,0,0))
|
178
195
|
end
|
179
196
|
|
180
197
|
context "restriction shorthand" do
|
@@ -183,17 +200,17 @@ RSpec.describe ValidatesTimeliness::Conversion do
|
|
183
200
|
end
|
184
201
|
|
185
202
|
it 'should evaluate :now as current time' do
|
186
|
-
expect(
|
203
|
+
expect(evaluate(:now, person)).to eq(Time.now)
|
187
204
|
end
|
188
205
|
|
189
206
|
it 'should evaluate :today as current time' do
|
190
|
-
expect(
|
207
|
+
expect(evaluate(:today, person)).to eq(Date.today)
|
191
208
|
end
|
192
209
|
|
193
210
|
it 'should not use shorthand if symbol if is record method' do
|
194
211
|
time = 1.day.from_now
|
195
212
|
allow(person).to receive(:now).and_return(time)
|
196
|
-
expect(
|
213
|
+
expect(evaluate(:now, person)).to eq(time)
|
197
214
|
end
|
198
215
|
end
|
199
216
|
end
|
@@ -212,13 +229,11 @@ RSpec.describe ValidatesTimeliness::Conversion do
|
|
212
229
|
with_config(:use_plugin_parser, false)
|
213
230
|
|
214
231
|
it 'should use Time.zone.parse attribute is timezone aware' do
|
215
|
-
|
216
|
-
expect(Time.zone).to receive(:parse)
|
232
|
+
expect(Timeliness::Parser).to_not receive(:parse)
|
217
233
|
parse('2000-01-01')
|
218
234
|
end
|
219
235
|
|
220
236
|
it 'should use value#to_time if use_plugin_parser setting is false and attribute is not timezone aware' do
|
221
|
-
@timezone_aware = false
|
222
237
|
value = '2000-01-01'
|
223
238
|
expect(value).to receive(:to_time)
|
224
239
|
parse(value)
|