Como determinar se um registro acabou de ser criado ou atualizado em after_save

95

O #new_record? função determina se um registro foi salvo. Mas é sempre falso no after_savegancho. Existe uma maneira de determinar se o registro é um registro recém-criado ou antigo da atualização?

Espero não usar outro retorno de chamada, como before_createdefinir um sinalizador no modelo ou exigir outra consulta no banco de dados.

Qualquer conselho é apreciado.

Editar: preciso determiná-lo no after_savegancho e, para meu caso de uso específico, não há nenhum carimbo de data updated_at/ updated_onhora

Aaron Qian
fonte
1
hmm talvez passe um parâmetro em um before_save? apenas pensando em voz alta
Viagem em

Respostas:

167

Eu estava tentando usar isso para um after_saveretorno de chamada.

Uma solução mais simples é usar id_changed?(uma vez que não muda update) ou mesmo created_at_changed?se as colunas de carimbo de data / hora estiverem presentes.

Atualizar: como @mitsy aponta, se esta verificação for necessária fora dos retornos de chamada, use id_previously_changed?. Veja a documentação .

Zubin
fonte
3
via ActiveModel :: Dirty
chaserx
6
É melhor diferenciar com um after_update e um after_create. Os callbacks podem compartilhar um método comum que leva um argumento para indicar se é uma criação ou atualização.
matthuhiggins,
2
Isso pode ter mudado. Pelo menos no Rails 4, o callback after_save é executado após o callback after_create ou after_update (veja guias.rubyonrails.org/active_record_callbacks.html ).
Mark
3
Verificar se nenhum desses campos funciona fora de after_save.
fatuhoku de
3
id_changed?será falso depois que o registro for salvo (fora dos ganchos, pelo menos). Nesse caso, você pode usarid_previously_changed?
mltsy de
30

Não há mágica de trilhos aqui que eu conheça, você terá que fazer isso sozinho. Você pode limpar isso usando um atributo virtual ...

Em sua classe de modelo:

def before_save
  @was_a_new_record = new_record?
  return true
end

def after_save
  if @was_a_new_record
    ...
  end
end
Jeff Paquette
fonte
26

Outra opção, para aqueles que fazem tem um updated_atcarimbo do tempo:

if created_at == updated_at
  # it's a newly created record
end
Colllin
fonte
Não é uma má ideia, mas parece que o tiro pode sair pela culatra em algumas situações (não necessariamente à prova de bala).
Ash Blue
Dependendo de quando uma migração é executada, os registros created_at e updated_at podem estar desativados. Além disso, você sempre corre a chance de alguém atualizar um registro logo após salvá-lo, o que pode colocar o tempo fora de sincronia. Não é uma má ideia, apenas parece que uma implementação mais à prova de bala poderia ser adicionada.
Ash Blue de
Além disso, eles podem ser iguais muito depois de o registro ter sido criado inicialmente. Se um registro não for atualizado por meses, vai parecer que acabou de ser criado .
bschaeffer
1
@bschaeffer Desculpe, minha pergunta era "é possível created_atigualar updated_atem um after_saveretorno de chamada a qualquer momento diferente de quando ele é criado pela primeira vez?"
colllin
1
@colllin: Ao criar um registro, created_ate updated_atserá igual em um after_saveretorno de chamada. Em todas as outras situações, eles não serão iguais no after_saveretorno de chamada.
bschaeffer
22

Existe um after_createcallback que só é acionado se o registro for um novo registro, após ser salvo . Também há um after_updateretorno de chamada para uso se este for um registro existente que foi alterado e salvo. O after_saveretorno de chamada é chamado em ambos os casos, após after_createouafter_update é chamado.

Usar after_create se precisar que algo aconteça uma vez depois que um novo registro for salvo.

Mais informações aqui: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

afiado
fonte
1
Ei, obrigado pela resposta, isso me ajudou muito. Saúde, cara, +10 :)
Adam McArthur
1
Isso pode não ser verdade. Se você tem associações em sua criação, after_create é chamado ANTES das associações serem criadas, então se você precisa ter certeza de que TUDO foi criado, então você precisa usar after_save
Niels Kristian
18

Uma vez que o objeto já foi salvo, você precisa verificar as alterações anteriores. O ID só deve mudar após a criação.

# true if this is a new record
@object.previous_changes[:id].any?

Também existe uma variável de instância @new_record_before_save. Você pode acessá-lo fazendo o seguinte:

# true if this is a new record
@object.instance_variable_get(:@new_record_before_save)

Ambos são muito feios, mas permitem que você saiba se o objeto foi criado recentemente. Espero que ajude!

Tom Rossi
fonte
Atualmente estou em byebug em um after_saveretorno de chamada usando Rails 4 e nenhum deles está trabalhando para identificar este novo registro.
MCB de
Eu diria que @object.previous_changes[:id].any?é bastante simples e elegante. Funciona para mim depois que o registro é atualizado (não chamo de after_save).
thekingoftruth
1
@object.id_previously_changed?é um pouco menos feio.
aNoble de
@MCB no after_saveRails 4 que você gostaria de olhar ao changes[:id]invés de previous_changes[:id]. No entanto, isso está mudando no Rails5.1 (veja a discussão em github.com/rails/rails/pull/25337 )
gmcnaughton
previous_changes.key?(:id)para um melhor entendimento.
Sebastian Palma
2

Via Rails 5.1+:

user = User.new
user.save!
user.saved_change_to_attribute?(:id) # => true
Jacka
fonte
1

Para Rails 4 (verificado em 4.2.11.1), os resultados de changese os previous_changesmétodos são hashes vazios {}na criação de objetos internos after_save. Portanto, attribute_changed?métodos comoid_changed? não funcionarão conforme o esperado.

Mas você pode aproveitar este conhecimento e - sabendo que pelo menos 1 atributo deve estar em changesatualização - verifique se changesestá vazio. Depois de confirmar que está vazio, você deve estar durante a criação do objeto:

after_save do
  if changes.empty?
    # code appropriate for object creation goes here ...
  end
end
criptógrafo
fonte
0

Gosto de ser específico mesmo sabendo que :idnão deve mudar no normal, mas

(byebug) id_change.first.nil?
true

É sempre mais barato ser específico em vez de encontrar um bug inesperado muito estranho.

Da mesma forma, se eu esperar truesinalização de argumento não confiável

def foo?(flag)
  flag == true
end

Isso economiza muitas horas para não ficar sentado em cima de insetos estranhos.

x'ES
fonte