Más en rubyonrails.org: Más de Ruby on Rails

** NO LEAS ESTE FICHERO EN GITHUB, LAS GUIAS ESTÁN PUBLICADAS EN http://www.guiasrails.es **

Validaciones Active Record

Esta guía te enseña como validar el estado de los objetos antes de guardarse en la base de datos utilizando las caracteríticas de valicación de Active Record.

Después de leer esta guía, conocerás:

1 Resumen de las Validaciones

Aquí hay un ejemplo de una validación muy simple:

class Person < ActiveRecord::Base
  validates :name, presence: true
end

Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false

Como puedes ver, nuestra validación nos hace saber que nuestro objeto Person no es válido sin un atributo name. La segunda instancia de Person no será guardado en la base de datos.

Antes de entrar en más detalles, hablaremos de como las validaciones quedan en el cuadro general de nuestra aplicación.

1.1 ¿Por qué Utilizamos Validaciones?

Las validaciones son utilizadas para asegurarse que solo datos válidos son guardados dentro de la base de datos. Por ejemplo, esto puede ser muy importante para que tu aplicacion se asegure que todos los usuarios proveen una dirección de correo electrónico y una dirección postal válidas. Las validaciones del nivel del Modelo, son la mejor manera de asegurar que solo los datos válidos son guardados en la base de datos. Son independientes del motor de base de datos, no los pueden sobrepasar los usuarios finales, y son recomendables para probar y mantener. Rails los hace fácil de utilizar, provee métodos ayudantes construídos previamente para las necesidades más comunes, y te permite crear tus propias validaciones también.

Hay varias otras maneras de validar los datos antes de guardarlos en tu base de datos, incluyendo una restricciones nativas de la base de datos, validaciones del lado del cliente y validaciones en al nivel del controlador. Aquí un resumen de las mismas:

  • Restricciones de la base de datos, y/o procedimientos almacenados que hacen el mecanismo de validación dependiente del motor de la base de datos y pueden hacer las pruebas y el mantenimiento más dificil. Sin embargo, si tu base de datos es utilizada por otras aplicaciones, puede ser una buena idea utilizar algunas restricciones en el nivel de base de datos. Adicionalmente, las validaciones en el nivel de base de datos, pueden mantener la seguridad en algunas cosas (por ejemplo la unicidad en tablas muy grandes) que son dificiles de implementar de otra manera.
  • Las validaciones del lado del cliente suelen ser utilizadas, pero son generalmente poco confiables si se utilizan solas. Si son implementadas utilizando JavaScript, pueden ser sobrepasadas si el JavaScript está quitado en el navegador del usuario. Sin embargo, si se combinan con otras técnicas, las validaciones del lado del clientes pueden ser una manera conveniente de proporcionar una respuesta inmediata a quienes utilicen tu página.
  • Las validaciones en el nivel del controlador pueden ser temporarlmente utilizadas, pero frecuentemente se vuelven incomprensibles y dificiles de probar y mantener. Simpre que sea posible, es una buena idea mantener los controlaroes limpios, esto hará que sea un placer trabajar en tu aplicación a largo plazo.

Elíjelas con certeza, en casos específicos. Esta es la opinión del equipo de Rails que las validaciones al nivel del modelo son las más apropiadas en la mayoría de las circunstancias.

1.2 ¿Cuando Ocurren las Validaciones?

Hay dos clases de objetos Active Record: aquellos que se corresponden a un registro en la base de datos y los que no. Cuando creas un objeto nuevo, por ejemplo usando el método new, ese objeto no pertenece a la base de datos aún. Una vez que llamas al método save el objeto recién será guardado en la tabla apropiada de la base de datos. Active Record utiliza el método de instancia new_record? para determinar si un objeto está ya en la base de datos o no. Considera la siguente clase Active Record:

class Person < ActiveRecord::Base
end

Podemos ver como trabaja mirando algunas salidas en rails console:

$ bin/rails console
>> p = Person.new(name: "John Doe")
=> #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil>
>> p.new_record?
=> true
>> p.save
=> true
>> p.new_record?
=> false

