Использование обратного вызова after_save для изменения одного и того же объекта без повторного запуска обратного вызова (рекурсия)
Если я добавлю обратный вызов after_save к модели ActiveRecord, и в этом обратном вызове я использую update_attribute для изменения объекта, обратный вызов вызывается снова, и поэтому происходит "переполнение стека" (хе-хе, не может устоять).
Можно ли избежать этого поведения, возможно, отключить обратный вызов во время его выполнения? Или есть другой подход?
Спасибо!
Ответы
Ответ 1
Один способ - установить переменную в классе и проверить ее значение в after_save.
- Сначала проверьте это. (если var)
- Перед вызовом update_attribute присвойте ему значение "false".
- вызывать update_attribute.
- Назначьте это значение "true".
- конец
Таким образом, он попытается сохранить только дважды. Это, скорее всего, ударит по вашей базе данных дважды, что может быть или не быть желательным.
У меня есть смутное чувство, что там что-то встроено, но это довольно надежный способ предотвратить определенную точку рекурсии практически в любом приложении.
Я также рекомендовал бы снова взглянуть на код, так как вероятно, что все, что вы делаете в after_save, должно быть сделано в before_save. Иногда это не так, но они довольно редки.
Ответ 2
Вместо этого вы можете использовать обратный вызов before_save?
Ответ 3
Я не видел этого ответа, поэтому я подумал, что добавлю его, если он поможет любому, кто ищет эту тему. (Предложение ScottD without_callbacks близко.)
ActiveRecord предоставляет update_without_callbacks
для этой ситуации, но это частный метод. Используйте send, чтобы получить доступ к нему в любом случае. Внутри обратного вызова для объекта, который вы сохраняете, именно причина этого.
Также здесь есть еще один SO-поток, который очень хорошо охватывает:
Как избежать обратных вызовов ActiveRecord?
Ответ 4
Также вы можете посмотреть плагин Without_callbacks. Он добавляет метод AR, который позволяет пропустить определенные обратные вызовы для данного блока.
Пример:
def your_after_save_func
YourModel.without_callbacks(:your_after_save_func) do
Your updates/changes
end
end
Ответ 5
Посмотрите, как update_attribute реализовано. Вместо этого используйте метод отправки:
send(name.to_s + '=', value)
Ответ 6
Если вы используете before_save, вы можете изменить любые дополнительные параметры до завершения сохранения, то есть вам не придется явно вызывать сохранение.
Ответ 7
Этот код даже не пытается устранить проблемы с потоками или concurrency, как и Rails. Если вам нужна эта функция, обратите внимание!
В принципе, идея состоит в том, чтобы подсчитать, на каком уровне рекурсивных вызовов "сохранить" вы находитесь, и разрешать after_save только после того, как вы выходите из верхнего уровня. Вы также захотите добавить обработку исключений.
def before_save
@attempted_save_level ||= 0
@attempted_save_level += 1
end
def after_save
if (@attempted_save_level == 1)
#fill in logic here
save #fires before_save, incrementing save_level to 2, then after_save, which returns without taking action
#fill in logic here
end
@attempted_save_level -= 1 # reset the "prevent infinite recursion" flag
end
Ответ 8
Спасибо, ребята, проблема в том, что я обновляю и другие объекты (братья и сестры, если хотите)... забыл упомянуть ту часть...
Таким образом, вопрос before_save не может быть и речи, потому что если сбой не удается, все изменения в других объектах должны быть возвращены, и это может стать беспорядочным:)
Ответ 9
Хитрость заключается только в использовании #update_column
:
- Проверки пропускаются.
- Обратные вызовы пропускаются.
- updated_at/updated_on не обновляется.
Кроме того, он просто выдает один быстрый запрос на обновление в db.
http://apidock.com/rails/ActiveRecord/Persistence/update_columns
Ответ 10
У меня тоже была эта проблема. Мне нужно сохранить атрибут, который зависит от идентификатора объекта. Я решил это, используя условный вызов для обратного вызова...
Class Foo << ActiveRecord::Base
after_save :init_bar_attr, :if => "bar_attr.nil?" # just make sure this is false after the callback runs
def init_bar_attr
self.bar_attr = "my id is: #{self.id}"
# careful now, let save only if we're sure the triggering condition will fail
self.save if bar_attr
end
Ответ 11
Иногда это связано с тем, что в моделях не указывается attr_accessible. Когда update_attribute хочет изменить атрибуты, если выясняется, что они недоступны и вместо них создаются новые объекты. При сохранении новых объектов он попадает в бесконечный цикл.
Ответ 12
Мне понадобилось gsub
имена путей в блоке текста, когда его запись была скопирована в другой контекст:
attr_accessor :original_public_path
after_save :replace_public_path, :if => :original_public_path
private
def replace_public_path
self.overview = overview.gsub(original_public_path, public_path)
self.original_public_path = nil
save
end
Ключом, чтобы остановить рекурсию, было назначение значения из атрибута, а затем установка атрибута на nil, чтобы условие :if
не выполнялось при последующем сохранении.
Ответ 13
Вы можете использовать after_save
в связи с if
следующим образом:
after_save :after_save_callback, if: Proc.new {
//your logic when to call the callback
}
или
after_save :after_save_callback, if: :call_if_condition
def call_if_condition
//condition for when to call the :after_save_callback method
end
call_if_condition
- метод. Определите сценарий, когда вызывать after_save_callback
в этом методе