Module: DatabaseValidations::Rescuer

Defined in:
lib/database_validations/lib/rescuer.rb

Class Method Summary collapse

Class Method Details

.find_matching_validator(instance, keys) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/database_validations/lib/rescuer.rb', line 42

def find_matching_validator(instance, keys)
  first_match = nil

  keys.each do |key|
    attribute_validator = instance._db_validators[key]
    next unless attribute_validator

    first_match ||= attribute_validator

    next if (keys.size > 1) && !foreign_key_invalid?(instance, attribute_validator)

    return attribute_validator
  end

  # TOCTOU fallback: if disambiguate queries all passed (concurrent insert),
  # use the first matching validator rather than leaving the error unhandled.
  first_match
end

.foreign_key_invalid?(instance, attribute_validator) ⇒ Boolean

Returns:

  • (Boolean)


61
62
63
64
65
66
67
68
69
70
# File 'lib/database_validations/lib/rescuer.rb', line 61

def foreign_key_invalid?(instance, attribute_validator)
  attribute = attribute_validator.attribute
  reflection = instance.class._reflect_on_association(attribute)
  return true unless reflection

  fk_value = instance.read_attribute(reflection.foreign_key)
  return true if fk_value.blank?

  !reflection.klass.exists?(reflection.association_primary_key => fk_value)
end

.handled?(instance, error, validate) ⇒ Boolean

Returns:

  • (Boolean)


5
6
7
8
9
10
11
12
13
14
15
# File 'lib/database_validations/lib/rescuer.rb', line 5

def handled?(instance, error, validate)
  Storage.prepare(instance.class) unless Storage.prepared?(instance.class)

  case error
  when ActiveRecord::RecordNotUnique
    process(validate, instance, error, for_unique_index: :unique_index_name, for_db_uniqueness: :unique_error_columns)
  when ActiveRecord::InvalidForeignKey
    process(validate, instance, error, for_db_presence: :foreign_key_error_column)
  else false
  end
end

.process(validate, instance, error, key_types) ⇒ Object



17
18
19
20
21
22
23
# File 'lib/database_validations/lib/rescuer.rb', line 17

def process(validate, instance, error, key_types)
  keys = resolve_keys(instance, error, key_types)
  attribute_validator = find_matching_validator(instance, keys)
  return false unless attribute_validator

  process_validator(validate, instance, attribute_validator)
end

.process_validator(validate, instance, attribute_validator) ⇒ Object



72
73
74
75
76
77
# File 'lib/database_validations/lib/rescuer.rb', line 72

def process_validator(validate, instance, attribute_validator)
  return false unless attribute_validator.validator.perform_rescue?(validate)

  attribute_validator.validator.apply_error(instance, attribute_validator.attribute)
  true
end

.resolve_keys(instance, error, key_types) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/database_validations/lib/rescuer.rb', line 25

def resolve_keys(instance, error, key_types)
  adapter = Adapters.factory(instance.class)

  key_types.flat_map do |key_generator, error_processor|
    result = adapter.public_send(error_processor, error)

    # FK adapters return an array of candidate columns, each generating a
    # separate key. Uniqueness adapters return columns that form a single
    # composite key, passed together to the key generator.
    if key_generator == :for_db_presence
      Array(result).map { |column| KeyGenerator.public_send(key_generator, column) }
    else
      [KeyGenerator.public_send(key_generator, result)]
    end
  end
end