Al crear y guardar un nuevo registro se enviará una operación SQL INSERT a la base de datos. En cambio al actualizar un registro existente enviaremos una operación SQL UPDATE. Las validaciones son tipicamente ejecutadas antes que estos comoandos se envien a la base de datos. Si alguna validación falla, el objeto será marcado como inválido y Active Record no creará las operaciones INSERT o UPDATE. De lo contrario se guardaría un objeto inválido en la base de datos. Puedes elegir tener validaciones específicas ejecutandose cuando un objeto es creado, guardado o actualizado.

Hay muchas maneras para cambiar el estado de un objeto en la base de datos. Algunos metodos dispararán validaciones, pero otros no lo harán. Esto significa que es posible guardar un objeto en la base de datos en un estado inválido si no tienes cuidado.

Los siguientes métodos disparan validaciones, y guardarán un objeto en la base de datos solo si el objeto es válido:

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

Las versiones bang (ej: save!) lanzan una excepción si el registro es inválido. Las versiones no-bang no, save y update retornan false, y create solo retorna el objeto.

1.3 Saltando las Validaciones

Los siguientes métodos saltan validaciones, y guardarán el objeto en la base de datos sin tener en cuenta su validez. Deben ser utilizados con precaución.

  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • toggle!
  • touch
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters

Nota que save también tiene la posibilidad de saltar validaciones si se le pasa de argumento validate: false. Esta técnica debe ser utilizada con precaución.

  • save(validate: false)

1.4 valid? e invalid?

Para verificar si un objeto es o no válido, Rails utiliza el método valid?. También puedes utilizar este método por ti mismo. valid? dispara tus validaciones y retorna true si no fueron encontrados errores en el objeto, y false de lo contrario. Como puedes ver aquí::

class Person < ActiveRecord::Base
  validates :name, presence: true
end

Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false

Después de que Active Record ha ejecutado las validaciones, cualquier error encontrado puede ser conocido a través de la instancia del método errors.messages, el cual retorna una colección de errores. Por definición, un objeto es válido si la colección está vacia después de ejecutar las validaciones.

Nota que un objeto instanciado con new no reportará errores mientras sea solo instanciado anque sea técnicamente inválido, porque las validaciones no serán ejecutadas cuando utilizas new.

class Person < ActiveRecord::Base
  validates :name, presence: true
end

>> p = Person.new
# => #<Person id: nil, name: nil>
>> p.errors.messages
# => {}

>> p.valid?
# => false
>> p.errors.messages
# => {name:["can't be blank"]}

>> p = Person.create
# => #<Person id: nil, name: nil>
>> p.errors.messages
# => {name:["can't be blank"]}

>> p.save
# => false

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

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

invalid? es simplemente la inversa de valid?. Dispara las validaciones, retornando true si cualquier error es encontrado en el ojbeto, y false en caso contrario.

1.5 errors[]

Para verificar si un atributo en particular de un objeto es válido, lo puedes verificar utilizando errors[:attribute]. Esto retornará un un array de totos los errores por :attribute. Si no hay errores en un attributo específico, es devuelto un array vacio.

Este método es solo utilizado después de que las validaciones se han ejectuado, porque solo inspecciona la colección de errores y no disparan validaciones por si mismos. Esto es diferente desde el método ActiveRecord::Base#invalid? que explicamos antes porque este no verifica la validez del objeto no tiene ese objetivo. Solo comprueba para ver si hay errores encontrados en un atributo individual del objeto.

class Person < ActiveRecord::Base
  validates :name, presence: true
end

>> Person.new.errors[:name].any? # => false
>> Person.create.errors[:name].any? # => true

Cubriremos los errores de validación en mayor profundidad en la sección Trabajando con los errores de validación. Por ahora, vamos a continuar con los helpers (ayudantes) de validación construidos que Rails provee por defecto.

2 Helpers de Validación

Active Record ofrece muchos helpers de validación predefinidos que puedes utilizar directamente dentro de las definiciones de tu clase. Estos helpers proveen las reglas de validación comunes. Cada vez que una validación falla, un mensaje de error es añadido a la colección de errors del objeto, y este mensaje es asociado con el attributo que está siendo validado.

Cada ayudante acepta un número arbitrario de nombres de atributos, entonces con una simple línea de código puedes añadir algún tipo de validadación a varios atributos.

