Jak ustalić, czy rekord został właśnie utworzony czy zaktualizowany w after_save

96

#New_record? funkcja określa, czy rekord został zapisany. Ale zawsze jest fałszywe wafter_save . Czy istnieje sposób ustalenia, czy rekord jest nowo utworzonym rekordem, czy starym z aktualizacji?

Mam nadzieję, że nie użyję innego wywołania zwrotnego, takiego jak before_createustawienie flagi w modelu lub wymaganie innego zapytania do bazy danych.

Każda rada jest mile widziana.

Edycja: trzeba to określić w after_savepodpięciu, a dla mojego konkretnego przypadku użycia nie ma updated_atani updated_onsygnatury czasowej

Aaron Qian
źródło
1
hmm może przekazać parametr w before_save? tylko głośno myślę
Wycieczka

Odpowiedzi:

169

Chciałem użyć tego do after_saveoddzwonienia.

Prostszym rozwiązaniem jest użycie id_changed?(ponieważ się nie zmieni update) lub nawet created_at_changed?jeśli obecne są kolumny z datownikiem.

Aktualizacja: jak wskazuje @mitsy, jeśli to sprawdzenie jest potrzebne poza wywołaniami zwrotnymi, użyj id_previously_changed?. Zobacz dokumentację .

Zubin
źródło
3
przez ActiveModel :: Dirty
chaserx,
6
Najlepiej jest to rozróżnić za pomocą after_update i after_create. Wywołania zwrotne mogą mieć wspólną metodę, która przyjmuje argument wskazujący, czy jest to tworzenie, czy aktualizacja.
matthuhiggins
2
To mogło się zmienić. Przynajmniej w Railsach 4 wywołanie zwrotne after_save działa po wywołaniu zwrotnym after_create lub after_update (zobacz guide.rubyonrails.org/active_record_callbacks.html ).
Mark
3
Sprawdzanie, czy żadne z tych pól nie działa poza domeną after_save.
fatuhoku
3
id_changed?będzie fałszywe po zapisaniu rekordu (przynajmniej poza hakami). W takim przypadku możesz użyćid_previously_changed?
mltsy
31

Nie ma tu magii szyn, o której wiem, będziesz musiał to zrobić sam. Możesz to wyczyścić za pomocą wirtualnego atrybutu ...

W swojej klasie modelu:

def before_save
  @was_a_new_record = new_record?
  return true
end

def after_save
  if @was_a_new_record
    ...
  end
end
Jeff Paquette
źródło
26

Jeszcze inna opcja dla tych, którzy zrobienia mają updated_attimestamp:

if created_at == updated_at
  # it's a newly created record
end
colllin
źródło
Niezły pomysł, ale wydaje się, że w niektórych sytuacjach może to przynieść odwrotny skutek (niekoniecznie kuloodporne).
Ash Blue
W zależności od tego, kiedy migracja zostanie uruchomiona, rekordy created_at i updated_at mogą być wyłączone. Zawsze też istnieje możliwość, że ktoś zaktualizuje rekord zaraz po jego początkowym zapisaniu, co może spowodować utratę synchronizacji czasu. To nie jest zły pomysł, po prostu wydaje się, że można dodać bardziej kuloodporną implementację.
Ash Blue
Mogą również być równe długo po utworzeniu rekordu. Jeśli rekord nie jest aktualizowany przez miesiące, będzie wyglądał, jakby został właśnie utworzony .
bschaeffer,
1
@bschaeffer Przepraszam, moje pytanie brzmiało: „Czy możliwe created_atjest wyrównanie updated_atw after_savewywołaniu zwrotnym w dowolnym momencie, poza momentem jego utworzenia?”
colllin
1
@colllin: Podczas tworzenia rekordu created_ati updated_atbędzie równy w after_savewywołaniu zwrotnym. We wszystkich innych sytuacjach nie będą one równe w after_savewywołaniu zwrotnym.
bschaeffer,
22

Istnieje after_createwywołanie zwrotne, które jest wywoływane tylko wtedy, gdy rekord jest nowym rekordem, po jego zapisaniu . Istnieje również after_updatewywołanie zwrotne do użycia, jeśli był to istniejący rekord, który został zmieniony i zapisany. after_saveZwrotna jest wywoływana, w obu przypadkach, po albo after_createczy after_updatejest tzw.

Użyj, after_createjeśli chcesz, aby coś się wydarzyło po zapisaniu nowego rekordu.

Więcej informacji tutaj: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

Sharpone
źródło
1
Hej, dzięki za odpowiedź, bardzo mi to pomogło. Pozdrawiam, +10 :)
Adam McArthur
1
W rzeczywistości może to nie być prawda. Jeśli masz skojarzenia w swoim utworze, to after_create nazywa się PRZED utworzeniem skojarzeń, więc jeśli chcesz się upewnić, że WSZYSTKO zostało utworzone, musisz użyć after_save
Niels Kristian
18

Ponieważ obiekt został już zapisany, musiałbyś spojrzeć na poprzednie zmiany. Identyfikator powinien się zmienić dopiero po utworzeniu.

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

Istnieje również zmienna instancji @new_record_before_save. Możesz uzyskać do niego dostęp, wykonując następujące czynności:

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

Oba są dość brzydkie, ale pozwolą ci wiedzieć, czy obiekt został nowo utworzony. Mam nadzieję, że to pomoże!

Tom Rossi
źródło
Obecnie jestem w trybie byebug w after_savewywołaniu zwrotnym przy użyciu Rails 4 i żaden z nich nie pracuje nad identyfikacją nowego rekordu.
MCB
Powiedziałbym, że @object.previous_changes[:id].any?jest to dość proste i eleganckie. U mnie działa po zaktualizowaniu rekordu (nie dzwonię z tego after_save).
thekingoftruth
1
@object.id_previously_changed?jest trochę mniej brzydki.
Szlachetny
@MCB in after_saveon Rails 4, na które wolałbyś changes[:id]raczej spojrzeć previous_changes[:id]. To się jednak zmienia w Railsach5.1 (zobacz dyskusję na github.com/rails/rails/pull/25337 )
gmcnaughton
previous_changes.key?(:id)dla lepszego zrozumienia.
Sebastian Palma
3

Rails 5.1+ sposób:

user = User.new
user.save!
user.saved_change_to_attribute?(:id) # => true
Jacka
źródło
1

Dla Rails 4 (sprawdzonych w 4.2.11.1) rezultatami changesi previous_changesmetod są puste skróty {}przy tworzeniu obiektów wewnątrz after_save. Więc attribute_changed?metody takie jak id_changed?nie będą działać zgodnie z oczekiwaniami.

Ale możesz skorzystać z tej wiedzy i - wiedząc, że musi być co najmniej 1 atrybut changes aktualizacji - sprawdzić, czy changesjest pusty. Gdy potwierdzisz, że jest pusty, musisz być podczas tworzenia obiektu:

after_save do
  if changes.empty?
    # code appropriate for object creation goes here ...
  end
end
cryptogopher
źródło
0

Lubię być konkretny, nawet jeśli o tym wiem :id normalnie nie powinno się to zmienić, ale

(byebug) id_change.first.nil?
true

Zawsze taniej jest być konkretnym, zamiast znajdować bardzo dziwny, nieoczekiwany błąd.

Tak samo, jeśli oczekuję trueflagi od niewiarygodnego argumentu

def foo?(flag)
  flag == true
end

Oszczędza to wiele godzin, aby nie siedzieć na dziwnych błędach.

x'ES
źródło