Error add rails

add(attribute, message = :invalid, options = {}) public

add(attribute, message = :invalid, options = {})
public

Adds message to the error messages and used validator type to
details on attribute. More than one error can be added to
the same attribute. If no message is supplied,
:invalid is assumed.

person.errors.add(:name)

person.errors.add(:name, :not_implemented, message: "must be implemented")


person.errors.messages


person.errors.details

If message is a symbol, it will be translated using the
appropriate scope (see generate_message).

If message is a proc, it will be called, allowing for things like
Time.now to be used within an error.

If the :strict option is set to true, it will
raise ActiveModel::StrictValidationFailed
instead of adding the error. :strict option can also be set to any other exception.

person.errors.add(:name, :invalid, strict: true)

person.errors.add(:name, :invalid, strict: NameIsInvalid)


person.errors.messages 

attribute should be set to :base if the error
is not directly associated with a single attribute.

person.errors.add(:base, :name_or_email_blank,
  message: "either name or email must be present")
person.errors.messages

person.errors.details

I have read that errors.add_to_base should be used for errors associated with the object and not a specific attribute. I am having trouble conceptualizing what this means. Could someone provide an example of when I would want to use each?

For example, I have a Band model and each Band has a Genre. When I validate the presence of a genre, if the genre is missing should the error be added to the base?

The more examples the better

Thank you!

asked Apr 1, 2009 at 17:15

Tony's user avatar

0

A missing genre would be a field error. A base error would be something like an exact duplicate of an existing record, where the problem wasn’t tied to any specific field but rather to the record as a whole (or at lest to some combination of fields).

answered Apr 1, 2009 at 17:26

MarkusQ's user avatar

MarkusQMarkusQ

21.7k3 gold badges56 silver badges67 bronze badges

In this example, you can see field validation (team must be chosen). And you can see a class/base level validation. For example, you required at least one method of contact, a phone or an email:

class Registrant
  include MongoMapper::Document

  # Attributes ::::::::::::::::::::::::::::::::::::::::::::::::::::::
  key :name, String, :required => true
  key :email, String
  key :phone, String

  # Associations :::::::::::::::::::::::::::::::::::::::::::::::::::::
  key :team_id, ObjectId
  belongs_to :team
...
  # Validations :::::::::::::::::::::::::::::::::::::::::::::::::::::
  validate :validate_team_selection
  validate :validate_contact_method
...

  private

  def validate_contact_method
    # one or the other must be provided
    if phone.empty? and email.empty?
      errors.add_to_base("At least one form of contact must be entered: phone or email" )
    end
  end

  def validate_team_selection
    if registration_setup.require_team_at_signup
      if team_id.nil?
        errors.add(:team, "must be selected" )
      end
    end
  end
end

answered Nov 1, 2010 at 20:27

Jon Kern's user avatar

Jon KernJon Kern

3,17631 silver badges34 bronze badges

0

Parent:
Object
Included modules:
Enumerable

Active Model Errors

Provides a modified Hash that you can include in your object for handling error messages and interacting with Action View helpers.

A minimal implementation could be:

class Person
  # Required dependency for ActiveModel::Errors
  extend ActiveModel::Naming

  def initialize
    @errors = ActiveModel::Errors.new(self)
  end

  attr_accessor :name
  attr_reader   :errors

  def validate!
    errors.add(:name, :blank, message: "cannot be nil") if name.nil?
  end

  # The following methods are needed to be minimally implemented

  def read_attribute_for_validation(attr)
    send(attr)
  end

  def self.human_attribute_name(attr, options = {})
    attr
  end

  def self.lookup_ancestors
    [self]
  end
end

The last three methods are required in your object for Errors to be able to generate error messages correctly and also handle multiple languages. Of course, if you extend your object with ActiveModel::Translation you will not need to implement the last two. Likewise, using ActiveModel::Validations will handle the validation related methods for you.

The above allows you to do:

person = Person.new
person.validate!            # => ["cannot be nil"]
person.errors.full_messages # => ["name cannot be nil"]
# etc..

Constants

CALLBACKS_OPTIONS
MESSAGE_OPTIONS

Attributes

Public Class Methods

# File activemodel/lib/active_model/errors.rb, line 79
def initialize(base)
  @base     = base
  @messages = apply_default_array({})
  @details = apply_default_array({})
end

Pass in the instance of the object that is using the errors object.

class Person
  def initialize
    @errors = ActiveModel::Errors.new(self)
  end
end

Public Instance Methods

# File activemodel/lib/active_model/errors.rb, line 165
def [](attribute)
  messages[attribute.to_sym]
end

When passed a symbol or a name of a method, returns an array of errors for the method.

person.errors[:name]  # => ["cannot be nil"]
person.errors['name'] # => ["cannot be nil"]

add(attribute, message = :invalid, options = {}) Show source

# File activemodel/lib/active_model/errors.rb, line 311
def add(attribute, message = :invalid, options = {})
  message = message.call if message.respond_to?(:call)
  detail  = normalize_detail(message, options)
  message = normalize_message(attribute, message, options)
  if exception = options[:strict]
    exception = ActiveModel::StrictValidationFailed if exception == true
    raise exception, full_message(attribute, message)
  end

  details[attribute.to_sym]  << detail
  messages[attribute.to_sym] << message
end

Adds message to the error messages and used validator type to details on attribute. More than one error can be added to the same attribute. If no message is supplied, :invalid is assumed.

person.errors.add(:name)
# => ["is invalid"]
person.errors.add(:name, :not_implemented, message: "must be implemented")
# => ["is invalid", "must be implemented"]

person.errors.messages
# => {:name=>["is invalid", "must be implemented"]}

person.errors.details
# => {:name=>[{error: :not_implemented}, {error: :invalid}]}

If message is a symbol, it will be translated using the appropriate scope (see generate_message).

If message is a proc, it will be called, allowing for things like Time.now to be used within an error.

If the :strict option is set to true, it will raise ActiveModel::StrictValidationFailed instead of adding the error. :strict option can also be set to any other exception.

person.errors.add(:name, :invalid, strict: true)
# => ActiveModel::StrictValidationFailed: Name is invalid
person.errors.add(:name, :invalid, strict: NameIsInvalid)
# => NameIsInvalid: Name is invalid

person.errors.messages # => {}

attribute should be set to :base if the error is not directly associated with a single attribute.

person.errors.add(:base, :name_or_email_blank,
  message: "either name or email must be present")
person.errors.messages
# => {:base=>["either name or email must be present"]}
person.errors.details
# => {:base=>[{error: :name_or_email_blank}]}

added?(attribute, message = :invalid, options = {}) Show source

# File activemodel/lib/active_model/errors.rb, line 340
def added?(attribute, message = :invalid, options = {})
  message = message.call if message.respond_to?(:call)

  if message.is_a? Symbol
    details[attribute.to_sym].include? normalize_detail(message, options)
  else
    self[attribute].include? message
  end
end

Returns true if an error on the attribute with the given message is present, or false otherwise. message is treated the same as for add.

person.errors.add :name, :blank
person.errors.added? :name, :blank           # => true
person.errors.added? :name, "can't be blank" # => true

If the error message requires options, then it returns true with the correct options, or false with incorrect or missing options.

person.errors.add :name, :too_long, { count: 25 }
person.errors.added? :name, :too_long, count: 25                     # => true
person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
person.errors.added? :name, :too_long, count: 24                     # => false
person.errors.added? :name, :too_long                                # => false
person.errors.added? :name, "is too long"                            # => false

# File activemodel/lib/active_model/errors.rb, line 251
def as_json(options = nil)
  to_hash(options && options[:full_messages])
end

Returns a Hash that can be used as the JSON representation for this object. You can pass the :full_messages option. This determines if the json object should contain full messages or not (false by default).

person.errors.as_json                      # => {:name=>["cannot be nil"]}
person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]}

# File activemodel/lib/active_model/errors.rb, line 131
def clear
  messages.clear
  details.clear
end

Clear the error messages.

person.errors.full_messages # => ["name cannot be nil"]
person.errors.clear
person.errors.full_messages # => []

# File activemodel/lib/active_model/errors.rb, line 154
def delete(key)
  attribute = key.to_sym
  details.delete(attribute)
  messages.delete(attribute)
end

Delete messages for key. Returns the deleted messages.

person.errors[:name]        # => ["cannot be nil"]
person.errors.delete(:name) # => ["cannot be nil"]
person.errors[:name]        # => []

each() { |attribute, error| … } Show source

# File activemodel/lib/active_model/errors.rb, line 183
def each
  messages.each_key do |attribute|
    messages[attribute].each { |error| yield attribute, error }
  end
end

Iterates through each error key, value pair in the error messages hash. Yields the attribute and the error for that attribute. If the attribute has more than one error message, yields once for each error message.

person.errors.add(:name, :blank, message: "can't be blank")
person.errors.each do |attribute, error|
  # Will yield :name and "can't be blank"
end

person.errors.add(:name, :not_specified, message: "must be specified")
person.errors.each do |attribute, error|
  # Will yield :name and "can't be blank"
  # then yield :name and "must be specified"
end

# File activemodel/lib/active_model/errors.rb, line 225
def empty?
  size.zero?
end

Returns true if no errors are found, false otherwise. If the error message is a string it can be empty.