Todos ellos acceptan las opciones :on y :message, las cuales definen cuando la validación se ejecuta y el mensaje que debería ser añadido a la colección errors si falla, respectivamente. La opción :on toma uno de los valores :create o :update. Hay un mensaje de error por defecto pra cada una de los helpers de validación. Estos mensajes son utilizados cuando la opción :message no está especificada. Vamos a repasar cada uno de los helpers disponibles.

2.1 acceptance

Este método valida si un checkbox en la interfaz de usuarios es chequeado al enviar un formulario. Esto es típicamente utilizado cuando el usuario necesita dar su conformidad sobre los términos y servicios de tu aplicación, confirmándolo después de leer algún texto o algo por el estilo. Esta validación es muy específica para aplicaciones web y esta 'acceptance' no necesita guardarse en ningún sitio de tu base de datos (si no tienes un campo para esto, el helper creará un attributo virtual).

class Person < ActiveRecord::Base
  validates :terms_of_service, acceptance: true
end

El mensaje de error por defecto de este helper es "must be accepted".

Puede recibir una opción :accept, la cual determina que valor que será considerado aceptable. Por defecto es "1" y puede ser cambiado facilmente.

class Person < ActiveRecord::Base
  validates :terms_of_service, acceptance: { accept: 'yes' }
end

2.2 validates_associated

Deberías utilizar este helper cuando tu modelo tiene asociaciones con otros modelos y también necesiten ser validados. Cuando intentas guardar tu objeto valid? será llamado por cada uno de los objetos asociados.

class Library < ActiveRecord::Base
  has_many :books
  validates_associated :books
end

Esta validación trabajará con todos los tipos de asociaciones.

No utilices validates_associated en ambas puntas de tus asociaciones, porque se llamarán la una a la otra en un ciclo infinito.

El mensaje de error por defecto para validates_associated es "is invalid". Nota que cada objeto asociado contendra sus propias colecciones errors; los errores no escalan hacia el modelo llamadado.

2.3 confirmation

Puedes utilizar este helper cuando tengas dos campos de texto que deban recibir exactamente el mismo contenido. Por ejemplo, puedes querer confirmar una direccion de email y un password. Esta validación crea un atributo virtual cuyo nombre es el campo que tiene que ser confirmado con la "_confirmation" añadida.

class Person < ActiveRecord::Base
  validates :email, confirmation: true
end

En la plantilla de la vista podrías utilizar algo como

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

Esta comprobación se lleva a cabo solo si email_confirmation no esnil. Para requerir confirmación, asegurate de añadir una comprobación presence al atributo de confirmación (veremos presence más adelante en esta guía):

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

El mensaje de error para este ayudante es "doesn't match confirmation".

2.4 exclusion

Este helper valida que los valores de los atributos no están incluidos en una configuración dada. En realidad, esta configuración puede ser cualquier objeto enumerable.

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

El helper exclusion tiene una opción :in que recibe la configuración de los valores que no serán aceptados por los atributos validados. La opción :in tiene un alias llamado :within que puedes utilizar para el mismo propósito, si lo quieres. Este ejemplo utiliza la opción :message para mostrar como puedes incluir los valores de los atributos.

El mensaje de error por defecto es "is reserved".

2.5 format

Este helper valida los valores de los atributos por pruebas si coinciden o no con una expresión regular dada, que se especifica utilizando la opción :with.

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

Alternativamente, puedes requerir que el atributo específico que no responda a la expresión regular utilizando la opción :without.

El mensaje de error por defecto es "is invalid".

2.6 inclusion

Este helper valida que los valores de los atributos estén incluidos en una configuración dada. En realidad, esta configuración puede ser un objeto enumerable.

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

El helper inclusion tiene una opción :in que recibe lo que debe ser incluido. La opción :in tiene un alias llamado :within que puedes utilizar para algunos propósitos, si lo deseas. Los ejemplos anteriores utilizan la opción :message para mostrar como puedes incluir valores de los atributos.

El mensaje de error para este helper es "is not included in the list".

2.7 length

Este helper valida la longitud de los valores de los atributos. Provee una variedad de opciones, entonces puedes especificar restricciones de longitud de diferentes maneras:

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

