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.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +7 -0
- data/.github/workflows/ci.yml +36 -0
- data/CHANGELOG.md +337 -0
- data/LICENSE +1 -1
- data/README.md +319 -0
- data/Rakefile +0 -10
- data/gemfiles/rails_6_0.gemfile +13 -0
- data/gemfiles/rails_6_1.gemfile +14 -0
- data/gemfiles/rails_edge.gemfile +13 -0
- data/lib/generators/validates_timeliness/templates/validates_timeliness.rb +2 -2
- data/lib/validates_timeliness/attribute_methods.rb +35 -78
- data/lib/validates_timeliness/{conversion.rb → converter.rb} +28 -14
- data/lib/validates_timeliness/extensions/date_time_select.rb +23 -34
- data/lib/validates_timeliness/extensions/multiparameter_handler.rb +38 -63
- data/lib/validates_timeliness/extensions.rb +3 -4
- data/lib/validates_timeliness/helper_methods.rb +8 -2
- data/lib/validates_timeliness/orm/active_model.rb +71 -0
- data/lib/validates_timeliness/orm/active_record.rb +3 -39
- data/lib/validates_timeliness/railtie.rb +8 -0
- data/lib/validates_timeliness/validator.rb +28 -17
- data/lib/validates_timeliness/version.rb +1 -1
- data/lib/validates_timeliness.rb +4 -4
- data/spec/spec_helper.rb +13 -11
- data/spec/support/model_helpers.rb +5 -6
- data/spec/support/tag_matcher.rb +35 -0
- data/spec/support/test_model.rb +6 -5
- data/spec/validates_timeliness/attribute_methods_spec.rb +8 -25
- data/spec/validates_timeliness/converter_spec.rb +247 -0
- data/spec/validates_timeliness/extensions/date_time_select_spec.rb +37 -34
- data/spec/validates_timeliness/extensions/multiparameter_handler_spec.rb +12 -13
- data/spec/validates_timeliness/helper_methods_spec.rb +9 -11
- data/spec/validates_timeliness/orm/active_record_spec.rb +85 -139
- data/spec/validates_timeliness/railtie_spec.rb +22 -0
- data/spec/validates_timeliness/validator/after_spec.rb +4 -6
- data/spec/validates_timeliness/validator/before_spec.rb +4 -6
- data/spec/validates_timeliness/validator/is_at_spec.rb +9 -7
- data/spec/validates_timeliness/validator/on_or_after_spec.rb +4 -6
- data/spec/validates_timeliness/validator/on_or_before_spec.rb +4 -6
- data/spec/validates_timeliness/validator_spec.rb +45 -29
- data/spec/validates_timeliness_spec.rb +9 -11
- data/validates_timeliness.gemspec +16 -4
- metadata +61 -31
- data/CHANGELOG.rdoc +0 -188
- data/README.rdoc +0 -299
- data/gemfiles/mongoid_2_1.gemfile +0 -16
- data/gemfiles/mongoid_2_2.gemfile +0 -16
- data/gemfiles/mongoid_2_3.gemfile +0 -16
- data/gemfiles/mongoid_2_4.gemfile +0 -16
- data/gemfiles/rails_3_0.gemfile +0 -15
- data/gemfiles/rails_3_1.gemfile +0 -15
- data/gemfiles/rails_3_2.gemfile +0 -15
- data/lib/validates_timeliness/orm/mongoid.rb +0 -49
- data/spec/validates_timeliness/conversion_spec.rb +0 -234
- data/spec/validates_timeliness/orm/mongoid_spec.rb +0 -223
@@ -1,60 +1,49 @@
|
|
1
1
|
module ValidatesTimeliness
|
2
2
|
module Extensions
|
3
|
-
module
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
10
|
+
POSITION = {
|
11
|
+
:year => 1, :month => 2, :day => 3, :hour => 4, :min => 5, :sec => 6
|
12
|
+
}.freeze
|
15
13
|
|
16
|
-
class
|
14
|
+
class DateTimeValue
|
17
15
|
attr_accessor :year, :month, :day, :hour, :min, :sec
|
18
16
|
|
19
|
-
def initialize(year
|
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
|
-
|
26
|
-
options
|
27
|
-
options
|
28
|
-
options
|
29
|
-
options
|
30
|
-
options
|
31
|
-
options
|
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
|
-
|
37
|
-
|
38
|
-
|
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
|
38
|
+
return super if pairs.empty?
|
50
39
|
|
51
|
-
values =
|
52
|
-
pairs.
|
53
|
-
position =
|
54
|
-
values[position.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
|
-
|
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
|
-
|
4
|
-
extend ActiveSupport::Concern
|
3
|
+
class AcceptsMultiparameterTime < Module
|
5
4
|
|
6
|
-
|
7
|
-
# assignment from the date/time select view helpers
|
5
|
+
def initialize(defaults: {})
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
19
|
+
super(value)
|
59
20
|
end
|
60
21
|
end
|
61
|
-
end
|
62
22
|
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
74
|
-
|
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 :
|
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::
|
7
|
+
::ActionView::Helpers::Tags::DateSelect.send(:prepend, ValidatesTimeliness::Extensions::TimelinessDateTimeSelect)
|
9
8
|
end
|
10
9
|
|
11
10
|
def self.enable_multiparameter_extension!
|
12
|
-
|
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
|
18
|
-
|
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
|
-
|
7
|
-
|
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
|
-
|
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
|
-
|
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
|
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 |=
|
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
|
-
@
|
60
|
-
|
61
|
-
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)
|
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(
|
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 = { :
|
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, :
|
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?(:
|
97
|
-
record.
|
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
|
101
|
-
|
102
|
-
|
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
|
data/lib/validates_timeliness.rb
CHANGED
@@ -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
|
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
|
-
:
|
40
|
-
:
|
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/
|
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 '
|
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
|
-
|
78
|
-
|
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(
|
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.
|
7
|
-
record.errors[attr_name].size.
|
8
|
-
record.errors[attr_name].first.
|
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.
|
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
|
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
|