如何避免运行 ActiveRecord 回调?

我有一些有 after _ save 回调的模型。通常这是可以的,但是在某些情况下,比如在创建开发数据时,我希望在不运行回调的情况下保存模型。有什么简单的方法吗?类似于..。

Person#save( :run_callbacks => false )

或者

Person#save_without_callbacks

我查了 Rails 的文档,什么都没找到。然而,根据我的经验,Rails 文档并不总是讲述整个故事。

更新

我发现 一篇博客文章解释了如何从这样的模型中删除回调函数:

Foo.after_save.clear

我找不到这种方法的文档记录,但它似乎是有效的。

127196 次浏览

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

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

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

after_save :something_cool, :unless => :skip_callbacks


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

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

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

这个解决方案只是 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)

这绝对是您只希望在控制台中或在进行一些随机测试时使用的东西。希望这个能帮上忙!

为什么您希望在开发中能够这样做?毫无疑问,这将意味着您正在使用无效数据构建应用程序,因此它的行为会很奇怪,并且不像您在生产中预期的那样。

如果你想用数据填充 dev db,一个更好的方法是构建一个 rake 任务,使用 faker gem 来构建有效数据,并将其导入 db,创建你想要的尽可能多或尽可能少的记录,但是如果你执意要这么做,并且有一个好的理由,我想 update _ without _ callback 和 create _ without _ callback 会很好地工作,但是当你试图按照自己的意愿弯曲导轨时,问问自己,你有一个好的理由,并且你所做的是否真的是一个好主意。

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

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)

在 Rails 2.3中(因为 update _ without _ callback 消失了,等等)处理这个问题的一种方法似乎是使用 update _ all,这是根据 验证和回调 Rails 指南的第12节跳过回调的方法之一。

另外,请注意,如果您正在 after _ callback 中执行一些操作,该操作将基于多个关联进行计算(例如,a has _ many assoc,其中您也接受 _ nested _ properties _ for) ,那么您将需要重新加载该关联,以防作为保存的一部分,其中一个成员被删除。

没有这些点到 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 与 Rails2. x 一起工作

铁轨3:

MyModel.send("_#{symbol}_callbacks") # list
MyModel.reset_callbacks symbol # reset
# 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

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

class NoCallbacksModel < ActiveRecord::Base
set_table_name 'table_name_of_model_that_has_callbacks'


include CommonModelMethods # if there are
:
:


end

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

斯蒂芬

使用 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 始终支持 ABC0和 :unless选项,所以上面的代码可以简化为:

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

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

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


更新:

@ 维克兰特 · 乔杜里(Vikrant Chaudhary)的解决方案似乎更好:

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

我最初的回答是:

看看这个链接: 如何跳过 ActiveRecord 回调?

在 Rails 3中,

假设我们有一个类定义:

class User < ActiveRecord::Base
after_save :generate_nick_name
end

方法一:

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

方法二: 当您希望在 rspec 文件或其他文件中跳过它们时,可以尝试这样做:

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

注意: 一旦这样做了,如果您不在 rspec 环境中,您应该重置回调:

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

在 Rails 3.0.5上对我来说很好用

Https://gist.github.com/576546

只需将这个补丁转储到 config/initializer/ski_ callback. rb 中即可

那么

{@project. save }

诸如此类。

都归功于作者

我在 Rails 3中编写了一个实现 update _ without _ callback 的插件:

Http://github.com/dball/skip_activerecord_callbacks

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

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

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

我认为它应该工作在任何轨道版本。

一个不需要 gem 或插件就可以在所有版本的 Rails 上运行的解决方案是直接发出更新语句。例句

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

这可能是(也可能不是)一个选项,这取决于您的更新有多复杂。这对于从 更新记录上的标志和 after _ save 回调(不需要重新触发回调)非常有效。

你可以使用 update_columns:

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

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

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

型号:

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

为了在 Rails 中创建测试数据,您可以使用以下技巧:

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

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

如果目标只是简单地插入一条没有回调或验证的记录,而且您希望这样做而不需要求助于其他 gem、添加条件检查、使用 RAW SQL 或以任何方式对您的退出代码进行未决处理,那么可以考虑使用指向现有 db 表的“影子对象”。像这样:

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

这适用于 Rails 的每个版本,是线程安全的,并且完全消除了所有的验证和回调,不需要对现有代码进行任何修改。您可以在实际导入之前插入类声明,然后就可以开始了。只要记住使用新类来插入对象,如:

ImportedPerson.new( person_attributes )

一些应该与 ActiveRecord的所有版本一起工作,而不依赖于可能存在也可能不存在的选项或 activerrecord 方法的东西。

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: 在同一个表上使用“不同的 activerrecord 模型”

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

应用程序/模型/关注点/save _ without _ callback. 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)

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

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

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

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

当我想运行一个 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,它略过 _ callback 为 true,所以它不运行方法中的其余代码。 要跳过回调,你只需要在保存、创建、销毁之前将 skip _ callback 设置为 true:

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

有了 Rails 6,你现在可以使用 插入方法

从文件:

在单个 SQLINSERT 中将多条记录插入到数据库中 它不实例化任何模型,也不触发 活动记录回调或验证 通过活动记录的类型转换和序列化。