Las opciones de restricción de longitud son:

  • :minimum - El atributo no puede tener menos caracteres que una longitud específica.
  • :maximum - El atributo no puede tener más caracteres que una longitud específica.
  • :in (o :within) - La longitud del atributo debe estar contenida en un intervalo dado. El valor para esta opción de ser un rango.
  • :is - La longitud del atributo debe ser igual a un valor dado.

Los mensajes de error por defecto dependen del tipo de validación de longitud que es utilizada. Puedes personalizar estos mensajes utilizando las opciones :wrong_length, :too_long, :too_short y %{count} como referencia para mostrar el número que corresponda para la restricción que se está utilizando. También pudes utilizar la opción :message para especificar un mensaje de error.

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

Este helper cuenta los caracteres por defecto, pero puedes separar el valor de diferentes maneras utilizanod la opción :tokenizer option:

class Essay < ActiveRecord::Base
  validates :content, length: {
    minimum: 300,
    maximum: 400,
    tokenizer: lambda { |str| str.split(/\s+/) },
    too_short: "must have at least %{count} words",
    too_long: "must have at most %{count} words"
  }
end

Nota que los mensajes de error por defecto están en plural (ej: "is too short (minimum is %{count} characters)"). Por esta razón, cuando :minimum es 1 debes indicar un mensaje personalizado o utilizar en su lugar presence: true. Cuando :in o :within tienen un un límite mínimo de 1, puedes proveer también un mensaje personalizado o llamar a presence antes que a length.

2.8 numericality

Este helper valida que tus atributos tienen solo valores numéricos. Por defecto, esto se comparará con una firma seguida por un entero o un número de punto flotante. Para especificar que solo números enteros están permitidos configuras a true :only_integer.

Si configuras :only_integer a true, luego se utilizará la expresión regular

/\A[+-]?\d+\Z/

para validar el valor del atributo. De otro modo, intentará convertir el valor a un número utilizando la expresión Float.

Nota que la expresión regular de arriba permite un caracter perdido de salto de línea.

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

Además :only_integer, este helper también acepta las siguientes opciones para añadir restricciones a los valores acceptables:

  • :greater_than - Especifica que el valor del atributo debe debe ser mayor que un valor determinado. El mensaje de error para esta opción es "must be greater than %{count}".
  • :greater_than_or_equal_to - Especifica que el valor del atributo debe mayor o igual a un valor determinado. El mensaje de error por defecto para esta opción es "must be greater than or equal to %{count}".
  • :equal_to - Especifica que el valor del atributo debe ser igual a un valor determinado. El mensaje de error por defecto de esta opción es "must be equal to %{count}".
  • :less_than - Especifica que el valor del atributo debe ser menor que un determinado valor. El mensaje de error de esta opción es "must be less than %{count}".
  • :less_than_or_equal_to - Especifica que el valor del atributo debe ser menor o igual de un valor determinado. El mensaje de error por defecto es "must be less than or equal to %{count}".
  • :odd - Especifica que el valor debe ser un número impar si es configurado a true. El mensaje de error por defecto para esta opción es "must be odd".
  • :even - Especifica que el valor del atributo debe ser un número par si es configurado a true. El mensaje de error por defecto para esta opción es "must be even".

Por defecto, numericality no permite valores nil. Puedes utilizar la opción allow_nil: true para permitirlo.

El mensaje de error por defecto en este caso es "is not a number".

2.9 presence

This helper validates that the specified attributes are not empty. It uses the blank? method to check if the value is either nil or a blank string, that is, a string that is either empty or consists of whitespace.

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

If you want to be sure that an association is present, you'll need to test whether the associated object itself is present, and not the foreign key used to map the association.

class LineItem < ActiveRecord::Base
  belongs_to :order
  validates :order, presence: true
end

In order to validate associated records whose presence is required, you must specify the :inverse_of option for the association:

class Order < ActiveRecord::Base
  has_many :line_items, inverse_of: :order
end

If you validate the presence of an object associated via a has_one or has_many relationship, it will check that the object is neither blank? nor marked_for_destruction?.

Since false.blank? is true, if you want to validate the presence of a boolean field you should use one of the following validations:

