Як визначити, чи запис просто створений чи оновлений у програмі after_save


95

# Новий_запис? функція визначає, чи збережено запис. Але це завжди хибно в after_saveгачку. Чи можна визначити, чи є запис нещодавно створеним чи старим з оновлення?

Я сподіваюсь не використовувати інший зворотний виклик, наприклад, before_createвстановити прапор у моделі або вимагати іншого запиту в db.

Будь-яка порада буде вдячна.

Редагувати: Потрібно визначити це в after_saveгачку, і для мого конкретного випадку використання відсутня updated_atабо updated_onмітка часу


1
хм, може, передати параметр в before_save? просто думати вголос
Поїздка

Відповіді:


168

Я прагнув використати це для after_saveзворотного дзвінка.

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

Оновлення: Як зазначає @mitsy, якщо ця перевірка потрібна поза зворотними викликами, скористайтеся id_previously_changed?. Див. Документи .



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?буде збережено як false після збереження запису (принаймні поза гачками). У цьому випадку ви можете скористатисяid_previously_changed?
mltsy

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
Niels Kristian

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)

І те, і інше досить потворне, але вони дозволять вам дізнатись, чи об’єкт був нещодавно створений. Сподіваюся, що це допомагає!


На даний момент я перебуваю в byebug під час after_saveзворотного виклику, використовуючи Rails 4, і жоден з них не працює над ідентифікацією цього нового запису.
MCB

Я б сказав, що @object.previous_changes[:id].any?це досить просто і елегантно. Це працює для мене після оновлення запису (я його не дзвоню after_save).
thekingoftruth

1
@object.id_previously_changed?трохи менш потворний.
Благородний

@MCB у after_saveRails 4, на який ви хотіли б подивитися, changes[:id]а не на previous_changes[:id]. Однак це змінюється в Rails5.1 (див. Обговорення на github.com/rails/rails/pull/25337 )
gmcnaughton

previous_changes.key?(:id)для кращого розуміння.
Себастьян Пальма,

2

Рейки 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?не працюватимуть, як очікувалося.

Але ви можете скористатися цими знаннями і - знаючи, що принаймні 1 атрибут повинен бути changesвключений під час оновлення - перевірити, чи 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

Це економить багато годин, щоб не сидіти на дивних помилках.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.