【问题标题】:How can I avoid running ActiveRecord callbacks?如何避免运行 ActiveRecord 回调?
【发布时间】:2010-10-12 13:49:52
【问题描述】:

我有一些具有 after_save 回调的模型。通常这很好,但在某些情况下,例如在创建开发数据时,我想保存模型而不运行回调。有没有一种简单的方法可以做到这一点?类似于...

Person#save( :run_callbacks => false )

Person#save_without_callbacks

我查看了 Rails 文档并没有找到任何东西。然而,根据我的经验,Rails 文档并不总是能说明问题。

更新

我发现 a blog post 解释了如何从这样的模型中删除回调:

Foo.after_save.clear

我找不到该方法的记录位置,但它似乎有效。

【问题讨论】:

  • 如果您在回调中执行破坏性或昂贵的操作(例如发送电子邮件),我建议您将其移出并与控制器或其他地方分开触发。这样您就不会在开发等过程中“意外”触发它。
  • 您接受的解决方案对我不起作用。我正在使用 Rails 3。我收到如下错误:--undefined method `update_without_callbacks' for #<0x10ae9b848>

标签: ruby-on-rails ruby rails-activerecord


【解决方案1】:

有了 Rails 6,您现在可以使用 insert methods

来自文档:

在单个 SQL INSERT 中将多条记录插入数据库 陈述。它不会实例化任何模型,也不会触发 Active Record 回调或验证。虽然传递的值去 通过 Active Record 的类型转换和序列化。

【讨论】:

    【解决方案2】:

    使用update_column (Rails >= v3.1) 或update_columns (Rails >= 4.0) 跳过回调和验证。同样使用这些方法,updated_at更新。

    #Rails >= v3.1 only
    @person.update_column(:some_attribute, 'value')
    #Rails >= v4.0 only
    @person.update_columns(attributes)
    

    http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_column

    #2:跳过在创建对象时也有效的回调

    class Person < ActiveRecord::Base
      attr_accessor :skip_some_callbacks
    
      before_validation :do_something
      after_validation :do_something_else
    
      skip_callback :validation, :before, :do_something, if: :skip_some_callbacks
      skip_callback :validation, :after, :do_something_else, if: :skip_some_callbacks
    end
    
    person = Person.new(person_params)
    person.skip_some_callbacks = true
    person.save
    

    更新(2020 年)

    显然是Rails has always supported :if and :unless options,所以上面的代码可以简化为:

    class Person < ActiveRecord::Base
      attr_accessor :skip_some_callbacks
    
      before_validation :do_something, unless: :skip_some_callbacks
      after_validation :do_something_else, unless: :skip_some_callbacks
    end
    
    person = Person.new(person_params)
    person.skip_some_callbacks = true
    person.save
    

    【讨论】:

    • 看起来它也适用于 2.x,并且还有许多其他类似的方法:guides.rubyonrails.org/…
    • 这不解决:create_without_callbacks :(我怎样才能运行类似的东西?(在Rails2中工作,在Rails3中删除)。
    • 假设@person 是某个控制器中的一个变量,这个解决方案意味着阅读您的模型类的人将无法理解回调。他们会看到after_create :something_cool 并认为“太好了,创建后会发生一些很酷的事情!”。要真正理解您的模型类,他们将不得不遍历您的所有控制器,寻找您决定注入逻辑的所有小地方。我不喜欢它>o
    • after_create ..., unless: :skip_some_callbacks 替换 skip_callback ..., if: :skip_some_callbacks 以使用 after_create 正确运行。
    【解决方案3】:

    当我想运行 Rake 任务但没有为我保存的每条记录运行回调时,我遇到了同样的问题。 这对我有用(Rails 5),它必须适用于几乎所有版本的 Rails:

    class MyModel < ApplicationRecord
      attr_accessor :skip_callbacks
      before_create :callback1
      before_update :callback2
      before_destroy :callback3
    
      private
      def callback1
        return true if @skip_callbacks
        puts "Runs callback1"
        # Your code
      end
      def callback2
        return true if @skip_callbacks
        puts "Runs callback2"
        # Your code
      end
      # Same for callback3 and so on....
    end
    

    它的工作方式是它只是在方法的第一行返回true,它skip_callbacks为true,所以它不会运行方法中的其余代码。 要跳过回调,您只需在保存、创建、销毁之前将 skip_callbacks 设置为 true:

    rec = MyModel.new() # Or Mymodel.find()
    rec.skip_callbacks = true
    rec.save
    

    【讨论】:

      【解决方案4】:

      对于自定义回调,请在回调中使用 attr_accessorunless

      如下定义您的模型:

      class Person << ActiveRecord::Base
      
        attr_accessor :skip_after_save_callbacks
      
        after_save :do_something, unless: :skip_after_save_callbacks
      
      end
      

      然后,如果您需要在不触发您定义的after_save 回调的情况下保存记录,请将skip_after_save_callbacks 虚拟属性设置为true

      person.skip_after_save_callbacks #=> nil
      person.save # By default, this *will* call `do_something` after saving.
      
      person.skip_after_save_callbacks = true
      person.save # This *will not* call `do_something` after saving.
      
      person.skip_after_save_callbacks = nil # Always good to return this value back to its default so you don't accidentally skip callbacks.
      

      【讨论】:

        【解决方案5】:

        在某些情况下,最up-voted 的答案可能看起来令人困惑。

        您可以只使用一个简单的if 检查您是否想跳过回调,如下所示:

        after_save :set_title, if: -> { !new_record? && self.name_changed? }
        

        【讨论】:

          【解决方案6】:

          我需要 Rails 4 的解决方案,所以我想出了这个:

          app/models/concerns/save_without_callbacks.rb

          module SaveWithoutCallbacks
          
            def self.included(base)
              base.const_set(:WithoutCallbacks,
                Class.new(ActiveRecord::Base) do
                  self.table_name = base.table_name
                end
                )
            end
          
            def save_without_callbacks
              new_record? ? create_without_callbacks : update_without_callbacks
            end
          
            def create_without_callbacks
              plain_model = self.class.const_get(:WithoutCallbacks)
              plain_record = plain_model.create(self.attributes)
              self.id = plain_record.id
              self.created_at = Time.zone.now
              self.updated_at = Time.zone.now
              @new_record = false
              true
            end
          
            def update_without_callbacks
              update_attributes = attributes.except(self.class.primary_key)
              update_attributes['created_at'] = Time.zone.now
              update_attributes['updated_at'] = Time.zone.now
              update_columns update_attributes
            end
          
          end
          

          在任何模型中:

          include SaveWithoutCallbacks
          

          那么你可以:

          record.save_without_callbacks
          

          Model::WithoutCallbacks.create(attributes)
          

          【讨论】:

            【解决方案7】:

            应该适用于所有版本的 ActiveRecord,而不依赖于可能存在或不存在的选项或 activerecord 方法。

            module PlainModel
              def self.included(base)
                plainclass = Class.new(ActiveRecord::Base) do
                  self.table_name = base.table_name
                end
                base.const_set(:Plain, plainclass)
              end
            end
            
            
            # usage
            class User < ActiveRecord::Base
              include PlainModel
            
              validates_presence_of :email
            end
            
            User.create(email: "")        # fail due to validation
            User::Plain.create(email: "") # success. no validation, no callbacks
            
            user = User::Plain.find(1)
            user.email = ""
            user.save
            

            TLDR:在同一张表上使用“不同的 activerecord 模型”

            【讨论】:

              【解决方案8】:

              如果目标是在不使用回调或验证的情况下简单地插入记录,并且您希望在不使用其他 gem、添加条件检查、使用 RAW SQL 或以任何方式处理现有代码的情况下执行此操作,请考虑使用指向您现有数据库表的“影子对象”。像这样:

              class ImportedPerson < ActiveRecord::Base
                self.table_name = 'people'
              end
              

              这适用于 Rails 的每个版本,是线程安全的,并且完全消除了所有验证和回调,而无需修改现有代码。您可以在实际导入之前将该类声明扔掉,您应该一切顺利。请记住使用您的新类插入对象,例如:

              ImportedPerson.new( person_attributes )
              

              【讨论】:

              • 有史以来最好的解决方案。优雅而简单!
              • 这对我来说非常有效,因为这是我只想在测试中做的事情,以模拟数据库“之前”状态,而不用机器污染我的生产模型对象以选择性地跳过回调。
              • 迄今为止最好的答案
              • 赞成,因为它展示了如何解决现有的 rails 约束并帮助我了解整个对象 MVC 的真正工作原理。如此简单干净。
              【解决方案9】:

              您可以使用sneaky-save gem:https://rubygems.org/gems/sneaky-save

              请注意,这无助于在没有验证的情况下保存关联。它抛出错误'created_at cannot be null',因为它与模型不同,它直接插入 sql 查询。为了实现这一点,我们需要更新 db 的所有自动生成的列。

              【讨论】:

                【解决方案10】:

                要在 Rails 中创建测试数据,您可以使用以下 hack:

                record = Something.new(attrs)
                ActiveRecord::Persistence.instance_method(:create_record).bind(record).call
                

                https://coderwall.com/p/y3yp2q/edit

                【讨论】:

                  【解决方案11】:

                  当我需要完全控制回调时,我会创建另一个用作开关的属性。简单有效:

                  型号:

                  class MyModel < ActiveRecord::Base
                    before_save :do_stuff, unless: :skip_do_stuff_callback
                    attr_accessor :skip_do_stuff_callback
                  
                    def do_stuff
                      puts 'do stuff callback'
                    end
                  end
                  

                  测试:

                  m = MyModel.new()
                  
                  # Fire callbacks
                  m.save
                  
                  # Without firing callbacks
                  m.skip_do_stuff_callback = true
                  m.save
                  
                  # Fire callbacks again
                  m.skip_do_stuff_callback = false
                  m.save
                  

                  【讨论】:

                    【解决方案12】:

                    你可以使用update_columns:

                    User.first.update_columns({:name => "sebastian", :age => 25})
                    

                    更新对象的给定属性,不调用 save,因此跳过验证和回调。

                    【讨论】:

                      【解决方案13】:

                      你可以在你的 Person 模型中尝试这样的事情:

                      after_save :something_cool, :unless => :skip_callbacks
                      
                      def skip_callbacks
                        ENV[RAILS_ENV] == 'development' # or something more complicated
                      end
                      

                      编辑: after_save 不是一个符号,但这至少是我第 1000 次尝试制作它。

                      【讨论】:

                      • 我真的认为这是最好的答案。这样,确定何时跳过回调的逻辑在模型中可用,并且您不会到处都有疯狂的代码片段来回避业务逻辑,或绕过send 的封装。 KOODOS
                      【解决方案14】:

                      无需使用 gem 或插件即可在所有版本的 Rails 上工作的解决方案是直接发出更新语句。例如

                      ActiveRecord::Base.connection.execute "update table set foo = bar where id = #{self.id}"
                      

                      这可能(也可能不是)是一个选项,具体取决于您的更新有多复杂。这适用于例如从 within after_save 回调(无需重新触发回调)更新记录上的标志。

                      【讨论】:

                      • 不知道为什么投反对票,但我仍然认为上述答案是合法的。有时,避免 ActiveRecord 行为问题的最佳方法是避免使用 ActiveRecord。
                      • 原则上赞成反对 -1。我们刚刚遇到了一个生产问题(背后有很长的故事),需要我们创建一个新记录(而不是更新),并且触发回调将是灾难性的。无论他们承认与否,上述所有答案都是黑客,而去数据库是最好的解决方案。这有合法的条件。虽然应该注意#{...} 的 SQL 注入。
                      【解决方案15】:

                      更新:

                      @Vikrant Chaudhary 的解决方案似乎更好:

                      #Rails >= v3.1 only
                      @person.update_column(:some_attribute, 'value')
                      #Rails >= v4.0 only
                      @person.update_columns(attributes)
                      

                      我原来的答案:

                      查看此链接:How to skip ActiveRecord callbacks?

                      在 Rails3 中,

                      假设我们有一个类定义:

                      class User < ActiveRecord::Base
                        after_save :generate_nick_name
                      end 
                      

                      方法一:

                      User.send(:create_without_callbacks)
                      User.send(:update_without_callbacks)
                      

                      方法2: 当你想在你的 rspec 文件或其他文件中跳过它们时,试试这个:

                      User.skip_callback(:save, :after, :generate_nick_name)
                      User.create!()
                      

                      注意:一旦完成,如果您不在 rspec 环境中,您应该重置回调:

                      User.set_callback(:save, :after, :generate_nick_name)
                      

                      在 Rails 3.0.5 上对我来说很好

                      【讨论】:

                        【解决方案16】:

                        如果您使用的是 Rails 2。您可以使用 SQL 查询来更新您的列,而无需运行回调和验证。

                        YourModel.connection.execute("UPDATE your_models SET your_models.column_name=#{value} WHERE your_models.id=#{ym.id}")
                        

                        我认为它应该适用于任何 Rails 版本。

                        【讨论】:

                          【解决方案17】:

                          我在 Rails 3 中编写了一个实现 update_without_callbacks 的插件:

                          http://github.com/dball/skip_activerecord_callbacks

                          我认为,正确的解决方案是首先重写模型以避免回调,但如果这在短期内不切实际,这个插件可能会有所帮助。

                          【讨论】:

                            【解决方案18】:

                            https://gist.github.com/576546

                            只需将此猴子补丁转储到 config/initializers/skip_callbacks.rb

                            然后

                            Project.skip_callbacks { @project.save }

                            或类似的。

                            感谢作者

                            【讨论】:

                              【解决方案19】:

                              看起来在 Rails 2.3 中处理此问题的一种方法(因为 update_without_callbacks 已经消失等),将使用 update_all,这是根据section 12 of the Rails Guide to validations and callbacks 跳过回调的方法之一。

                              另外,请注意,如果您在 after_ 回调中执行某项操作,该操作基于许多关联(即 has_many assoc,您还执行 Accept_nested_attributes_for)进行计算,您将需要重新加载关联,以防万一保存时,其中一名成员被删除。

                              【讨论】:

                                【解决方案20】:

                                这些都不是 without_callbacks 插件,它只是做你需要的......

                                class MyModel < ActiveRecord::Base
                                  before_save :do_something_before_save
                                
                                  def after_save
                                    raise RuntimeError, "after_save called"
                                  end
                                
                                  def do_something_before_save
                                    raise RuntimeError, "do_something_before_save called"
                                  end
                                end
                                
                                o = MyModel.new
                                MyModel.without_callbacks(:before_save, :after_save) do
                                  o.save # no exceptions raised
                                end
                                

                                http://github.com/cjbottaro/without_callbacks 适用于 Rails 2.x

                                【讨论】:

                                  【解决方案21】:

                                  另一种方法是使用验证钩子而不是回调。例如:

                                  class Person < ActiveRecord::Base
                                    validate_on_create :do_something
                                    def do_something
                                      "something clever goes here"
                                    end
                                  end
                                  

                                  这样你可以默认获取do_something,但是你可以很容易地覆盖它:

                                  @person = Person.new
                                  @person.save(false)
                                  

                                  【讨论】:

                                  • 这似乎是个坏主意 - 您应该将事物用于其预期目的。你最不想要的就是你的验证有副作用。
                                  【解决方案22】:

                                  一种选择是为此类操作使用单独的模型,使用同一张表:

                                  class NoCallbacksModel < ActiveRecord::Base
                                    set_table_name 'table_name_of_model_that_has_callbacks'
                                  
                                    include CommonModelMethods # if there are
                                    :
                                    :
                                  
                                  end
                                  

                                  (同样的方法可能更容易绕过验证)

                                  斯蒂芬

                                  【讨论】:

                                    【解决方案23】:
                                    # for rails 3
                                      if !ActiveRecord::Base.private_method_defined? :update_without_callbacks
                                        def update_without_callbacks
                                          attributes_with_values = arel_attributes_values(false, false, attribute_names)
                                          return false if attributes_with_values.empty?
                                          self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
                                        end
                                      end
                                    

                                    【讨论】:

                                      【解决方案24】:

                                      此解决方案仅适用于 Rails 2。

                                      我刚刚对此进行了调查,我认为我有一个解决方案。您可以使用两种 ActiveRecord 私有方法:

                                      update_without_callbacks
                                      create_without_callbacks
                                      

                                      您将不得不使用 send 来调用这些方法。例子:

                                      p = Person.new(:name => 'foo')
                                      p.send(:create_without_callbacks)
                                      
                                      p = Person.find(1)
                                      p.send(:update_without_callbacks)
                                      

                                      这绝对是您真正想在控制台中或在进行一些随机测试时使用的东西。希望这会有所帮助!

                                      【讨论】:

                                      • 它对我不起作用。我正在使用 Rails 3。我收到如下错误:--undefined method `update_without_callbacks' for #<0x10ae9b848>
                                      【解决方案25】:

                                      导轨 3:

                                      MyModel.send("_#{symbol}_callbacks") # list  
                                      MyModel.reset_callbacks symbol # reset
                                      

                                      【讨论】:

                                      【解决方案26】:

                                      防止所有 after_save 回调的唯一方法是让第一个回调返回 false。

                                      也许您可以尝试类似(未经测试):

                                      class MyModel < ActiveRecord::Base
                                        attr_accessor :skip_after_save
                                      
                                        def after_save
                                          return false if @skip_after_save
                                          ... blah blah ...
                                        end
                                      end
                                      
                                      ...
                                      
                                      m = MyModel.new # ... etc etc
                                      m.skip_after_save = true
                                      m.save
                                      

                                      【讨论】:

                                      • 我喜欢尝试(未经测试)。惊险刺激。
                                      • 经过测试并且可以正常工作。我认为这是一个非常好的和干净的解决方案,谢谢!
                                      【解决方案27】:

                                      为什么您希望能够在开发中做到这一点?当然,这意味着您正在使用无效数据构建应用程序,因此它的行为会很奇怪,并且与您在生产环境中所期望的不同。

                                      如果你想用数据填充你的开发数据库,​​一个更好的方法是构建一个 rake 任务,该任务使用 faker gem 来构建有效数据并将其导入数据库,创建任意多或少的记录,但是如果您对此深信不疑,并且有充分的理由我想 update_without_callbacks 和 create_without_callbacks 可以正常工作,但是当您尝试按照自己的意愿弯曲轨道时,请问问自己您有充分的理由,以及您所做的是否真的很好想法。

                                      【讨论】:

                                      • 我不想在没有验证的情况下保存,只是没有回调。我的应用程序正在使用回调将一些静态 HTML 写入文件系统(有点像 CMS)。我不想在加载开发数据时这样做。
                                      • 只是一个想法,我想过去每当我看到这种问题时,它都会出于不好的原因试图解决问题。
                                      【解决方案28】:

                                      不是最干净的方式,但您可以将回调代码包装在检查 Rails 环境的条件中。

                                      if Rails.env == 'production'
                                        ...
                                      

                                      【讨论】:

                                        猜你喜欢
                                        • 2021-09-25
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2020-03-15
                                        • 2018-02-06
                                        • 2017-08-18
                                        • 1970-01-01
                                        相关资源
                                        最近更新 更多