Usando ActiveRecord, há uma maneira de obter os valores antigos de um registro durante after_update
-
03-07-2019 - |
Pergunta
Configuração usando um exemplo simples: Eu tenho uma tabela (Totals
) que detém a soma da coluna amount
de cada registro em uma segunda tabela (Things
)
Quando um thing.amount
é atualizado, eu gostaria de simplesmente adicionar a diferença entre o valor antigo eo novo valor para total.sum
.
Agora eu estou subtraindo self.amount
durante before_update
e adicionando self.amount
durante after_update
. Este lugares WAY demasiada confiança na atualização seguinte.
Restrição: Não quero simplesmente recalcular a soma de todas as transações
. Pergunta: Muito simplesmente, eu gostaria de acessar o valor original durante uma chamada de retorno after_update
. Que forma você chegar a fazer isso?
Update: eu estou indo com a idéia de Luke Francl. Durante uma chamada de retorno after_update
você ainda tem acesso aos valores self.attr_was
que é exatamente o que eu queria. Eu também decidiu ir com uma implementação after_update
porque eu quero manter este tipo de lógica no modelo. Dessa forma, não importa o quanto eu decidir transações de atualização no futuro, eu vou saber que estou atualizando a soma das transações corretamente. Obrigado a todos por suas sugestões de implementação.
Solução
Ditto o que todo mundo está dizendo sobre transações.
Dito isto ...
ActiveRecord a partir de Rails 2.1 mantém o controle dos valores de atributo de um objeto. Então, se você tem um total
atributo, você terá um método total_changed?
e um método total_was
que retorna o valor antigo.
Não há necessidade de acrescentar nada ao seu modelo de acompanhar isso mais.
Update: Aqui está a documentação para ActiveModel :: sujo conforme solicitado.
Outras dicas
Algumas outras pessoas estão mencionando envolvendo tudo isso em uma transação, mas acho que isso é feito para você; você só precisa acionar a reversão levantando uma exceção para erros na after_ * retornos de chamada.
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Toda a cadeia de chamada de retorno de um salve, salve !, ou destruir chamada executado dentro de uma transação. Isso inclui after_ ganchos *. Se tudo correr bem um COMMIT é executada uma vez que a cadeia tenha sido concluída.
Se um before_ * callback cancela a ação de um ROLLBACK é emitido. Você também pode desencadear uma ROLLBACK levantar uma exceção em qualquer um dos retornos de chamada, incluindo after_ ganchos *. Note, no entanto, que, nesse caso, o cliente precisa estar ciente disso, porque um simples salvar elevará tal exceção em vez de silêncio retornar false.
acrescentando "_was" para o seu atributo vai dar-lhe o valor anterior antes de salvar os dados.
Esses métodos são chamados métodos sujos .
Felicidades!
Para obter todos os campos alterados, com seus valores antigos e novos, respectivamente:
person = Person.create!(:name => 'Bill')
person.name = 'Bob'
person.save
person.changes # => {"name" => ["Bill", "Bob"]}
ActiveRecord :: sujo é um módulo que está embutido no ActiveRecord para rastreamento alterações de atributo. Assim você pode usar thing.amount_was
para obter o valor antigo.
Adicione esta ao seu modelo:
def amount=(new_value)
@old_amount = read_attribute(:amount)
write_attribute(:amount,new_value)
end
Em seguida, use @old_amount em seu código after_update.
Em primeiro lugar, você deve fazer isso em uma transação para garantir que seus dados é escrito juntos.
Para responder à sua pergunta, você poderia apenas definir uma variável membro para o valor antigo no before_update, que você pode então acesso no after_update, no entanto, isso não é uma solução muito elegante.
Idea 1: Enrole a atualização em uma transação de banco de dados, de modo que se a atualização falhar os totais da tabela não é alterado: ActiveRecord Transações docs
Idea. 2: Stash o valor antigo em @old_total durante o before_update