Como detectar mudanças de atributos no modelo?

86

Eu gostaria de criar uma função de retorno de chamada em trilhos que executa depois que um modelo é salvo.

Eu tenho este modelo, Reivindicação que tem um atributo 'status' que muda dependendo do estado da reivindicação, os valores possíveis são pendentes, endossados, aprovados, rejeitados

O banco de dados tem 'estado' com o valor padrão de 'pendente'.

Gostaria de executar certas tarefas depois que o modelo for criado pela primeira vez ou atualizado de um estado para outro, dependendo de qual estado ele muda.

Minha ideia é ter uma função no modelo:

    after_save :check_state

    def check_state
      # if status changed from nil to pending (created)
      do this

      # if status changed from pending to approved
      performthistask
     end

Minha pergunta é como faço para verificar o valor anterior antes da mudança dentro do modelo?

David C
fonte

Respostas:

173

Você deve olhar para o módulo ActiveModel :: Dirty : Você deve ser capaz de realizar as seguintes ações no seu modelo de reivindicação:

claim.status_changed?  # returns true if 'status' attribute has changed
claim.status_was       # returns the previous value of 'status' attribute
claim.status_change    # => ['old value', 'new value'] returns the old and 
                       # new value for 'status' attribute

claim.name = 'Bob'
claim.changed # => ["name"]
claim.changes # => {"name" => ["Bill", "Bob"]}

Oh! as alegrias do Rails!

Harish Shetty
fonte
6
Isso não funcionará depois que o modelo for salvo, que é o que ele pediu.
Tom Rossi
4
@TomRossi, as dirtychamadas funcionam no after_save(tanto no Rails 2.3 quanto no 3.x). Eu usei várias vezes.
Harish Shetty
11
@TomRossi, os flags sujos são zerados após o commit, então eles não estarão disponíveis em after_commitcallbacks introduzidos no Rails 3.x. Eles certamente funcionarão after_save.
Harish Shetty
Eu não fazia ideia! Eu pensei que eles foram reiniciados assim que foi salvo!
Tom Rossi de
5
@TomRossi Comecei com a mesma suposição alguns anos atrás. Quando tentei verificar os sinalizadores sujos em after_save funcionou. Em essência, after_saveé um retorno de chamada para um estado entre after DMLe before_commit. Você pode encerrar a transação inteira after_savelançando uma exceção. Se você quiser fazer algo após salvar sem afetar a operação atual, use after_commit:-)
Harish Shetty
38

você pode usar isso

self.changed

ele retorna um array de todas as colunas que mudaram neste registro

você também pode usar

self.changes

que retorna um hash de colunas que mudaram e antes e depois dos resultados como matrizes

Zeacuss
fonte
7
Apenas uma pequena nota para dizer que não é necessário usar self.neles - você pode apenas dizer changede changes.
usuário664833
@ user664833 Mais especificamente, você pode omitir selfwhen no próprio modelo, mas pode chamá-los em qualquer objeto com object.changede object.changes. :)
Joshua Pinter
4

Eu recomendo que você dê uma olhada em um dos plug-ins de máquina de estado disponíveis:

Qualquer um deles permitirá que você configure estados e transições entre estados. Maneira muito útil e fácil de lidar com seus requisitos.

Toby Hede
fonte
Estou dando uma chance ao rubyist-aasm. Digamos que eu tenha a classe Claim <ActiveRecord :: Base include AASM aasm_column: status aasm_initial_state: pendente aasm_state: pendente,: enter =>: enter_pending def enter_pending Notifier.deliver_pending_notification (self) end end E meu campo de status em meu banco de dados tem o valor padrão de "pendente". Se eu fosse fazer um Claim.create sem preencher o campo de status (para que ele execute 'pendente'), o AASM executará o método 'enter_pending'?
David C
2

Para Rails 5.1+, você deve usar o método de atributo de registro ativo: saved_change_to_attribute?

save_change_to_attribute? (Attr_name, ** options) `

Este atributo mudou quando salvamos pela última vez? Este método pode ser chamado como em saved_change_to_name?vez de saved_change_to_attribute?("name"). Se comporta de forma semelhante a attribute_changed?. Este método é útil em pós-chamadas para determinar se a chamada para salvar alterou um determinado atributo.

Opções

from Quando aprovado, este método retornará falso a menos que o valor original seja igual à opção fornecida

to Quando passado, este método retornará falso a menos que o valor tenha sido alterado para o valor fornecido

Portanto, seu modelo ficará assim, se você quiser chamar algum método com base na mudança no valor do atributo:

class Claim < ApplicationRecord
  
  after_save :do_this, if: Proc.new { saved_change_to_status?(from: nil, to: 'pending') }

  after_save :do_that, if: Proc.new { saved_change_to_status?(from: 'pending', to: 'approved') }

  
  def do_this
    ..
    ..
  end

  def do_that
    ..
    ..
  end

end

E se você não quiser verificar a mudança de valor no retorno de chamada, você pode fazer o seguinte:

class Claim < ApplicationRecord

  after_save: :do_this, if: saved_change_to_status?


  def do_this
    ..
    ..
  end

end
Rajkaran Mishra
fonte
0

Já vi a questão surgir em muitos lugares, então escrevi um pequeno rubygem para ela, para tornar o código um pouco melhor (e evitar um milhão de instruções if / else em todos os lugares): https://github.com/ronna-s / on_change . Espero que ajude.

Ronna
fonte
0

Você ficará muito melhor usando uma solução bem testada, como a gem state_machine .

Paz Aricha
fonte