Module: ActiveRecord::DatabaseValidations::Rescues

Included in:
Base
Defined in:
lib/activerecord/database_validations/rescues.rb

Constant Summary collapse

NOT_NULL_PATTERNS =
[
  /^Mysql2::Error: Column '(.+?)' cannot be null:/,
  /^Mysql2::Error: Field '(.+?)' doesn't have a default value:/,
  /^PG::NotNullViolation: ERROR:  null value in column "(.+?)" violates not-null constraint\nDETAIL:/,
]
UNIQUE_PATTERNS_BY_COLUMN =
[
  /^PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint ".+?"\nDETAIL:  Key \((?:.+?, )*(.+?)\)=\(.+?\) already exists\./,
]
UNIQUE_PATTERNS_BY_INDEX =
[
  /^Mysql2::Error: Duplicate entry '.+?' for key '(.+?)':/,
]
PRIMARY_INDEXES =
[
  'PRIMARY',
]
FOREIGN_KEY_PATTERNS_BY_COLUMN =
[
  /^Mysql2::Error: Cannot add or update a child row: a foreign key constraint fails \(`.+?`\.`.+?`, CONSTRAINT `.+?` FOREIGN KEY \(`(.+?)`\) REFERENCES `.+?` \(`.+?`\)(?: ON [A-Z ]+)?\):/,
  /^PG::ForeignKeyViolation: ERROR:  insert or update on table ".+?" violates foreign key constraint ".+?"\nDETAIL:  Key \((.+?)\)=\(.+?\) is not present in table ".+?"\.\n:/,
]
FOREIGN_KEY_PATTERNS_BY_FOREIGN_KEY =
[
]

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



2
3
4
5
6
# File 'lib/activerecord/database_validations/rescues.rb', line 2

def self.included(base)
  base.around_save :rescue_database_not_null
  base.around_save :rescue_database_unique
  base.around_save :rescue_database_foreign_key
end

Instance Method Details

#rescue_database_foreign_keyObject



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/activerecord/database_validations/rescues.rb', line 65

def rescue_database_foreign_key
  self.class.foreign_keys if FOREIGN_KEY_PATTERNS_BY_FOREIGN_KEY.any? # load he foreign keys not inside a failing transaction (eg. PG::InFailedSqlTransaction)
  begin
    yield
  rescue ActiveRecord::InvalidForeignKey => e
    column_name = if FOREIGN_KEY_PATTERNS_BY_COLUMN.any? { |p| e.message =~ p }
                    $1
                  elsif FOREIGN_KEY_PATTERNS_BY_FOREIGN_KEY.any? { |p| e.message =~ p }
                    foreign_key = self.class.foreign_keys.find { |i| i.name == $1 }
                    raise if foreign_key.nil?
                    foreign_key.column
                  else
                    raise
                  end
    errors.add(column_name, :inclusion)
    raise ActiveRecord::RecordInvalid.new(self)
  end
end

#rescue_database_not_nullObject



13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/activerecord/database_validations/rescues.rb', line 13

def rescue_database_not_null
  begin
    yield
  rescue ActiveRecord::StatementInvalid => e
    if NOT_NULL_PATTERNS.any? { |p| e.message =~ p }
      column_name = $1
      errors.add(column_name, :blank)
      raise ActiveRecord::RecordInvalid.new(self)
    else
      raise
    end
  end
end

#rescue_database_uniqueObject



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/activerecord/database_validations/rescues.rb', line 36

def rescue_database_unique
  self.class.indexes if UNIQUE_PATTERNS_BY_INDEX.any? # load the indexes not inside a failed transaction (eg. PG::InFailedSqlTransaction)
  begin
    yield
  rescue ActiveRecord::RecordNotUnique => e
    column_name = if UNIQUE_PATTERNS_BY_COLUMN.any? { |p| e.message =~ p }
                    $1
                  elsif UNIQUE_PATTERNS_BY_INDEX.any? { |p| e.message =~ p }
                    if PRIMARY_INDEXES.include?($1)
                      self.class.primary_key
                    else
                      index = self.class.indexes.find { |i| i.name == $1 }
                      raise if index.nil?
                      index.columns[-1]
                    end
                  else
                    raise
                  end
    errors.add(column_name, :taken)
    raise ActiveRecord::RecordInvalid.new(self)
  end
end