Validações e contexto no Rails
15 Aug 2015Muitas vezes quem está trabalhando com Rails
se pergunta: Como eu posso fazer uma validação de um model que só funciona em uma parte do sistema?. Um bom exemplo desse caso de uso seria uma aplicação de agendamento de pacientes. Vejamos o seguinte problema.
Nossa tabela de paciente tem os seguintes dados:
# timestamp_create_pacientes.rb
class CreatePacientes < ActiveRecord::Migration
def change
create_table :pacientes do |t|
t.string :nome
t.date :nascimento
t.string :cpf
t.integer :sexo
t.string :nome_mae
t.string :endereco
end
end
end
E as seguintes validações:
# paciente.rb
class Paciente < ActiveRecord::Base
validates :nome, :cpf, :sexo, :nascimento, presence: true
end
Dessa maneira ao tentar salvar um paciente sem nome, CPF, sexo ou data de nascimento, o ActiveRecord
vai reclamar e falar que esses campos precisam ser preenchidos. Até aí tudo bem, se tivermos um cadastro de paciente separado na aplicação. Porém, em um novo requisito do cliente aparece a necessidade de se criar um pré-cadastro ao realizar um agendamento, pois o usuário, no caso uma secretária, não terá tempo de preencher todos os dados ao telefone com o paciente. O requisito é o seguinte: Na tela de agendamento, deve aparecer a opção de se pré-cadastrar um novo paciente informando o nome, o telefone.. E agora??
Refatorando o código
No nosso model
acabamos de falar que não se pode salvar um paciente sem nome, CPF, sexo e data de nascimento. Porém se o usuário usar a tela de agendamento para um pré-cadastro, como devemos proceder.
A API
de validações do ActiveRecord
nos dá a possibilidade de realizar validações condicionais.
# paciente.rb
class Paciente < ActiveRecord::Base
attr_accessor :tela_paciente
validates :nome, presence: true
validates :cpf, presence: true, if: Proc.new{|p| p.tela_paciente?}
validates :sexo, presence: true, if: Proc.new{|p| p.tela_paciente?}
validates :nascimento, presence: true
end
# pacientes_controller.rb
def create
@paciente = Paciente.new paciente_params
@paciente.tela_paciente = true
if @paciente.save
#...
end
end
# agendamentos_controller.rb
def create
@agendamento = Agendamento.new agendamento_params
@paciente = Paciente.new paciente_params
@paciente.tela_paciente = false
if @paciente.save
@agendamento.paciente_id = @paciente.id
if @agendamento.save
#...
end
end
end
Criamos um atributo virtual (que não é persistido no banco) para informar de onde o paciente está sendo salvo e manipulado. Antes disparar a validação, o Proc
é executado e a condição é checada para verificar se o fluxo continua ou não. Bem simples, porém um pouco poluído.
Contextos
Uma outra maneira de trabalhar com validações condicionais, é indicar o contexto ao realizar uma operação como salvar ou atualizar um model
. Como assim contextos?
O método save
pode receber um hash de contexto da seguinte maneira:
# pacientes_controller.rb
def create
@paciente = Paciente.new paciente_params
@paciente.tela_paciente = true
if @paciente.save(context: :tela_paciente)
#...
end
end
# paciente.rb
class Paciente < ActiveRecord::Base
validates :endereco, presence: true, on: :tela_paciente
#...
end
Essa validação só será executada quando chamarmos o save
de pacientes passando o contexto tela_paciente
. Você pode se perguntar, Mas e as outras validações?. Todas elas serão executadas, mas a que requer o contexto só será disparada da maneira que mostramos.