person.errors.full_messages # => ["name cannot be nil"]
person.errors.empty?        # => false

# File activemodel/lib/active_model/errors.rb, line 412
def full_message(attribute, message)
  return message if attribute == :base
  attribute = attribute.to_s

  if self.class.i18n_customize_full_message && @base.class.respond_to?(:i18n_scope)
    attribute = attribute.remove(/[d]/)
    parts = attribute.split(".")
    attribute_name = parts.pop
    namespace = parts.join("/") unless parts.empty?
    attributes_scope = "#{@base.class.i18n_scope}.errors.models"

    if namespace
      defaults = @base.class.lookup_ancestors.map do |klass|
        [
          :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.attributes.#{attribute_name}.format",
          :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.format",
        ]
      end
    else
      defaults = @base.class.lookup_ancestors.map do |klass|
        [
          :"#{attributes_scope}.#{klass.model_name.i18n_key}.attributes.#{attribute_name}.format",
          :"#{attributes_scope}.#{klass.model_name.i18n_key}.format",
        ]
      end
    end

    defaults.flatten!
  else
    defaults = []
  end

  defaults << :"errors.format"
  defaults << "%{attribute} %{message}"

  attr_name = attribute.tr(".", "_").humanize
  attr_name = @base.class.human_attribute_name(attribute, default: attr_name)

  I18n.t(defaults.shift,
    default:  defaults,
    attribute: attr_name,
    message:   message)
end

Returns a full message for a given attribute.

person.errors.full_message(:name, 'is invalid') # => "Name is invalid"

The `“%{attribute} %{message}”` error format can be overridden with either

  • activemodel.errors.models.person/contacts/addresses.attributes.street.format

  • activemodel.errors.models.person/contacts/addresses.format

  • activemodel.errors.models.person.attributes.name.format

  • activemodel.errors.models.person.format

  • errors.format

# File activemodel/lib/active_model/errors.rb, line 381
def full_messages
  map { |attribute, message| full_message(attribute, message) }
end

Returns all the full error messages in an array.

class Person
  validates_presence_of :name, :address, :email
  validates_length_of :name, in: 5..30
end

person = Person.create(address: '123 First St.')
person.errors.full_messages
# => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]

Also aliased as: to_a

# File activemodel/lib/active_model/errors.rb, line 396
def full_messages_for(attribute)
  attribute = attribute.to_sym
  messages[attribute].map { |message| full_message(attribute, message) }
end

Returns all the full error messages for a given attribute in an array.

class Person
  validates_presence_of :name, :email
  validates_length_of :name, in: 5..30
end

person = Person.create()
person.errors.full_messages_for(:name)
# => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]

generate_message(attribute, type = :invalid, options = {}) Show source

# File activemodel/lib/active_model/errors.rb, line 480
def generate_message(attribute, type = :invalid, options = {})
  type = options.delete(:message) if options[:message].is_a?(Symbol)
  value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)

  options = {
    model: @base.model_name.human,
    attribute: @base.class.human_attribute_name(attribute),
    value: value,
    object: @base
  }.merge!(options)

  if @base.class.respond_to?(:i18n_scope)
    i18n_scope = @base.class.i18n_scope.to_s
    defaults = @base.class.lookup_ancestors.flat_map do |klass|
      [ :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
        :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
    end
    defaults << :"#{i18n_scope}.errors.messages.#{type}"

    catch(:exception) do
      translation = I18n.translate(defaults.first, options.merge(default: defaults.drop(1), throw: true))
      return translation unless translation.nil?
    end unless options[:message]
  else
    defaults = []
  end

  defaults << :"errors.attributes.#{attribute}.#{type}"
  defaults << :"errors.messages.#{type}"

  key = defaults.shift
  defaults = options.delete(:message) if options[:message]
  options[:default] = defaults

  I18n.translate(key, options)
end

Translates an error message in its default scope (activemodel.errors.messages).

Error messages are first looked up in activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE, if it’s not there, it’s looked up in activemodel.errors.models.MODEL.MESSAGE and if that is not there also, it returns the translation of the default message (e.g. activemodel.errors.messages.MESSAGE). The translated model name, translated attribute name and the value are available for interpolation.

When using inheritance in your models, it will check all the inherited models too, but only if the model itself hasn’t been found. Say you have class Admin < User; end and you wanted the translation for the :blank error message for the title attribute, it looks for these translations:

  • activemodel.errors.models.admin.attributes.title.blank

  • activemodel.errors.models.admin.blank

  • activemodel.errors.models.user.attributes.title.blank

  • activemodel.errors.models.user.blank

  • any default you provided through the options hash (in the activemodel.errors scope)

  • activemodel.errors.messages.blank

  • errors.attributes.title.blank

  • errors.messages.blank

# File activemodel/lib/active_model/errors.rb, line 142
def include?(attribute)
  attribute = attribute.to_sym
  messages.key?(attribute) && messages[attribute].present?
end

Returns true if the error messages include an error for the given key attribute, false otherwise.

person.errors.messages        # => {:name=>["cannot be nil"]}
person.errors.include?(:name) # => true
person.errors.include?(:age)  # => false

# File activemodel/lib/active_model/errors.rb, line 214
def keys
  messages.select do |key, value|
    !value.empty?
  end.keys
end

Returns all message keys.

person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
person.errors.keys     # => [:name]

# File activemodel/lib/active_model/errors.rb, line 110
def merge!(other)
  @messages.merge!(other.messages) { |_, ary1, ary2| ary1 + ary2 }
  @details.merge!(other.details) { |_, ary1, ary2| ary1 + ary2 }
end

Merges the errors from other.

other — The ActiveModel::Errors instance.

Examples

person.errors.merge!(other)

of_kind?(attribute, message = :invalid) Show source

# File activemodel/lib/active_model/errors.rb, line 361
def of_kind?(attribute, message = :invalid)
  message = message.call if message.respond_to?(:call)

  if message.is_a? Symbol
    details[attribute.to_sym].map { |e| e[:error] }.include? message
  else
    self[attribute].include? message
  end
end

Returns true if an error on the attribute with the given message is present, or false otherwise. message is treated the same as for add.

person.errors.add :age
person.errors.add :name, :too_long, { count: 25 }
person.errors.of_kind? :age                                            # => true
person.errors.of_kind? :name                                           # => false
person.errors.of_kind? :name, :too_long                                # => true
person.errors.of_kind? :name, "is too long (maximum is 25 characters)" # => true
person.errors.of_kind? :name, :not_too_long                            # => false
person.errors.of_kind? :name, "is too long"                            # => false

# File activemodel/lib/active_model/errors.rb, line 195
def size
  values.flatten.size
end

Returns the number of error messages.

person.errors.add(:name, :blank, message: "can't be blank")
person.errors.size # => 1
person.errors.add(:name, :not_specified, message: "must be specified")
person.errors.size # => 2

Also aliased as: count

# File activemodel/lib/active_model/errors.rb, line 120
def slice!(*keys)
  keys = keys.map(&:to_sym)
  @details.slice!(*keys)
  @messages.slice!(*keys)
end

Removes all errors except the given keys. Returns a hash containing the removed errors.

person.errors.keys                  # => [:name, :age, :gender, :city]
person.errors.slice!(:age, :gender) # => { :name=>["cannot be nil"], :city=>["cannot be nil"] }
person.errors.keys                  # => [:age, :gender]

# File activemodel/lib/active_model/errors.rb, line 260
def to_hash(full_messages = false)
  if full_messages
    messages.each_with_object({}) do |(attribute, array), messages|
      messages[attribute] = array.map { |message| full_message(attribute, message) }
    end
  else
    without_default_proc(messages)
  end
end

Returns a Hash of attributes with their error messages. If full_messages is true, it will contain full messages (see full_message).

person.errors.to_hash       # => {:name=>["cannot be nil"]}
person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}

# File activemodel/lib/active_model/errors.rb, line 241
def to_xml(options = {})
  to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
end

Returns an xml formatted representation of the Errors hash.

person.errors.add(:name, :blank, message: "can't be blank")
person.errors.add(:name, :not_specified, message: "must be specified")
person.errors.to_xml
# =>
#  <?xml version="1.0" encoding="UTF-8"?>
#  <errors>
#    <error>name can't be blank</error>
#    <error>name must be specified</error>
#  </errors>

# File activemodel/lib/active_model/errors.rb, line 204
def values
  messages.select do |key, value|
    !value.empty?
  end.values
end

Returns all message values.

person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
person.errors.values   # => [["cannot be nil", "must be specified"]]

© 2004–2019 David Heinemeier Hansson
Licensed under the MIT License.

Overview

Provides error related functionalities you can include in your object for handling error messages and interacting with Action View helpers.

A minimal implementation could be:

class Person
  # Required dependency for ActiveModel::Errors
  extend ActiveModel::Naming

  def initialize
    @errors = ActiveModel::Errors.new(self)
  end

  attr_accessor :name
  attr_reader   :errors

  def validate!
    errors.add(:name, :blank, message: "cannot be nil") if name.nil?
  end

  # The following methods are needed to be minimally implemented

  def read_attribute_for_validation(attr)
    send(attr)
  end

  def self.human_attribute_name(attr, options = {})
    attr
  end

  def self.lookup_ancestors
    [self]
  end
end

