2008-10-19 133 views
27

如果我將一個after_save回調添加到ActiveRecord模型,並在該回調中使用update_attribute更改對象,則會再次調用回調,並且所以發生了「堆棧溢出」(呵呵,無法抗拒)。使用after_save回調修改同一對象而不觸發回調(遞歸)

是否有可能避免這種行爲,也許可以在執行期間禁用回調?還是有另一種方法?

謝謝!

回答

13

一種解決方法是在類中設置一個變量,並在after_save中檢查其值。

  1. 請先檢查一下。 (if var)
  2. 在調用update_attribute之前,將它指定爲'false'值。
  3. 調用update_attribute。
  4. 將它指定爲「真」值。
  5. 結束

通過這種方式,它只會嘗試保存兩次。這可能會打擊你的數據庫兩次,這可能會或可能不需要。

我有一種模糊的感覺,即有內置的東西,但這是一種相當簡單的方法來防止任何應用程序中的遞歸特定點。 我還建議再次查看代碼,因爲無論你在after_save中做什麼都應該在before_save中完成。有時候這不是真的,但它們相當罕見。

+0

太棒了!我也在尋找內置的方法,但到目前爲止似乎沒有,但如果您可以設置一個特殊屬性來告訴Rails暫時暫停回調,那將是非常棒的......您的方法就像這樣,非常感謝! – Ivan 2008-10-19 11:37:10

6

看看如何實現update_attribute。使用send方法代替:

send(name.to_s + '=', value) 
10

你可以使用before_save回調嗎?

3

如果使用before_save,則可以在保存完成之前修改任何其他參數,這意味着您不必顯式調用save。

2

謝謝你們,問題是,我更新其他對象了(如果你願意的兄弟姐妹)...忘了提,部分...

所以before_save是不可能的,因爲如果保存失敗所有修改到其他對象將不得不恢復,並可能變得凌亂:)

3

此代碼甚至不嘗試解決線程或併發問題,就像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 
+0

這很聰明,謝謝! – Ivan 2008-10-20 22:35:02

7

你也可以看看插件Without_callbacks。它向AR添加了一種方法,可以讓您跳過給定塊的某些回調。 例如:

def your_after_save_func 
    YourModel.without_callbacks(:your_after_save_func) do 
    Your updates/changes 
    end 
end 
+0

不知道該插件,它會派上用場,謝謝! – Ivan 2008-10-20 22:39:27

1

我也有這個問題。我需要保存一個取決於對象ID的屬性。我解決了它的使用條件調用回調...

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's save only if we're sure the triggering condition will fail  
     self.save if bar_attr 
    end 
10

我沒有看到這個答案,所以我想如果它有助於任何有關這個主題的搜索我會添加它。 (ScottD的without_callbacks建議很接近。)

ActiveRecord爲這種情況提供了update_without_callbacks,但它是一種私有方法。無論如何請使用send來訪問它。在你保存的對象的回調中,正是使用它的原因。

而且還有另外一個SO線程這裏覆蓋此相當不錯: How can I avoid running ActiveRecord callbacks?

1

有時候,這是因爲在模型不指定attr_accessible的。當update_attribute想要編輯屬性時,如果發現它們不可訪問並且創建新的對象。在保存新對象時,它將進入一個無止境的循環。

0

我有一個需要在文本塊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 

鍵停止遞歸是分配從屬性值和然後將該屬性設置爲零,以便在隨後的保存中不滿足:if條件。

0

可以在關聯使用after_saveif如下:

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