Подтвердить что ты не робот

Использование обратного вызова after_save для изменения одного и того же объекта без повторного запуска обратного вызова (рекурсия)

Если я добавлю обратный вызов after_save к модели ActiveRecord, и в этом обратном вызове я использую update_attribute для изменения объекта, обратный вызов вызывается снова, и поэтому происходит "переполнение стека" (хе-хе, не может устоять).

Можно ли избежать этого поведения, возможно, отключить обратный вызов во время его выполнения? Или есть другой подход?

Спасибо!

4b9b3361

Ответ 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 в этом методе