我怎样才能避免运行ActiveRecordcallback?
我有一些有after_savecallback的模型。 通常这很好,但是在某些情况下,比如在创build开发数据的时候,我想保存模型而不需要运行callback。 有一个简单的方法来做到这一点? 类似于…
Person#save( :run_callbacks => false )
要么
Person#save_without_callbacks
我看了Rails文档,没有find任何东西。 但是根据我的经验,Rails文档并不总是讲述整个故事。
UPDATE
我发现了一篇博文 ,解释了如何从模型中移除callback:
Foo.after_save.clear
我无法find该方法的logging,但似乎工作。
这个解决scheme只有Rails 2。
我只是调查了这一点,我想我有一个解决scheme。 有两个你可以使用的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)
这绝对是你真正想在控制台中使用或者在做一些随机testing的时候。 希望这可以帮助!
使用update_column
(Rails> = v3.1)或update_columns
(Rails> = 4.0)跳过callback和validation。 此外,对于这些方法, 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:跳过在创build对象时也起作用的callback
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
更新:
@Vikrant Chaudhary的解决scheme似乎更好:
#Rails >= v3.1 only @person.update_column(:some_attribute, 'value') #Rails >= v4.0 only @person.update_columns(attributes)
我原来的答案是:
看到这个链接: 如何跳过ActiveRecordcallback?
在Rails3中,
假设我们有一个类的定义:
class User < ActiveRecord::Base after_save :generate_nick_name end
Approach1:
User.send(:create_without_callbacks) User.send(:update_without_callbacks)
方法2:当你想在你的rspec文件中跳过它们时,试试这个:
User.skip_callback(:save, :after, :generate_nick_name) User.create!()
注意:一旦完成,如果你不在rspec环境中,你应该重置callback:
User.set_callback(:save, :after, :generate_nick_name)
在轨道3.0.5上工作正常
导轨3:
MyModel.send("_#{symbol}_callbacks") # list MyModel.reset_callbacks symbol # reset
你可以在你的Person模型中尝试这样的事情:
after_save :something_cool, :unless => :skip_callbacks def skip_callbacks ENV[RAILS_ENV] == 'development' # or something more complicated end
编辑: after_save不是一个符号,但这是至less第一次尝试使它成为一个。
如果目标是简单地插入一个没有callback或validation的logging,并且您希望不使用额外的gem,添加条件检查,使用RAW SQL,或以任何方式使用退出的代码来实现,可以考虑使用“阴影对象“指向你现有的数据库表。 像这样:
class ImportedPerson < ActiveRecord::Base self.table_name = 'people' end
这适用于Rails的每个版本,是线程安全的,并且完全消除所有的validation和callback,而不需要修改现有的代码。 你可以在实际导入之前把这个类的声明扔掉,你应该很好。 只要记住使用你的新类插入对象,如:
ImportedPerson.new( person_attributes )
你可以使用update_columns
:
User.first.update_columns({:name => "sebastian", :age => 25})
更新对象的给定属性,而不调用保存,从而跳过validation和callback。
阻止所有after_savecallback的唯一方法是让第一个返回false。
也许你可以尝试像(未经testing):
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.3中处理这个问题的一种方法(因为update_without_callbacks不见了,等等),将使用update_all,这是根据Rails指南的第12部分跳过callback的方法之一, 用于validation和callback 。
此外,请注意,如果您在after_callback中执行了某些操作,则会执行基于多个关联的计算(例如has_many assoc,您也在accept_nested_attributes_for中执行操作),则需要重新加载关联,以作为保存的一部分,其中一名成员被删除。
https://gist.github.com/576546
把这个猴子补丁转储到config / initializers / skip_callbacks.rb
然后
Project.skip_callbacks { @project.save }
或类似的。
都归功于作者
在一些情况下, up-voted
答案可能会让人困惑。
你可以使用一个简单的检查,如果你想跳过callback,像这样:
after_save :set_title, if: -> { !new_record? && self.name_changed? }
一个不需要使用gem或插件就能在所有版本的Rails中工作的解决scheme就是直接发布update语句。 例如
ActiveRecord::Base.connection.execute "update table set foo = bar where id = #{self.id}"
这可能(也可能不会)是一个选项,取决于你的更新有多复杂。 这适用于例如在 after_savecallback中更新logging上的标志(不重新触发callback)。
# 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
这些都指向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
我写了一个在Rails 3中实现update_without_callbacks的插件:
http://github.com/dball/skip_activerecord_callbacks
我认为正确的解决scheme是重写模型以避免callback,但如果这在短期内不切实际,这个插件可能会有所帮助。
如果您使用的是Rails 2.您可以使用SQL查询更新您的列,而无需运行callback和validation。
YourModel.connection.execute("UPDATE your_models SET your_models.column_name=#{value} WHERE your_models.id=#{ym.id}")
我认为它应该在任何rails版本中工作。
当我需要完全控制callback时,我创build了另一个用作开关的属性。 简单而有效:
模型:
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
testing:
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
你可以使用偷偷摸摸的gem: https : //rubygems.org/gems/sneaky-save 。
请注意,如果没有validation,这将无助于保存关联。 它会抛出错误“created_at不能为空”,因为它直接插入SQL查询不像模型。 为了实现这个,我们需要更新所有自动生成的db列。
你为什么要在开发中做到这一点? 这当然意味着你正在用无效的数据构build你的应用程序,因此它会performance得很奇怪,而不是你所期望的那样。
如果你想使用数据填充你的开发数据库,更好的方法是build立一个使用faker gem构build有效数据并将其导入数据库的rake任务,并根据需要创build尽可能多或less量的logging,但是如果你是脚跟我想这个update_without_callbacks和create_without_callbacks可以正常工作,但是当你试图按照自己的意愿弯曲钢轨时,问问自己,你有一个很好的理由,如果你正在做的是一个好主意。
一种select是使用相同的表格为这种操作提供单独的模型:
class NoCallbacksModel < ActiveRecord::Base set_table_name 'table_name_of_model_that_has_callbacks' include CommonModelMethods # if there are : : end
(同样的方法可以让绕过validation更容易)
斯蒂芬
另一种方法是使用validation钩子而不是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中创buildtesting数据,您可以使用下面的方法:
record = Something.new(attrs) ActiveRecord::Persistence.instance_method(:create_record).bind(record).call
一些应该与所有版本的ActiveRecord
工作,而不依赖于可能或不可能存在的选项或主动logging方法。
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:在同一张表上使用“不同的主动logging模式”
我需要一个Rails 4的解决scheme,所以我想出了这个:
应用程序/模型/关注/ 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)
不是最干净的方式,但是您可以将callback代码封装在检查Rails环境的条件中。
if Rails.env == 'production' ...