Как я могу избежать запуска обратных вызовов ActiveRecord?
У меня есть некоторые модели, которые имеют обратные вызовы after_save. Обычно это нормально, но в некоторых ситуациях, например, при создании данных разработки, я хочу сохранить модели без запуска обратных вызовов. Есть ли простой способ сделать это? Что-то похожее на...
Person#save( :run_callbacks => false )
или же
Person#save_without_callbacks
Я посмотрел в документации по Rails и ничего не нашел. Однако, по моему опыту, документы Rails не всегда рассказывают всю историю.
ОБНОВИТЬ
Я нашел сообщение в блоге, которое объясняет, как можно удалить обратные вызовы из модели, подобной этой:
Foo.after_save.clear
Я не мог найти, где этот метод задокументирован, но, похоже, он работает.
29 ответов
Это решение только для 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)
Это определенно то, что вы действительно захотите использовать только в консоли или во время случайных тестов. Надеюсь это поможет!
Использование 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
#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
Обновлено:
Решение @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
Approach1:
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
Если цель состоит в том, чтобы просто вставить запись без обратных вызовов или проверок, и вы хотели бы сделать это, не прибегая к дополнительным гемам, не добавляя условные проверки, не используя RAW SQL, или каким-либо образом возившись с выходящим кодом, подумайте об использовании "тени". объект ", указывающий на вашу существующую таблицу БД. Вот так:
class ImportedPerson < ActiveRecord::Base
self.table_name = 'people'
end
Это работает с каждой версией Rails, является потокобезопасным и полностью исключает все проверки и обратные вызовы без каких-либо изменений в существующем коде. Вы можете просто добавить эту декларацию класса прямо перед вашим фактическим импортом, и вам будет хорошо. Просто не забудьте использовать новый класс для вставки объекта, например:
ImportedPerson.new( person_attributes )
Рельсы 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 не является символом, но это по крайней мере в 1000-й раз я пытался сделать его одним.
Ты можешь использовать update_columns
:
User.first.update_columns({:name => "sebastian", :age => 25})
Обновляет данные атрибуты объекта без вызова save, следовательно, пропуская проверки и обратные вызовы.
Единственный способ предотвратить все обратные вызовы 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
Похоже, что один из способов справиться с этим в Rails 2.3 (так как update_without_callbacks пропал и т. Д.) Заключается в использовании update_all, который является одним из методов, пропускающих обратные вызовы в соответствии с разделом 12 Руководства Rails по проверкам и обратным вызовам.
Кроме того, обратите внимание, что если вы что-то делаете в обратном вызове after_, который выполняет вычисления, основанные на множестве ассоциаций (то есть на ассоциации has_many, где вы также делаете accept_nested_attributes_for), вам потребуется перезагрузить ассоциацию, в случае, если это является частью сохранения Один из его участников был удален.
Большинство up-voted
ответ может показаться запутанным в некоторых случаях.
Вы можете использовать просто if
проверьте, хотите ли вы пропустить обратный вызов, например так:
after_save :set_title, if: -> { !new_record? && self.name_changed? }
с Rails 6 теперь вы можете использовать методы вставки
из документации:
Вставляет несколько записей в базу данных одним оператором SQL INSERT. Он не создает экземпляров каких-либо моделей и не запускает обратные вызовы или проверки Active Record. Хотя переданные значения проходят преобразование типов и сериализацию Active Record.
https://gist.github.com/576546
просто сбросьте этот monkey-patch в config/initializers/skip_callbacks.rb
затем
Project.skip_callbacks { @project.save }
или т.п.
вся заслуга автора
Мне нужно было решение для 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)
Решение, которое должно работать во всех версиях Rails без использования гема или плагина, заключается в простой выдаче операторов обновления напрямую. например
ActiveRecord::Base.connection.execute "update table set foo = bar where id = #{self.id}"
Это может (или не может) быть вариантом в зависимости от сложности вашего обновления. Это хорошо работает, например, для обновления флагов записи из обратного вызова after_save (без повторного запуска обратного вызова).
Когда мне нужен полный контроль над обратным вызовом, я создаю еще один атрибут, который используется в качестве переключателя. Просто и эффективно:
Модель:
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
Вы можете использовать драгоценный камень sneaky-save: https://rubygems.org/gems/sneaky-save.
Обратите внимание, что это не поможет сохранить ассоциации без проверок. Он выдает ошибку "create_at не может быть нулевым", так как он напрямую вставляет SQL-запрос в отличие от модели. Чтобы реализовать это, нам нужно обновить все автоматически сгенерированные столбцы БД.
Для создания тестовых данных в Rails вы используете этот хак:
record = Something.new(attrs)
ActiveRecord::Persistence.instance_method(:create_record).bind(record).call
Ни один из этих пунктов 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
# 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
Если вы используете Rails 2. Вы можете использовать SQL-запрос для обновления вашего столбца без выполнения обратных вызовов и проверок.
YourModel.connection.execute("UPDATE your_models SET your_models.column_name=#{value} WHERE your_models.id=#{ym.id}")
Я думаю, что это должно работать в любых версиях рельсов.
Для настраиваемых обратных вызовов используйте attr_accessor
и 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_callbacks
виртуальный атрибут 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.
Я написал плагин, который реализует update_without_callbacks в Rails 3:
http://github.com/dball/skip_activerecord_callbacks
Правильное решение, я думаю, это переписать ваши модели, во-первых, чтобы избежать обратных вызовов, но если это непрактично в ближайшей перспективе, этот плагин может помочь.
Один из вариантов - иметь отдельную модель для таких манипуляций, используя одну и ту же таблицу:
class NoCallbacksModel < ActiveRecord::Base
set_table_name 'table_name_of_model_that_has_callbacks'
include CommonModelMethods # if there are
:
:
end
(Тот же подход может облегчить обход проверок)
Stephan
Другим способом будет использование проверочных хуков вместо обратных вызовов. Например:
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)
Почему вы хотите иметь возможность сделать это в разработке? Конечно, это будет означать, что вы создаете свое приложение с неверными данными, и поэтому оно будет вести себя странно, а не так, как вы ожидаете в рабочей среде.
Если вы хотите заполнить свою базу данных dev данными, лучшим подходом было бы создать задачу rake, которая использовала бы фейкерный гем для создания правильных данных и импортировать их в базу данных, создавая столько или несколько записей, сколько вы хотите, но если вы - пята Я думаю, что update_without_callbacks и create_without_callbacks будут работать нормально, но когда вы пытаетесь согнуть рельсы под свою волю, спросите себя, есть ли у вас веская причина, и действительно ли то, что вы делаете, действительно хорошая идея.
Я столкнулся с той же проблемой, когда хотел запустить задачу 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
В Rails 7 мы можем сделать что-то вроде этого, чтобы пропустить все обратные вызовы:
person.save!(callbacks: false)
То, что должно работать со всеми версиями 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" поверх одной и той же таблицы
Не самый чистый способ, но вы можете заключить код обратного вызова в условие, которое проверяет среду Rails.
if Rails.env == 'production'
...