Как я могу избежать запуска обратных вызовов ActiveRecord? - PullRequest
130 голосов
/ 11 марта 2009

У меня есть некоторые модели с обратными вызовами after_save. Обычно это нормально, но в некоторых ситуациях, например, при создании данных разработки, я хочу сохранить модели без запуска обратных вызовов. Есть ли простой способ сделать это? Что-то похожее на ...

Person#save( :run_callbacks => false )

или

Person#save_without_callbacks

Я посмотрел в документации по Rails и ничего не нашел. Однако, по моему опыту, документы Rails не всегда рассказывают всю историю.

UPDATE

Я нашел пост в блоге , в котором объясняется, как можно удалить обратные вызовы из модели, подобной этой:

Foo.after_save.clear

Я не смог найти, где описан этот метод, но, похоже, он работает.

Ответы [ 25 ]

3 голосов
/ 08 февраля 2013

Решение, которое должно работать во всех версиях Rails без использования гема или плагина, заключается в простой выдаче операторов обновления напрямую. например,

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

Это может (или не может) быть вариантом в зависимости от сложности вашего обновления. Это хорошо работает, например, для обновления флагов записи с в обратного вызова after_save (без повторного запуска обратного вызова).

3 голосов
/ 23 февраля 2017

Самый up-voted ответ может показаться запутанным в некоторых случаях.

Вы можете использовать простую if проверку, хотите ли вы пропустить обратный вызов, например:

after_save :set_title, if: -> { !new_record? && self.name_changed? }
1 голос
/ 13 апреля 2012

Я написал плагин, который реализует update_without_callbacks в Rails 3:

http://github.com/dball/skip_activerecord_callbacks

Правильное решение, я думаю, состоит в том, чтобы сначала переписать ваши модели, чтобы избежать обратных вызовов, но если это непрактично в ближайшей перспективе, этот плагин может помочь.

1 голос
/ 17 декабря 2010
# 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 голос
/ 15 октября 2010

Ни один из этих пунктов к 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 голос
/ 31 августа 2012

Если вы используете Rails 2. Вы можете использовать SQL-запрос для обновления вашего столбца без выполнения обратных вызовов и проверок.

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

Я думаю, что это должно работать в любых версиях рельсов.

1 голос
/ 26 октября 2011

Вы можете использовать драгоценный камень подлый: https://rubygems.org/gems/sneaky-save.

Обратите внимание, что это не поможет сохранить ассоциации без проверок. Он выдает ошибку «create_at не может быть нулевым», поскольку он напрямую вставляет SQL-запрос в отличие от модели. Чтобы реализовать это, нам нужно обновить все автоматически сгенерированные столбцы базы данных.

1 голос
/ 29 мая 2014

Для создания тестовых данных в Rails вы используете этот хак:

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

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

1 голос
/ 12 августа 2016

Мне нужно было решение для 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)
1 голос
/ 14 апреля 2014

Когда мне нужен полный контроль над обратным вызовом, я создаю еще один атрибут, который используется в качестве переключателя. Просто и эффективно:

Модель:

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
...