153

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

Person#save( :run_callbacks => false )

或者

Person#save_without_callbacks

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

更新

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

Foo.after_save.clear

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

4

28 回答 28

238

使用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一直支持:if:unlessoptions,所以上面的代码可以简化为:

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
于 2011-09-12T10:08:18.910 回答
71

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

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

于 2009-03-11T04:29:37.333 回答
30

更新:

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

在rails 3.0.5上对我来说很好

于 2011-11-30T02:59:48.790 回答
26

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

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

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

ImportedPerson.new( person_attributes )
于 2015-12-09T22:41:27.893 回答
21

导轨 3:

MyModel.send("_#{symbol}_callbacks") # list  
MyModel.reset_callbacks symbol # reset
于 2010-11-26T08:03:40.230 回答
17

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

after_save :something_cool, :unless => :skip_callbacks

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

编辑: after_save 不是一个符号,但这至少是我第 1000 次尝试将其变为一个符号。

于 2009-03-11T00:02:55.663 回答
11

您可以使用update_columns

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

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

于 2013-04-02T23:05:06.450 回答
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
于 2009-03-11T03:07:06.453 回答
6

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

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

于 2010-04-13T05:27:26.520 回答
5

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

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

after_save :set_title, if: -> { !new_record? && self.name_changed? }
于 2017-02-23T09:57:23.197 回答
3

https://gist.github.com/576546

只需将此猴子补丁转储到 config/initializers/skip_callbacks.rb

然后

Project.skip_callbacks { @project.save }

之类的。

所有功劳归于作者

于 2012-02-24T23:26:42.190 回答
3

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

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

这可能(也可能不是)是一个选项,具体取决于您的更新有多复杂。这适用于例如从 after_save 回调中更新记录上的标志无需重新触发回调)。

于 2013-02-08T18:02:40.113 回答
2

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

模型:

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
于 2014-04-14T08:39:12.737 回答
2

我需要 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)
于 2016-08-12T15:05:02.940 回答
2

使用 Rails 6,您现在可以使用插入方法

从文档中:

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

于 2021-05-26T08:57:30.493 回答
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

于 2010-10-15T15:26:49.413 回答
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
于 2010-12-17T07:21:17.203 回答
1

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

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

于 2011-10-26T03:47:24.943 回答
1

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

http://github.com/dball/skip_activerecord_callbacks

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

于 2012-04-13T14:59:38.227 回答
1

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

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

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

于 2012-08-31T11:31:13.770 回答
1

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

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

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

于 2014-05-29T16:15:54.443 回答
1

对于自定义回调,在回调中使用 anattr_accessor和 an unless

如下定义您的模型:

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.
于 2020-01-16T20:16:52.717 回答
0

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

如果您想用数据填充您的开发数据库,​​更好的方法是构建一个 rake 任务,该任务使用 faker gem 来构建有效数据并将其导入到数据库中,创建任意多或少的记录,但如果您是脚后跟坚持下去并有充分的理由我想 update_without_callbacks 和 create_without_callbacks 可以正常工作,但是当您尝试按照自己的意愿弯曲轨道时,请问问自己您有充分的理由,以及您正在做的事情是否真的是个好主意。

于 2009-03-11T09:18:13.440 回答
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)
于 2009-08-14T21:44:58.207 回答
0

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

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

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

斯蒂芬

于 2011-06-06T19:00:06.317 回答
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:在同一张表上使用“不同的活动记录模型”

于 2016-03-07T05:17:11.590 回答
0

当我想运行 Rake 任务但没有为我保存的每条记录运行回调时,我遇到了同样的问题。这对我有用(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
于 2020-08-14T00:24:38.383 回答
-7

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

if Rails.env == 'production'
  ...
于 2009-03-10T23:59:26.513 回答