validates :boolean_field_name, presence: true
validates :boolean_field_name, inclusion: { in: [true, false] }
validates :boolean_field_name, exclusion: { in: [nil] }

By using one of these validations, you will ensure the value will NOT be nil which would result in a NULL value in most cases.

2.10 absence

This helper validates that the specified attributes are absent. It uses the present? method to check if the value is not either nil or a blank string, that is, a string that is either empty or consists of whitespace.

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

If you want to be sure that an association is absent, you'll need to test whether the associated object itself is absent, and not the foreign key used to map the association.

class LineItem < ActiveRecord::Base
  belongs_to :order
  validates :order, absence: true
end

In order to validate associated records whose absence is required, you must specify the :inverse_of option for the association:

class Order < ActiveRecord::Base
  has_many :line_items, inverse_of: :order
end

If you validate the absence of an object associated via a has_one or has_many relationship, it will check that the object is neither present? nor marked_for_destruction?.

Since false.present? is false, if you want to validate the absence of a boolean field you should use validates :field_name, exclusion: { in: [true, false] }.

The default error message is "must be blank".

2.11 uniqueness

This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index on both columns in your database. See the MySQL manual for more details about multiple column indexes.

class Account < ActiveRecord::Base
  validates :email, uniqueness: true
end

The validation happens by performing an SQL query into the model's table, searching for an existing record with the same value in that attribute.

There is a :scope option that you can use to specify other attributes that are used to limit the uniqueness check:

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

There is also a :case_sensitive option that you can use to define whether the uniqueness constraint will be case sensitive or not. This option defaults to true.

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

Note that some databases are configured to perform case-insensitive searches anyway.

The default error message is "has already been taken".

2.12 validates_with

This helper passes the record to a separate class for validation.

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

class Person < ActiveRecord::Base
  validates_with GoodnessValidator
end

Errors added to record.errors[:base] relate to the state of the record as a whole, and not to a specific attribute.

The validates_with helper takes a class, or a list of classes to use for validation. There is no default error message for validates_with. You must manually add errors to the record's errors collection in the validator class.

To implement the validate method, you must have a record parameter defined, which is the record to be validated.

Like all other validations, validates_with takes the :if, :unless and :on options. If you pass any other options, it will send those options to the validator class as options:

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

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

Note that the validator will be initialized only once for the whole application life cycle, and not on each validation run, so be careful about using instance variables inside it.

If your validator is complex enough that you want instance variables, you can easily use a plain old Ruby object instead:

class Person < ActiveRecord::Base
  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[:base] << "This person is evil"
    end
  end

  # ...
end

2.13 validates_each

This helper validates attributes against a block. It doesn't have a predefined validation function. You should create one using a block, and every attribute passed to validates_each will be tested against it. In the following example, we don't want names and surnames to begin with lower case.

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

The block receives the record, the attribute's name and the attribute's value. You can do anything you like to check for valid data within the block. If your validation fails, you should add an error message to the model, therefore making it invalid.

3 Common Validation Options

These are common validation options:

3.1 :allow_nil

The :allow_nil option skips the validation when the value being validated is nil.

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

3.2 :allow_blank

The :allow_blank option is similar to the :allow_nil option. This option will let validation pass if the attribute's value is blank?, like nil or an empty string for example.

class Topic < ActiveRecord::Base
  validates :title, length: { is: 5 }, allow_blank: true
end

Topic.create(title: "").valid?  # => true
Topic.create(title: nil).valid? # => true

3.3 :message

As you've already seen, the :message option lets you specify the message that will be added to the errors collection when validation fails. When this option is not used, Active Record will use the respective default error message for each validation helper.

3.4 :on

The :on option lets you specify when the validation should happen. The default behavior for all the built-in validation helpers is to be run on save (both when you're creating a new record and when you're updating it). If you want to change it, you can use on: :create to run the validation only when a new record is created or on: :update to run the validation only when a record is updated.

class Person < ActiveRecord::Base
  # it will be possible to update email with a duplicated value
  validates :email, uniqueness: true, on: :create

  # it will be possible to create the record with a non-numerical age
  validates :age, numericality: true, on: :update

  # the default (validates on both create and update)
  validates :name, presence: true
