Class: ActiveRecord::Validations::DatabaseConstraintsValidator

Inherits:
ActiveModel::EachValidator
  • Object
show all
Defined in:
lib/active_record/validations/database_constraints.rb

Constant Summary collapse

TYPE_LIMITS =
{
  char:       { validator: ActiveModel::Validations::LengthValidator },
  varchar:    { validator: ActiveModel::Validations::LengthValidator },
  varbinary:  { validator: ActiveModel::Validations::BytesizeValidator },

  tinytext:   { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 **  8 - 1 },
  text:       { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 16 - 1 },
  mediumtext: { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 24 - 1 },
  longtext:   { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 32 - 1 },

  tinyblob:   { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 **  8 - 1 },
  blob:       { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 16 - 1 },
  mediumblob: { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 24 - 1 },
  longblob:   { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 32 - 1 },
}
VALID_CONSTRAINTS =
Set[:size, :basic_multilingual_plane, :not_null, :range]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ DatabaseConstraintsValidator

Returns a new instance of DatabaseConstraintsValidator.



28
29
30
31
32
# File 'lib/active_record/validations/database_constraints.rb', line 28

def initialize(options = {})
  @constraints = Set.new(Array.wrap(options[:in]) + Array.wrap(options[:with]))
  @constraint_validators = {}
  super
end

Instance Attribute Details

#constraintsObject (readonly)

Returns the value of attribute constraints.



24
25
26
# File 'lib/active_record/validations/database_constraints.rb', line 24

def constraints
  @constraints
end

Instance Method Details

#attribute_validators(klass, attribute) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/active_record/validations/database_constraints.rb', line 92

def attribute_validators(klass, attribute)
  @constraint_validators[attribute] ||= begin
    column = klass.columns_hash[attribute.to_s] or raise ArgumentError.new("Model #{self.class.name} does not have column #{column_name}!")

    [
      not_null_validator(klass, column),
      size_validator(klass, column),
      basic_multilingual_plane_validator(klass, column),
      range_validator(klass, column),
    ].compact
  end
end

#basic_multilingual_plane_validator(klass, column) ⇒ Object



86
87
88
89
90
# File 'lib/active_record/validations/database_constraints.rb', line 86

def basic_multilingual_plane_validator(klass, column)
  return unless constraints.include?(:basic_multilingual_plane)
  return unless column.text? && column.collation =~ /\Autf8(?:mb3)?_/
  ActiveModel::Validations::BasicMultilingualPlaneValidator.new(attributes: [column.name.to_sym], class: klass)
end

#check_validity!Object

Raises:

  • (ArgumentError)


34
35
36
37
38
39
# File 'lib/active_record/validations/database_constraints.rb', line 34

def check_validity!
  invalid_constraints = constraints - VALID_CONSTRAINTS

  raise ArgumentError, "You have to specify what constraints to validate for." if constraints.empty?
  raise ArgumentError, "#{invalid_constraints.map(&:inspect).join(',')} is not a valid constraint." unless invalid_constraints.empty?
end

#not_null_validator(klass, column) ⇒ Object



41
42
43
44
45
46
# File 'lib/active_record/validations/database_constraints.rb', line 41

def not_null_validator(klass, column)
  return unless constraints.include?(:not_null)
  return if column.null

  ActiveModel::Validations::NotNullValidator.new(attributes: [column.name.to_sym], class: klass)
end

#range_validator(klass, column) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/active_record/validations/database_constraints.rb', line 63

def range_validator(klass, column)
  return unless constraints.include?(:range)
  return unless column.number?

  unsigned = column.sql_type =~ / unsigned\z/
  case column.type
  when :decimal
    args = { attributes: [column.name.to_sym], class: klass, allow_nil: true }
    args[:less_than] = maximum = 10 ** (column.precision - column.scale)
    if unsigned
      args[:greater_than_or_equal_to] = 0
    else
      args[:greater_than] = 0 - maximum
    end
    ActiveModel::Validations::NumericalityValidator.new(args)

  when :integer
    maximum = unsigned ? 1 << (column.limit * 8) : 1 << (column.limit * 8 - 1)
    minimum = unsigned ? 0 : 0 - maximum
    ActiveModel::Validations::NumericalityValidator.new(attributes: [column.name.to_sym], class: klass, greater_than_or_equal_to: minimum, less_than: maximum, allow_nil: true, only_integer: true)
  end
end

#size_validator(klass, column) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/active_record/validations/database_constraints.rb', line 48

def size_validator(klass, column)
  return unless constraints.include?(:size)
  return unless column.text? || column.binary?

  column_type     = column.sql_type.sub(/\(.*\z/, '').gsub(/\s/, '_').to_sym
  type_limit      = TYPE_LIMITS.fetch(column_type, {})
  validator_class = type_limit[:validator]
  maximum         = column.limit || type_limit[:default_maximum]
  encoding        = column.text? ? determine_encoding(column) : nil

  if validator_class && maximum
    validator_class.new(attributes: [column.name.to_sym], class: klass, maximum: maximum, encoding: encoding)
  end
end

#validate_each(record, attribute, value) ⇒ Object



105
106
107
108
109
# File 'lib/active_record/validations/database_constraints.rb', line 105

def validate_each(record, attribute, value)
  attribute_validators(record.class, attribute).each do |validator|
    validator.validate_each(record, attribute, value)
  end
end