08: ACTIVE RECORD: Validaciones - LPC-Ltda/Ruby-on-Rails GitHub Wiki
He comprado esta máquina maravillosa - un ordenador. Ahora soy algo así como una autoridad en dioses, así que identifiqué la máquina - me parece ser un dios del Antiguo Testamento con un montón de reglas y sin piedad. -Joseph Campbell
El API de validaciones de Active Model, junto con su funcionalidad suplementaria en Active Record, le permite definir declarativamente estados válidos para sus objetos de modelos. Los métodos de validación enganchan dentro del ciclo de vida de un objeto de modelo Active Record y está habilitado para inspeccionar el objeto para determinar si ciertos atributos están seteados, tienen valores en un determinado rango, o pasan cualquier otro obstáculo que usted especifique.
En este capítulo, describiremos los métodos de validación disponibles y cómo usarlos efectivamente. También exploraremos cómo estos métodos de validación interactúan con sus atributos del modelo y cómo el sistema de mensajes de error empaquetados pueden ser usados efectivamente en la interface de usuario de la aplicación para proveer feedback descriptivo.
Finalmente, cubriremos cómo usar la funcionalidad de validación de Active Model por usted mismo, - clases No-Active Record.
Los problemas de validación son también conocidos como (redoble de tambores, por favor..) errores! Cada objeto de modelo de Active Record contiene una colección de errores, accesibles (sorprendentemente) como el atributo errors
.
Cuando un objeto modelo es válido, la colección errors
está vacía. De hecho, cuando usted llama a valid?
sobre un objeto modelo, realiza los siguientes tres pasos para buscar errores (levemente simplificado):
- Limpia la colección
errors
- Corre las validaciones
- Retorna si la colección
errors
del modelo esta ahora vacía o no
Si la colección errors
final está vacía, el objeto es válido. En casos donde usted tiene que escribir la lógica de validación actual por usted mismo, usted marca un objeto inválido al agregar items a la colección de errores usando su método add
. Tan simple como eso.
Cubriremos los métodos de la clase Errors
en más detalle después. Hace más sentido ver los métodos de validación primero.
Cuando sea posible, usted debe setear validaciones para sus modelos declarativamente usando una o más de los siguientes métodos de clase disponibles para todas las clases Active Record. A menos que se indique lo contrario, todas los métodos validates
aceptan un número variable de atributos más opciones. Hay algunas opciones para estos métodos de validaciones que son comunes para todos ellos, y serán cubiertas al final de esta sección.
El método validate_absence_of
se asegura que atributos específicos estén en blanco. Usa el método blank?
, definido sobre Object
, el cual retorna true
para valores que son nil
o el string en blanco "". Este es el opuesto de la validación comunmente usada validates_presence_of
cubierta luego en esta sección.
class Account < ActiveRecord::Base
validates_absence_of :something_unwanted
end
Cuando la validación validates_absence_of
falla, un mensaje de error es almacenado en el objeto modelo leído "attribute must be blank".
Muchas aplicaciones web tienen pantallas en las cuales el usuario es invitado a aceptar términos de servicio o algún concepto similar, que usualmente involucra el chequeo de un box. No se requiere ninguna columna de base de datos real que coincide con el atributo declarado en la validación. Cuando usted llama este método, creará atributos virtuales automáticamente para cada atributo nombrado que usted especifique. Yo veo esta validación como un tipo de azúcar sintáctica ya que es específica para la programación de aplicaciones web.
class Account < ActiveRecord::Base
validates_acceptance_of :privacy_policy, :terms_of_service
end
Usted puede usar estas validaciones con o sin una columna booleana en la tabla que respalda su modelo. Un atributo transiente será creado si es necesario. Elija almacenar el valor en una base de datos sólo si usted necesita dar seguimiento de si el usuario aceptó los términos para auditoria u otras razones. Eso sí, no aceptando el término evitaría creación del registro, pero es bueno saber lo que se admite.
Cuando la validación validates_acceptance_of
falla, un mensaje de error el almacenado en el objeto modelo "attribute must be accepted".
La opción :accepted
permite de manera simple cambiar el valor que se considera aceptado. El valor por defecto es "1", lo cual coincide con el valor entregado por un checkbox generado usando los métodos helpers de Rails.
class Cancellation < ActiveRecord::Base
validates_acceptance_of :account_cancellation, accept: 'YES'
end
Si usted usa el ejemplo precedente en conjunto con el campo de texto conectado al atributo account_cancellation
, el usuario tendrá que tipear la palabra YES con el fin de que el objeto cancellación sea válido.
Usado para asegurarse que todos los objetos asociados son válidos al grabar. Trabaja con cualquier tipo de asociación y es específica de Active Records (no para Active Model). Enfatizamos el todos porque el comportamiento por defecto de las asociaciones has_many
es asegurar la validez de los nuevos registros hijos al grabar.
Sugerencia: usted probablemente no necesita esta validación particular hoy en día ya que las asociaciones
has_many
por defecto tienenvalidate: true
. Adicionalmente, note que una de las implicancias es que setearvalidate: true
sin cuidado en una asociaciónbelongs_to
puede causar problemas de loops infinitos.
Un validates_associated
sobre belongs_to
no fallará si la asociación es nil
. Si usted necesita asegurarse de que la asociación está llena y válida, usted debe usar validates_associated
en conjunto con validates_presence_of
.
Es posible obtener un comportamiento similar usando una combinación de las opciones
:autosave
y:validate
sobre unhas_many
.
El método `validates_confirmation_of es otro caso de azucar sintáctica para aplicaciones web, ya que es común incluir ingresos duales de campos de texto para asegurarnos que el usuario ingrese correctamente datos críticos como la password o la dirección de email. Esta validación creará un atributo virtual para el valor de confirmación y compara los dos atributos para asegurarse que ellos coincidan con el propósito de que el modelo sea válido.
Aquí un ejemplo, usando nuestro modelo ficticio Account
nuevamente:
class Account < ActiveRecord::Base
validates_confirmation_of :password
end
La interface de usuario usada para setear los valores del modelo Account
necesitará incluir campos de texto extra nombrados con el sufijo _confirmation
, y cuando sea enviado, el valor de estos campos deberá coincidir para pasar la validación. Un ejemplo simplificado del código de la coincidencia en la vista es provisto.
= form_for account do |f|
= f,label :login
= f.text_field : login
= f.label :password
= f.password_field :password
= f.label :password_confirmation
= f.password_field :password_confirmation
= f.submit
El método validates_each
es una forma un poco más libre que sus compañeros en la familia de validaciones en el sentido que no tiene una función de validación predefinida. A cambio, usted le entrega un arreglo de nombres de atributos para chequear y proporciona un block Ruby para ser usado en el chequeo de la validez de cada atributo. Note que los parámetros para la instancia del modelo (record), el nombre del atributo como un símbolo, y el valor a chequear son pasados como parámetro del block. La función block designa el objeto modelo como válido o no en la medida que agrega a su arreglo errors
o no. El valor que retorna el block es ignorado.
NO hay muchas situaciones donde este método es necesario, pero un ejemplo plausible es cuando interactuamos con servicios externos para la validación. Usted puede envolver la validación externa en una fachada específica para su aplicación y entonces llamarla usando un block validates_each
.
class Invoice < ActiveRecord::Base
validates_each :supplier_id, :purchase_order do |record, attr, value|
record.errors.add(attr) unless PurchasingSystem.validate(attr, value)
end
end
Para usar validates_format_of
, usted debe conocer como usar expresiones regulares de Ruby (http://rubular.com). Pase al método uno o más atributos para chequear y una expresión regular como la opción (requerida) :with
. Un buen ejemplo, como se muestra en la documentación Rails, es chequear el formato de un email válido:
class Person < ActiveRecord::Base
validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/
end
Por cierto, este ejemplo no es un corrector de formato de dirección de correo electrónico RFC compilant. Si usted necesita validar direcciones de email, intente el plugin en http://github.com/spectator/validates_email
Las expresiones regulares son increíbles pero pueden ser muy complejas, particularmente cuando validan nombres de dominios o direcciones de correo. Usted puede usar #{} dentro de una expresión regular, así divide su expresión regular en pedazos como este:
validates_format_of :name, with: /\A((localhost)|#{DOMAIN}|#{NUMERIC_IP})#{PORT}\z/
Esta expresión en más directa y fácil de comprender. Las constantes mismas no son fáciles de entender pero más simples de si ellas fueran enredadas juntas:
PORT = /(([:]\d+)?)/
DOMAIN = (/[a-z0-9\-]+\.?)*([a-z0-9]{2,})\.[a.z]{2,}/
NUMERIC_IP = /(?>(?:1?\d?\d|2[0-4]\d|25[0-5])\.){3}(?:1?\d?\d|2[0-4]\d|25[0-5])(?:\/(?:[12]?\d|3[012])|-(?:1\d?\d|2[0-4]\d|25[0-5])\.){3}(?:1?\d?\d|2[0-4]\d|25[0-5]))?/
Voy a tomar tu legibilidad y levantar su prueba de aislamiento. Tu expresión regular misma es una constante así que se puede probar
Estos métodos toman un número variable de nombres de atributos y una opción :in
. Cuando se ejecutan, chequean que el valor del atributo está incluido (o excluido respectivamente) en el objeto enumerable pasado como la opción :in
.
Los ejemplos en la documentación Rails son probablemente algunas de las mejores ilustraciones de su uso, así que me inspiraré en ellos:
class Person < ActiveRecord::Base
validates_inclusion_of :gender, in: %w( m f ), message: 'O RLY?'
...
class Acoount < ActiveRecord::Base
validates_exclusion_of :username, in: %w( admin superuser ), message: 'Borat says "Naughty, naughty!"'
...
Note que en los ejemplos, he introducido el uso de la opción :message
, común a todos los métodos de validación, para personalizar el mensaje de error construido y agregado a la colección Errors
cuando la validación falla. Cubriremos el mensaje de error por defecto y cómo personalizarlo después en este capítulo.
El método validates_length_of
toma una variedad de diferentes opciones y le permite especificar restricciones de largo para un atributo dado de su modelo.
class Account < ActiveRecord::Base
validates_length_of :login, minimum: 5
end
** 8.2.8.1 Opciones de restricción**
Las opciones :minimum
y :maximum
trabajan como se espera, pero no las use juntas. Para especificar un rango, use la opción :within
y pase un rango Ruby, como en los siguientes ejemplos:
class Account < ActiveRecord::Base
validates_length_of :username, within: 5..20
end
Para especificar el largo exacto use la opción :is
:
class Account < ActiveRecord::Base
validates_length_of :account_number, is: 16
end
** 8.2.8.2 Opciones de mensaje de error**
Rails le da la habilidad de generar un mensaje de error detallado para validates_length_of
vía las opciones :too_long
, :too_short
y wrong_length
. Use %{count} en su mensaje de error personalizado como un placeholder para el número correspondiente a la restricción.
class Account < ActiveRecord::Base
validates_length_of :account_number, is: 16, wrong_length: "Should be %{count} charecters long"
end
El método torpemente llamado validates_numericality_of
es usado para asegurarnos que un atributo puede tener sólo un valor numérico.
La opción :only_integer
le permite especificar adicionalmente que el valor debe ser un valor entero y por defecto es falso.
class Account < ActiveRecord::Base
validates_numericality_of :account_number, only_integer: true
end
Las opciones :even
y odd
hacen lo que usted espera y son útiles para cosas como, no sé, chequear la valencia de electrones (En realidad no soy lo suficientemente creativo para pensar como puede usar esta validación, pero usted podrá).
Las siguientes opciones de comparación también están disponibles:
:equal_to
:greater_than
:greater_than_or_equal_to
:less_than
:less_than_or_equal_to
:other_than
8.2.9.1 Infinito y otros valores especiales float
Interesante, Ruby tiene el concepto de infinito empaquetado. Si usted no ha visto el infinito antes, intente lo siguiente en la consola:
>> (1.0/0.0)
=> Infinity
Infinity
es considerado un numero para validates_numericality_of
. Las bases de datos (como PostgreSQL) que soportan el estándar IEEE 754 pueden permitir que valores especiales de float como Infinity
puedan ser almacenados. Los otros valores especiales son el infinito positivo (-INF), infinito negativo (-INF), y no-número (NaN). IEEE 754 también distingue entre el cero positivo (+0) y el cero negativo (-0). NaN
es usado para representar resultados de operaciones indefinidos.
Uno de los métodos de validación más comunes, validates_presence_of
es usado para denotar atributos obligatorios. Este método chequea si un atributo está en blanco usando el método blank?
, definido sobre Object
, el cual retorna true
para valores que son nil
o strings en blanco ("").
class Account < ActiveRecord::Base
validates_presence_of :username, :email, :account_number
end
Un error común es usar validates_presence_of
con un atributo booleano, como el campo que respalda un check box. Si usted desea asegurarse que el atributo es true
, use validates_acceptace_of
. El valor booleano false
es considerado en blanco, así si usted desea asegurarse de que sólo valores true
o false
son seteados en su modelo, use el siguiente patrón:
validates_inclusion_of :protected, in: [true, false]
8.2.10.1 Validando la presencia y/o existencia de objetos asociados
Cuando usted esta tratando de asegurarse que una asociación esta presente, pase validates_presence_of
a su atributo llave externa, no a la variable de asociación misma. Note que la asociación puede fallar en casos donde tanto el padre como el hijo están sin grabar (ya que la llave externa estará en blanco).
Muchos desarrolladores tratan de usar esta validación con la intención de asegurarse que los objetos asociados existan realmente en la base de datos. Personalmente, pienso que puede ser un caso de uso válido para una restricción de llave externa actual en la base de datos, pero si usted desea hacer el chequeo en su código Rails, entonces emule el siguiente ejemplo:
class Timesheet < ActiveRecord::Base
belongs_to :user
validates_presence_of :user_id
validate :user_exists
protected
def user_exists
errors.add(:user_id, "doesn't exist") unless User.exists?(user_id)
end
end
Sin una validación, si su aplicación viola una restricción de llave externa, usted puede obtener una excepción de Active Record.
El método validates_uniqueness_of
, también exclusivo de Active Record, se asegura que el valor de un atributo es único para todos los modelos del mismo tipo. Esta validación no trabaja al agregar una restricción de unicidad al nivel de la base de datos. Trabaja construyendo y ejecutando una consulta por registros coincidentes en la base de datos. Si cualquier registro es retornado cuando este método hace la consulta, la evaluación falla.
class Account < ActiveRecord::Base
validates_uniqueness_of :username
end
Al especificar una opción :scope
, atributos adicionales pueden ser usados para determinar la unicidad. Usted puede pasar a :scope
uno o más nombres de atributos como símbolos (poniendo múltiples símbolos en un arreglo).
class Address < ActiveRecord::Base
validates_uniqueness_of :line_two, scope: [:line_one, :city, :zip]
end
También es posible especificar si hacemos una restricción de unicidad con sensibilidad a las mayúsculas vía la opción case_sensitive
(ignorada para atributos no texto).
Con la inclusión del soporte para arreglos de columnas de PostgreSQL en Rails 4, el método validates_uniqueness_of
puede ser usado para validar que todos los items en ese arreglo son únicos. Los arreglos de columnas de PostgreSQL son cubiertos en detalle en el Capítulo 9 "Active Record Avanzado".
Esta validación no es a prueba de tontos, debido a una potencial competencia entre la consulta SELECT que chequea los duplicados y los INSERT o UPDATE que almacenan el registro. Una excepción de Active Record puede ser generada como un resultado, así que prepárese para manejar esta falla en su controlador. Yo recomiendo que usted use una restricción de índice único en la base de datos si usted debe estar absolutamente seguro de que el valor de la columna es único.
8.2.11.1 Obligando la unicidad de modelos join
En la medida que usted use modelos join (con has_many :through
), es muy común necesitar que la relación sea única. Considere una aplicación que modelo students, courses y resgistrations con el código siguiente:
class Student < ActiveRecord::Base
has_many :resgistrations
has_many :courses, through: :registrations
end
class Registration < ActiveRecord::Base
belongs_to :student
belongs_to :course
end
class Course < ActiveRecord::Base
has_many :registrations
has_many :students, through: :resgistrations
end
Como asegurarnos de que un usuario no se registre más de una vez a un curso particular? La forma más concisa es usar validates_uniqueness_of
con una restricción :scope
. Lo importante de recordar con esta técnica es referenciar la llave externa, no los nombres de las asociaciones:
class Registration < ActiveRecord::Base
belongs_to :student
belongs_to :course
validates_uniqueness_of :student_id, scope: course_id, message: "can only register once per course"
end
Note que ya que el mensaje de error por defecto generado cuando esta validación falla podría no tener sentido, se puede usar "Student can only register once per course."
Los lectores astutos notarán que la validación fue sobre
student_id
pero el mensaje de error referencia a "Students".
8.2.11.2 Limitar la vista de la restricción
A partir de Rails 4, uno puede especificar criterios que restrinjan una validación de unicidad contra un conjunto de registros setando la opción :conditions
.
Para ilustrar, asumamos que tenemos un artículo que requiere títulos que deben ser únicos contra todos los artículos publicados en una base de datos. Podemos lograr esto usando validates_uniqueness_of
al hacer lo siguiente:
class Article < ActiveRecord::Base
validates_uniqueness_of :title, conditions: -> { where.not(published_at: nil) }
...
end
Cuando el modelo es grabado, Active Record consultará por el título contra todos los artículos en la base de datos que son publicados. Si no se retornan resultados, el modelo es válido.
Todos los métodos de validación que hemos discutido hasta el momento son esencialmente locales a la clase en la cual son usados. Si usted desea desarrollar una suite de clases de validación personalizadas y reusables, entonces usted necesita una forma de aplicarlos a sus modelos, y eso es lo que el método validates_with
le permite hacer.
Para implementar un validador personalizado, extender ActiveRecord::Validator
e implemente el método validate
. El registro será validado si está disponible como record
, y usted manipula su hash errors
para hacer el log de la validación.
Los ejemplos siguientes, desde el excelente blog de Ryan Daigle sobre esta característica (http://ryandaigle.com/articles/2009/8/11/what-s-new-in-edge-rails-independent-model-validators), muestra un validador de campo email reusable:
class EmailValidator < ActiveRecord::Validator
def validate()
record.errors[:email] << "is not valid" unless record.email =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/
end
end
class Account < ActiveRecord::Base
validates_with EmailValidator
end
El ejemplo asume la existencia de un atributo email en el registro. Si usted necesita hacer su validador reusable más flaxible, usted puede acceder la opción de validación en tiempo de ejecución vía el hash options
como el siguiente código:
class EmailValidator < ActiveRecord::Validator
def validate()
email_field = options[:attr]
record.errors[email_field] << "is not valid" unless record.send(email_field) =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/
end
end
class Account < ActiveRecord::Base
validates_with EmailValidator, attr: :email
end
Donde quiera que usted realice una operación bang (como save!
) y la validación falla, usted debe estar preparado para rescatar ActiveRecord::RecordInvalid
. Las fallas de la validación pueden causar que RecordInvalid
sea levantado y su mensaje contendrá una descripción de la falla.
Aquí un pequeño ejemplo desde una de mis aplicaciones que tiene validaciones restrictivas sobre el modelo User
.
>> u = User.new
=> #<User ...>
>> u.save!
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank,
password confirmation can't be blank, Password is too short (nimimum
is 5 characters), Email can't be blank, Email address format is bad
Las siguientes opciones aplican a todos los métodos de validación.
En algunos casos, usted sólo necesita gatillar una validación si el valor está presente -en otras palabras, el atributo es opcional. Hay dos opciones que proveen esta funcionalidad.
La opción :allow_blank
salta la validación si el valor es blank de acuerdo al método blank?
. Similarmente, la opción :allow_nil
salta la validación si el valor del atributo es nil
; sólo chequea nil
, los strings vacíos no estan considerados nil -son considerados blank.
Las opciones :if
y :unless
son cubiertas en la siguiente sección, "Validaciones condicionales".
Como hemos discutido anteriormente en el capítulo, la forma en que el proceso de validación registra fallas es agregando ítems a la colección Errors
del objeto modelo que está siendo chequeado. Parte del ítem error es un mensaje específico que describe la falla de validación. Todos los métodos de validación aceptan la opción :message
para que usted pueda sobre-escribir el formato del mensaje de error por defecto.
class Account < ActiveRecord::Base
validates_uniqueness_of :username, message: "is already taken"
end
El archivo local de English en ActiveModel define la mayoría de los templates de mensajes de error estándar.
inclusion: "is not inlcuded in the list"
exclusion: "is reserved"
invalid: "in invalid"
confirmation: "doesn't match %{attribute}"
accepted: "must be accepted"
empty: "can't be empty"
blank: "can't be blank"
present: "must be blank"
too_long: "is too long (maximum is %{count} characters)"
too_short: "is too short (minimum is %{count} characters)"
wrong_length: "is the wrong length (should be %{count} characters)"
not_a_number: "is not a number"
not_an_integer: "is not an integer"
greater_than: "must be greater than %{count}"
greater_than_or_equal_to: "must be grater than or equal to &{count}"
equal_to: "must be equal to %{count}"
less_than: "must be less than &{count}"
less_than_or_equal_to: "must be less than or equal to %{count}"
other_than: "must be other than %{count}"
odd: "must be odd"
even: "must be even"
El mensaje por defecto sólo usa la variable count
para interpolaciones, donde es apropiado, pero model
, attribute
y value
están siempre disponibles.
validates_uniqueness_of :username, message: "%{value} is already registered"
Por defecto, las validaciones corren al grabar (create y update). Si usted necesita, puede limitar una validación dada a sólo una de esas operaciones pasando una opción :on
con :create
o :update
.
Asumiendo que su aplicación no soporta cambios de email, un buen uso para on: :create
puede ser en conjunto con validates_uniqueness_of
, ya que la consulta de unicidad en bases de datos grandes puede consumir mucho tiempo.
class Account < ActiveRecord::Base
validates_uniqueness_of :email, on: :create
end
Nueva en Rails 4 es la opción de validación :strict
. Seteando :strict
en true
causa que una excepción -ActiveModel::StrictValidationFailed
- sea levantada cuando el modelo no es válido.
class Account < ActiveRecord::Base
validates :email, presence: { strict: true }
end
Para sobre-escribir el tipo de excepción levantada sobre el error, pase la excepción personalizada a la opción :strict
.
Ya que todos los métodos de validación son implementados vía el API de Callbacks de Active Model, ellos aceptan también las opciones :if
y :unless
para determinar en tiempo de ejecución (y no durante la definición de clase) si la validación necesita ser ejecutada o no. Los siguientes tres tipos de argumentos pueden ser entregados como opciones :if
y :unless
:
Simbolo El nombre de un método a invocar como un símbolo. Esta es probablemente la opción más común y ofrece el mejor desempeño.
String* Un retazo de código Ruby a eval
puede ser útil cuando la condición es realmente muy corta, pero tenga en mente que evaluar instrucciones es relativamente más lento.
Proc Un bloque de código para ser evaluado con instance_eval
, de modo que self
es el registro actual. Quizá la opción más elegante para condicionales de una línea.
validates_presence_of :approver, if: -> { approved? && !legacy? }
Cuando tiene sentido usar validaciones condicionales? La respuesta es cuando un objeto pueda ser válidamente registrado en más de un estado. Un ejemplo muy común involucra el modelo User
(o Person
), usado para el login y autenticación.
validates_presence_of :password, if: :password_requiered?
validates_presence_of : password_confirmation, if: :password_required?
validates_length_of :password, within 4..40, if: :password_required?
validates_confirmation_of :password, if: :password_required?
Este código no es DRY (es repetitivo). Usted puede refactorizarlo para hacerlo un poco más dry usando el método with_options
que Rails mezcla dentro del objeto.
with_options if: password_required? do |user|
user.validates_presence_of :password
user.validates_presence_of :password_confirmation
user.validates_length_of :password, within: 4..40
user.validates_confirmation_of :password
end
Todas las validaciones ejemplo chequean los dos casos cuando un campo password (texto plano) debe ser requerido con el fin de que el modelo sea válido.
def password_requiered?
encrypted_password.blank? || !password.blank?
end
El primer caso es si el atributo encrypted_password
es blank, porque eso significa que estamos tratando con una nueva instancia de User
a la que aún no se le ha dado una password. El otro caso es cuando el atributo password no es blank; quizá esto pasa durante la actualización y el usuario intenta volver a setear su propia password.
Otra forma de generar validaciones condicionales esta soportadas por los contextos de validación. Declare una validación y pase el nombre de un contexto de validación específico de la aplicación como valor de la opción :on
. Esta validación puede ahora ser sólo chequeada cuando es explícitamente invocada usando `record.valid?(context_name).
Considere el ejemplo siguiente que involucra una aplicación de generación de reportes. Grabar un reporte sin nombre está bien pero publicar uno sin nombre no.
class Report < ActiveReport::Base
validates_presence_of :name, on: : publish
end
class ReportsController < ApplicationController
expose(:report)
# POST /reports/1/publish
def publish
if report.valid? :publish
redirect_to report, notice: "Report published"
else
flash.now.alert = "Can't publish unnamed reports!"
render :show
end
end
end
Introducido en Rails 3, el método validates
identifica un atributo y acepta opciones que corresponden a las que hemos cubierto en el capítulo. Usando validates
puede apretar el código de su modelo simpáticamente.
validates :username, presence: true,
format: { with: /[A-Za-z0-9]+/ },
length: { minimum: 3 },
uniqueness: true
Las siguientes opciones están disponibles para usar con el método validates
.
absence: true Alias para validates_absence_of
. Entrega opciones adicionales al reemplazar true
con un hash.
validates :unwanted, absence: { message: "You shouldn't have set that" }
acceptance: true Alias para validates_acceptance_of
, usado tipicamente con check boxes que indican la aceptación de términos. Entrega opciones adicionales al reemplazar true
por un hash.
validates :terms, acceptance: { message: "You must accept terms." }
confirmation: true Alias para validates_confirmation_of
, usado típicamente para asegurarnos que los campos de confirmación email y password coincidan correctamente. Entrega opciones adicionales al reeplazar true
por un hash.
validates :email, confirmation: { message: 'Try again.' }
exclusion: { in: [1,2,3] } Alias para validates_exclusion_of
. Si su única opción es el arreglo contra el cual se excluye, usted puede acortar la sintaxis entregando un arreglo como el valor.
validates :username, exclusion: %w(admin superuser)
format: { with: /.*/ } Alias para validates_format_of
. Si su única opción es la expresión regular, usted puede acortar la sintaxis al valor como lo siguiente:
format: /[A-Za-z0-9]+/
inclusion: { in: [1,2,3] } Alias para validates_inclusion_of
. Si su única opción es el arreglo contra lo cual se incluye, usted puede acortar la sintaxis hasta el arreglo de valores.
validates :gender, inclusion: %w(male female)
length: { minimum: 0, maximum: 1000} Alias para validates_length_of
. Si su únicas opciones son el mínimo y el máximo, usted puede acortar la sintaxis entregando un rango de valores Ruby.
validates :username, length: 3..20
numericality: true Alias para validates_numericality_of
. Entrega opciones adicionales reemplazando true
por un hash.
validates :quantity, numericality: { message: 'Supply a number.' }
presence: true Alias para validates_presence_of
. Entrega opciones adicionales al reemplazar true
por un hash.
validates :username, presence: { message: 'How do you expect to login?' }
uniqueness: true Alias para validates_uniqueness_of
. Entrega opciones adicionales al reemplazar true
por un hash.
validates :quantity, uniqueness: { message: "You're SOL on that login choice, buddy!" }
Cuando las macros de validación declarativas no son suficientes para su aplicación, Rails le da algunas técnicas personalizadas.
Rails tiene la habilidad de agregar macros de validación personalizada (disponible para todas sus clases de modelos) extendiendo ActiveModel::EachValidator
.
El siguiente ejemplo es tonto pero mustra claramente la funcionalidad.
class ReportLikeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value["Report"]
record.errors.add(attribute, 'does not appear to be a Report')
end
end
end
Ahora que su validación personalizada existe, esta disponible para ser usada con la macro validates
en su modelo.
class Report < ActiveRecord::Base
validates :name, report_like: true
end
La llave report_like
es inferida desde el nombre de la clase del validador, el cual en este caso es ReportLikeValidator
.
Usted puede recibir opciones vía el método validates
agregando un método initializer
a su clase de validador personalizado. Por ejemplo, hagamos que ReportLikeValidator
más genérico.
class LikeValidator < ActiveModel::EachValidator
def initializer(options)
@with = options[:with]
super
end
def validate_each(record, attribute, value)
unless value[@with]
record.errors.add(attribute, "does not appear to be like #{@with}")
end
end
end
Nuestro código de modelo podría cambiar a lo siguiente:
class Report < ActiveRecord::Base
validates :name, like: { with: "Report" }
end
Esta técnica involucra la herencia desde ActiveModel::Validator
y la implementación de un método validate
que toma el registro a validar.
Lo mostraré con un ejemplo realmente malvado.
class RandomlyValidator < ActiveModel::Validator
def validate(record)
record.errors[:base] << "FAIL #1" unless first_hurdle(record)
record.errors[:base] << "FAIL #2" unless second_hurdle(record)
record.errors[:base] << "FAIL #3" unless third_hurdle(record)
end
private
def first_hurdle(record)
rand > 0.3
end
def second_hurdle(record)
rand > 0.6
end
def third_hurdle(record)
rand > 0.9
end
Use su validador personalizado en un modelo con la macro validates_with
.
class Report < ActiveRecord::Base
validates_with RandomlyValidator
end
Un método de instancia validate
puede ser una forma de chequear el estado de su objeto holísticamente y poner el código para hacerlo dentro de la clase modelo misma. (Esta es una técnica antigua que no puedo endosar completamente, agrega complejidad a su clase de modelo innecesariamente, dado lo fácil que es crear una clase de validador personalizado).
Por ejemplo, asuma que usted está tratando con un objeto modelo con un conjunto de tres atributos enteros (:attr1, :attr2, y :attr3) y un atributo total precalculado. El total debe ser siempre igual a la suma de los tres atributos:
class CompletelyLameTotalExample < ActiveRecord::Base
def validate
if total != (attr1 + attr2 + attr3)
errors[:total] << "The total doesn't add up!"
end
end
end
Usted puede alternativamente agregar un mensaje de error al objeto completo en lugar de sólo a un atributo particular usando la llave :base
, como a continuación:
errors[:base] << "The total doesn't add up!"
Recuerde, la forma de marcar un objeto como no válido es agregar a su objeto Errors
. El valor retornado de un método de validación personalizado no es usado.
Los métodos update_attribute
y update_column
no invocan validaciones, aunque su método acompañante update
si lo hace. Quienquiera que haya escrito los dosumentos de las API piensa que este comportamiento es "especialmente útil para banderas booleanas sobre registros existentes".
Yo no sé si esto es verdad o no, pero sí sé que es fuente de discordia en la comunidad. Desafortunadamente, no tengo mucho más que agregar más que un simple consejo de sentido común. Sea muy cuidadoso al usar los métodos update_attribute
o update_column
. Pueden llevar sus modelos fácilmente a estados no válidos.
Algunos métodos son provistos para permitirle agregar errores de validación a la colección manualmente y alterar el estado del hash Errors
.
8.8.0.1 errors[:base] = msg
Agrega un mensaje de error relacionado con el estado del objeto en su totalidad y no con el valor de un atributo en particular. Construya sus mensajes de error con frases completas, porque Rails no hace ningún proceso adicional para hacerlas más legibles.
8.8.0.2 errors[:attribute] = msg
Agrega un mensaje de error relacionado con un atributo en particular. El mensaje debe ser un fragmento de frase que se lea naturalmente cuando se anteponga al nombre en mayúsculas del atributo.
8.8.0.3 clear
Como usted puede imaginar, el método clear
limpia la colección Errors
.
También es posible chequear el objeto Errors
por fallas de validación en atributos específicos con sólo usar la notación de paréntesis cuadrados. Un arreglo es siempre retornado, pero estará vacío si no hay errores de validación para el atributo especificado.
>> user.errors[:login]
=> ["Zed is already registered"]
>> user.errors[:password]
=> []
Alternativamente, uno puede accesar los mensajes de error completos para un atributo específico usando el método full_messages_for
, como si estuviera accesando fallas de validación para atributos con la notación de paréntesis cuadrados, un arreglo será siempre retornado.
>> user.errors.full_messages_for(:email)
=> ["Email can't be blank"]
A pesar de que las validaciones son código declarativo, si usted está haciendo TDD, entonces usted deberá especificarlas antes de escribirlas. Afortunadamente, la librería Shoulda Matchers de Thoughtbot contiene un conjunto de matchers diseñados para probar fácilemente validaciones.
describe Post do
it { should validate_uniqueness_of(:title) }
it { should validate_presence_of(:body).with_message(/wtf/) }
it { should validate_presence_of(:title) }
it { should validate_numericality_of(:user_id) }
end
describe User do
it { should_not allow_value("blah").for(:email) }
it { should_not allow_value("b lah").for(:email) }
it { should allow_value("[email protected]").for(:email) }
it { should allow_value("[email protected]").for(:email) }
it { should ensure_length_of(:email)is_at_least(1).is_at_most(100) }
it { should ensure_inclusion_of(:age).in_range(1..100) }
end