1 Visión General de las Validaciones
Aquí hay un ejemplo de una validación muy simple:
class Person < ApplicationRecord
validates :name, presence: true
end
irb> Person.create(name: "John Doe").valid?
=> true
irb> Person.create(name: nil).valid?
=> false
Como puedes ver, nuestra validación nos informa que nuestro Person
no es válido sin un atributo name
. El segundo Person
no se guardará en la base de datos.
Antes de profundizar en más detalles, hablemos de cómo las validaciones encajan en el panorama general de tu aplicación.
1.1 ¿Por qué Usar Validaciones?
Las validaciones se utilizan para asegurar que solo datos válidos se guarden en tu base de datos. Por ejemplo, puede ser importante para tu aplicación asegurarse de que cada usuario proporcione una dirección de correo electrónico y una dirección postal válidas. Las validaciones a nivel de modelo son la mejor manera de asegurar que solo datos válidos se guarden en tu base de datos. Son independientes de la base de datos, no pueden ser eludidas por los usuarios finales, y son convenientes para probar y mantener. Rails proporciona ayudantes integrados para necesidades comunes y te permite crear tus propios métodos de validación también.
Hay varias otras maneras de validar datos antes de que se guarden en tu base de datos, incluyendo restricciones nativas de la base de datos, validaciones del lado del cliente y validaciones a nivel de controlador. Aquí hay un resumen de los pros y contras:
- Las restricciones de la base de datos y/o los procedimientos almacenados hacen que los mecanismos de validación dependan de la base de datos y pueden hacer que las pruebas y el mantenimiento sean más difíciles. Sin embargo, si tu base de datos es utilizada por otras aplicaciones, puede ser una buena idea usar algunas restricciones a nivel de base de datos. Además, las validaciones a nivel de base de datos pueden manejar de manera segura algunas cosas (como la unicidad en tablas muy utilizadas) que pueden ser difíciles de implementar de otra manera.
- Las validaciones del lado del cliente pueden ser útiles, pero generalmente son poco confiables si se usan solas. Si se implementan usando JavaScript, pueden ser eludidas si JavaScript está desactivado en el navegador del usuario. Sin embargo, si se combinan con otras técnicas, la validación del lado del cliente puede ser una manera conveniente de proporcionar retroalimentación inmediata a los usuarios mientras usan tu sitio.
- Las validaciones a nivel de controlador pueden ser tentadoras de usar, pero a menudo se vuelven inmanejables y difíciles de probar y mantener. Siempre que sea posible, es una buena idea mantener tus controladores simples, ya que hará que tu aplicación sea un placer de trabajar a largo plazo.
Elige estas opciones en ciertos casos específicos. Es la opinión del equipo de Rails que las validaciones a nivel de modelo son las más apropiadas en la mayoría de las circunstancias.
1.2 ¿Cuándo Ocurre la Validación?
Hay dos tipos de objetos Active Record: aquellos que corresponden a una fila dentro de tu base de datos y aquellos que no. Cuando creas un objeto nuevo, por ejemplo usando el método new
, ese objeto aún no pertenece a la base de datos. Una vez que llamas a save
sobre ese objeto, se guardará en la tabla de base de datos apropiada. Active Record utiliza el método de instancia new_record?
para determinar si un objeto ya está en la base de datos o no. Considera la siguiente clase de Active Record:
class Person < ApplicationRecord
end
Podemos ver cómo funciona observando algunos resultados de 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
Crear y guardar un nuevo registro enviará una operación SQL INSERT
a la base de datos. Actualizar un registro existente enviará una operación SQL UPDATE
en su lugar. Las validaciones generalmente se ejecutan antes de que estos comandos se envíen a la base de datos. Si alguna validación falla, el objeto será marcado como no válido y Active Record no realizará la operación INSERT
o UPDATE
. Esto evita almacenar un objeto no válido en la base de datos. Puedes elegir que se ejecuten validaciones específicas cuando un objeto se crea, guarda o actualiza.
PRECAUCIÓN: Hay muchas maneras de cambiar el estado de un objeto en la base de datos. Algunos métodos activarán validaciones, pero otros no. Esto significa que es posible guardar un objeto en la base de datos en un estado no válido si no tienes cuidado.
Los siguientes métodos activan validaciones y guardarán el objeto en la base de datos solo si el objeto es válido:
Las versiones con signo de exclamación (por ejemplo, save!
) lanzan una excepción si el registro es inválido. Las versiones sin signo de exclamación no lo hacen: save
y update
devuelven false
, y create
devuelve el objeto.
1.3 Omitiendo Validaciones
Los siguientes métodos omiten validaciones y guardarán el objeto en la base de datos independientemente de su validez. Deben usarse con precaución. Consulta la documentación del método para obtener más información.
decrement!
decrement_counter
increment!
increment_counter
insert
insert!
insert_all
insert_all!
toggle!
touch
touch_all
update_all
update_attribute
update_attribute!
update_column
update_columns
update_counters
upsert
upsert_all
Ten en cuenta que save
también tiene la capacidad de omitir validaciones si se pasa validate: false
como argumento. Esta técnica debe usarse con precaución.
save(validate: false)
1.4 valid?
e invalid?
Antes de guardar un objeto Active Record, Rails ejecuta tus validaciones. Si estas validaciones producen algún error, Rails no guarda el objeto.
También puedes ejecutar estas validaciones por tu cuenta. valid?
activa tus validaciones y devuelve true si no se encontraron errores en el objeto, y false en caso contrario. Como viste arriba:
class Person < ApplicationRecord
validates :name, presence: true
end
irb> Person.create(name: "John Doe").valid?
=> true
irb> Person.create(name: nil).valid?
=> false
Después de que Active Record ha realizado las validaciones, cualquier falla puede ser accedida a través del método de instancia errors
, que devuelve una colección de errores. Por definición, un objeto es válido si esta colección está vacía después de ejecutar las validaciones.
Ten en cuenta que un objeto instanciado con new
no reportará errores incluso si es técnicamente inválido, porque las validaciones se ejecutan automáticamente solo cuando el objeto se guarda, como con los métodos create
o 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?
es la inversa de valid?
. Activa tus validaciones, devolviendo true si se encontraron errores en el objeto, y false en caso contrario.
1.5 errors[]
Para verificar si un atributo particular de un objeto es válido, puedes usar errors[:attribute]
. Devuelve un array de todos los mensajes de error para :attribute
. Si no hay errores en el atributo especificado, se devuelve un array vacío.
Este método solo es útil después de que las validaciones se hayan ejecutado, porque solo inspecciona la colección de errores y no activa las validaciones por sí mismo. Es diferente del método ActiveRecord::Base#invalid?
explicado anteriormente porque no verifica la validez del objeto en su conjunto. Solo comprueba si hay errores encontrados en un atributo individual del objeto.
class Person < ApplicationRecord
validates :name, presence: true
end
irb> Person.new.errors[:name].any?
=> false
irb> Person.create.errors[:name].any?
=> true
Cubriremos los errores de validación en mayor profundidad en la sección Trabajando con Errores de Validación.
2 Ayudantes de Validación
Active Record ofrece muchos ayudantes de validación predefinidos que puedes usar directamente dentro de tus definiciones de clase. Estos ayudantes proporcionan reglas de validación comunes. Cada vez que una validación falla, se agrega un error a la colección errors
del objeto, y esto se asocia con el atributo que se está validando.
Cada ayudante acepta un número arbitrario de nombres de atributos, por lo que con una sola línea de código puedes agregar el mismo tipo de validación a varios atributos.
Todos ellos aceptan las opciones :on
y :message
, que definen cuándo se debe ejecutar la validación y qué mensaje se debe agregar 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 predeterminado para cada uno de los ayudantes de validación. Estos mensajes se utilizan cuando no se especifica la opción :message
. Echemos un vistazo a cada uno de los ayudantes disponibles.
Para ver una lista de los ayudantes predeterminados disponibles, echa un vistazo a ActiveModel::Validations::HelperMethods
.
2.1 acceptance
Este método valida que un checkbox en la interfaz de usuario fue marcado cuando se envió un formulario. Esto se utiliza típicamente cuando el usuario necesita aceptar los términos de servicio de tu aplicación, confirmar que se ha leído un texto o cualquier concepto similar.
class Person < ApplicationRecord
validates :terms_of_service, acceptance: true
end
Esta verificación se realiza solo si terms_of_service
no es nil
. El mensaje de error predeterminado para este ayudante es "must be accepted". También puedes pasar un mensaje personalizado a través de la opción message
.
class Person < ApplicationRecord
validates :terms_of_service, acceptance: { message: 'must be abided' }
end
También puede recibir una opción :accept
, que determina los valores permitidos que se considerarán como aceptables. Por defecto es ['1', true]
y se puede cambiar fácilmente.
class Person < ApplicationRecord
validates :terms_of_service, acceptance: { accept: 'yes' }
validates :eula, acceptance: { accept: ['TRUE', 'accepted'] }
end
Esta validación es muy específica para aplicaciones web y esta 'aceptación' no necesita registrarse en ningún lugar de tu base de datos. Si no tienes un campo para ello, el ayudante creará un atributo virtual. Si el campo existe en tu base de datos, la opción accept
debe establecerse en o incluir true
o de lo contrario la validación no se ejecutará.
2.2 confirmation
Debes usar este ayudante cuando tengas dos campos de texto que deben recibir exactamente el mismo contenido. Por ejemplo, es posible que desees confirmar una dirección de correo electrónico o una contraseña. Esta validación crea un atributo virtual cuyo nombre es el nombre del campo que debe confirmarse con "_confirmation" añadido.
class Person < ApplicationRecord
validates :email, confirmation: true
end
En tu plantilla de vista podrías usar algo como
<%= text_field :person, :email %>
<%= text_field :person, :email_confirmation %>
NOTA: Esta verificación se realiza solo si email_confirmation
no es nil
. Para requerir confirmación, asegúrate de agregar una verificación de presencia para el atributo de confirmación (veremos presence
más adelante en esta guía):
class Person < ApplicationRecord
validates :email, confirmation: true
validates :email_confirmation, presence: true
end
También hay una opción :case_sensitive
que puedes usar para definir si la restricción de confirmación será sensible a mayúsculas o no. Esta opción por defecto es true.
class Person < ApplicationRecord
validates :email, confirmation: { case_sensitive: false }
end
El mensaje de error predeterminado para este ayudante es "doesn't match confirmation". También puedes pasar un mensaje personalizado a través de la opción message
.
Generalmente, cuando usas este validador, querrás combinarlo con la opción :if
para validar el campo "_confirmation" solo cuando el campo inicial haya cambiado y no cada vez que guardes el registro. Más sobre validaciones condicionales más adelante.
class Person < ApplicationRecord
validates :email, confirmation: true
validates :email_confirmation, presence: true, if: :email_changed?
end
2.3 comparison
Esta verificación validará una comparación entre dos valores comparables.
class Promotion < ApplicationRecord
validates :end_date, comparison: { greater_than: :start_date }
end
El mensaje de error predeterminado para este ayudante es "failed comparison". También puedes pasar un mensaje personalizado a través de la opción message
.
Estas opciones son todas compatibles:
:greater_than
- Especifica que el valor debe ser mayor que el valor suministrado. El mensaje de error predeterminado para esta opción es "must be greater than %{count}".:greater_than_or_equal_to
- Especifica que el valor debe ser mayor o igual al valor suministrado. El mensaje de error predeterminado para esta opción es "must be greater than or equal to %{count}".:equal_to
- Especifica que el valor debe ser igual al valor suministrado. El mensaje de error predeterminado para esta opción es "must be equal to %{count}".:less_than
- Especifica que el valor debe ser menor que el valor suministrado. El mensaje de error predeterminado para esta opción es "must be less than %{count}".:less_than_or_equal_to
- Especifica que el valor debe ser menor o igual al valor suministrado. El mensaje de error predeterminado para esta opción es "must be less than or equal to %{count}".:other_than
- Especifica que el valor debe ser diferente al valor suministrado. El mensaje de error predeterminado para esta opción es "must be other than %{count}".
NOTA: El validador requiere que se proporcione una opción de comparación. Cada opción acepta un valor, proc o símbolo. Cualquier clase que incluya Comparable puede ser comparada.
2.4 format
Este ayudante valida los valores de los atributos probando si coinciden con una expresión regular dada, que se especifica usando la opción :with
.
class Product < ApplicationRecord
validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
message: "only allows letters" }
end
Inversamente, al usar la opción :without
en su lugar, puedes requerir que el atributo especificado no coincida con la expresión regular.
En cualquier caso, la opción :with
o :without
proporcionada debe ser una expresión regular o un proc o lambda que devuelva una.
El mensaje de error predeterminado es "is invalid".
ADVERTENCIA. usa \A
y \z
para coincidir con el inicio y el final de la cadena, ^
y $
coinciden con el inicio/final de una línea. Debido al uso frecuente incorrecto de ^
y $
, necesitas pasar la opción multiline: true
en caso de que uses alguno de estos dos anclajes en la expresión regular proporcionada. En la mayoría de los casos, deberías estar usando \A
y \z
.
2.5 inclusion
Este ayudante valida que los valores de los atributos estén incluidos en un conjunto dado. De hecho, este conjunto puede ser cualquier objeto enumerable.
class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }
end
El ayudante inclusion
tiene una opción :in
que recibe el conjunto de valores que serán aceptados. La opción :in
tiene un alias llamado :within
que puedes usar para el mismo propósito, si lo deseas. El ejemplo anterior usa la opción :message
para mostrar cómo puedes incluir el valor del atributo. Para ver todas las opciones, consulta la documentación del mensaje.
El mensaje de error predeterminado para este ayudante es "is not included in the list".
2.6 exclusion
El opuesto de inclusion
es... ¡exclusion
!
Este ayudante valida que los valores de los atributos no estén incluidos en un conjunto dado. De hecho, este conjunto puede ser cualquier objeto enumerable.
class Account < ApplicationRecord
validates :subdomain, exclusion: { in: %w(www us ca jp),
message: "%{value} is reserved." }
end
El ayudante exclusion
tiene una opción :in
que recibe el conjunto de valores que no serán aceptados para los atributos validados. La opción :in
tiene un alias llamado :within
que puedes usar para el mismo propósito, si lo deseas. Este ejemplo usa la opción :message
para mostrar cómo puedes incluir el valor del atributo. Para ver todas las opciones del argumento del mensaje, consulta la documentación del mensaje.
El mensaje de error predeterminado es "is reserved".
Alternativamente a un enumerable tradicional (como un Array), puedes proporcionar un proc, lambda o símbolo que devuelva un enumerable. Si el enumerable es un rango numérico, de tiempo o de fecha y hora, la prueba se realiza con Range#cover?
, de lo contrario con include?
. Cuando se usa un proc o lambda, se pasa la instancia bajo validación como argumento.
2.7 length
Este ayudante valida la longitud de los valores de los atributos. Proporciona una variedad de opciones, por lo que puedes especificar restricciones de longitud de diferentes maneras:
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
Las posibles opciones de restricción de longitud son:
:minimum
- El atributo no puede tener menos de la longitud especificada.:maximum
- El atributo no puede tener más de la longitud especificada.:in
(o:within
) - La longitud del atributo debe estar incluida en un intervalo dado. El valor para esta opción debe ser un rango.:is
- La longitud del atributo debe ser igual al valor dado.
Los mensajes de error predeterminados dependen del tipo de validación de longitud que se esté realizando. Puedes personalizar estos mensajes usando las opciones :wrong_length
, :too_long
y :too_short
, y %{count}
como un marcador de posición para el número correspondiente a la restricción de longitud que se está utilizando. Aún puedes usar la opción :message
para especificar un mensaje de error.
class Person < ApplicationRecord
validates :bio, length: { maximum: 1000,
too_long: "%{count} characters is the maximum allowed" }
end
Ten en cuenta que los mensajes de error predeterminados son plurales (por ejemplo, "is too short (minimum is %{count} characters)"). Por esta razón, cuando :minimum
es 1, deberías proporcionar un mensaje personalizado o usar presence: true
en su lugar. Cuando :in
o :within
tienen un límite inferior de 1, deberías proporcionar un mensaje personalizado o llamar a presence
antes de length
.
NOTA: Solo se puede usar una opción de restricción a la vez, aparte de las opciones :minimum
y :maximum
, que se pueden combinar juntas.
2.8 numericality
Este ayudante valida que tus atributos tengan solo valores numéricos. Por defecto, coincidirá con un signo opcional seguido de un número entero o decimal.
Para especificar que solo se permiten números enteros, establece :only_integer
en true. Luego usará la siguiente expresión regular para validar el valor del atributo.
/\A[+-]?\d+\z/
De lo contrario, intentará convertir el valor a un número usando Float
. Los Float
se convierten a BigDecimal
usando el valor de precisión de la columna o un máximo de 15 dígitos.
class Player < ApplicationRecord
validates :points, numericality: true
validates :games_played, numericality: { only_integer: true }
end
El mensaje de error predeterminado para :only_integer
es "must be an integer".
Además de :only_integer
, este ayudante también acepta la opción :only_numeric
que especifica que el valor debe ser una instancia de Numeric
e intenta analizar el valor si es una String
.
NOTA: Por defecto, numericality
no permite valores nil
. Puedes usar la opción allow_nil: true
para permitirlo. Ten en cuenta que para las columnas Integer
y Float
, las cadenas vacías se convierten en nil
.
El mensaje de error predeterminado cuando no se especifican opciones es "is not a number".
También hay muchas opciones que se pueden usar para agregar restricciones a los valores aceptables:
:greater_than
- Especifica que el valor debe ser mayor que el valor suministrado. El mensaje de error predeterminado para esta opción es "must be greater than %{count}".:greater_than_or_equal_to
- Especifica que el valor debe ser mayor o igual al valor suministrado. El mensaje de error predeterminado para esta opción es "must be greater than or equal to %{count}".:equal_to
- Especifica que el valor debe ser igual al valor suministrado. El mensaje de error predeterminado para esta opción es "must be equal to %{count}".:less_than
- Especifica que el valor debe ser menor que el valor suministrado. El mensaje de error predeterminado para esta opción es "must be less than %{count}".:less_than_or_equal_to
- Especifica que el valor debe ser menor o igual al valor suministrado. El mensaje de error predeterminado para esta opción es "must be less than or equal to %{count}".:other_than
- Especifica que el valor debe ser diferente al valor suministrado. El mensaje de error predeterminado para esta opción es "must be other than %{count}".:in
- Especifica que el valor debe estar en el rango suministrado. El mensaje de error predeterminado para esta opción es "must be in %{count}".:odd
- Especifica que el valor debe ser un número impar. El mensaje de error predeterminado para esta opción es "must be odd".:even
- Especifica que el valor debe ser un número par. El mensaje de error predeterminado para esta opción es "must be even".
2.9 presence
Este ayudante valida que los atributos especificados no estén vacíos. Utiliza el método Object#blank?
para comprobar si el valor es nil
o una cadena vacía, es decir, una cadena que esté vacía o que consista solo en espacios en blanco.
class Person < ApplicationRecord
validates :name, :login, :email, presence: true
end
Si deseas asegurarte de que una asociación esté presente, necesitarás verificar si el objeto asociado en sí está presente, y no solo la clave foránea utilizada para mapear la asociación. De esta manera, no solo se verifica que la clave foránea no esté vacía, sino también que el objeto referenciado exista.
class Supplier < ApplicationRecord
has_one :account
validates :account, presence: true
end
Para validar registros asociados cuya presencia es requerida, debes especificar la opción :inverse_of
para la asociación:
class Order < ApplicationRecord
has_many :line_items, inverse_of: :order
end
NOTA: Si deseas asegurarte de que la asociación esté tanto presente como válida, también necesitas usar validates_associated
. Más sobre eso abajo.
Si validas la presencia de un objeto asociado a través de una relación has_one
o has_many
, verificará que el objeto no sea blank?
ni marked_for_destruction?
.
Dado que false.blank?
es true, si deseas validar la presencia de un campo booleano, debes usar una de las siguientes validaciones:
# El valor _debe_ ser true o false
validates :boolean_field_name, inclusion: [true, false]
# El valor _no debe_ ser nil, es decir, true o false
validates :boolean_field_name, exclusion: [nil]
Al usar una de estas validaciones, te asegurarás de que el valor NO sea nil
, lo que resultaría en un valor NULL
en la mayoría de los casos.
El mensaje de error predeterminado es "can't be blank".
2.10 absence
Este ayudante valida que los atributos especificados estén ausentes. Utiliza el método Object#present?
para comprobar si el valor no es nil
o una cadena vacía, es decir, una cadena que esté vacía o que consista solo en espacios en blanco.
class Person < ApplicationRecord
validates :name, :login, :email, absence: true
end
Si deseas asegurarte de que una asociación esté ausente, necesitarás verificar si el objeto asociado en sí está ausente, y no solo la clave foránea utilizada para mapear la asociación.
class LineItem < ApplicationRecord
belongs_to :order
validates :order, absence: true
end
Para validar registros asociados cuya ausencia es requerida, debes especificar la opción :inverse_of
para la asociación:
class Order < ApplicationRecord
has_many :line_items, inverse_of: :order
end
NOTA: Si deseas asegurarte de que la asociación esté tanto presente como válida, también necesitas usar validates_associated
. Más sobre eso abajo.
Si validas la ausencia de un objeto asociado a través de una relación has_one
o has_many
, verificará que el objeto no sea present?
ni marked_for_destruction?
.
Dado que false.present?
es false, si deseas validar la ausencia de un campo booleano, debes usar validates :field_name, exclusion: { in: [true, false] }
.
El mensaje de error predeterminado es "must be blank".
2.11 uniqueness
Este ayudante valida que el valor del atributo sea único justo antes de que el objeto se guarde.
class Account < ApplicationRecord
validates :email, uniqueness: true
end
La validación ocurre realizando una consulta SQL en la tabla del modelo, buscando un registro existente con el mismo valor en ese atributo.
Hay una opción :scope
que puedes usar para especificar uno o más atributos que se utilizan para limitar la verificación de unicidad:
class Holiday < ApplicationRecord
validates :name, uniqueness: { scope: :year,
message: "should happen once per year" }
end
ADVERTENCIA. Esta validación no crea una restricción de unicidad en la base de datos, por lo que puede suceder que dos conexiones diferentes a la base de datos creen dos registros con el mismo valor para una columna que deseas que sea única. Para evitar eso, debes crear un índice único en esa columna en tu base de datos.
Para agregar una restricción de unicidad en tu base de datos, usa la declaración add_index
en una migración e incluye la opción unique: true
.
Si deseas crear una restricción de base de datos para evitar posibles violaciones de una validación de unicidad usando la opción :scope
, debes crear un índice único en ambas columnas en tu base de datos. Consulta el manual de MySQL y el manual de MariaDB para obtener más detalles sobre índices de columnas múltiples, o el manual de PostgreSQL para ejemplos de restricciones únicas que se refieren a un grupo de columnas.
También hay una opción :case_sensitive
que puedes usar para definir si la restricción de unicidad será sensible a mayúsculas, insensible a mayúsculas o si debe respetar la colación predeterminada de la base de datos. Esta opción por defecto es respetar la colación predeterminada de la base de datos.
class Person < ApplicationRecord
validates :name, uniqueness: { case_sensitive: false }
end
ADVERTENCIA. Ten en cuenta que algunas bases de datos están configuradas para realizar búsquedas insensibles a mayúsculas de todos modos.
Hay una opción :conditions
que puedes especificar como un fragmento SQL WHERE
para limitar la búsqueda de la restricción de unicidad (por ejemplo, conditions: -> { where(status: 'active') }
).
El mensaje de error predeterminado es "has already been taken".
Consulta validates_uniqueness_of
para obtener más información.
2.12 validates_associated
Debes usar este ayudante cuando tu modelo tenga asociaciones que siempre necesiten ser validadas. Cada vez que intentes guardar tu objeto, se llamará a valid?
en cada uno de los objetos asociados.
class Library < ApplicationRecord
has_many :books
validates_associated :books
end
Esta validación funcionará con todos los tipos de asociación.
PRECAUCIÓN: No uses validates_associated
en ambos extremos de tus asociaciones. Se llamarían entre sí en un bucle infinito.
El mensaje de error predeterminado para validates_associated
es "is invalid". Ten en cuenta que cada objeto asociado contendrá su propia colección errors
; los errores no se propagan al modelo que llama.
NOTA: validates_associated
solo se puede usar con objetos ActiveRecord, hasta ahora todo lo mencionado también se puede usar en cualquier objeto que incluya ActiveModel::Validations
.
2.13 validates_each
Este ayudante valida atributos contra un bloque. No tiene una función de validación predefinida. Debes crear una usando un bloque, y cada atributo pasado a validates_each
será probado contra él.
En el siguiente ejemplo, rechazaremos nombres y apellidos que comiencen con minúsculas.
class Person < ApplicationRecord
validates_each :name, :surname do |record, attr, value|
record.errors.add(attr, 'must start with upper case') if /\A[[:lower:]]/.match?(value)
end
end
El bloque recibe el registro, el nombre del atributo y el valor del atributo.
Puedes hacer cualquier cosa que desees para verificar datos válidos dentro del bloque. Si tu validación falla, debes agregar un error al modelo, haciéndolo inválido.
2.14 validates_with
Este ayudante pasa el registro a una clase separada para la validación.
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
No hay mensaje de error predeterminado para validates_with
. Debes agregar manualmente errores a la colección errors
del registro en la clase del validador.
NOTA: Los errores agregados a record.errors[:base]
se relacionan con el estado del registro en su conjunto.
Para implementar el método de validación, debes aceptar un parámetro record
en la definición del método, que es el registro a validar.
Si deseas agregar un error en un atributo específico, pásalo como primer argumento, como record.errors.add(:first_name, "please choose another name")
. Cubriremos errores de validación en mayor detalle más adelante.
def validate(record)
if record.some_field != "acceptable"
record.errors.add :some_field, "this field is unacceptable"
end
end
El ayudante validates_with
toma una clase, o una lista de clases para usar en la validación.
class Person < ApplicationRecord
validates_with MyValidator, MyOtherValidator, on: :create
end
Como todas las demás validaciones, validates_with
toma las opciones :if
, :unless
y :on
. Si pasas cualquier otra opción, las enviará a la clase del validador como 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
Ten en cuenta que el validador se inicializará solo una vez para todo el ciclo de vida de la aplicación, y no en cada ejecución de validación, así que ten cuidado al usar variables de instancia dentro de él.
Si tu validador es lo suficientemente complejo como para que desees variables de instancia, puedes usar fácilmente un objeto Ruby normal en su lugar:
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
Cubriremos validaciones personalizadas más adelante.
3 Opciones Comunes de Validación
Hay varias opciones comunes compatibles con los validadores que acabamos de revisar, ¡vamos a repasarlas ahora!
NOTA: No todas estas opciones son compatibles con cada validador, consulta la documentación de la API para ActiveModel::Validations
.
Al usar cualquiera de los métodos de validación que acabamos de mencionar, también hay una lista de opciones comunes compartidas junto con los validadores. ¡Las cubriremos ahora!
:allow_nil
: Omite la validación si el atributo esnil
.:allow_blank
: Omite la validación si el atributo está en blanco.:message
: Especifica un mensaje de error personalizado.:on
: Especifica los contextos donde esta validación está activa.:strict
: Lanza una excepción cuando la validación falla.:if
y:unless
: Especifica cuándo la validación debe o no debe ocurrir.
3.1 :allow_nil
La opción :allow_nil
omite la validación cuando el valor que se está validando es nil
.
class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }, allow_nil: true
end
irb> Coffee.create(size: nil).valid?
=> true
irb> Coffee.create(size: "mega").valid?
=> false
Para ver todas las opciones del argumento del mensaje, consulta la documentación del mensaje.
3.2 :allow_blank
La opción :allow_blank
es similar a la opción :allow_nil
. Esta opción permitirá que la validación pase si el valor del atributo es blank?
, como nil
o una cadena vacía, por ejemplo.
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
3.3 :message
Como ya has visto, la opción :message
te permite especificar el mensaje que se agregará a la colección errors
cuando la validación falle. Cuando no se usa esta opción, Active Record usará el mensaje de error predeterminado respectivo para cada ayudante de validación.
La opción :message
acepta un String
o Proc
como su valor.
Un valor de String
para :message
puede contener opcionalmente cualquiera/todos de %{value}
, %{attribute}
y %{model}
, que serán reemplazados dinámicamente cuando la validación falle. Este reemplazo se realiza usando la gema i18n, y los marcadores de posición deben coincidir exactamente, no se permiten espacios.
class Person < ApplicationRecord
# Mensaje codificado
validates :name, presence: { message: "must be given please" }
# Mensaje con valor de atributo dinámico. %{value} será reemplazado
# con el valor real del atributo. %{attribute} y %{model}
# también están disponibles.
validates :age, numericality: { message: "%{value} seems wrong" }
end
Un valor de Proc
para :message
recibe dos argumentos: el objeto que se está validando y un hash con pares clave-valor :model
, :attribute
y :value
.
class Person < ApplicationRecord
validates :username,
uniqueness: {
# objeto = objeto persona que se está validando
# datos = { model: "Person", attribute: "Username", value: <username> }
message: ->(object, data) do
"Hey #{object.name}, #{data[:value]} is already taken."
end
}
end
3.4 :on
La opción :on
te permite especificar cuándo debe suceder la validación. El comportamiento predeterminado para todos los ayudantes de validación integrados es ejecutarse al guardar (tanto cuando estás creando un nuevo registro como cuando lo estás actualizando). Si deseas cambiarlo, puedes usar on: :create
para ejecutar la validación solo cuando se crea un nuevo registro o on: :update
para ejecutar la validación solo cuando se actualiza un registro.
class Person < ApplicationRecord
# será posible actualizar el correo electrónico con un valor duplicado
validates :email, uniqueness: true, on: :create
# será posible crear el registro con una edad no numérica
validates :age, numericality: true, on: :update
# el valor predeterminado (valida tanto en la creación como en la actualización)
validates :name, presence: true
end
También puedes usar on:
para definir contextos personalizados. Los contextos personalizados deben activarse explícitamente pasando el nombre del contexto a valid?
, invalid?
o save
.
class Person < ApplicationRecord
validates :email, uniqueness: true, on: :account_setup
validates :age, numericality: true, on: :account_setup
end
irb> person = Person.new(age: 'treinta y tres')
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)
ejecuta ambas validaciones sin guardar el modelo. person.save(context: :account_setup)
valida person
en el contexto account_setup
antes de guardar.
Pasar un array de símbolos también es aceptable.
class Book
include ActiveModel::Validations
validates :title, presence: true, on: [:update, :ensure_title]
end
irb> book = Book.new(title: nil)
irb> book.valid?
=> true
irb> book.valid?(:ensure_title)
=> false
irb> book.errors.messages
=> {:title=>["can't be blank"]}
Cuando se activa mediante un contexto explícito, las validaciones se ejecutan para ese contexto, así como para cualquier validación sin un contexto.
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"]}
Cubriremos más casos de uso para on:
en la guía de callbacks.
4 Validaciones Estrictas
También puedes especificar validaciones para que sean estrictas y lanzar ActiveModel::StrictValidationFailed
cuando el objeto sea inválido.
class Person < ApplicationRecord
validates :name, presence: { strict: true }
end
irb> Person.new.valid?
ActiveModel::StrictValidationFailed: Name can't be blank
También existe la posibilidad de pasar una excepción personalizada a la opción :strict
.
class Person < ApplicationRecord
validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
end
irb> Person.new.valid?
TokenGenerationException: Token can't be blank
5 Validación Condicional
A veces tendrá sentido validar un objeto solo cuando se cumpla un predicado dado. Puedes hacer eso usando las opciones :if
y :unless
, que pueden tomar un símbolo, un Proc
o un Array
. Puedes usar la opción :if
cuando quieras especificar cuándo la validación debe ocurrir. Alternativamente, si deseas especificar cuándo la validación no debe ocurrir, entonces puedes usar la opción :unless
.
5.1 Usando un Símbolo con :if
y :unless
Puedes asociar las opciones :if
y :unless
con un símbolo correspondiente al nombre de un método que se llamará justo antes de que ocurra la validación. Esta es la opción más comúnmente utilizada.
class Order < ApplicationRecord
validates :card_number, presence: true, if: :paid_with_card?
def paid_with_card?
payment_type == "card"
end
end
5.2 Usando un Proc con :if
y :unless
Es posible asociar :if
y :unless
con un objeto Proc
que se llamará. Usar un objeto Proc
te da la capacidad de escribir una condición en línea en lugar de un método separado. Esta opción es más adecuada para condiciones de una sola línea.
class Account < ApplicationRecord
validates :password, confirmation: true,
unless: Proc.new { |a| a.password.blank? }
end
Como lambda
es un tipo de Proc
, también se puede usar para escribir condiciones en línea aprovechando la sintaxis abreviada.
validates :password, confirmation: true, unless: -> { password.blank? }
5.3 Agrupando Validaciones Condicionales
A veces es útil que múltiples validaciones usen una condición. Esto se puede lograr fácilmente usando 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
Todas las validaciones dentro del bloque with_options
habrán pasado automáticamente la condición if: :is_admin?
.
5.4 Combinando Condiciones de Validación
Por otro lado, cuando múltiples condiciones definen si una validación debe ocurrir o no, se puede usar un Array
. Además, puedes aplicar tanto :if
como :unless
a la misma validación.
class Computer < ApplicationRecord
validates :mouse, presence: true,
if: [Proc.new { |c| c.market.retail? }, :desktop?],
unless: Proc.new { |c| c.trackpad.present? }
end
La validación solo se ejecuta cuando todas las condiciones :if
y ninguna de las condiciones :unless
se evalúan como true
.
6 Realizando Validaciones Personalizadas
Cuando los ayudantes de validación integrados no son suficientes para tus necesidades, puedes escribir tus propios validadores o métodos de validación según lo prefieras.
6.1 Validadores Personalizados
Los validadores personalizados son clases que heredan de ActiveModel::Validator
. Estas clases deben implementar el método validate
, que toma un registro como argumento y realiza la validación sobre él. El validador personalizado se llama usando el método validates_with
.
class MyValidator < ActiveModel::Validator
def validate(record)
unless record.name.start_with? 'X'
record.errors.add :name, "Provide a name starting with X, please!"
end
end
end
class Person < ApplicationRecord
validates_with MyValidator
end
La forma más sencilla de agregar validadores personalizados para validar atributos individuales es con el conveniente ActiveModel::EachValidator
. En este caso, la clase de validador personalizado debe implementar un método validate_each
que toma tres argumentos: registro, atributo y valor. Estos corresponden a la instancia, el atributo a validar y el valor del atributo en la instancia pasada.
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless URI::MailTo::EMAIL_REGEXP.match?(value)
record.errors.add attribute, (options[:message] || "is not an email")
end
end
end
class Person < ApplicationRecord
validates :email, presence: true, email: true
end
Como se muestra en el ejemplo, también puedes combinar validaciones estándar con tus propios validadores personalizados.
6.2 Métodos Personalizados
También puedes crear métodos que verifiquen el estado de tus modelos y agreguen errores a la colección errors
cuando sean inválidos. Debes registrar estos métodos usando el método de clase validate
, pasando los símbolos para los nombres de los métodos de validación.
Puedes pasar más de un símbolo para cada método de clase y las respectivas validaciones se ejecutarán en el mismo orden en que fueron registradas.
El método valid?
verificará que la colección errors
esté vacía, por lo que tus métodos de validación personalizados deben agregar errores a ella cuando desees que la validación falle:
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
if discount > total_value
errors.add(:discount, "can't be greater than total value")
end
end
end
Por defecto, tales validaciones se ejecutarán cada vez que llames a valid?
o guardes el objeto. Pero también es posible controlar cuándo ejecutar estas validaciones personalizadas dando una opción :on
al método validate
, con cualquiera de estos valores: :create
o :update
.
class Invoice < ApplicationRecord
validate :active_customer, on: :create
def active_customer
errors.add(:customer_id, "is not active") unless customer.active?
end
end
Consulta la sección anterior para obtener más detalles sobre :on
.
6.3 Listando Validadores
Si deseas conocer todos los validadores para un objeto dado, no busques más allá de validators
.
Por ejemplo, si tenemos el siguiente modelo usando un validador personalizado y un validador integrado:
class Person < ApplicationRecord
validates :name, presence: true, on: :create
validates :email, format: URI::MailTo::EMAIL_REGEXP
validates_with MyOtherValidator, strict: true
end
Ahora podemos usar validators
en el modelo "Person" para listar todos los validadores, o incluso verificar un campo específico usando validators_on
.
irb> Person.validators
#=> [#<ActiveRecord::Validations::PresenceValidator:0x10b2f2158
@attributes=[:name], @options={:on=>:create}>,
#<MyOtherValidatorValidator:0x10b2f17d0
@attributes=[:name], @options={:strict=>true}>,
#<ActiveModel::Validations::FormatValidator:0x10b2f0f10
@attributes=[:email],
@options={:with=>URI::MailTo::EMAIL_REGEXP}>]
#<MyOtherValidator:0x10b2f0948 @options={:strict=>true}>]
irb> Person.validators_on(:name)
#=> [#<ActiveModel::Validations::PresenceValidator:0x10b2f2158
@attributes=[:name], @options={on: :create}>]
7 Trabajando con Errores de Validación
Los métodos valid?
e invalid?
solo proporcionan un estado resumido sobre la validez. Sin embargo, puedes profundizar en cada error individual usando varios métodos de la colección errors
.
La siguiente es una lista de los métodos más comúnmente usados. Consulta la documentación de ActiveModel::Errors
para obtener una lista de todos los métodos disponibles.
7.1 errors
La puerta de entrada a través de la cual puedes profundizar en varios detalles de cada error.
Esto devuelve una instancia de la clase ActiveModel::Errors
que contiene todos los errores, cada error está representado por un objeto 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
=> []
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.first.details
=> {:error=>:too_short, :count=>3}
7.2 errors[]
errors[]
se usa cuando deseas verificar los mensajes de error para un atributo específico. Devuelve un array de cadenas con todos los mensajes de error para el atributo dado, cada cadena con un mensaje de error. Si no hay errores relacionados con el atributo, devuelve un array vacío.
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)"]
7.3 errors.where
y Objeto de Error
A veces podemos necesitar más información sobre cada error además de su mensaje. Cada error está encapsulado como un objeto ActiveModel::Error
, y el método where
es la forma más común de acceso.
where
devuelve un array de objetos de error filtrados por varios grados de condiciones.
class Person < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
end
Podemos filtrar solo por el attribute
pasándolo como primer parámetro a errors.where(:attr)
. El segundo parámetro se usa para filtrar el type
de error que queremos llamando a errors.where(:attr, :type)
.
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.where(:name)
=> [ ... ] # todos los errores para el atributo :name
irb> person.errors.where(:name, :too_short)
=> [ ... ] # errores :too_short para el atributo :name
Por último, podemos filtrar por cualquier options
que pueda existir en el tipo de error dado.
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.where(:name, :too_short, minimum: 3)
=> [ ... ] # todos los errores de nombre siendo demasiado cortos y el mínimo es 2
Puedes leer varias informaciones de estos objetos de error:
irb> error = person.errors.where(:name).last
irb> error.attribute
=> :name
irb> error.type
=> :too_short
irb> error.options[:count]
=> 3
También puedes generar el mensaje de error:
irb> error.message
=> "is too short (minimum is 3 characters)"
irb> error.full_message
=> "Name is too short (minimum is 3 characters)"
El método full_message
genera un mensaje más amigable para el usuario, con el nombre del atributo capitalizado precedido. (Para personalizar el formato que usa full_message
, consulta la guía de I18n.)
7.4 errors.add
El método add
crea el objeto de error tomando el attribute
, el type
de error y un hash de opciones adicionales. Esto es útil al escribir tu propio validador, ya que te permite definir situaciones de error muy específicas.
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"
7.5 errors[:base]
Puedes agregar errores que estén relacionados con el estado del objeto en su conjunto, en lugar de estar relacionados con un atributo específico. Para hacer esto, debes usar :base
como el atributo al agregar un nuevo error.
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 ..."
7.6 errors.size
El método size
devuelve el número total de errores para el objeto.
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
7.7 errors.clear
El método clear
se usa cuando intencionalmente deseas borrar la colección errors
. Por supuesto, llamar a errors.clear
sobre un objeto inválido no lo hará realmente válido: la colección errors
ahora estará vacía, pero la próxima vez que llames a valid?
o cualquier método que intente guardar este objeto en la base de datos, las validaciones se ejecutarán nuevamente. Si alguna de las validaciones falla, la colección errors
se llenará nuevamente.
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
8 Mostrando Errores de Validación en Vistas
Una vez que has creado un modelo y agregado validaciones, si ese modelo se crea a través de un formulario web, probablemente quieras mostrar un mensaje de error cuando una de las validaciones falle.
Debido a que cada aplicación maneja este tipo de cosas de manera diferente, Rails no incluye ningún ayudante de vista para ayudarte a generar estos mensajes directamente. Sin embargo, debido a la rica cantidad de métodos que Rails te ofrece para interactuar con las validaciones en general, puedes construir los tuyos propios. Además, al generar un scaffold, Rails colocará algo de ERB en el _form.html.erb
que genera, que muestra la lista completa de errores en ese modelo.
Suponiendo que tenemos un modelo que se ha guardado en una variable de instancia llamada @article
, se ve así:
<% 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 %>
Además, si usas los ayudantes de formulario de Rails para generar tus formularios, cuando ocurre un error de validación en un campo, generará un <div>
adicional alrededor de la entrada.
<div class="field_with_errors">
<input id="article_title" name="article[title]" size="30" type="text" value="">
</div>
Luego puedes estilizar este div como desees. El scaffold predeterminado que genera Rails, por ejemplo, agrega esta regla CSS:
.field_with_errors {
padding: 2px;
background-color: red;
display: table;
}
Esto significa que cualquier campo con un error termina con un borde rojo de 2 píxeles.
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.
También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.
Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.
Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.