Usando callbacks para calcular e armazenar dados numa aplicação Rails.
Considerando que grande parte da motivação para ter rotinas de trabalho num sistema é diminuição da margem de erros, é comum que a gente queira calcular certas informações dentro do sistema, ao invés de permitir que o usuário às insira.
No nosso caso onde a aplicação Rails será responsável por rotinas de uma operadora de turismo, um exemplo deste tipo de campo seria o total de um pacote ou roteiro após o desconto ser aplicado. Ao invés do usuário inserir o subtotal, o desconto, e depois o total (tendo calculado este manualmente), vamos fazer com que ele possa simplesmente colocar o subtotal e o desconto, deixando que o sistema calcule o total final.
Vamos lá?
1 - Crie a classe Itinerary
rails generate scaffold itinerary subtotal:integer discount:integer total:integer
Vamos usar scaffold nesta criação, pois neste momento não estamos preocupados com os detalhes do CRUD. Queremos somente focar neste dado calculado.
Lembre-se de rodar a migração na sequência:
rails db:migrate
(Se quiser mais detalhes sobre esta parte, veja este post, onde focamos nas mágicas do scaffold do Rails.)
2 - Crie um teste para a classe
# spec/models/itinerary_spec.rb require 'rails_helper' describe Itinerary do it 'saves total' do itinerary = Itinerary.create(subtotal: 5, discount: 3, total: 2) expect(itinerary.total).to eq 2 end end
Podemos falar mais sobre testes de classes em outro post. O mais importante aqui é que ele esteja no diretório certo e especifique valores para subtotal, desconto, e total.
O teste acima foi criado para que ele passe agora, do jeito que a classe está. Como já criamos a classe e uma série de coisas no scaffold do primeiro passo, é importante fazer isso antes e nos certificarmos de que está tudo como esperamos. Veja na minha tela abaixo que está tudo ok, o teste desta forma passou:
Agora que temos certeza de que está tudo em ordem com a classe e o teste neste momento, vamos alterar o teste para o cenário que buscamos. Queremos criar uma situação onde o total não seja inserido, mas sim calculado pela própria classe antes de salvar, certo? Então, o ajuste no nosso teste é retirar o total: 2 dali dos nossos parâmetros e continuar com a expectativa de que o total seja 2, deixando o teste assim:
# spec/models/itinerary_spec.rb require 'rails_helper' describe Itinerary do it 'saves total' do itinerary = Itinerary.create(subtotal: 5, discount: 3) expect(itinerary.total).to eq 2 end end
E desta vez, quando rodamos o teste, temos problemas:
Tudo certo. Este era o problema que queríamos. O teste está nos dizendo que ao procurar pelo valor 2, ele acabou não encontrando nada. Faz sentido, pois não estamos colocando nada lá.
Agora começa a parte boa!
3 - Crie o método do cálculo
Dentro da nossa classe que no momento está assim:
# app/models/itinerary.rb class Itinerary < ApplicationRecord end
Precisamos inserir este método:
def assign_total self.total = subtotal - discount end
Ele pede para que o Rails subtraia o valor do desconto do valor subtotal e armazene esta diferença no total do objeto em questão.
4 - Escolha o callback
O Rails coloca vários callbacks à nossa disposição. Este link nos mostra todos na ordem em que acontecem. Para escolher o que vamos usar, precisamos pensar em duas coisas:
- Porque nosso callback vai existir?
- Quando ele deve agir?
A resposta da primeira pergunta é que nosso callback vai existir porque queremos tirar do usuário a responsabilidade de calcular o total. Sobre a segunda, ele deve agir quando o objeto for salvo, independente dele estar sendo criado ou atualizado.
Com estas questões esclarecidas, fica claro que o nosso callback vai ser o before_save, afinal ele roda logo antes do objeto ser salvo, não importante se ele já existia ou não.
5 - Configure o callback para chamar o método
Agora que já temos o método e sabemos qual callback vamos usar, podemos configurá-lo com a seguinte linha:
before_save :assign_total
Nossa classe completa fica assim:
# app/models/itinerary.rb class Itinerary < ApplicationRecord before_save :assign_total def assign_total self.total = subtotal - discount end end
E quando rodamos novamente o teste, temos:
Passou! Tudo certo!
Pronto!
Agora nossa aplicação Rails não salva mais o valor total vindo do usuário, mas sim o resultado do cálculo feito pela classe antes de salvar os dados no banco!
Um passo além
Agora que a nossa classe não usa mais o valor total passado pelo usuário, não seria melhor impedir que ele insira um no formulário?
Fazemos isso retirando o campo no arquivo app/views/itineraries/_form.html.erb.
Depois de retirar o campo do formulário, é indicado também retirar ele da lista de dados aceitos no arquivo app/controllers/itineraries_controller.rb.




Nenhum comentário:
Postar um comentário