end

4 Strict Validations

You can also specify validations to be strict and raise ActiveModel::StrictValidationFailed when the object is invalid.

class Person < ActiveRecord::Base
  validates :name, presence: { strict: true }
end

Person.new.valid?  # => ActiveModel::StrictValidationFailed: Name can't be blank

There is also an ability to pass custom exception to :strict option.

class Person < ActiveRecord::Base
  validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
end

Person.new.valid?  # => TokenGenerationException: Token can't be blank

5 Conditional Validation

Sometimes it will make sense to validate an object only when a given predicate is satisfied. You can do that by using the :if and :unless options, which can take a symbol, a string, a Proc or an Array. You may use the :if option when you want to specify when the validation should happen. If you want to specify when the validation should not happen, then you may use the :unless option.

5.1 Using a Symbol with :if and :unless

You can associate the :if and :unless options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option.

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

  def paid_with_card?
    payment_type == "card"
  end
end

5.2 Using a String with :if and :unless

You can also use a string that will be evaluated using eval and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition.

class Person < ActiveRecord::Base
  validates :surname, presence: true, if: "name.nil?"
end

5.3 Using a Proc with :if and :unless

Finally, it's possible to associate :if and :unless with a Proc object which will be called. Using a Proc object gives you the ability to write an inline condition instead of a separate method. This option is best suited for one-liners.

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

5.4 Grouping Conditional validations

Sometimes it is useful to have multiple validations use one condition, it can be easily achieved using with_options.

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

All validations inside of with_options block will have automatically passed the condition if: :is_admin?

5.5 Combining Validation Conditions

On the other hand, when multiple conditions define whether or not a validation should happen, an Array can be used. Moreover, you can apply both :if and :unless to the same validation.

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

The validation only runs when all the :if conditions and none of the :unless conditions are evaluated to true.

6 Performing Custom Validations

When the built-in validation helpers are not enough for your needs, you can write your own validators or validation methods as you prefer.

6.1 Custom Validators

Custom validators are classes that extend ActiveModel::Validator. These classes must implement a validate method which takes a record as an argument and performs the validation on it. The custom validator is called using the validates_with method.

class MyValidator < ActiveModel::Validator
  def validate(record)
    unless record.name.starts_with? 'X'
      record.errors[:name] << 'Need a name starting with X please!'
    end
  end
end

class Person
  include ActiveModel::Validations
  validates_with MyValidator
end

The easiest way to add custom validators for validating individual attributes is with the convenient ActiveModel::EachValidator. In this case, the custom validator class must implement a validate_each method which takes three arguments: record, attribute, and value. These correspond to the instance, the attribute to be validated, and the value of the attribute in the passed instance.

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors[attribute] << (options[:message] || "is not an email")
    end
  end
end

class Person < ActiveRecord::Base
  validates :email, presence: true, email: true
end

As shown in the example, you can also combine standard validations with your own custom validators.

6.2 Custom Methods

You can also create methods that verify the state of your models and add messages to the errors collection when they are invalid. You must then register these methods by using the validate class method, passing in the symbols for the validation methods' names.

You can pass more than one symbol for each class method and the respective validations will be run in the same order as they were registered.

class Invoice < ActiveRecord::Base
  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
    if discount > total_value
      errors.add(:discount, "can't be greater than total value")
    end
  end
end

By default such validations will run every time you call valid?. It is also possible to control when to run these custom validations by giving an :on option to the validate method, with either: :create or :update.

class Invoice < ActiveRecord::Base
  validate :active_customer, on: :create

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

7 Working with Validation Errors

In addition to the valid? and invalid? methods covered earlier, Rails provides a number of methods for working with the errors collection and inquiring about the validity of objects.

The following is a list of the most commonly used methods. Please refer to the ActiveModel::Errors documentation for a list of all the available methods.

7.1 errors

Returns an instance of the class ActiveModel::Errors containing all errors. Each key is the attribute name and the value is an array of strings with all errors.

class Person < ActiveRecord::Base
  validates :name, presence: true, length: { minimum: 3 }
end

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

person = Person.new(name: "John Doe")
person.valid? # => true
person.errors.messages # => {}

