Как избежать обратных вызовов ActiveRecord?

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

Person#save( :run_callbacks => false )

или

Person#save_without_callbacks

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

ОБНОВЛЕНИЕ

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

Foo.after_save.clear

Я не мог найти, где этот метод задокументирован, но кажется, что он работает.

Ответ 1

Это решение - только 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)

Это определенно то, что вы действительно хотите использовать только в консоли или при выполнении некоторых случайных тестов. Надеюсь, это поможет!

Ответ 2

Используйте 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

Ответ 3


Обновлено:

@Решение 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)

Approach2: Если вы хотите пропустить их в своих файлах rspec или что-то еще, попробуйте следующее:

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

ПРИМЕЧАНИЕ: как только это будет сделано, если вы не находитесь в среде rspec, вы должны reset обратные вызовы:

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

отлично работает для меня на рейках 3.0.5

Ответ 4

рельсы 3:

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

Ответ 5

Вы можете попробовать что-то подобное в своей модели Person:

after_save :something_cool, :unless => :skip_callbacks

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

EDIT: after_save не является символом, но, по крайней мере, в тысячу раз, когда я пытался сделать это.

Ответ 6

Если целью является просто вставить запись без обратных вызовов или проверок, и вы хотели бы сделать это, не прибегая к дополнительным драгоценным камням, добавляя условные проверки, используя RAW SQL или futzing с вашим выходным кодом каким-либо образом, подумайте об использовании "теневой объект", указывающий на вашу существующую таблицу db. Например:

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

Это работает с каждой версией Rails, является потокобезопасным и полностью исключает все проверки и обратные вызовы без каких-либо изменений в вашем существующем коде. Вы можете просто подбросить это объявление класса прямо перед вашим фактическим импортом, и вам должно быть хорошо идти. Просто не забудьте использовать свой новый класс для вставки объекта, например:

ImportedPerson.new( person_attributes )

Ответ 7

Вы можете использовать update_columns:

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

Обновляет заданные атрибуты объекта, не вызывая сохранения, поэтому пропускает проверки и обратные вызовы.

Ответ 8

Единственный способ предотвратить все последующие обратные вызовы after_save состоит в том, чтобы первый возвращал false.

Возможно, вы могли бы попробовать что-то вроде (untested):

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

Ответ 9

Похоже, что один из способов справиться с этим в Rails 2.3 (так как update_without_callbacks ушел и т.д.) было бы использовать update_all, который является одним из методов, который пропускает обратные вызовы по раздел 12 руководства Rails для проверок и обратных вызовов.

Кроме того, обратите внимание, что если вы делаете что-то в своем обратном вызове after_, который выполняет вычисления на основе многих ассоциаций (например, has_many, где вы также принимаете accepts_nested_attributes_for), вам необходимо перезагрузить ассоциацию, если часть из сохранения, один из его членов был удален.

Ответ 10

https://gist.github.com/576546

просто выгрузите этот патч обезьяны в config/initializers/skip_callbacks.rb

затем

Project.skip_callbacks { @project.save }

или тому подобное.

весь кредит автору

Ответ 11

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

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

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

Ответ 12

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

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

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

Ответ 13

Ни один из этих точек в плагине 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

Ответ 14

# 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

Ответ 15

Вы можете использовать sneaky-save gem: https://rubygems.org/gems/sneaky-save.

Примечание. Это не может помочь в сохранении ассоциаций без проверки. Он выдает ошибку "created_at не может быть null", поскольку он непосредственно вставляет sql-запрос в отличие от модели. Чтобы реализовать это, нам нужно обновить все автоматически сгенерированные столбцы db.

Ответ 16

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

http://github.com/dball/skip_activerecord_callbacks

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

Ответ 17

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

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

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

Ответ 18

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

Модель:

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

Ответ 19

Почему вы хотите иметь возможность сделать это в процессе разработки? Разумеется, это будет означать, что вы создаете приложение с недопустимыми данными и, как таковое, оно будет вести себя странно и не так, как вы ожидаете в процессе производства.

Если вы хотите заполнить свои данные db данными, лучшим подходом было бы создание рейк-задачи, которая использовала бы фейерверк для создания достоверных данных и импортировала бы его в db, создавая столько или несколько записей, сколько захотите, но если вы склонны к каблуку, и у меня есть веская причина, я полагаю, что update_without_callbacks и create_without_callbacks будут работать нормально, но когда вы пытаетесь согнуть рельсы по своему желанию, спросите себя, что у вас есть веская причина, и если то, что вы делаете, действительно хорошо идея.

Ответ 20

Другим способом было бы использовать проверки проверки вместо обратных вызовов. Например:

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)

Ответ 21

Один из вариантов - иметь отдельную модель для таких манипуляций, используя ту же таблицу:

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(Тот же подход может облегчить прохождение проверки)

Stephan

Ответ 22

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

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

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

Ответ 23

Что-то, что должно работать со всеми версиями 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

TL;DR: используйте "другую модель activerecord" по той же таблице

Ответ 24

Мне понадобилось решение для 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)

Ответ 25

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

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