如何避免运行ActiveRecord回调?


140

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

Person#save( :run_callbacks => false )

要么

Person#save_without_callbacks

我查看了Rails文档,没有找到任何东西。但是,以我的经验,Rails文档并不总是能讲述整个故事。

更新

我找到了一篇博客文章,其中解释了如何从这样的模型中删除回调:

Foo.after_save.clear

我找不到该方法的文档记录,但似乎可行。


8
如果您在回调中执行破坏性或昂贵的操作(例如发送电子邮件),则建议将其移出并与控制器或其他地方分开触发。这样,您就不会在开发中“意外”触发它
。– ryanb

2
您接受的解决方案不适用于我。我使用导轨3.我收到这样的错误: -为#未定义的方法`update_without_callbacks' <用户:0x10ae9b848>
莫希特耆那

YAA该博客后工作... ...
莫希特耆那


不会Foo.after_save.clear删除整个模型的回调吗?然后,您如何建议还原它们?
Joshua Pinter

Answers:


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)

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


7
它对我不起作用。我使用导轨3.我收到这样的错误: -为#未定义的方法`update_without_callbacks' <用户:0x10ae9b848>
莫希特耆那

你的建议是行不通的,但在更新部分中提到的博客文章正在..
莫希特耆那教

这也将跳过验证。
Daniel Pietzsch

对于任何版本的Rails,我都有另一个解决方案。它对我们很好。在我的博客文章中查看:railsguides.net/2014/03/25/skip-callbacks-in-tests
ka8725 2014年

224

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

2
看起来它与2.x的工作为好,并有那类似的操作等方法主机:guides.rubyonrails.org/...
rogerdpack

15
这不解决::create_without_callbacks((我如何运行类似的代码?(在Rails2中工作,在Rails3中删除)。)
nzifnab 2012年

假设@person某个控制器中的变量是一个变量,则此解决方案意味着阅读模型类的人将无法理解回调。他们将看到after_create :something_cool并认为“很棒,创建后会发生一些很棒的事情!”。为了真正理解您的模型类,他们将必须遍历您所有的控制器,寻找您决定注入逻辑的所有小地方。我不喜欢> o <;;
Ziggy

1
更换skip_callback ..., if: :skip_some_callbacksafter_create ..., unless: :skip_some_callbacks与after_create正常运行这一点。
sakurashinken

28

更新:

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

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

我的原始答案:

看到此链接: 如何跳过ActiveRecord回调?

在Rails3中,

假设我们有一个类定义:

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


20

导轨3:

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

11
真好 也可以使用MyModel.skip_callback(:create,:after,:my_callback)进行精确控制。.有关所有lobang的信息,请参见ActiveSupport :: Callbacks :: ClassMethods文档
tardate

4
有用的信息:中的“符号” reset_callbacks不是:after_save,而是:saveapidock.com/rails/v3.0.9/ActiveSupport/Callbacks/ClassMethods/...
nessur

19

如果目标是只插入一条没有回调或验证的记录,而您又不想借助其他gem,添加条件检查,使用RAW SQL或以任何方式对退出的代码进行处理,则可以考虑使用“阴影”对象”,指向您现有的数据库表。像这样:

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

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

ImportedPerson.new( person_attributes )

4
最好的解决方案。优雅而简单!
拉斐尔·奥利维拉

1
这对我来说真的很好,因为我只想在测试中执行此操作,以模拟数据库“之前”状态,而不用机械污染我的生产模型对象以选择跳过回调。
Douglas Lovell '18

1
到目前为止最好的答案
robomc

1
被推荐,因为它显示了如何解决现有的轨道约束,并帮助我了解了整个对象MVC的工作原理。如此简单干净。
Michael Schmitz,

17

您可以在Person模型中尝试以下操作:

after_save :something_cool, :unless => :skip_callbacks

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

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


1
我真的认为这是最好的答案。这样,确定何时跳过回调的逻辑在模型中可用,并且您到处都没有疯狂的代码片段来回溯业务逻辑或使用规避封装send。KOODOS
Ziggy

10

您可以使用update_columns

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

在不调用保存的情况下更新对象的给定属性,因此跳过验证和回调。


7

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

1
我喜欢尝试(未经测试)。惊险刺激。
Adamantish

经过测试,可以正常工作。我认为这是一个非常好的干净的解决方案,谢谢!
kernification

5

看起来在Rails 2.3中处理此问题的一种方法(因为update_without_callbacks消失了,等等),将使用update_all,这是根据Rails指南第12节中有关验证和回调的方法跳过回调的方法之一。

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


4

https://gist.github.com/576546

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

然后

Project.skip_callbacks { @project.save }

或类似的东西。

全部归功于作者


4

最多 up-voted在某些情况下答案似乎令人困惑。

if如果要跳过回调,则可以使用简单的检查,例如:

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

3

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

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

根据更新的复杂程度,这可能是(也可能不是)一个选项。这对于例如 after_save回调中更新记录上的标志(而不重新触发该回调)非常有效。


不知道为什么要投票,但我仍然认为上述答案是合理的。有时,避免ActiveRecord行为问题的最佳方法是避免使用ActiveRecord。
Dave Smylie 2014年

原则上反对-1。我们只是遇到了一个生产问题(背后有一个很长的故事),它要求我们创建一个新记录(而不是更新),并且触发回调将是灾难性的。上面的所有答案都是黑客,无论他们是否承认,进入数据库都是最好的解决方案。有合法的条件。虽然应该注意使用SQL注入SQL #{...}
sinisterchipmunk 2014年

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

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兼容



1

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

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

我认为它可以在任何Rails版本中使用。


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


1

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

请注意,这在没有验证的情况下无法帮助保存关联。它会引发错误“ created_at不能为空”,因为它直接插入sql查询而不是模型。为此,我们需要更新所有自动生成的db列。


1

我需要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)

0

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

如果要用数据填充dev db,更好的方法是构建一个rake任务,该任务使用伪造的gem来构建有效数据并将其导入db中,从而根据需要创建任意数量的记录,但是如果您不喜欢弯腰,有一个很好的理由,我猜想update_without_callbacks和create_without_callbacks可以很好地工作,但是当您试图按照自己的意愿弯腰时,请问问自己您有很好的理由,而您所做的是否真的是个好主意。


我不是在没有验证的情况下尝试保存,而只是没有回调。我的应用程序正在使用回调将一些静态HTML写入文件系统(类似于CMS)。在加载开发数据时,我不想这样做。
Ethan

只是一个想法,我想过去每当我看到这样的问题时,它都会出于种种原因而试图解决问题。
nitecoder

0

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

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

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

史蒂芬


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)

3
这似乎是一个坏主意-您应该将它们用于预期的目的。您想要的最后一件事是您的验证有副作用。
chug2k 2012年

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模型”


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_callbacksvirtual属性设置为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

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

if Rails.env == 'production'
  ...
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.