Class: ValidatesTimeliness::Validator

Inherits:
ActiveModel::EachValidator
  • Object
show all
Defined in:
lib/validates_timeliness/validator.rb

Constant Summary collapse

RESTRICTIONS =
{
  :is_at        => :==,
  :before       => :<,
  :after        => :>,
  :on_or_before => :<=,
  :on_or_after  => :>=,
}.freeze
DEFAULT_ERROR_VALUE_FORMATS =
{
  :date => '%Y-%m-%d',
  :time => '%H:%M:%S',
  :datetime => '%Y-%m-%d %H:%M:%S'
}.freeze
RESTRICTION_ERROR_MESSAGE =
"Error occurred validating %s for %s restriction:\n%s"
RESERVED_OPTIONS =
(RESTRICTIONS.keys + RESTRICTIONS.keys.map { |option| :"#{option}_message" }).freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Validator

Returns a new instance of Validator.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/validates_timeliness/validator.rb', line 30

def initialize(options)
  @type = options.delete(:type) || :datetime
  @allow_nil, @allow_blank = options.delete(:allow_nil), options.delete(:allow_blank)

  if range = options.delete(:between)
    raise ArgumentError, ":between must be a Range or an Array" unless range.is_a?(Range) || range.is_a?(Array)
    options[:on_or_after] = range.first
    if range.is_a?(Range) && range.exclude_end?
      options[:before] = range.last
    else
      options[:on_or_before] = range.last
    end
  end

  @restrictions_to_check = RESTRICTIONS.keys & options.keys

  super

  setup_timeliness_validated_attributes(options[:class]) if options[:class]
end

Instance Attribute Details

#attributesObject (readonly)

Returns the value of attribute attributes.



6
7
8
# File 'lib/validates_timeliness/validator.rb', line 6

def attributes
  @attributes
end

#converterObject (readonly)

Returns the value of attribute converter.



6
7
8
# File 'lib/validates_timeliness/validator.rb', line 6

def converter
  @converter
end

#typeObject (readonly)

Returns the value of attribute type.



6
7
8
# File 'lib/validates_timeliness/validator.rb', line 6

def type
  @type
end

Class Method Details

.kindObject



26
27
28
# File 'lib/validates_timeliness/validator.rb', line 26

def self.kind
  :timeliness
end

Instance Method Details

#add_error(record, attr_name, message, restriction_value: nil, value: nil) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/validates_timeliness/validator.rb', line 88

def add_error(record, attr_name, message, restriction_value: nil, value: nil)
  error_options = options.except(*RESERVED_OPTIONS).merge!(
    restriction: format_error_value(restriction_value),
    value: value
  )

  message_text = options[:"#{message}_message"]
  error_options[:message] = message_text if message_text.present?

  record.errors.add(attr_name, message, **error_options)
end

#attribute_raw_value(record, attr_name) ⇒ Object



106
107
108
109
110
# File 'lib/validates_timeliness/validator.rb', line 106

def attribute_raw_value(record, attr_name)
  if record.respond_to?(:read_timeliness_attribute_before_type_cast)
    record.read_timeliness_attribute_before_type_cast(attr_name.to_s)
  end
end

#format_error_value(value) ⇒ Object



100
101
102
103
104
# File 'lib/validates_timeliness/validator.rb', line 100

def format_error_value(value)
  return unless value
  format = I18n.t(@type, default: DEFAULT_ERROR_VALUE_FORMATS[@type], scope: 'validates_timeliness.error_value_formats')
  value.strftime(format)
end

#initialize_converter(record, attr_name) ⇒ Object



117
118
119
120
121
122
123
124
# File 'lib/validates_timeliness/validator.rb', line 117

def initialize_converter(record, attr_name)
  ValidatesTimeliness::Converter.new(
    type: @type,
    time_zone_aware: time_zone_aware?(record, attr_name),
    format: options[:format],
    ignore_usec: options[:ignore_usec]
  )
end

#setup_timeliness_validated_attributes(model) ⇒ Object



51
52
53
54
55
56
# File 'lib/validates_timeliness/validator.rb', line 51

def setup_timeliness_validated_attributes(model)
  if model.respond_to?(:timeliness_validated_attributes)
    model.timeliness_validated_attributes ||= []
    model.timeliness_validated_attributes |= attributes
  end
end

#time_zone_aware?(record, attr_name) ⇒ Boolean

Returns:

  • (Boolean)


112
113
114
115
# File 'lib/validates_timeliness/validator.rb', line 112

def time_zone_aware?(record, attr_name)
  record.class.respond_to?(:skip_time_zone_conversion_for_attributes) &&
    !record.class.skip_time_zone_conversion_for_attributes.include?(attr_name.to_sym)
end

#validate_each(record, attr_name, value) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/validates_timeliness/validator.rb', line 58

def validate_each(record, attr_name, value)
  raw_value = attribute_raw_value(record, attr_name) || value
  return if (@allow_nil && raw_value.nil?) || (@allow_blank && raw_value.blank?)

  @converter = initialize_converter(record, attr_name)

  value = @converter.parse(raw_value) if value.is_a?(String) || options[:format]
  value = @converter.type_cast_value(value)

  add_error(record, attr_name, :"invalid_#{@type}") and return if value.blank?

  validate_restrictions(record, attr_name, value)
end

#validate_restrictions(record, attr_name, value) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/validates_timeliness/validator.rb', line 72

def validate_restrictions(record, attr_name, value)
  @restrictions_to_check.each do |restriction|
    begin
      restriction_value = @converter.type_cast_value(@converter.evaluate(options[restriction], record))
      unless value.send(RESTRICTIONS[restriction], restriction_value)
        add_error(record, attr_name, restriction, value: value, restriction_value: restriction_value) and break
      end
    rescue => e
      unless ValidatesTimeliness.ignore_restriction_errors
        message = RESTRICTION_ERROR_MESSAGE % [ attr_name, restriction.inspect, e.message ]
        add_error(record, attr_name, message) and break
      end
    end
  end
end