【问题标题】:Using the after_save callback to modify the same object without triggering the callback again (recursion)使用 after_save 回调修改同一个对象而不再次触发回调(递归)
【发布时间】:2008-10-19 02:06:52
【问题描述】:

如果我向 ActiveRecord 模型添加 after_save 回调,并在该回调中使用 update_attribute 更改对象,则再次调用回调,因此会发生“堆栈溢出”(呵呵,无法抗拒)。

是否有可能避免这种行为,也许在执行期间禁用回调?还是有其他方法?

谢谢!

【问题讨论】:

    标签: ruby-on-rails


    【解决方案1】:

    一种解决方法是在类中设置一个变量,并在 after_save 中检查它的值。

    1. 先检查一下。 (如果是 var)
    2. 在调用 update_attribute 之前将其分配给“false”值。
    3. 调用 update_attribute。
    4. 将其分配给“真”值。
    5. 结束

    这样,它只会尝试保存两次。这可能会两次访问您的数据库,这可能是可取的,也可能是不可取的。

    我有一种模糊的感觉,就是内置了一些东西,但这是一种相当简单的方法,可以防止几乎任何应用程序中的特定递归点。 我还建议再次查看代码,因为您在 after_save 中所做的任何事情都应该在 before_save 中完成。有时这不是真的,但它们相当罕见。

    【讨论】:

    • 太棒了!我也搜索了一种内置方法,但到目前为止似乎没有,但如果你可以设置一个特殊属性来告诉 Rails 暂时挂起该回调,那就太好了......你的方法有点像那样做,非常感谢!
    【解决方案2】:

    您能改用 before_save 回调吗?

    【讨论】:

      【解决方案3】:

      我没有看到这个答案,所以我想我会添加它,以防它帮助任何人搜索这个主题。 (ScottD 的 without_callbacks 建议很接近。)

      ActiveRecord 针对这种情况提供了update_without_callbacks,但它是一个私有方法。无论如何,请使用 send 访问它。正在保存的对象的回调中正是使用它的原因。

      这里还有另一个 SO 线程很好地涵盖了这一点: How can I avoid running ActiveRecord callbacks?

      【讨论】:

        【解决方案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 方法:

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

          【讨论】:

            【解决方案6】:

            如果您使用 before_save,您可以在保存完成之前修改任何附加参数,这意味着您不必显式调用 save。

            【讨论】:

              【解决方案7】:

              这段代码甚至没有尝试解决线程或并发问题,就像 Rails 本身一样。如果您需要该功能,请注意!

              基本上,我们的想法是记录您的“save”递归调用的级别,并且仅在您退出最顶层时才允许 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】:

                谢谢大家,问题是我也更新了 other 对象(如果你愿意的话,兄弟姐妹)...忘记提及那部分...

                所以 before_save 是不可能的,因为如果保存失败,则必须恢复对其他对象的所有修改,这可能会变得混乱 :)

                【讨论】:

                  【解决方案9】:

                  诀窍就是使用#update_column

                  • 跳过验证。
                  • 回调被跳过。
                  • updated_at/updated_on 未更新。

                  此外,它只是向数据库发出一个快速更新查询。

                  http://apidock.com/rails/ActiveRecord/Persistence/update_columns

                  【讨论】:

                    【解决方案10】:

                    我也有这个问题。我需要保存一个取决于对象 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
                    

                    【讨论】:

                      【解决方案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_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 的场景

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 1970-01-01
                            • 2011-11-28
                            • 1970-01-01
                            • 1970-01-01
                            • 2013-03-22
                            • 2016-04-20
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多