7.2 errors[]

errors[] is used when you want to check the error messages for a specific attribute. It returns an array of strings with all error messages for the given attribute, each string with one error message. If there are no errors related to the attribute, it returns an empty array.

class Person < ActiveRecord::Base
  validates :name, presence: true, length: { minimum: 3 }
end

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

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

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

7.3 errors.add

The add method lets you manually add messages that are related to particular attributes. You can use the errors.full_messages or errors.to_a methods to view the messages in the form they might be displayed to a user. Those particular messages get the attribute name prepended (and capitalized). add receives the name of the attribute you want to add the message to, and the message itself.

class Person < ActiveRecord::Base
  def a_method_used_for_validation_purposes
    errors.add(:name, "cannot contain the characters !@#%*()_-+=")
  end
end

person = Person.create(name: "!@#")

person.errors[:name]
 # => ["cannot contain the characters !@#%*()_-+="]

person.errors.full_messages
 # => ["Name cannot contain the characters !@#%*()_-+="]

Another way to do this is using []= setter

  class Person < ActiveRecord::Base
    def a_method_used_for_validation_purposes
      errors[:name] = "cannot contain the characters !@#%*()_-+="
    end
  end

  person = Person.create(name: "!@#")

  person.errors[:name]
   # => ["cannot contain the characters !@#%*()_-+="]

  person.errors.to_a
   # => ["Name cannot contain the characters !@#%*()_-+="]

7.4 errors[:base]

You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since errors[:base] is an array, you can simply add a string to it and it will be used as an error message.

class Person < ActiveRecord::Base
  def a_method_used_for_validation_purposes
    errors[:base] << "This person is invalid because ..."
  end
end

7.5 errors.clear

The clear method is used when you intentionally want to clear all the messages in the errors collection. Of course, calling errors.clear upon an invalid object won't actually make it valid: the errors collection will now be empty, but the next time you call valid? or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the errors collection will be filled again.

class Person < ActiveRecord::Base
  validates :name, presence: true, length: { minimum: 3 }
end

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

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

p.save # => false

p.errors[:name]
# => ["can't be blank", "is too short (minimum is 3 characters)"]

7.6 errors.size

The size method returns the total number of error messages for the object.

class Person < ActiveRecord::Base
  validates :name, presence: true, length: { minimum: 3 }
end

person = Person.new
person.valid? # => false
person.errors.size # => 2

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

8 Displaying Validation Errors in Views

Once you've created a model and added validations, if that model is created via a web form, you probably want to display an error message when one of the validations fail.

Because every application handles this kind of thing differently, Rails does not include any view helpers to help you generate these messages directly. However, due to the rich number of methods Rails gives you to interact with validations in general, it's fairly easy to build your own. In addition, when generating a scaffold, Rails will put some ERB into the _form.html.erb that it generates that displays the full list of errors on that model.

Assuming we have a model that's been saved in an instance variable named @article, it looks like this:

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

    <ul>
    <% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

Furthermore, if you use the Rails form helpers to generate your forms, when a validation error occurs on a field, it will generate an extra <div> around the entry.

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

You can then style this div however you'd like. The default scaffold that Rails generates, for example, adds this CSS rule:

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

This means that any field with an error ends up with a 2 pixel red border.

Participa

Se te anima a ayudar a mejorar la calidad de esta guía.

Por favor contribuye si hay errores tipográficos o de conceptos. Para empezar, puedes leer nuestra sección de colaboración con la sección de documentación contribuciones a la documentación.

También es posible encontrar contenidos incompletos, o cosas que no están actualizadas. Por favor añade cualquier documentación faltante en master. Asegúrate de revisar las guías Las guías paralelas primero para verificar si las cuestiones ya se han subido o no a la rama principal. Comprueba las Directrices de Ruby on Rails para el estilo y convenciones.

Si por cualquier razón encuentras algo para arreglar, pero no lo puedes por ti mismo, por favor, abre una incidencia abrir una incidencia.

Y por último pero no menos importante, cualquier tipo de discusión con respecto a la documentación de Ruby on Rails es muy bienvenida en la lista de correo RubyOnRails-docs lista de correo rubyonrails-docs.