1 Resumo das Validações
Este é um exemplo de uma validação simples:
class Person < ApplicationRecord
validates :name, presence: true
end
irb> Person.create(name: "John Doe").valid?
=> true
irb> Person.create(name: nil).valid?
=> false
Como você pode ver, nossa validação nos deixa saber que nossa Person
não é
válida sem o atributo name
. A segunda Person
não existirá no banco de dados.
Antes de entrarmos em maiores detalhes, vamos falar sobre como as validações funcionam na nossa aplicação.
1.1 Por que usar validações?
Validações são usadas para garantir que só dados válidos sejam salvos no seu banco de dados. Por exemplo, pode ser importante para sua aplicação garantir que todo usuário forneça um endereço de e-mail e endereço de correspondência válidos. Validações de model são a melhor maneira de garantir que só dados válidos sejam salvos em seu banco de dados. Eles são bancos de dados agnósticos, não podem ser contornados por usuários, e são fáceis de manter e de testar. O Rails fornece ajudantes build-in para necessidades comuns, e também permite que você crie seus próprios métodos de validação.
Exitem outros modos de validar dados antes deles serem salvos no seu banco de dados, incluindo restrições nativas do banco de dados, validações no lado do cliente e validações no nível do controller. Este é um sumário dos prós e contras:
- Restrições no banco de dados e/ou procedimentos armazenados tornam as validações dependentes do banco de dados e podem tornar o processo de testar e a manutenção mais difíceis. No entanto, se seu banco de dados é usado por outras aplicações, pode ser uma boa ideia usar algumas restrições diretamente no banco de dados. Adicionalmente, validações no nível de banco de dados são seguras para lidar com algumas coisas (como singularidade em tabelas muito utilizadas) que seriam difíceis de implementar de outra forma.
- Validações no lado do cliente são úteis, mas no geral não são seguras quando utilizadas sozinhas. Se elas forem implementadas usando JavaScript, elas podem ser contornadas se o JavaScript estiver desligado no navegador do usuário. No entanto se forem combinadas com outras técnicas, essas validações podem ser um método mais conveniente de fornecer ao usuário um retorno imediato enquanto eles navegam no seu site.
- Utilizar validações no nível do controller pode ser tentador, mas frequentemente se tornam pesadas e de manutenção e testagem difíceis. Sempre que possível, é uma boa prática manter seus controllers leves, o que irá tornar a sua aplicação prazerosa de se trabalhar com o passar do tempo.
Escolha essa opção de validação em alguns casos específicos. É da opinião da equipe do Rails que as validações de model são mais apropriadas na maior parte das circunstâncias.
1.2 Quando as validações ocorrem?
Existem dois tipos de objetos de Active Record: aqueles que correspondem
a uma linha no seu banco de dados e aqueles que não correspondem. Quando você cria
um objeto novo, por exemplo, usando o método new
, esse objeto ainda não existe no
banco de dados. Uma vez que você chame o save
sob esse objeto ele será salvo na
tabela apropriada no seu banco de dados. O Active Record usa o método de
instância new_record?
para determinar se o objeto já existe no banco de dados ou
não.
Considere a seguinte classe do Active Record:
class Person < ApplicationRecord
end
Podemos ver como ela funciona olhando para o resultado no 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
Ao criar e salvar um novo record será enviada uma operação SQL de
INSERT
para o seu banco de dados. Atualizando um registro
existente irá mandar uma operação SQL de UPDATE
no lugar.
Validações são tipicamente realizadas antes que esses comandos sejam
enviados para seu banco de dados. Se alguma validação falhar, o objeto será
marcados como inválido e o Active Record não irá executar as
operações de INSERT
ou UPDATE
. Isso evita que um dado
inválido seja armazenado no banco de dados. Você pode escolher validações
específicas que atuem quando um objeto for criado, salvo, ou editado.
Existem muitos modos de alterar o estado de um objeto no banco de dados. Alguns métodos irão acionar validações, mas alguns não vão. Isso significa que é possível salvar um objeto inválido no banco de dados se você não tomar cuidado.
Os métodos a seguir acionam validações e só vão salvar objetos que forem válidos no banco de dados:
create
create!
save
save!
update
update!
As versões bang (ex: save!
) levantam uma exceção se o objeto for
inválido. As versões normais não fazem isso: save
e update
retornam false
,
e create
retorna o objeto.
1.3 Pulando Validações
Os seguintes métodos pulam validações, e irão salvar o objeto no banco de dados independente da sua validade. Eles devem ser usados com cuidado.
decrement!
decrement_counter
increment!
increment_counter
insert
insert!
insert_all
insert_all!
toggle!
touch
touch_all
update_all
update_attribute
update_column
update_columns
update_counters
upsert
upsert_all
Note que save
também tem a habilidade de pular validações se for
estabelecido validate: false
como argumento. Essa técnica deve ser
usada com cuidado.
save(validate: false)
1.4 valid?
e invalid?
Antes de salvar um objeto do Active Record, Rails executa suas validações. Se essas validações produzirem um erro, o Rails não salva o objeto.
Você também pode executar essas validações por si só. valid?
ativa suas validações,
retornando true
, se nenhum erro for encontrado no objeto, ou false
,
caso contrário.
Como dito acima:
class Person < ApplicationRecord
validates :name, presence: true
end
irb> Person.create(name: "John Doe").valid?
=> true
irb> Person.create(name: nil).valid?
=> false
Depois do Active Record executar as validações, qualquer erro encontrado
pode ser acessado através do método de instância errors
, que
retorna uma coleção de erros. Por definição, um objeto é válido se essa coleção
estiver vazia após serem executadas as validações.
Note que um objeto instanciado com new
não informará nenhum erro mesmo que
ele seja tecnicamente inválido, porque as validações são executadas automaticamente
apenas quando o objeto é salvo, como acontece com os métodos create
ou 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?
é simplesmente o inverso de valid?
. Desencadeia suas validações e
retorna true
se algum erro for encontrado no objeto, e false
caso contrário.
1.5 errors[]
Para verificar se um determinado atributo de um objeto é válido, você pode
usar errors[:attribute]
. Isso retorna um array com todos os
erros para o :attribute
. Se não houver nenhum erro para o atributo
especificado, um array vazio é exibido.
Esse método só é útil após as validações terem sido executadas, porque ele só
inspeciona as coleções de erros e não aciona nenhuma validação em si. É
diferente do método ActiveRecord::Base#invalid?
explicado acima porque
não verifica ao todo se um objeto é válido. Apenas verifica se existem
erros em um determinado atributo do objeto.
class Person < ApplicationRecord
validates :name, presence: true
end
irb> Person.new.errors[:name].any?
=> false
irb> Person.create.errors[:name].any?
=> true
Nós vamos cobrir os erros das validações em maior detalhe na seção Trabalhando com Erros de Validações.
2 Helpers de Validação
O Active Record oferece vários helpers de validação pré-definidos que você
pode utilizar dentro das suas definições de classes. Esses helpers
providenciam regras de validações comuns. Toda vez que uma validação falha, um
erro é adicionado a coleção errors
do objeto, e sua mensagem é
associada com o atributo que está sendo validado.
Cada helper aceita um número arbitrário de nomes de atributos, então com uma única linha de código você consegue adicionar o mesmo tipo de validação para vários atributos.
Todas as validações aceitam as opções :on
e :message
, que definem quando
as validações devem ser utilizadas e qual a mensagem que será adicionada a
coleção errors
caso ela falhe, respectivamente. A opção :on
utiliza-se de
um dos valores :create
ou :update
. Existe uma mensagem padrão de erro para
cada um dos helpers de validação. Essas mensagens são utilizadas quando a
opção :message
não é especificada. Vamos dar uma olhada em cada um dos
helpers disponíveis.
2.1 acceptance
Esse método valida se um checkbox foi marcado quando um formulário foi submetido. Tipicamente isso é utilizado quando o usuário necessita de concordar com os termos de serviço de sua aplicação, confirmar que algum texto foi lido, ou qualquer conceito similar.
class Person < ApplicationRecord
validates :terms_of_service, acceptance: true
end
Essa verificação só é feita se terms_of_service
não é nil
.
A mensagem padrão de erro para esse helper é "must be accepted".
Você também pode passar uma mensagem customizada com a opção de message
.
class Person < ApplicationRecord
validates :terms_of_service, acceptance: { message: 'must be abided' }
end
O método também pode receber uma opção de :accept
, que determina os valores
que serão considerados como aceito. Ele tem como padrão os valores ['1', true]
e pode ser facilmente mudado.
class Person < ApplicationRecord
validates :terms_of_service, acceptance: { accept: 'yes' }
validates :eula, acceptance: { accept: ['TRUE', 'accepted'] }
end
Essa validação é bem específica para aplicações web e a "aceitação" não precisa
ser gravada em lugar nenhum do seu banco de dados. Se você não tem um campo para
isso o helper criará um atributo virtual. Se o campo existe no seu banco de
dados a opção accept
deve ser definida como true
ou a validação não irá
acontecer.
2.2 validates_associated
Você deve usar esse helper quando seu modelo tiver associações com outros
modelos que também precisam ser validados. Quando você tentar salvar seu objeto,
valid?
será chamado para cada um dos seus objetos associados.
class Library < ApplicationRecord
has_many :books
validates_associated :books
end
Essa validação funcionará com todos os tipos de associação.
Não utilize validates_associated
nos dois lados de suas associações.
Eles vão chamar umas as outras em um loop infinito.
A mensagem padrão de erro para validates_associated
é "is invalid". Repare
que cada objeto associado terá sua própria coleção de errors
; erros não irão
se juntar no modelo onde a validação foi chamada.
2.3 confirmation
Você deve utilizar esse helper quando você tem dois campos de texto que devem receber exatamente o mesmo conteúdo. Por exemplo, você pode querer confirmar um endereço de email ou uma senha. Essa validação cria um atributo virtual onde o nome é o nome do atributo que deve ser confirmado com "_confirmation" anexado.
class Person < ApplicationRecord
validates :email, confirmation: true
end
No seu template de view você pode utilizar algo como
<%= text_field :person, :email %>
<%= text_field :person, :email_confirmation %>
Essa checagem só é feita se o email_confirmation
não é nil
. Para requisitar
uma confirmação tenha certeza que adicionou uma checagem de presença para o
atributo de confirmação (nós iremos ver presence
em breve nesse guia):
class Person < ApplicationRecord
validates :email, confirmation: true
validates :email_confirmation, presence: true
end
Também existe a opção :case_sensitive
caso você queira definir se a restrição
de confirmação deve ser sensível a letras maiúsculas e minúsculas. Essa opção
por padrão é verdadeira.
class Person < ApplicationRecord
validates :email, confirmation: { case_sensitive: false }
end
A mensagem padrão de erro para esse helper é "doesn't match confirmation".
2.4 comparison
Esta verificação validará uma comparação entre quaisquer dois valores comparáveis.
O validador requer que uma opção de comparação seja fornecida. Cada opção aceita um
valor, proc ou symbol. Qualquer classe que inclua Comparable
pode ser comparada.
class Promotion < ApplicationRecord
validates :start_date, comparison: { greater_than: :end_date }
end
Todas essas opções são suportadas:
-
:greater_than
- Especifica que o valor deve ser maior que o valor fornecido. A mensagem de erro padrão para esta opção é "must be greater than %{count}". -
:greater_than_or_equal_to
- Especifica que o valor deve ser maior que ou igual ao valor fornecido. A mensagem de erro padrão para esta opção é "must be greater than or equal to %{count}". -
:equal_to
- Especifica que o valor deve ser igual ao valor fornecido. A mensagem de erro padrão para esta opção é "must be equal to %{count}". -
:less_than
- Especifica que o valor deve ser menor que o valor fornecido. O mensagem de erro padrão para esta opção é "must be less than %{count}". -
:less_than_or_equal_to
- Especifica que o valor deve ser menor ou igual ao valor fornecido. A mensagem de erro padrão para esta opção é "must be less than or equal to %{count}". -
:other_than
- Especifica que o valor deve ser diferente do valor fornecido. A mensagem de erro padrão para esta opção é "must be other than %{count}".
2.5 exclusion
Esse helper valida os atributos que não estão incluídos em uma coleção. Na verdade, essa coleção pode ser qualquer objeto enumerável.
class Account < ApplicationRecord
validates :subdomain, exclusion: { in: %w(www us ca jp),
message: "%{value} is reserved." }
end
O helper de exclusion
tem a opção :in
que recebe uma coleção de valores
que não serão aceitas para os atributos validados. A opção :in
tem um atalho
chamado :within
que pode ser utilizado para o mesmo propósito, caso queira.
Esse exemplo usa a opção :message
para mostrar como você pode incluir o valor
do atributo na mensagem de erro. Para uma lista completa das opções do argumento
de mensagem por favor veja a documentação sobre mensagens.
A mensagem de erro padrão é "is reserved".
2.6 format
Esse helper valida os valores dos atributos testando se eles correspondem uma
expressão regular dada, que é especificada com a opção :with
.
class Product < ApplicationRecord
validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
message: "only allows letters" }
end
Alternativamente, você pode requerer que um atributo específico não
corresponde com a expressão regular usando a opção :without
.
A mensagem de erro padrão é "is invalid".
2.7 inclusion
Esse helper valida os atributos que estão incluídos em uma coleção. Na verdade, essa coleção pode ser qualquer objeto enumerável.
class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }
end
O helper de inclusion
tem a opção :in
que recebe uma coleção de valores
que serão aceitas para os atributos validados. A opção :in
tem um atalho
chamado :within
que pode ser utilizado para o mesmo propósito, caso queira.
Esse exemplo usa a opção :message
para mostrar como você pode incluir o valor
do atributo na mensagem de erro. Para uma lista completa das opções do argumento
de mensagem por favor veja a documentação da mensagem.
A mensagem de erro padrão para esse helper é "is not included in the list".
2.8 length
Esse helper valida o tamanho dos valores dos atributos. Ele disponibiliza uma variedade de opções, então você pode especificar o tamanho das restrições de maneiras diferentes.
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
The possible length constraint options are:
As possíveis opções de restrições de tamanho são:
-
:minimum
- O atributo não pode ser menor que o tamanho especificado. -
:maximum
- O atributo não pode ser maior que o tamanho especificado. -
:in
(or:within
) - O tamanho do atributo deve estar dentro do alcance de um intervalo dado. O valor dessa opção deve ser um intervalo. -
:is
- O tamanho do atributo deve ser igual ao valor passado.
O valor padrão da mensagem de erro depende do tipo de validação sendo usado.
Você pode customizar essas mensagens usando as opções :wrong_length
,
:too_long
e :muito curto
e %{count}
como um espaço reservado para o número
correspondente ao do tamanho da restrição sendo utilizada. Você ainda pode
utilizar a opção :message
para especificar uma mensagem de erro.
class Person < ApplicationRecord
validates :bio, length: { maximum: 1000,
too_long: "%{count} characters is the maximum allowed" }
end
Note que as mensagens de erro padrão estão em plural (por exemplo: "is too short
(minimum is %{count} characters)"). Por essa razão, quando :minimum
é 1 você
deve disponibilizar uma mensagem customizada ou utilizar presence: true
no
lugar. Quando :in
ou :within
tem um limite menor que 1, você deve
disponibilizar ou uma mensagem customizada ou usar presence
antes do
length
.
2.9 numericality
Esse helper válida se seus atributos contém somente valores numéricos. Por padrão, ele vai corresponder um número inteiro ou real precedido de um sinal opcional de negativo ou positivo (+ ou -).
Para especificar que somente números inteiros são permitidos mude :only_integer
para verdadeiro.
Então ele vai usar
/\A[+-]?\d+\z/
como expressão regular para validar o valor do atributo. Se não, ele vai tentar
converter o valor para um número usando a classe Float
. Float
s são
transformados em BigDecimal
usando a precisão da coluna ou 15.
class Player < ApplicationRecord
validates :points, numericality: true
validates :games_played, numericality: { only_integer: true }
end
A mensagem de erro padrão para :only_integer
é "must be an integer".
Além de :only_integer
, esse helper também aceita as seguintes opções para
adicionar restrições aos valores aceitáveis:
-
:greater_than
- Especifica que o valor deve ser maior que o valor informado. A mensagem padrão para esse erro é "must be greater than %{count}". -
:greater_than_or_equal_to
- Especifica que o valor deve ser maior ou igual que o valor informado. A mensagem padrão para esse erro é "must be greater than or equal to %{count}". -
:equal_to
- Especifica que o valor deve ser igual que o valor informado. A mensagem padrão para esse erro é "must be equal to %{count}". -
:less_than
- Especifica que o valor deve ser menor que o valor informado. A mensagem padrão para esse erro é "must be less than %{count}". -
:less_than_or_equal_to
- Especifica que o valor deve ser menor ou igual que o valor informado. A mensagem padrão para esse erro é "must be less than or equal to %{count}". -
:other_than
- Especifica que o valor deve ser diferente que o valor informado. A mensagem padrão para esse erro é "must be other than %{count}". -
:in
- Especifica que o valor deve estar no intervalo fornecido. A mensagem de erro padrão para esta opção é must be in %{count}". -
:odd
- Especifica que o valor deve ser ímpar se definido como verdadeiro. A mensagem padrão para esse erro é "must be odd". -
:even
- Especifica que o valor deve ser par se definido como verdadeiro. A mensagem padrão para esse erro é "must be even".
Por padrão, numericality
não permite valores nil
. Você pode utilizar
allow_nil: true
para permitir isso.
A mensagem de erro padrão para quando nenhuma opção é especificada é "is not a number".
2.10 presence
Esse helper que os atributos especificados não estão vazios. Ele utiliza o
método blank?
para verificar se o valor é nil
ou uma string em branco,
isso é, uma string que está vazia ou só contém espaços.
class Person < ApplicationRecord
validates :name, :login, :email, presence: true
end
Se você quer ter certeza que uma associação está presente, você precisará testar se o objeto associado por ele mesmo está presente, e não a chave estrangeira utilizada para mapear a associação. Dessa maneira, não só é checado que a chave estrangeira existe como também se o objeto referenciado existe.
class Supplier < ApplicationRecord
has_one :account
validates :account, presence: true
end
Para validar registros associados cuja presença é necessária, você deve
especificar a opção :inverse_of
para a associação:
Se você quiser garantir que a associação está presente e é válida, você também precisa usar validates_associated
.
class Order < ApplicationRecord
has_many :line_items, inverse_of: :order
end
Se você validar a presença de um objeto associado através de um relacionamento
has_one
ouhas_many
, ele verificará se o objeto não está blank?
nem
marked_for_destruction?
.
Como false.blank?
é verdadeiro, se você deseja validar a presença de um valor
booleano no campo, você deve usar uma das seguintes validações:
validates :boolean_field_name, inclusion: [true, false]
validates :boolean_field_name, exclusion: [nil]
Ao usar uma dessas validações, você garantirá que o valor NÃO será nil
o que resultaria em um valor NULL
na maioria dos casos.
2.11 absence
Este helper valida que os atributos especificados estão ausentes. Ele usa o
método present?
para verificar se o valor não é nil
ou uma string em
branco, isso é, uma string que está vazia ou só contém caracteres em branco.
class Person < ApplicationRecord
validates :name, :login, :email, absence: true
end
Se você quer ter certeza que uma associação está ausente, você precisará testar se o objeto associado por ele mesmo está ausente, e não a chave estrangeira utilizada para mapear a associação.
class LineItem < ApplicationRecord
belongs_to :order
validates :order, absence: true
end
Para validar registros associados cuja ausência é necessária, você deve
especificar a opção :inverse_of
para a associação:
class Order < ApplicationRecord
has_many :line_items, inverse_of: :order
end
Se você validar a ausência de um objeto associado através de um relacionamento
has_one
ouhas_many
, ele verificará se o objeto não está present?
nem
marked_for_destruction?
.
Como false.present?
é false, se você quer validar a ausência de um campo
booleano você deve usar validates :field_name,
exclusion: { in: [true, false] }
.
A mensagem padrão de erro é "must be blank".
2.12 uniqueness
Este helper valida que o valor do atributo é único antes de o objeto ser salvo. Ele não cria uma restrição de exclusividade no banco de dados, portanto pode acontecer de duas conexões diferentes ao banco de dados criarem dois registros com o mesmo valor para uma coluna que você pretende tornar exclusiva. Para evitar isso, você deve criar um índice exclusivo nessa coluna no seu banco de dados.
class Account < ApplicationRecord
validates :email, uniqueness: true
end
A validação ocorre executando uma consulta SQL na tabela do modelo, procurando um registro existente com o mesmo valor nesse atributo.
Existe uma opção :scope
que você pode usar para especificar um ou mais
atributos usados para limitar a verificação de exclusividade:
class Holiday < ApplicationRecord
validates :name, uniqueness: { scope: :year,
message: "should happen once per year" }
end
Se você deseja criar uma restrição no banco de dados para previnir possiveis
violações em uma validação de exclusividade usando a opção de :scope
, você
deve criar uma indexação única em ambas as colunas em seu banco de dados. Veja
o manual do MySQL
para mais detalhes sobre indexação de múltiplas colunas ou
o manual do Postgres
para exemplos de restrições únicas que referenciam esse grupo de colunas
Há também uma opção :case_sensitive
que você pode usar para definir se a restrição de exclusividade fará distinção entre maiúsculas e minúsculas, ou respeitará o agrupamento do banco de dados padrão. A opção padrão respeita o agrupamento (collation) do banco de dados.
class Person < ApplicationRecord
validates :name, uniqueness: { case_sensitive: false }
end
Observe que alguns bancos de dados estão configurados para executar pesquisas que não diferenciam maiúsculas de minúsculas.
A mensagem de erro padrão é "has already been taken".
2.13 validates_with
Esse helper passa o registro para uma classe separada para ser feita a validação.
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
Os erros adicionados ao record.errors[:base]
estão relacionados ao
estado do registro como um todo, e não a um atributo específico.
O helper validates_with
pega uma classe ou uma lista de classes para usar
na validação. Não há mensagem de erro padrão para validates_with
. Você deve
adicionar manualmente erros à coleção de erros do registro na classe validadora.
Para implementar o método validador, você deve ter um parâmetro record
definido, que é o registro a ser validado.
Como todas as outras validações, validates_with
utiliza as opções:if
,
:unless
e :on
. Se você passar outras opções, essas opções serão enviadas
para a classe validadora 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
Observe que o validador será inicializado apenas uma vez durante todo o ciclo de vida do aplicativo, e não em cada execução de validação, portanto, tenha cuidado ao usar variáveis de instância nele.
Se o seu validador for suficientemente complexo para que você deseje variáveis de instância, você poderá facilmente usar um objeto Ruby puro no 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
2.14 validates_each
Este helper valida atributos em relação a um bloco. Não possui uma função de
validação predefinida. Você deve criar um usando um bloco, e todos os atributos
passados para validates_each
serão testados contra ele. No exemplo a seguir,
não queremos que nomes e sobrenomes comecem com letras minúsculas.
class Person < ApplicationRecord
validates_each :name, :surname do |record, attr, value|
record.errors.add(attr, 'must start with upper case') if value =~ /\A[[:lower:]]/
end
end
O bloco recebe o registro, o nome do atributo e o valor do atributo. Você pode fazer o que quiser para verificar dados válidos dentro do bloco. Se sua validação falhar, você deverá adicionar um erro ao modelo, tornando-o inválido.
3 Opções de Validação Comuns
Essas são opções de validação comuns:
3.1 :allow_nil
A opção :allow_nil
pula a validação quando o valor que está sendo validado é
nil
.
class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }, allow_nil: true
end
Para opções completas para argumento de mensagem, consulte documentação de mensagem.
3.2 :allow_blank
A opção :allow_blank
é semelhante à opção :allow_nil
. Esta opção
deixará a validação passar se o valor do atributo for blank?
,
string vazia por exemplo.
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 você já viu, a opção :message
permite que você especifique a mensagem que
será adicionada à coleção errors
quando a validação falhar. Quando esta
opção não é usada, o Active Record
usará a respectiva mensagem de erro padrão
para cada helper de validação. A opção :message
aceita uma String
ou Proc
.
Um valor String
de :message
pode conter opcionalmente qualquer/tudo de %{value}
,
%{attribute}
, e %{model}
que será substituído dinamicamente quando
a validação falhar. Esta substituição é feita usando a gem I18n, e as
posições devem ser exatamente correspondentes, não são permitidos espaços.
Um valor Proc
de :message
recebe dois argumentos: o objeto à ser validado, e
uma hash com os pares de chave-valor :model
, :attributes
e :value
.
class Person < ApplicationRecord
# Hard-coded message
validates :name, presence: { message: "must be given please" }
# Mensagem com valor de atributo dinâmico. %{value} será substituído
# com o valor real do atributo. %{attribute} e %{model}
# também estão disponíveis.
validates :age, numericality: { message: "%{value} seems wrong" }
# Proc
validates :username,
uniqueness: {
# object = person object being validated
# data = { model: "Person", attribute: "Username", value: <username> }
message: ->(object, data) do
"Hey #{object.name}, #{data[:value]} is already taken."
end
}
end
3.4 :on
A opção :on
permite que você especifique quando a validação deve acontecer. O
comportamento padrão para todos os helpers de validação integrados é ser executado
ao salvar (tanto ao criar um novo registro quanto ao atualizá-lo). Se você
quiser mudar, você pode usar on: :create
para rodar a validação apenas quando um
novo registro é criado ou on: :update
para rodar a validação apenas quando um registro
é atualizado.
class Person < ApplicationRecord
# 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
Você também pode usar on:
para definir contextos customizados. O contexto
customizado precisa ser acionado explicitamente passando o nome do contexto para
valid?
, invalid?
, ou save
.
class Person < ApplicationRecord
validates :email, uniqueness: true, on: :account_setup
validates :age, numericality: true, on: :account_setup
end
irb> person = Person.new(age: 'thirty-three')
irb> person.valid?
=> true
irb> person.valid?(:account_setup)
=> false
irb> person.errors.messages
=> {:email=>["has already been taken"], :age=>["is not a number"]}
person.valid?(:account_setup)
executa ambas as validações sem salvar
o model. person.save(context: :account_setup)
valida person
no
contexto de account_setup
antes de salvar.
Quando acionado por um contexto explícito, as validações são executadas para esse contexto, assim como quaisquer validações sem um 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"]}
4 Validações Estritas
Você também pode especificar validações como estritas e lançar um
ActiveModel::StrictValidationFailed
quando o objeto é inválido.
class Person < ApplicationRecord
validates :name, presence: { strict: true }
end
irb> Person.new.valid?
ActiveModel::StrictValidationFailed: Name can't be blank
Também é possível passar uma exceção personalizada para a opção :strict
.
class Person < ApplicationRecord
validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
end
irb> Person.new.valid?
TokenGenerationException: Token can't be blank
5 Validação com Condicional
Às vezes fará sentido validar um objeto apenas quando uma determinada condição for satisfeita.
Você pode fazer isso usando :if
e :unless
, que podem ser usados como um symbol,
uma Proc
ou um Array
. Você pode usar o :if
quando quiser especificar quando uma
validação deve ocorrer.
Se você quiser especificar quando uma validação não deve ocorrer, você pode usar o :unless
.
5.1 Usando um Symbol com :if
e :unless
Você pode associar o :if
e :unless
com um symbol correspondente ao nome do método
que será chamado logo antes da validação acontecer.
Essa é a opção mais usada.
class Order < ApplicationRecord
validates :card_number, presence: true, if: :paid_with_card?
def paid_with_card?
payment_type == "card"
end
end
5.2 Usando uma Proc com :if
e :unless
É possível associar :if
e :unless
com um objeto Proc
que será chamado.
O uso de um objeto Proc
permite escrever uma condição em apenas uma linha
ao invés de em um método separado.
class Account < ApplicationRecord
validates :password, confirmation: true,
unless: Proc.new { |a| a.password.blank? }
end
Como as Lambdas
são tipos de Proc
, elas também podem ser usadas para escrever condições em apenas uma linha de forma mais curta.
validates :password, confirmation: true, unless: -> { password.blank? }
5.3 Agrupando Validações com Condicionais
Às vezes, é útil ter várias validações usando a mesma condição.
Isso pode ser feito facilmente 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 as validações dentro do bloco with_options
terão automaticamente passado a condição if: :is_admin?
5.4 Combinando Validações com Condicionais
Por outro lado, quando várias condições definem se uma validação deve ou não acontecer,
podemos usar um Array
. Além disso, você pode usar ambos :if
e :unless
para a mesma validação.
class Computer < ApplicationRecord
validates :mouse, presence: true,
if: [Proc.new { |c| c.market.retail? }, :desktop?],
unless: Proc.new { |c| c.trackpad.present? }
end
A validação é executada apenas quando todas as condições :if
e nenhuma das condições :unless
resultarem em true
.
6 Realizando Validações Customizadas
Quando os helpers de validação embutidos não são o bastante para suas necessidades, você pode implementar seus próprios validadores ou métodos de validação como preferir.
6.1 Validadores Customizados
Validadores Customizados são classes que herdam de ActiveModel::Validator
. Essas
classes devem implementar o método validate
, que recebe um record como argumento
e realiza as validações nele. O validador customizado é chamado usando o
método validates_with
.
class MyValidator < ActiveModel::Validator
def validate(record)
unless record.name.start_with? 'X'
record.errors.add :name, "Need a name starting with X please!"
end
end
end
class Person
include ActiveModel::Validations
validates_with MyValidator
end
A maneira mais fácil de adicionar validadores customizados para atributos individuais
é com a conveniente classe ActiveModel::EachValidator
. Nesse caso, a classe validadora
customizada deve implementar um método validate_each
, que recebe três argumentos:
record, attribute e value. Esses correspondem respectivamente à instância, ao atributo
a ser validado e ao valor do atributo na instância recebida.
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors.add attribute, (options[:message] || "is not an email")
end
end
end
class Person < ApplicationRecord
validates :email, presence: true, email: true
end
Como mostrado no exemplo acima, você também pode combinar validações padrão com seus próprios validadores customizados.
6.2 Métodos Customizados
Você também pode criar métodos que verificam o estado de seus models e
adicionam erros à coleção errors
quando eles são inválidos. Você
deve então registrar esses métodos usando o método de classe validate
passando os symbols dos nomes dos métodos de validação.
Você pode passar mais do que um symbol para cada método da classe e as respectivas validações serão executadas na mesma ordem que elas foram registradas.
O método valid?
verificará se a coleção errors
está vazia,
sendo assim seus métodos de validação customizados devem adicionar erros
à ela quando você desejar que as validações falhem:
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.today269G
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 padrão, tais validações executarão a cada chamada ao método valid?
ou ao salvar o objeto. Mas também é possível controlar quando executar essas
validações customizadas informando uma opção :on
para o método validate
,
com :create
ou :update
.
class Invoice < ApplicationRecord
validate :active_customer, on: :create
def active_customer
errors.add(:customer_id, "is not active") unless customer.active?
end
end
7 Trabalhando com Erros de Validação
Em adição aos métodos valid?
e invalid?
cobertos anteriormente, o Rails provê outros métodos para trabalhar com a coleção errors
e verificar a validade dos objetos.
A seguir é exibida uma lista dos métodos mais comumente utilizados. Por favor, verifique a documentação do ActiveModel::Errors
para uma lista de todos os métodos disponíveis.
7.1 errors
O portão através do qual você pode ver vários detalhes de cada erro.
Retorna uma instância da classe ActiveModel::Errors
contendo todos os erros,
cada erro é representado por um 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
=> []
7.2 errors[]
errors[]
é utilizado quando você quiser verificar as mensagens de erro de um atributo em específico. O método retorna um array de strings com todas as mensagens de erro para o atributo informado, cada string contendo uma mensagem de erro. Se não houver erros relacionados com o atributo, o método retorna um array vazio.
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
e Objetos de Erro
Às vezes, podemos precisar de mais informações sobre cada erro e sua mensagem. Cada erro é encapsulado como um objeto ActiveModel::Error
e o método where
é a forma mais comum de acesso.
where
retorna um array de objetos de erro, filtrados por vários graus de condições.
class Person < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.where(:name)
=> [ ... ] # todos os erros para o atributo :name
irb> person.errors.where(:name, :too_short)
=> [ ... ] # erros :too_short para o atributo :name
Você pode ler várias informações desses objetos de erro:
irb> error = person.errors.where(:name).last
irb> error.attribute
=> :name
irb> error.type
=> :too_short
irb> error.options[:count]
=> 3
Você também pode gerar a mensagem de erro:
irb> error.message
=> "is too short (minimum is 3 characters)"
irb> error.full_message
=> "Name is too short (minimum is 3 characters)"
O método full_message
gera uma mensagem mais legível, que começa com o atributo com a primeira letra maiúscula. (Para customizar o formato que o método full_message
usa, veja o guia de I18n.)
7.4 errors.add
O método add
permite que você crie um objeto de erro usando o atributo em particular, o tipo do erro e um hash de opções adicional. Isso pode ser útil quando tiver escrevendo seus validadores (validators).
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]
Voce pode adicionar erros relacionadas ao estado do objeto como um todo, ao invés de estarem relacionadas a um atributo em específico. Você pode adicionar erros ao :base
quando quiser dizer que o objeto é inválido, não importando os valores de seus atributos.
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.clear
O método clear
é usado quando você intencionalmente quiser limpar toda a coleção errors
. É claro que, ao chamar o método errors.clear
sobre um objeto inválido não irá torná-lo válido: a coleção errors
estará agora vazia, mas a próxima vez que você chamar valid?
ou qualquer método que tente salvar esse objeto na base de dados, as validações serão executadas novamente. Se qualquer uma das validações falhar, a coleção errors
será preenchida de novo.
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
7.7 errors.size
O método size
retorna o número total de erros para o 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: "[email protected]")
irb> person.valid?
=> true
irb> person.errors.size
=> 0
8 Exibindo Erros de Validação nas Views
Uma vez criado o model e adicionada as validações, se o model é criado via formulário web, você provavelmente quer mostrar uma mensagem de erro quando uma das validações falharem.
Devido a cada aplicação lidar com esse tipo de cenário de forma diferente, o Rails
não inclui nenhum helper na view para ajudar a gerar essas mensagens
diretamente.
Contudo, devido ao rico número de métodos que o Rails nos da para interagirmos
com validações em geral, podemos criar as nossas próprias validações.
Além disso, quando geramos o scaffold, o Rails colocará algum ERB dentro
de _form.html.erb
que ele gera, exibindo a lista completa de erros naquele
model.
Supondo que temos um modelo que foi salvo em uma variável de instância chamada
@article
, terá a seguinte aparência:
<% 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 %>
Portanto, se você usar os helpers de formulário do Rails para gerar seus
formulários, quando um erro de validação ocorrer em um campo, isso vai gerar
uma <div>
extra ao redor da entrada.
<div class="field_with_errors">
<input id="article_title" name="article[title]" size="30" type="text" value="">
</div>
Você pode definir o estilo desta div como preferir. O scaffold padrão que o Rails gera, por exemplo, adiciona essa regra CSS:
.field_with_errors {
padding: 2px;
background-color: red;
display: table;
}
Isso significa que qualquer campo com erro termina com 2 pixels de borda vermelha.
Feedback
Você é incentivado a ajudar a melhorar a qualidade deste guia.
Por favor, contribua caso veja quaisquer erros, inclusive erros de digitação. Para começar, você pode ler nossa sessão de contribuindo com a documentação.
Você também pode encontrar conteúdo incompleto ou coisas que não estão atualizadas. Por favor, adicione qualquer documentação em falta na main do Rails. Certifique-se de checar o Edge Guides (en-US) primeiro para verificar se o problema já foi resolvido ou não no branch main. Verifique as Diretrizes do Guia Ruby on Rails para estilo e convenções.
Se, por qualquer motivo, você encontrar algo para consertar, mas não conseguir consertá-lo, por favor abra uma issue no nosso Guia.
E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem vinda na forum oficial do Ruby on Rails e nas issues do Guia em português.