如何避免运行ActiveRecord回调?

时间:2009-03-10 23:52:23

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

我有一些具有after_save回调的模型。通常这很好,但在某些情况下,比如在创建开发数据时,我想保存模型而不运行回调。有一个简单的方法吗?类似于......的东西。

Person#save( :run_callbacks => false )

Person#save_without_callbacks

我查看了Rails文档但没有找到任何内容。但是根据我的经验,Rails文档并不总能说出整个故事。

更新

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

Foo.after_save.clear

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

28 个答案:

答案 0 :(得分:217)

使用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

答案 1 :(得分:72)

此解决方案仅适用于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 :(得分:28)


更新:

@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 

方法1:

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

Approach2: 如果您想在rspec文件或其他内容中跳过它们,请尝试以下操作:

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

注意:完成此操作后,如果您不在rspec环境中,则应重置回调:

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

在rails 3.0.5上运行正常。

答案 3 :(得分:20)

rails 3:

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

答案 4 :(得分:16)

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

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

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

ImportedPerson.new( person_attributes )

答案 5 :(得分:15)

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

after_save :something_cool, :unless => :skip_callbacks

def skip_callbacks
  ENV[RAILS_ENV] == 'development' # or something more complicated
end

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

答案 6 :(得分:9)

您可以使用update_columns

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

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

答案 7 :(得分:5)

阻止所有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

答案 8 :(得分:5)

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

另外,请注意,如果您在after_回调中执行某些操作,即基于多个关联(即has_many assoc,您也执行accepts_nested_attributes_for)进行计算,则需要重新加载关联,以防作为部分保存,其中一个成员被删除。

答案 9 :(得分:4)

https://gist.github.com/576546

将这个猴子补丁转储到config / initializers / skip_callbacks.rb

然后

Project.skip_callbacks { @project.save }

等。

归功于作者

答案 10 :(得分:3)

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

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

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

答案 11 :(得分:3)

应该在不使用gem或插件的情况下在所有Rails版本中运行的解决方案只是直接发出更新语句。例如

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

根据您的更新的复杂程度,这可能是(也可能不是)。这非常适用于例如更新来自 的on_save回调记录(不重新触发回调)。

答案 12 :(得分:1)

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

型号:

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

答案 13 :(得分:1)

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

来自文档:

<块引用>

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

答案 14 :(得分:1)

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

应用程序/模型/关切/ 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)

答案 15 :(得分:1)

要在Rails中创建测试数据,请使用此hack:

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

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

答案 16 :(得分:1)

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

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

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

答案 17 :(得分:1)

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

http://github.com/dball/skip_activerecord_callbacks

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

答案 18 :(得分:1)

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

答案 19 :(得分:1)

你可以使用偷偷摸摸的宝石:https://rubygems.org/gems/sneaky-save

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

答案 20 :(得分:1)

# 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

答案 21 :(得分:0)

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

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 :(得分:0)

一种选择是使用相同的表格为这种操作建立单独的模型:

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(相同的方法可能会使绕过验证更容易)

斯蒂芬

答案 23 :(得分:0)

为什么您希望能够在开发中执行此操作?当然这意味着您正在使用无效数据构建应用程序,因此它会表现得很奇怪,而不是您在生产中所期望的那样。

如果要使用数据填充dev db,更好的方法是构建一个rake任务,使用faker gem构建有效数据并将其导入到db中,根据需要创建多个或几个记录,但是如果你坚持下去并且有充分的理由我认为update_without_callbacks和create_without_callbacks会正常工作,但当你试图弯曲轨道时,请问你自己有充分的理由,如果你正在做的事情真的很好想法。

答案 24 :(得分:0)

适用于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 :(得分:0)

对于自定义回调,请在回调中使用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.

答案 26 :(得分:0)

当我想运行Rake Task时却遇到同样的问题,但是我没有为我保存的每条记录运行回调。 这对我有用(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

答案 27 :(得分:-3)

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

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