代码之家  ›  专栏  ›  技术社区  ›  Ethan

如何避免运行ActiveRecord回调?

  •  124
  • Ethan  · 技术社区  · 15 年前

    我有一些模型在保存回调之后。通常可以,但在某些情况下,比如创建开发数据时,我希望保存模型而不运行回调。有简单的方法吗?类似于…

    Person#save( :run_callbacks => false )
    

    Person#save_without_callbacks
    

    我查看了Rails文档,没有发现任何东西。然而,根据我的经验,Rails文档并不总能讲述整个故事。

    更新

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

    Foo.after_save.clear
    

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

    25 回复  |  直到 6 年前
        1
  •  71
  •   efalcao    14 年前

    此解决方案仅适用于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)
    

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

        2
  •  215
  •   Vikrant Chaudhary    10 年前

    使用 update_column (轨道>=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
    
        3
  •  27
  •   Community Reversed Engineer    7 年前

    更新:

    @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?

    在铁路3中,

    假设我们有一个类定义:

    class User < ActiveRecord::Base
      after_save :generate_nick_name
    end 
    

    方法1

    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)
    

    在轨道3.0.5上对我来说很好

        4
  •  19
  •   guai    14 年前

    轨道3:

    MyModel.send("_#{symbol}_callbacks") # list  
    MyModel.reset_callbacks symbol # reset
    
        5
  •  15
  •   Benjamin Manns    11 年前

    您可以在您的个人模型中尝试类似的操作:

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

    编辑: 节后不是一个符号,但这至少是我第1000次尝试使它成为一个符号。

        6
  •  15
  •   Brad Werth    9 年前

    如果目标是简单地插入一个不带回调或验证的记录,并且您希望这样做而不使用其他gems、添加条件检查、使用原始SQL或以任何方式使用现有代码,请考虑使用指向现有DB表的“影子对象”。就像这样:

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

    这适用于Rails的每个版本,是线程安全的,并且完全消除了所有验证和回调,而不修改现有代码。您可以在实际导入之前将该类声明直接放入,这样就可以很好地执行了。只需记住使用新类插入对象,例如:

    ImportedPerson.new( person_attributes )
    
        7
  •  9
  •   Dorian    10 年前

    你可以使用 update_columns :

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

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

        8
  •  5
  •   rfunduk    15 年前

    防止所有“保存”回调的唯一方法是让第一个回调返回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
    
        9
  •  5
  •   rogerdpack    13 年前

    在Rails 2.3中,处理这个问题的一种方法是使用update-all,它是根据 section 12 of the Rails Guide to validations and callbacks .

    另外,请注意,如果您在after_u回调中执行某项操作,该操作将根据多个关联进行计算(即,a有多个关联,其中您也接受了嵌套的属性),则需要重新加载关联,以防作为保存的一部分,它的一个成员被删除。

        10
  •  4
  •   fringd    12 年前

    https://gist.github.com/576546

    只需将这个monkey补丁转储到config/initializers/skip_callbacks.rb

    然后

    Project.skip_callbacks { @project.save }

    诸如此类。

    全部归功于作者

        11
  •  3
  •   Aleks    7 年前

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

    你只需要一个简单的 if 检查是否要跳过回调,如下所示:

    after_save :set_title, if: -> { !new_record? && self.name_changed? }
    
        12
  •  2
  •   Dave Smylie    11 年前

    不使用gem或plugin就可以跨所有版本的Rails工作的解决方案是直接发出update语句。如

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

    根据更新的复杂程度,这可能是一个选项(也可能不是)。这对于更新记录中的标志(例如 在内部 保存后回调(不重新触发回调)。

        13
  •  1
  •   Sasha Alexandrov    14 年前
    # 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
    
        14
  •  1
  •   nickgrim    13 年前

    这些都没有指向 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

        15
  •  1
  •   Donald ball    12 年前

    我写了一个插件,在Rails 3中实现了不带回调的更新:

    http://github.com/dball/skip_activerecord_callbacks

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

        16
  •  1
  •   oivoodoo    12 年前

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

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

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

        17
  •  1
  •   tothemario    10 年前

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

    模型:

    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
    
        18
  •  1
  •   Wojtek Kruszewski    10 年前

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

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

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

        19
  •  1
  •   ArtOfWarfare    10 年前

    你可以使用鬼鬼祟祟的拯救宝石: https://rubygems.org/gems/sneaky-save .

    注意,如果没有验证,这对保存关联没有帮助。它抛出错误“created_at cannot be null”,因为它直接插入与模型不同的SQL查询。为了实现这一点,我们需要更新数据库中所有自动生成的列。

        20
  •  1
  •   Steve Friedman    8 年前

    我需要一个针对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)
    
        21
  •  0
  •   nitecoder    15 年前

    为什么您希望在开发中能够做到这一点?当然,这意味着您正在使用无效数据构建应用程序,因此,它的行为将异常,而不是您在生产中所期望的那样。

    如果你想用数据填充你的开发数据库,一个更好的方法是建立一个rake任务,使用faker gem来构建有效的数据并将其导入数据库,创建尽可能多或很少的记录,但是如果你执意于它,并且有一个很好的理由,我猜在没有回调的情况下更新和在没有回调的情况下创建都可以,B但是,当你试图将铁轨弯曲到你的意愿时,问问你自己你有一个很好的理由,并且你所做的是否真的是一个好主意。

        22
  •  0
  •   Stephan Wehner    13 年前

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

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

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

    斯蒂芬

        23
  •  0
  •   Ryenski    13 年前

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

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

    这样,您可以在默认情况下获得“做某件事”,但您可以轻松地用以下方法覆盖它:

    @person = Person.new
    @person.save(false)
    
        24
  •  0
  •   choonkeat    8 年前

    应该与所有版本的 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模型”

        25
  •  -3
  •   james    15 年前

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

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