Как определить, была ли запись только что создана или обновлена ​​в after_save

96

#New_record? функция определяет, была ли сохранена запись. Но на after_saveкрючке всегда ложно . Есть ли способ определить, является ли запись новой записью или старой после обновления?

Я надеюсь не использовать другой обратный вызов, например, before_createчтобы установить флаг в модели или потребовать другой запрос в базу данных.

Любой совет приветствуется.

Edit: Нужно, чтобы определить его в after_saveкрюке, и для моего конкретного случая использования, нет updated_atили updated_onметку времени

Аарон Цянь
источник
1
хм, может быть, передать параметр в before_save? просто думаю вслух
Поездка

Ответы:

169

Я хотел использовать это для after_saveобратного вызова.

Более простое решение - использовать id_changed?(поскольку оно не изменится update) или даже created_at_changed?при наличии столбцов с отметками времени.

Обновление: как указывает @mitsy, если эта проверка необходима вне обратных вызовов, используйте id_previously_changed?. См. Документы .

Зубин
источник
3
через ActiveModel :: Dirty
chaserx
6
Лучше всего различать after_update и after_create. Обратные вызовы могут использовать общий метод, который принимает аргумент, чтобы указать, создание или обновление.
matthuhiggins
2
Это могло измениться. По крайней мере, в Rails 4 обратный вызов after_save запускается после обратного вызова after_create или after_update (см. Guides.rubyonrails.org/active_record_callbacks.html ).
Марк
3
Проверка того, что ни одно из этих полей не работает вне after_save.
fatuhoku
3
id_changed?будет ложным после сохранения записи (по крайней мере, за пределами хуков). В таком случае можно использоватьid_previously_changed?
млцы
31

Насколько я знаю, здесь нет рельсовой магии, тебе придется сделать это самому. Вы можете очистить это с помощью виртуального атрибута ...

В вашем классе модели:

def before_save
  @was_a_new_record = new_record?
  return true
end

def after_save
  if @was_a_new_record
    ...
  end
end
Джефф Пакетт
источник
26

Еще один вариант для тех, у кого есть updated_atвременная метка:

if created_at == updated_at
  # it's a newly created record
end
Коллин
источник
Неплохая идея, но кажется, что в некоторых ситуациях это может иметь неприятные последствия (не обязательно пуленепробиваемое).
Ash Blue
В зависимости от того, когда выполняется миграция, записи created_at и updated_at могут быть отключены. Также у вас всегда есть шанс, что кто-то обновит запись сразу после ее первоначального сохранения, что может привести к рассинхронизации времени. Это неплохая идея, просто кажется, что можно добавить более пуленепробиваемую реализацию.
Ash Blue
Также они могут быть одинаковыми спустя долгое время после создания записи. Если запись не обновляется в течение нескольких месяцев, она будет выглядеть так, как будто она только что создана .
bschaeffer
1
@bschaeffer Извините, у меня был вопрос: "Можно created_atли updated_atв обратном вызове выполнить равенство в after_saveлюбое время, кроме того, когда он был впервые создан?"
colllin
1
@colllin: при создании записи created_atи updated_atбудет равно в обратном вызове after_save. Во всех других ситуациях они не будут равны в after_saveобратном вызове.
bschaeffer
22

Существует after_createобратный вызов, который вызывается только в том случае, если запись является новой записью, после ее сохранения . Существует также after_updateобратный вызов для использования, если это была существующая запись, которая была изменена и сохранена. after_saveОбратного вызова вызывается в обоих случаях, после того как after_createили after_updateназывается.

Используйте, after_createесли вам нужно, чтобы что-то произошло один раз после сохранения новой записи.

Подробнее здесь: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

острый
источник
1
Привет, спасибо за ответ, это мне очень помогло. Ура, чувак, +10 :)
Адам МакАртур
1
На самом деле это может быть неправдой. Если у вас есть ассоциации в вашем творении, то after_create вызывается ДО того, как ассоциации будут созданы, поэтому, если вам нужно убедиться, что ВСЕ создано, тогда вам нужно использовать after_save
Нильс Кристиан
18

Поскольку объект уже был сохранен, вам нужно будет просмотреть предыдущие изменения. Идентификатор должен изменяться только после создания.

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

Также есть переменная экземпляра @new_record_before_save. Вы можете получить к нему доступ, выполнив следующие действия:

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

Оба довольно уродливые, но они позволят вам узнать, был ли объект создан заново. Надеюсь, это поможет!

Том Росси
источник
В настоящее время я нахожусь в процессе after_saveобратного вызова с использованием Rails 4, и ни один из них не работает, чтобы идентифицировать эту новую запись.
MCB
Я бы сказал, что @object.previous_changes[:id].any?это довольно просто и элегантно. У меня работает после обновления записи (откуда не звоню after_save).
thekingoftruth
1
@object.id_previously_changed?немного менее некрасиво.
aNoble
@MCB в after_saveRails 4, на который вы бы хотели посмотреть changes[:id]вместо previous_changes[:id]. Однако это изменилось в Rails5.1 (см. Обсуждение в github.com/rails/rails/pull/25337 )
gmcnaughton 03
1
previous_changes.key?(:id)для лучшего понимания.
Себастьян Пальма,
3

Способ Rails 5.1+:

user = User.new
user.save!
user.saved_change_to_attribute?(:id) # => true
Джека
источник
1

Для Rails 4 (проверено на 4.2.11.1) результаты changesи previous_changesметоды - это пустые хэши {}при создании объекта внутри after_save. Так что attribute_changed?такие методы id_changed?не будут работать должным образом.

Но вы можете воспользоваться этим знанием и, зная, что при changesобновлении должен присутствовать хотя бы 1 атрибут, проверьте, не changesявляется ли он пустым. Как только вы подтвердите, что он пуст, вы должны быть во время создания объекта:

after_save do
  if changes.empty?
    # code appropriate for object creation goes here ...
  end
end
криптограф
источник
0

Мне нравится быть конкретным, даже если я знаю, что :idобычно это не должно измениться, но

(byebug) id_change.first.nil?
true

Всегда дешевле быть конкретным, чем найти очень странную неожиданную ошибку.

Точно так же, если я ожидаю trueфлага от недостоверного аргумента

def foo?(flag)
  flag == true
end

Это экономит много часов, чтобы не сидеть на странных ошибках.

x'ES
источник