The last three methods are required in your object for Errors to be able to generate error messages correctly and also handle multiple languages. Of course, if you extend your object with Translation you will not need to implement the last two. Likewise, using Validations will handle the validation related methods for you.

The above allows you to do:

person = Person.new
person.validate!            # => ["cannot be nil"]
person.errors.full_messages # => ["name cannot be nil"]
# etc..

Constant Summary

  • EMPTY_ARRAY =
    Internal use only

    # File ‘activemodel/lib/active_model/errors.rb’, line 240

    [].freeze

Class Method Summary


  • .new(base) ⇒ Errors

    constructor

    Pass in the instance of the object that is using the errors object.

Instance Attribute Summary


  • #errors
    (also: #objects)

    readonly

    The actual array of Error objects This method is aliased to #objects.


  • #objects

    readonly

::Enumerable — Included

#many?

Returns true if the enumerable has more than 1 element.

Instance Method Summary


  • #[](attribute)

    When passed a symbol or a name of a method, returns an array of errors for the method.


  • #add(attribute, type = :invalid, **options)

    Adds a new error of type on attribute.


  • #added?(attribute, type = :invalid, options = {}) ⇒ Boolean

    Returns true if an error matches provided attribute and type, or false otherwise.


  • #as_json(options = nil)

    Returns a ::Hash that can be used as the JSON representation for this object.


  • #attribute_names

    Returns all error attribute names.


  • #delete(attribute, type = nil, **options)


  • #details

    Returns a ::Hash of attributes with an array of their error details.


  • #each(&block)

    Iterates through each error object.


  • #full_message(attribute, message)

    Returns a full message for a given attribute.


  • #full_messages
    (also: #to_a)

    Returns all the full error messages in an array.


  • #full_messages_for(attribute)

    Returns all the full error messages for a given attribute in an array.


  • #generate_message(attribute, type = :invalid, options = {})

    Translates an error message in its default scope (activemodel.errors.messages).


  • #group_by_attribute

    Returns a ::Hash of attributes with an array of their Error objects.


  • #has_key?(attribute)


  • #import(error, override_options = {})


  • #include?(attribute) ⇒ Boolean
    (also: #has_key?, #key?)

    Returns true if the error messages include an error for the given key attribute, false otherwise.


  • #key?(attribute)


  • #merge!(other)


  • #messages

    Returns a ::Hash of attributes with an array of their error messages.


  • #messages_for(attribute)

    Returns all the error messages for a given attribute in an array.


  • #of_kind?(attribute, type = :invalid) ⇒ Boolean

    Returns true if an error on the attribute with the given type is present, or false otherwise.


  • #to_a

    Alias for #full_messages.


  • #to_hash(full_messages = false)

    Returns a ::Hash of attributes with their error messages.


  • #where(attribute, type = nil, **options)

    Search for errors matching attribute, type, or options.


  • #normalize_arguments(attribute, type, **options)

    private

  • #copy!(other)

    Internal use only

    Copies the errors from other.


  • #initialize_dup(other)

    Internal use only

  • #inspect

    Internal use only

::Enumerable — Included

#compact_blank

Returns a new ::Array without the blank items.

#exclude?

The negative of the Enumerable#include?.

#excluding

Returns a copy of the enumerable excluding the specified elements.

#in_order_of

Returns a new ::Array where the order has been set to that provided in the series, based on the key of the objects in the original enumerable.

#including

Returns a new array that includes the passed elements.

#index_by

Convert an enumerable to a hash, using the block result as the key and the element as the value.

#index_with

Convert an enumerable to a hash, using the element as the key and the block result as the value.

#maximum

Calculates the maximum from the extracted elements.

#minimum

Calculates the minimum from the extracted elements.

#pick

Extract the given key from the first element in the enumerable.

#pluck

Extract the given key from each element in the enumerable.

#sole

Returns the sole item in the enumerable.

#sum

Calculates a sum from the elements.

#without

Alias for Enumerable#excluding.

#as_json,
#_original_sum_with_required_identity

We can’t use Refinements here because Refinements with ::Module which will be prepended doesn’t work well bugs.ruby-lang.org/issues/13446.

::ActiveSupport::EnumerableCoreExt::Constants — Included

Constructor Details

.new(base) ⇒ Errors

Pass in the instance of the object that is using the errors object.

class Person
  def initialize
    @errors = ActiveModel::Errors.new(self)
  end
end

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 92
def initialize(base)
  @base = base
  @errors = []
end

Instance Attribute Details

#errors
Also known as: #objects

The actual array of Error objects This method is aliased to #objects.

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 82
attr_reader :errors

#objects

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 83
alias :objects :errors

Instance Method Details

#[](attribute)

When passed a symbol or a name of a method, returns an array of errors for the method.

person.errors[:name]  # => ["cannot be nil"]
person.errors['name'] # => ["cannot be nil"]

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 204
def [](attribute)
  messages_for(attribute)
end

#add(attribute, type = :invalid, **options)

Adds a new error of type on attribute. More than one error can be added to the same attribute. If no type is supplied, :invalid is assumed.

person.errors.add(:name)
# Adds <#ActiveModel::Error attribute=name, type=invalid>
person.errors.add(:name, :not_implemented, message: "must be implemented")
# Adds <#ActiveModel::Error attribute=name, type=not_implemented,
                            options={:message=>"must be implemented"}>

person.errors.messages
# => {:name=>["is invalid", "must be implemented"]}

If type is a string, it will be used as error message.

If type is a symbol, it will be translated using the appropriate scope (see #generate_message).

person.errors.add(:name, :blank)
person.errors.messages
# => {:name=>["can't be blank"]}

person.errors.add(:name, :too_long, { count: 25 })
person.errors.messages
# => ["is too long (maximum is 25 characters)"]

If type is a proc, it will be called, allowing for things like Time.now to be used within an error.

If the :strict option is set to true, it will raise StrictValidationFailed instead of adding the error. :strict option can also be set to any other exception.

person.errors.add(:name, :invalid, strict: true)
# => ActiveModel::StrictValidationFailed: Name is invalid
person.errors.add(:name, :invalid, strict: NameIsInvalid)
# => NameIsInvalid: Name is invalid

person.errors.messages # => {}

attribute should be set to :base if the error is not directly associated with a single attribute.

person.errors.add(:base, :name_or_email_blank,
  message: "either name or email must be present")
person.errors.messages
# => {:base=>["either name or email must be present"]}
person.errors.details
# => {:base=>[{error: :name_or_email_blank}]}

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 317
def add(attribute, type = :invalid, **options)
  attribute, type, options = normalize_arguments(attribute, type, **options)
  error = Error.new(@base, attribute, type, **options)

  if exception = options[:strict]
    exception = ActiveModel::StrictValidationFailed if exception == true
    raise exception, error.full_message
  end

  @errors.append(error)

  error
end

#added?(attribute, type = :invalid, options = {}) ⇒ Boolean

Returns true if an error matches provided attribute and type, or false otherwise. type is treated the same as for #add.

person.errors.add :name, :blank
person.errors.added? :name, :blank           # => true
person.errors.added? :name, "can't be blank" # => true

If the error requires options, then it returns true with the correct options, or false with incorrect or missing options.

person.errors.add :name, :too_long, { count: 25 }
person.errors.added? :name, :too_long, count: 25                     # => true
person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
person.errors.added? :name, :too_long, count: 24                     # => false
person.errors.added? :name, :too_long                                # => false
person.errors.added? :name, "is too long"                            # => false

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 347
def added?(attribute, type = :invalid, options = {})
  attribute, type, options = normalize_arguments(attribute, type, **options)

  if type.is_a? Symbol
    @errors.any? { |error|
      error.strict_match?(attribute, type, **options)
    }
  else
    messages_for(attribute).include?(type)
  end
end

#as_json(options = nil)

Returns a ::Hash that can be used as the JSON representation for this object. You can pass the :full_messages option. This determines if the json object should contain full messages or not (false by default).

person.errors.as_json                      # => {:name=>["cannot be nil"]}
person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]}

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 222
def as_json(options = nil)
  to_hash(options && options[:full_messages])
end

#attribute_names

Returns all error attribute names

person.errors.messages        # => {:name=>["cannot be nil", "must be specified"]}
person.errors.attribute_names # => [:name]

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 212
def attribute_names
  @errors.map(&:attribute).uniq.freeze
end

#copy!(other)

This method is for internal use only.

Copies the errors from other. For copying errors but keep @base as is.

Parameters

  • other — The Errors instance.

Examples

person.errors.copy!(other)

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 113
def copy!(other)   @errors = other.errors.deep_dup
  @errors.each { |error|
    error.instance_variable_set(:@base, @base)
  }
end

#delete(attribute, type = nil, **options)

Delete messages for key. Returns the deleted messages.

person.errors[:name]        # => ["cannot be nil"]
person.errors.delete(:name) # => ["cannot be nil"]
person.errors[:name]        # => []

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 190
def delete(attribute, type = nil, **options)
  attribute, type, options = normalize_arguments(attribute, type, **options)
  matches = where(attribute, type, **options)
  matches.each do |error|
    @errors.delete(error)
  end
  matches.map(&:message).presence
end

#details

Returns a ::Hash of attributes with an array of their error details.

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 251
def details
  hash = group_by_attribute.transform_values do |errors|
    errors.map(&:details)
  end
  hash.default = EMPTY_ARRAY
  hash.freeze
  hash
end

#each(&block)

Iterates through each error object.

person.errors.add(:name, :too_short, count: 2)
person.errors.each do |error|
  # Will yield <#ActiveModel::Error attribute=name, type=too_short,
                                    options={:count=>3}>
end

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 78
rdoc_method :method: each

#full_message(attribute, message)

Returns a full message for a given attribute.

person.errors.full_message(:name, 'is invalid') # => "Name is invalid"

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 426
def full_message(attribute, message)
  Error.full_message(attribute, message, @base)
end

#full_messages
Also known as: #to_a

Returns all the full error messages in an array.

class Person
  validates_presence_of :name, :address, :email
  validates_length_of :name, in: 5..30
end

person = Person.create(address: '123 First St.')
person.errors.full_messages
# => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 390
def full_messages
  @errors.map(&:full_message)
end

#full_messages_for(attribute)

Returns all the full error messages for a given attribute in an array.

class Person
  validates_presence_of :name, :email
  validates_length_of :name, in: 5..30
end

person = Person.create()
person.errors.full_messages_for(:name)
# => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 405
def full_messages_for(attribute)
  where(attribute).map(&:full_message).freeze
end

#generate_message(attribute, type = :invalid, options = {})

Translates an error message in its default scope (activemodel.errors.messages).

Error messages are first looked up in activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE, if it’s not there, it’s looked up in activemodel.errors.models.MODEL.MESSAGE and if that is not there also, it returns the translation of the default message (e.g. activemodel.errors.messages.MESSAGE). The translated model name, translated attribute name, and the value are available for interpolation.

When using inheritance in your models, it will check all the inherited models too, but only if the model itself hasn’t been found. Say you have class Admin < User; end and you wanted the translation for the :blank error message for the title attribute, it looks for these translations:

  • activemodel.errors.models.admin.attributes.title.blank

  • activemodel.errors.models.admin.blank

  • activemodel.errors.models.user.attributes.title.blank

  • activemodel.errors.models.user.blank

  • any default you provided through the options hash (in the activemodel.errors scope)

  • activemodel.errors.messages.blank

  • errors.attributes.title.blank

  • errors.messages.blank

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 454
def generate_message(attribute, type = :invalid, options = {})
  Error.generate_message(attribute, type, @base, options)
end

#group_by_attribute

Returns a ::Hash of attributes with an array of their Error objects.

person.errors.group_by_attribute
# => {:name=>[<#ActiveModel::Error>, <#ActiveModel::Error>]}

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 264
def group_by_attribute
  @errors.group_by(&:attribute)
end

#has_key?(attribute)

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 182
alias :has_key? :include?

#import(error, override_options = {})

Imports one error. Imported errors are wrapped as a NestedError, providing access to original error object. If attribute or type needs to be overridden, use override_options.

Options

  • :attribute — Override the attribute the error belongs to.

  • :type — Override type of the error.

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 129
def import(error, override_options = {})
  [:attribute, :type].each do |key|
    if override_options.key?(key)
      override_options[key] = override_options[key].to_sym
    end
  end
  @errors.append(NestedError.new(@base, error, override_options))
end

#include?(attribute) ⇒ Boolean
Also known as: #has_key?, #key?

Returns true if the error messages include an error for the given key attribute, false otherwise.

person.errors.messages        # => {:name=>["cannot be nil"]}
person.errors.include?(:name) # => true
person.errors.include?(:age)  # => false

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 177
def include?(attribute)
  @errors.any? { |error|
    error.match?(attribute.to_sym)
  }
end

#initialize_dup(other)

This method is for internal use only.

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 97
def initialize_dup(other)   @errors = other.errors.deep_dup
  super
end

#inspect

This method is for internal use only.

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 458
def inspect   inspection = @errors.inspect

  "#<#{self.class.name} #{inspection}>"
end

#key?(attribute)

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 183
alias :key? :include?

#merge!(other)

Merges the errors from other, each Error wrapped as NestedError.

Parameters

  • other — The Errors instance.

Examples

person.errors.merge!(other)

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 149
def merge!(other)
  return errors if equal?(other)

  other.errors.each { |error|
    import(error)
  }
end

#messages

Returns a ::Hash of attributes with an array of their error messages.

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 243
def messages
  hash = to_hash
  hash.default = EMPTY_ARRAY
  hash.freeze
  hash
end

#messages_for(attribute)

Returns all the error messages for a given attribute in an array.

class Person
  validates_presence_of :name, :email
  validates_length_of :name, in: 5..30
end

person = Person.create()
person.errors.messages_for(:name)
# => ["is too short (minimum is 5 characters)", "can't be blank"]

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 419
def messages_for(attribute)
  where(attribute).map(&:message)
end

#normalize_arguments(attribute, type, **options)

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 465
def normalize_arguments(attribute, type, **options)
    if type.respond_to?(:call)
    type = type.call(@base, options)
  end

  [attribute.to_sym, type, options]
end

#of_kind?(attribute, type = :invalid) ⇒ Boolean

Returns true if an error on the attribute with the given type is present, or false otherwise. type is treated the same as for #add.

person.errors.add :age
person.errors.add :name, :too_long, { count: 25 }
person.errors.of_kind? :age                                            # => true
person.errors.of_kind? :name                                           # => false
person.errors.of_kind? :name, :too_long                                # => true
person.errors.of_kind? :name, "is too long (maximum is 25 characters)" # => true
person.errors.of_kind? :name, :not_too_long                            # => false
person.errors.of_kind? :name, "is too long"                            # => false

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 370
def of_kind?(attribute, type = :invalid)
  attribute, type = normalize_arguments(attribute, type)

  if type.is_a? Symbol
    !where(attribute, type).empty?
  else
    messages_for(attribute).include?(type)
  end
end

#to_a

Alias for #full_messages.

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 393
alias :to_a :full_messages

#to_hash(full_messages = false)

Returns a ::Hash of attributes with their error messages. If #full_messages is true, it will contain full messages (see #full_message).

person.errors.to_hash       # => {:name=>["cannot be nil"]}
person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 231
def to_hash(full_messages = false)
  message_method = full_messages ? :full_message : :message
  group_by_attribute.transform_values do |errors|
    errors.map(&message_method)
  end
end

#where(attribute, type = nil, **options)

Search for errors matching attribute, type, or options.

Only supplied params will be matched.

person.errors.where(:name) # => all name errors.
person.errors.where(:name, :too_short) # => all name errors being too short
person.errors.where(:name, :too_short, minimum: 2) # => all name errors being too short and minimum is 2

[ GitHub ]


# File 'activemodel/lib/active_model/errors.rb', line 164
def where(attribute, type = nil, **options)
  attribute, type, options = normalize_arguments(attribute, type, **options)
  @errors.select { |error|
    error.match?(attribute, type, **options)
  }
end

Это руководство научит, как осуществлять валидацию состояния объектов до того, как они будут направлены в базу данных, используя особенность валидаций Active Record.

После прочтения руководства вы узнаете:

  • Как использовать встроенные хелперы валидации Active Record
  • Как создавать свои собственные методы валидации
  • Как работать с сообщениями об ошибках, генерируемыми в процессе валидации

Вот пример очень простой валидации:

class Person < ApplicationRecord
  validates :name, presence: true
end
irb> Person.create(name: "John Doe").valid?
=> true
irb> Person.create(name: nil).valid?
=> false

Как видите, наша валидация позволяет узнать, что наш Person не валиден без атрибута name. Второй Person не будет персистентным в базе данных.

Прежде чем погрузиться в подробности, давайте поговорим о том, как валидации вписываются в общую картину приложения.

Валидации используются, чтобы быть уверенными, что только валидные данные сохраняются в вашу базу данных. Например, для вашего приложения может быть важно, что каждый пользователь предоставил валидный электронный и почтовый адреса. Валидации на уровне модели — наилучший способ убедиться, что в базу данных будут сохранены только валидные данные. Они не зависят от базы данных, не могут быть обойдены конечными пользователями и удобны в тестировании и обслуживании. Rails предоставляет встроенные хелперы для общих нужд, а также позволяет создавать свои собственные методы валидации.

Есть несколько способов валидации данных, прежде чем они будут сохранены в вашу базу данных, включая ограничения, встроенные в базу данных, валидации на клиентской части и валидации на уровне контроллера. Вкратце о плюсах и минусах:

  • Ограничения базы данных и/или хранимые процедуры делают механизмы валидации зависимыми от базы данных, что делает тестирование и поддержку более трудными. Однако, если ваша база данных используется другими приложениями, валидация на уровне базы данных может безопасно обрабатывать некоторые вещи (такие как уникальность в нагруженных таблицах), которые затруднительно выполнять по-другому.
  • Валидации на клиентской части могут быть очень полезны, но в целом ненадежны, если используются в одиночку. Если они используют JavaScript, они могут быть пропущены, если JavaScript отключен в клиентском браузере. Однако, если этот способ комбинировать с другими, валидации на клиентской части могут быть удобным способом предоставить пользователям немедленную обратную связь при использовании вашего сайта.
  • Валидации на уровне контроллера заманчиво делать, но это часто приводит к громоздкости и трудности тестирования и поддержки. Во всех случаях, когда это возможно, держите свои контроллеры ‘тощими’, тогда с вашим приложением будет приятно работать в долгосрочной перспективе.

Выбирайте их под свои определенные специфичные задачи. Общее мнение команды Rails состоит в том, что валидации на уровне модели — наиболее подходящий вариант во многих случаях.

Есть два типа объектов Active Record: те, которые соответствуют строке в вашей базе данных, и те, которые нет. Когда создаете новый объект, например, используя метод new, этот объект еще не привязан к базе данных. Как только вы вызовете save, этот объект будет сохранен в подходящую таблицу базы данных. Active Record использует метод экземпляра new_record? для определения, есть ли уже объект в базе данных или нет. Рассмотрим следующий класс Active Record:

class Person < ApplicationRecord
end

Можно увидеть, как он работает, взглянув на результат bin/rails console:

irb> p = Person.new(name: "John Doe")
=> #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil>

irb> p.new_record?
=> true

irb> p.save
=> true

irb> p.new_record?
=> false

Создание и сохранение новой записи посылает операцию SQL INSERT базе данных. Обновление существующей записи вместо этого посылает операцию SQL UPDATE. Валидации обычно запускаются до того, как эти команды посылаются базе данных. Если любая из валидаций проваливается, объект помечается как недействительный и Active Record не выполняет операцию INSERT или UPDATE. Это помогает избежать хранения невалидного объекта в базе данных. Можно выбирать запуск специфичных валидаций, когда объект создается, сохраняется или обновляется.

Есть разные методы изменения состояния объекта в базе данных. Некоторые методы вызывают валидации, некоторые нет. Это означает, что возможно сохранить в базу данных объект с недействительным статусом, если вы будете не внимательны.

Следующие методы вызывают валидацию, и сохраняют объект в базу данных только если он валиден:

  • create
  • create!
  • save
  • save!
  • update
  • update!

Версии с восклицательным знаком (т.е. save!) вызывают исключение, если запись недействительна. Невосклицательные версии не вызывают: save и update возвращают false, create возвращает объект.

Следующие методы пропускают валидации, и сохраняют объект в базу данных, независимо от его валидности. Их нужно использовать осторожно.

  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • insert
  • insert!
  • insert_all
  • insert_all!
  • toggle!
  • touch
  • touch_all
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters
  • upsert
  • upsert_all

Заметьте, что save также имеет способность пропустить валидации, если передать validate: false как аргумент. Этот способ нужно использовать осторожно.

  • save(validate: false)

Перед сохранением объекта Active Record, Rails запускает ваши валидации. Если валидации производят какие-либо ошибки, Rails не сохраняет этот объект.

Вы также можете запускать эти валидации самостоятельно. valid? вызывает ваши валидации и возвращает true, если ни одной ошибки не было найдено у объекта, иначе false.

class Person < ApplicationRecord
  validates :name, presence: true
end
irb> Person.create(name: "John Doe").valid?
=> true
irb> Person.create(name: nil).valid?
=> false

После того, как Active Record выполнит валидации, все найденные ошибки будут доступны в методе экземпляра errors, возвращающем коллекцию ошибок. По определению объект валиден, если эта коллекция будет пуста после запуска валидаций.

Заметьте, что объект, созданный с помощью new не сообщает об ошибках, даже если технически невалиден, поскольку валидации автоматически запускаются только когда сохраняется объект, как в случае с методами create или save.

class Person < ApplicationRecord
  validates :name, presence: true
end
irb> p = Person.new
=> #<Person id: nil, name: nil>
irb> p.errors.size
=> 0

irb> p.valid?
=> false
irb> p.errors.objects.first.full_message
=> "Name can't be blank"

irb> p = Person.create
=> #<Person id: nil, name: nil>
irb> p.errors.objects.first.full_message
=> "Name can't be blank"

irb> p.save
=> false

irb> p.save!
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank

irb> Person.create!
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank

invalid? это антипод valid?. Он запускает ваши валидации, возвращая true, если для объекта были добавлены ошибки, и false в противном случае.

Чтобы проверить, является или нет конкретный атрибут объекта валидным, можно использовать errors[:attribute], который возвращает массив со всеми сообщениями об ошибке атрибута, когда нет ошибок по определенному атрибуту, возвращается пустой массив.

Этот метод полезен только после того, как валидации были запущены, так как он всего лишь исследует коллекцию errors, но сам не вызывает валидации. Он отличается от метода ActiveRecord::Base#invalid?, описанного выше, тем, что не проверяет валидность объекта в целом. Он всего лишь проверяет, какие ошибки были найдены для отдельного атрибута объекта.

class Person < ApplicationRecord
  validates :name, presence: true
end
irb> Person.new.errors[:name].any?
=> false
irb> Person.create.errors[:name].any?
=> true

Мы рассмотрим ошибки валидации подробнее в разделе Работаем с ошибками валидации.

Active Record предлагает множество предопределенных валидационных хелперов, которые вы можете использовать прямо внутри ваших определений класса. Эти хелперы предоставляют общие правила валидации. Каждый раз, когда валидация проваливается, ошибка добавляется в коллекцию errors объекта и связывается с атрибутом, который подлежал валидации.

Каждый хелпер принимает произвольное количество имен атрибутов, поэтому в одной строчке кода можно добавить валидации одинакового вида для нескольких атрибутов.

Они все принимают опции :on и :message, которые определяют, когда валидация должна быть запущена, и какое сообщение должно быть добавлено в коллекцию errors, если она провалится. Опция :on принимает одно из значений :create или :update. Для каждого валидационного хелпера есть свое сообщение об ошибке по умолчанию. Эти сообщения используются, если не определена опция :message. Давайте рассмотрим каждый из доступных хелперов.

Этот метод проверяет, что чекбокс в пользовательском интерфейсе был нажат, когда форма была подтверждена. Обычно используется, когда пользователю нужно согласиться с условиями использования вашего приложения, подтвердить, что некоторый текст прочтен, или другой подобной концепции.

class Person < ApplicationRecord
  validates :terms_of_service, acceptance: true
end

Эта проверка выполнится, только если terms_of_service не nil. Для этого хелпера сообщение об ошибке по умолчанию следующее «must be accepted». Можно передать произвольное сообщение с помощью опции message.

class Person < ApplicationRecord
  validates :terms_of_service, acceptance: { message: 'must be abided' }
end

Также он может получать опцию :accept, которая определяет допустимые значения, которые будут считаться принятыми. По умолчанию это «1», но его можно изменить.

class Person < ApplicationRecord
  validates :eula, acceptance: { accept: ['TRUE', 'accepted'] }
end

Эта валидация очень специфична для веб-приложений, и ее принятие не нужно записывать куда-либо в базу данных. Если у вас нет поля для него, хелпер создаст виртуальный атрибут. Если поле существует в базе данных, опция accept должна быть установлена или включать true, а иначе эта валидация не будет выполнена.

Этот хелпер можно использовать, если у вас есть два текстовых поля, из которых нужно получить полностью идентичное содержание. Например, вы хотите подтверждение адреса электронной почты или пароля. Эта валидация создает виртуальный атрибут, имя которого равно имени подтверждаемого поля с добавлением «_confirmation».

class Person < ApplicationRecord
  validates :email, confirmation: true
end

В вашем шаблоне вью нужно использовать что-то вроде этого

<%= text_field :person, :email %>
<%= text_field :person, :email_confirmation %>

Эта проверка выполняется, только если email_confirmation не равно nil. Чтобы требовать подтверждение, нужно добавить еще проверку на существование проверяемого атрибута (мы рассмотрим presence чуть позже):

class Person < ApplicationRecord
  validates :email, confirmation: true
  validates :email_confirmation, presence: true
end

Также имеется опция :case_sensitive, которую используют, чтобы определить, должно ли ограничение подтверждения быть чувствительным к регистру. Эта опция по умолчанию true.

class Person < ApplicationRecord
  validates :email, confirmation: { case_sensitive: false }
end

По умолчанию сообщение об ошибке для этого хелпера такое «doesn’t match confirmation».

Эта проверка проводит валидацию сравнения между любыми двумя сравниваемыми значениями. Этот валидатор требует предоставления опции сравнения. Каждая опция принимает значение, proc или символ. Любой класс, включающий Comparable, может быть сравнен.

class Promotion < ApplicationRecord
  validates :end_date, comparison: { greater_than: :start_date }
end

Все эти опции поддерживаются:

  • :greater_than — Указывает что значение должно быть больше, чем предоставленное значение. Сообщение об ошибке для этой опции такое «must be greater than %{count}».
  • :greater_than_or_equal_to — Указывает что значение должно быть больше или равно предоставленному значению. Сообщение об ошибке для этой опции такое «must be greater than or equal to %{count}».
  • :equal_to — Указывает что значение должно быть равно предоставленному значению. Сообщение об ошибке для этой опции такое «must be equal to %{count}».
  • :less_than — Указывает что значение должно быть меньше, чем предоставленное значение. Сообщение об ошибке для этой опции такое «must be less than %{count}».
  • :less_than_or_equal_to — Указывает что значение должно быть меньше или равно предоставленному значению. Сообщение об ошибке для этой опции такое «must be less than or equal to %{count}».
  • :other_than — Указывает что значение должно быть иным, чем предоставленное значение. Сообщение об ошибке для этой опции такое «must be other than %{count}».

Этот хелпер проводит валидацию того, что значения атрибутов не включены в указанный набор. Фактически, этот набор может быть любым перечисляемым объектом.

class Account < ApplicationRecord
  validates :subdomain, exclusion: { in: %w(www us ca jp),
    message: "%{value} is reserved." }
end

Хелпер exclusion имеет опцию :in, которая получает набор значений, которые не должны приниматься проверяемыми атрибутами. Опция :in имеет псевдоним :within, который используется для тех же целей. Этот пример использует опцию :message, чтобы показать вам, как можно включать значение атрибута. Для того, чтобы увидеть все опции аргументов сообщения смотрите документацию по message.

Значение сообщения об ошибке по умолчанию «is reserved«.

Этот хелпер проводит валидацию значений атрибутов, тестируя их на соответствие указанному регулярному выражению, которое определяется с помощью опции :with.

class Product < ApplicationRecord
  validates :legacy_code, format: { with: /A[a-zA-Z]+z/,
    message: "only allows letters" }
end

В качестве альтернативы можно потребовать, чтобы указанный атрибут не соответствовал регулярному выражению, используя опцию :without.

Значение сообщения об ошибке по умолчанию «is invalid«.

Этот хелпер проводит валидацию значений атрибутов на включение в указанный набор. Фактически этот набор может быть любым перечисляемым объектом.

class Coffee < ApplicationRecord
  validates :size, inclusion: { in: %w(small medium large),
    message: "%{value} is not a valid size" }
end

Хелпер inclusion имеет опцию :in, которая получает набор значений, которые должны быть приняты. Опция :in имеет псевдоним :within, который используется для тех же целей. Предыдущий пример использует опцию :message, чтобы показать вам, как можно включать значение атрибута. Для того, чтобы увидеть все опции смотрите документацию по message.

Значение сообщения об ошибке по умолчанию для этого хелпера такое «is not included in the list«.

Этот хелпер проводит валидацию длины значений атрибутов. Он предлагает ряд опций, с помощью которых вы можете определить ограничения по длине разными способами:

class Person < ApplicationRecord
  validates :name, length: { minimum: 2 }
  validates :bio, length: { maximum: 500 }
  validates :password, length: { in: 6..20 }
  validates :registration_number, length: { is: 6 }
end

Возможные опции ограничения длины такие:

  • :minimum — атрибут не может быть меньше определенной длины.
  • :maximum — атрибут не может быть больше определенной длины.
  • :in (или :within) — длина атрибута должна находиться в указанном интервале. Значение этой опции должно быть интервалом.
  • :is — длина атрибута должна быть равной указанному значению.

Значение сообщения об ошибке по умолчанию зависит от типа выполняемой валидации длины. Можно настроить эти сообщения, используя опции :wrong_length, :too_long и :too_short, и %{count} как местозаполнитель (placeholder) числа, соответствующего длине используемого ограничения. Можете использовать опцию :message для определения сообщения об ошибке.

class Person < ApplicationRecord
  validates :bio, length: { maximum: 1000,
    too_long: "%{count} characters is the maximum allowed" }
end

Отметьте, что сообщения об ошибке по умолчанию во множественном числе (т.е., «is too short (minimum is %{count} characters)»). По этой причине, когда :minimum равно 1, следует предоставить собственное сообщение или использовать вместо него presence: true. Когда :in или :within имеют как нижнюю границу 1, следует или предоставить собственное сообщение, или вызвать presence перед length.

Этот хелпер проводит валидацию того, что ваши атрибуты имеют только числовые значения. По умолчанию, этому будет соответствовать возможный знак первым символом, и следующее за ним целочисленное или с плавающей запятой число.

Чтобы определить, что допустимы только целочисленные значения, установите :only_integer в true. Тогда будет использоваться регулярное выражение

для проведения валидации значения атрибута. В противном случае, он будет пытаться конвертировать значение в число, используя Float. Float приводятся к BigDecimal с использованием значения precision 15.

class Player < ApplicationRecord
  validates :points, numericality: true
  validates :games_played, numericality: { only_integer: true }
end

Для :only_integer значение сообщения об ошибке по умолчанию «must be an integer».

Кроме :only_integer, хелпер validates_numericality_of также принимает следующие опции для добавления ограничений к приемлемым значениям:

  • :greater_than — Указывает, что значение должно быть больше, чем значение опции. По умолчанию сообщение об ошибке для этой опции такое «must be greater than %{count}».
  • :greater_than_or_equal_to — Указывает, что значение должно быть больше или равно значению опции. По умолчанию сообщение об ошибке для этой опции такое «must be greater than or equal to %{count}».
  • :equal_to — Указывает, что значение должно быть равно значению опции. По умолчанию сообщение об ошибке для этой опции такое «must be equal to %{count}».
  • :less_than — Указывает, что значение должно быть меньше, чем значение опции. По умолчанию сообщение об ошибке для этой опции такое «must be less than %{count}».
  • :less_than_or_equal_to — Указывает, что значение должно быть меньше или равно значению опции. По умолчанию сообщение об ошибке для этой опции такое «must be less than or equal to %{count}».
  • :other_than — Указывает, что значение должно отличаться от представленного значения. По умолчанию сообщение об ошибке для этой опции такое «must be other than %{count}».
  • :in — Указывает, что значение должно быть в предоставленном диапазоне. По умолчанию сообщение об ошибке для этой опции такое «must be in %{count}».
  • :odd — Указывает, что значение должно быть нечетным, если установлено true. По умолчанию сообщение об ошибке для этой опции такое «must be odd».
  • :even — Указывает, что значение должно быть четным, если установлено true. По умолчанию сообщение об ошибке для этой опции такое «must be even».

По умолчанию numericality не допускает значения nil. Чтобы их разрешить, можно использовать опцию allow_nil: true.

По умолчанию сообщение об ошибке «is not a number».

Этот хелпер проводит валидацию того, что определенные атрибуты не пустые. Он использует метод blank? для проверки того, является ли значение или nil, или пустой строкой (это строка, которая или пуста, или состоит из пробелов).

class Person < ApplicationRecord
  validates :name, :login, :email, presence: true
end

Если хотите быть уверенным, что связь существует, нужно проверить, существует ли сам связанный объект, а не внешний ключ, используемый для связи.

class Supplier < ApplicationRecord
  has_one :account
  validates :account, presence: true
end

Для того, чтобы проверять связанные записи, чье присутствие необходимо, нужно определить опцию :inverse_of для связи:

Если хотите убедиться, что связь и существует, и валидна, нужно также использовать validates_associated.

class Order < ApplicationRecord
  has_many :line_items, inverse_of: :order
end

При проведении валидации существования объекта, связанного отношением has_one или has_many, будет проверено, что объект ни blank?, ни marked_for_destruction?.

Так как false.blank? это true, если хотите провести валидацию существования булева поля, нужно использовать одну из следующих валидаций:

validates :boolean_field_name, inclusion: [true, false]
validates :boolean_field_name, exclusion: [nil]

При использовании одной из этих валидаций, вы можете быть уверены, что значение не будет nil, которое в большинстве случаев преобразуется в NULL значение.

Этот хелпер проверяет, что указанные атрибуты отсутствуют. Он использует метод present? для проверки, что значение является либо nil, либо пустой строкой (то есть либо нулевой длины, либо состоящей из пробелов).

class Person < ApplicationRecord
  validates :name, :login, :email, absence: true
end

Если хотите убедиться, что отсутствует связь, необходимо проверить, что отсутствует сам связанный объект, а не внешний ключ, используемый для связи.

class LineItem < ApplicationRecord
  belongs_to :order
  validates :order, absence: true
end

Чтобы проверять связанные объекты, отсутствие которых требуется, для связи необходимо указать опцию :inverse_of:

class Order < ApplicationRecord
  has_many :line_items, inverse_of: :order
end

Если проверяете отсутствие объекта, связанного отношением has_one или has_many, он проверит, что объект и не present?, и не marked_for_destruction?.

Поскольку false.present? является false, если хотите проверить отсутствие булева поля, следует использовать validates :field_name, exclusion: { in: [true, false] }.

По умолчанию сообщение об ошибке «must be blank».

Этот хелпер проводит валидацию того, что значение атрибута уникально, перед тем, как объект будет сохранен. Он не создает условие уникальности в базе данных, следовательно, может произойти так, что два разных подключения к базе данных создадут две записи с одинаковым значением для столбца, который вы подразумеваете уникальным. Чтобы этого избежать, нужно создать индекс unique на оба столбцах в вашей базе данных.

class Account < ApplicationRecord
  validates :email, uniqueness: true
end

Валидация производится путем SQL-запроса в таблицу модели, поиска существующей записи с тем же значением атрибута.

Имеется опция :scope, которую можно использовать для определения одного и более атрибутов, используемых для ограничения проверки уникальности:

class Holiday < ApplicationRecord
  validates :name, uniqueness: { scope: :year,
    message: "should happen once per year" }
end

Если хотите создать ограничение на уровне базы данных, чтобы предотвратить возможные нарушения валидации уникальности с помощью опции :scope, необходимо создать индекс уникальности на обоих столбцах базы данных. Подробнее об индексах для нескольких столбцов смотрите в мануале MySQL, или примеры ограничений уникальности, относящихся к группе столбцов в мануале PostgreSQL.

Также имеется опция :case_sensitive, которой можно определить, будет ли ограничение уникальности чувствительно к регистру, не чувствительным к регистру или соответствовать сортировке базы данных по умолчанию. Опцией по умолчанию является соответствие сортировке базы данных по умолчанию.

class Person < ApplicationRecord
  validates :name, uniqueness: { case_sensitive: false }
end

Отметьте, что некоторые базы данных настроены на выполнение чувствительного к регистру поиска в любом случае.

По умолчанию сообщение об ошибке «has already been taken».

Этот хелпер можно использовать, когда у вашей модели есть связи, которые всегда нужно проверять на валидность. Каждый раз, когда вы пытаетесь сохранить свой объект, будет вызван метод valid? для каждого из связанных объектов.

class Library < ApplicationRecord
  has_many :books
  validates_associated :books
end

Эта валидация работает со всеми типами связей.

Не используйте validates_associated на обоих концах ваших связей, они будут вызывать друг друга в бесконечном цикле.

Для validates_associated сообщение об ошибке по умолчанию следующее «is invalid». Заметьте, что каждый связанный объект имеет свою собственную коллекцию errors; ошибки не добавляются к вызывающей модели.

Этот хелпер передает запись в отдельный класс для валидации.

class GoodnessValidator < ActiveModel::Validator
  def validate(record)
    if record.first_name == "Evil"
      record.errors.add :base, "This person is evil"
    end
  end
end

class Person < ApplicationRecord
  validates_with GoodnessValidator
end

Ошибки, добавляемые в record.errors[:base] относятся к состоянию записи в целом, а не к определенному атрибуту.

Хелпер validates_with принимает класс или список классов для использования в валидации. Для validates_with нет сообщения об ошибке по умолчанию. Следует вручную добавлять ошибки в коллекцию errors записи в классе валидатора.

Для применения метода validate, необходимо иметь определенным параметр record, который является записью, проходящей валидацию.

Подобно всем другим валидациям, validates_with принимает опции :if, :unless и :on. Если передадите любые другие опции, они будут переданы в класс валидатора как options:

class GoodnessValidator < ActiveModel::Validator
  def validate(record)
    if options[:fields].any? { |field| record.send(field) == "Evil" }
      record.errors.add :base, "This person is evil"
    end
  end
end

class Person < ApplicationRecord
  validates_with GoodnessValidator, fields: [:first_name, :last_name]
end

Отметьте, что валидатор будет инициализирован только один раз на протяжении всего жизненного цикла приложения, а не при каждом запуске валидации, поэтому будьте аккуратнее с использованием переменных экземпляра в нем.

Если ваш валидатор настолько сложный, что вы хотите использовать переменные экземпляра, вместо него проще использовать обычные объекты Ruby:

class Person < ApplicationRecord
  validate do |person|
    GoodnessValidator.new(person).validate
  end
end

class GoodnessValidator
  def initialize(person)
    @person = person
  end

  def validate
    if some_complex_condition_involving_ivars_and_private_methods?
      @person.errors.add :base, "This person is evil"
    end
  end

  # ...
end

Этот хелпер помогает провести валидацию атрибутов с помощью блока кода. Он не имеет предопределенной валидационной функции. Вы должны создать ее, используя блок, и каждый атрибут, указанный в validates_each, будет протестирован в нем. В следующем примере нам не нужны имена и фамилии, начинающиеся с маленькой буквы.

class Person < ApplicationRecord
  validates_each :name, :surname do |record, attr, value|
    record.errors.add(attr, 'must start with upper case') if value =~ /A[[:lower:]]/
  end
end

Блок получает запись, имя атрибута и значение атрибута. Вы можете делать что угодно для проверки валидности данных внутри блока. Если валидация проваливается, следует добавить ошибку в модель, которое делает ее невалидной.

Есть несколько общих опций валидаций:

Опция :allow_nil пропускает валидацию, когда проверяемое значение равно nil.

class Coffee < ApplicationRecord
  validates :size, inclusion: { in: %w(small medium large),
    message: "%{value} is not a valid size" }, allow_nil: true
end

Для того, чтобы увидеть все опции аргументов сообщения смотрите документацию по message.

Опция :allow_blank подобна опции :allow_nil. Эта опция пропускает валидацию, если значение атрибута blank?, например nil или пустая строка.

class Topic < ApplicationRecord
  validates :title, length: { is: 5 }, allow_blank: true
end
irb> Topic.create(title: "").valid?
=> true
irb> Topic.create(title: nil).valid?
=> true

Как мы уже видели, опция :message позволяет определить сообщение, которое будет добавлено в коллекцию errors, когда валидация проваливается. Если эта опция не используется, Active Record будет использовать соответствующие сообщения об ошибках по умолчанию для каждого валидационного хелпера. Опция :message принимает String или Proc.

Значение String в :message может опционально содержать любые из %{value}, %{attribute} и %{model}, которые будут динамически заменены, когда валидация провалится. Эта замена выполняется, если используется гем I18n, и местозаполнитель должен полностью совпадать, пробелы не допускаются.

Значение Proc в :message задается с двумя аргументами: проверяемым объектом и хэшем с ключами :model, :attribute и :value.

class Person < ApplicationRecord
  # Жестко закодированное сообщение
  validates :name, presence: { message: "must be given please" }

  # Сообщение со значением с динамическим атрибутом. %{value} будет заменено
  # фактическим значением атрибута. Также доступны %{attribute} и %{model}.
  validates :age, numericality: { message: "%{value} seems wrong" }

  # Proc
  validates :username,
    uniqueness: {
      # object = person object being validated
      # data = { model: "Person", attribute: "Username", value: <username> }
      message: ->(object, data) do
        "Hey #{object.name}, #{data[:value]} is already taken."
      end
    }
end

Опция :on позволяет определить, когда должна произойти валидация. Стандартное поведение для всех встроенных валидационных хелперов это запускаться при сохранении (и когда создается новая запись, и когда она обновляется). Если хотите изменить это, используйте on: :create, для запуска валидации только когда создается новая запись, или on: :update, для запуска валидации когда запись обновляется.

class Person < ApplicationRecord
  # будет возможно обновить email с дублирующим значением
  validates :email, uniqueness: true, on: :create

  # будет возможно создать запись с нечисловым возрастом
  validates :age, numericality: true, on: :update

  # по умолчанию (проверяет и при создании, и при обновлении)
  validates :name, presence: true
end

on: также можно использовать для определения пользовательского контекста. Пользовательские контексты должны быть явно включены с помощью передачи имени контекста в valid?, invalid? или save.

class Person < ApplicationRecord
  validates :email, uniqueness: true, on: :account_setup
  validates :age, numericality: true, on: :account_setup
end
irb> person = Person.new(age: 'thirty-three')
irb> person.valid?
=> true
irb> person.valid?(:account_setup)
=> false
irb> person.errors.messages
=> {:email=>["has already been taken"], :age=>["is not a number"]}

person.valid?(:account_setup) выполнит обе валидации без сохранения модели. person.save(context: :account_setup) перед сохранением валидирует person в контексте account_setup.

При вызове с явным контекстом, будут запущены валидации не только этого контекста, но и валидации без контекста.

class Person < ApplicationRecord
  validates :email, uniqueness: true, on: :account_setup
  validates :age, numericality: true, on: :account_setup
  validates :name, presence: true
end
irb> person = Person.new
irb> person.valid?(:account_setup)
=> false
irb> person.errors.messages
=> {:email=>["has already been taken"], :age=>["is not a number"], :name=>["can't be blank"]}

Также можно определить валидации строгими, чтобы они вызывали ActiveModel::StrictValidationFailed, когда объект невалиден.

class Person < ApplicationRecord
  validates :name, presence: { strict: true }
end
irb> Person.new.valid?
ActiveModel::StrictValidationFailed: Name can't be blank

Также возможно передать собственное исключение в опцию :strict.

class Person < ApplicationRecord
  validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
end
irb> Person.new.valid?
TokenGenerationException: Token can't be blank

Иногда имеет смысл проводить валидацию объекта только при выполнении заданного предиката. Это можно сделать, используя опции :if и :unless, которые принимают символ, Proc или Array. Опцию :if можно использовать, если необходимо определить, когда валидация должна произойти. Если же нужно определить, когда валидация не должна произойти, воспользуйтесь опцией :unless.

Вы можете связать опции :if и :unless с символом, соответствующим имени метода, который будет вызван перед валидацией. Это наиболее часто используемый вариант.

class Order < ApplicationRecord
  validates :card_number, presence: true, if: :paid_with_card?

  def paid_with_card?
    payment_type == "card"
  end
end

Можно связать :if и :unless с объектом Proc, который будет вызван. Использование объекта Proc дает возможность написать встроенное условие вместо отдельного метода. Этот вариант лучше всего подходит для однострочного кода.

class Account < ApplicationRecord
  validates :password, confirmation: true,
    unless: Proc.new { |a| a.password.blank? }
end

Так как Lambda это тип Proc, они также могут быть использованы для написания вложенных условий более кратко.

validates :password, confirmation: true, unless: -> { password.blank? }

Иногда полезно иметь несколько валидаций с одним условием. Это легко достигается с использованием with_options.

class User < ApplicationRecord
  with_options if: :is_admin? do |admin|
    admin.validates :password, length: { minimum: 10 }
    admin.validates :email, presence: true
  end
end

Во все валидации внутри with_options будет автоматически передано условие if: :is_admin?.

С другой стороны, может использоваться массив, когда несколько условий определяют, должна ли произойти валидация. Более того, в одной и той же валидации можно применить и :if:, и :unless.

class Computer < ApplicationRecord
  validates :mouse, presence: true,
                    if: [Proc.new { |c| c.market.retail? }, :desktop?],
                    unless: Proc.new { |c| c.trackpad.present? }
end

Валидация выполнится только тогда, когда все условия :if и ни одно из условий :unless будут вычислены со значением true.

Когда встроенных валидационных хелперов недостаточно для ваших нужд, можете написать свои собственные валидаторы или методы валидации.

Собственные валидаторы это классы, наследуемые от ActiveModel::Validator. Эти классы должны реализовать метод validate, принимающий запись как аргумент и выполняющий валидацию на ней. Собственный валидатор вызывается с использованием метода validates_with.

class MyValidator < ActiveModel::Validator
  def validate(record)
    unless record.name.start_with? 'X'
      record.errors.add :name, "Need a name starting with X please!"
    end
  end
end
 
class Person < ApplicationRecord
  validates_with MyValidator
end

Простейшим способом добавить собственные валидаторы для валидации отдельных атрибутов является наследуемость от ActiveModel::EachValidator. В этом случае класс собственного валидатора должен реализовать метод validate_each, принимающий три аргумента: запись, атрибут и значение. Это будут соответствующие экземпляр, атрибут, который будет проверяться и значение атрибута в переданном экземпляре:

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ URI::MailTo::EMAIL_REGEXP
      record.errors.add attribute, (options[:message] || "is not an email")
    end
  end
end

class Person < ApplicationRecord
  validates :email, presence: true, email: true
end

Как показано в примере, можно объединять стандартные валидации со своими произвольными валидаторами.

Также возможно создать методы, проверяющие состояние ваших моделей и добавляющие ошибки в коллекцию errors, когда они невалидны. Затем эти методы следует зарегистрировать, используя метод класса validate, передав символьные имена валидационных методов.

Можно передать более одного символа для каждого метода класса, и соответствующие валидации будут запущены в том порядке, в котором они зарегистрированы.

Метод valid? проверит, что коллекция errors пуста, поэтому ваши собственные методы валидации должны добавить ошибки в нее, когда вы хотите, чтобы валидация провалилась:

class Invoice < ApplicationRecord
  validate :expiration_date_cannot_be_in_the_past,
    :discount_cannot_be_greater_than_total_value

  def expiration_date_cannot_be_in_the_past
    if expiration_date.present? && expiration_date < Date.today
      errors.add(:expiration_date, "can't be in the past")
    end
  end

  def discount_cannot_be_greater_than_total_value
    errors.add(:discount, "can't be greater than total value") if
      discount > total_value
  end
end

По умолчанию такие валидации будут выполнены каждый раз при вызове valid? или сохранении объекта. Но также возможно контролировать, когда выполнять собственные валидации, передав опцию :on в метод validate, с ключами: :create или :update.

class Invoice < ApplicationRecord
  validate :active_customer, on: :create

  def active_customer
    errors.add(:customer_id, "is not active") unless customer.active?
  end
end

Методы valid? и invalid? предоставляют только итоговый статус валидности. Однако, можно погрузиться в каждую отдельную ошибку с помощью различных методов из коллекции errors.

Предлагаем список наиболее часто используемых методов. Если хотите увидеть список всех доступных методов, обратитесь к документации по ActiveModel::Errors.

Шлюз, через который можно извлечь различные подробности о каждой ошибке.

Он возвращает экземпляр класса ActiveModel::Errors, содержащий все ошибки, каждая ошибка представлена объектом ActiveModel::Error.

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.full_messages
=> ["Name can't be blank", "Name is too short (minimum is 3 characters)"]

irb> person = Person.new(name: "John Doe")
irb> person.valid?
=> true
irb> person.errors.full_messages
=> []

errors[] используется, когда вы хотите проверить сообщения об ошибке для определенного атрибута. Он возвращает массив строк со всеми сообщениями об ошибке для заданного атрибута, каждая строка с одним сообщением об ошибке. Если нет ошибок, относящихся к атрибуту, возвратится пустой массив.

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new(name: "John Doe")
irb> person.valid?
=> true
irb> person.errors[:name]
=> []

irb> person = Person.new(name: "JD")
irb> person.valid?
=> false
irb> person.errors[:name]
=> ["is too short (minimum is 3 characters)"]

irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors[:name]
=> ["can't be blank", "is too short (minimum is 3 characters)"]

Иногда нам нужно больше подробностей о каждой ошибке, кроме ее сообщения. Каждая ошибка инкапсулирована как объект ActiveModel::Error, и метод where является наиболее простым способом доступа.

where возвращает массив объектов ошибки, фильтрованный различными условиями.

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new
irb> person.valid?
=> false

irb> person.errors.where(:name)
=> [ ... ] # все ошибки, связанные с атрибутом :name

irb> person.errors.where(:name, :too_short)
=> [ ... ] # ошибки :too_short для атрибута :name

Из этих объектов ошибки можно считывать разную информацию:

irb> error = person.errors.where(:name).last

irb> error.attribute
=> :name
irb> error.type
=> :too_short
irb> error.options[:count]
=> 3

Также можно сгенерировать сообщение об ошибке:

irb> error.message
=> "is too short (minimum is 3 characters)"
irb> error.full_message
=> "Name is too short (minimum is 3 characters)"

Метод full_message генерирует более дружелюбное сообщение с добавлением имени атрибута с большой буквы вначале. (Чтобы настроить формат, используемый full_message, посмотрите руководство I18n.)

Метод add создает объект ошибки, принимая attribute, type ошибки и дополнительный хэш опций. Это полезно для написания собственного валидатора.

class Person < ApplicationRecord
  validate do |person|
    errors.add :name, :too_plain, message: "is not cool enough"
  end
end
irb> person = Person.create
irb> person.errors.where(:name).first.type
=> :too_plain
irb> person.errors.where(:name).first.full_message
=> "Name is not cool enough"

Можете добавлять ошибки, которые относятся к состоянию объекта в целом, а не к отдельному атрибуту. Можно добавлять ошибки к :base, когда вы хотите сообщить, что этот объект невалиден, вне зависимости от значений его атрибутов.

class Person < ApplicationRecord
  validate do |person|
    errors.add :base, :invalid, message: "This person is invalid because ..."
  end
end
irb> person = Person.create
irb> person.errors.where(:base).first.full_message
=> "This person is invalid because ..."

Метод clear используется, когда вы намеренно хотите очистить коллекцию errors. Естественно, вызов errors.clear для невалидного объекта фактически не сделает его валидным: сейчас коллекция errors будет пуста, но в следующий раз, когда вы вызовете valid? или любой метод, который пытается сохранить этот объект в базу данных, валидации выполнятся снова. Если любая из валидаций провалится, коллекция errors будет заполнена снова.

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.empty?
=> false

irb> person.errors.clear
irb> person.errors.empty?
=> true

irb> person.save
=> false

irb> person.errors.empty?
=> false

Метод size возвращает количество ошибок для объекта.

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.size
=> 2

irb> person = Person.new(name: "Andrea", email: "andrea@example.com")
irb> person.valid?
=> true
irb> person.errors.size
=> 0

Как только вы создали модель и добавили валидации, если эта модель создается с помощью веб-формы, то вы, возможно хотите отображать сообщение об ошибке, когда одна из валидаций проваливается.

Поскольку каждое приложение обрабатывает подобные вещи по-разному, в Rails нет какого-то хелпера вью для непосредственной генерации этих сообщений. Однако, благодаря богатому набору методов, которые Rails в целом дает для взаимодействия с валидациями, можно создать свой собственный. Кроме того, при генерации скаффолда, Rails поместит некоторый ERB в _form.html.erb, генерируемый для отображения полного списка ошибок этой модели.

Допустим, у нас имеется модель, сохраненная в переменную экземпляра @article, это выглядит следующим образом:

<% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %> prohibited this article from being saved:</h2>
    <ul>
      <% @article.errors.each do |error| %>
        <li><%= error.full_message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

Более того, при использовании хелперов форм Rails для создания форм, когда у поля происходит ошибка валидации, генерируется дополнительный <div> вокруг содержимого.

<div class="field_with_errors">
  <input id="article_title" name="article[title]" size="30" type="text" value="">
</div>

Этот div можно стилизовать по желанию. К примеру, дефолтный скаффолд, который генерирует Rails, добавляет это правило CSS:

.field_with_errors {
  padding: 2px;
  background-color: red;
  display: table;
}

Это означает, что любое поле с ошибкой обведено красной рамкой толщиной 2 пикселя.

Понравилась статья? Поделить с друзьями:
  • Error adb exited with exit code 1 performing streamed install adb failed to install
  • Error adapter malfunctioning
  • Error activity not started unable to resolve intent
  • Error activating xkb configuration
  • Error activating bean validation integration