Как я могу избежать выполнения обратных вызовов ActiveRecord?
у меня есть некоторые модели, которые имеют after_save обратные вызовы. Обычно это нормально, но в некоторых ситуациях, например при создании базы данных, я хочу сохранить модели без обратных работать. Есть ли простой способ сделать это? Что-то похожее...
Person#save( :run_callbacks => false )
или
Person#save_without_callbacks
Я посмотрел в документах Rails и ничего не нашел. Однако, по моему опыту, документы Rails не всегда рассказывают всю историю.
обновление
Я нашел блоге это объясняет, как вы можете удалить обратные вызовы из модели, как это:
Foo.after_save.clear
Я не мог найти, где этот метод документирован, но он, кажется, работает.
25 ответов:
это решение только рельсы 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
(рельсы >= 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
обновление:
@ решение Викранта Чаудхари кажется лучше:
#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)
Approach2: Если вы хотите пропустить их в своих файлах rspec или что-то еще, попробуйте следующее:
User.skip_callback(:save, :after, :generate_nick_name) User.create!()
Примечание.: как только это будет сделано, если вы не находитесь в среде rspec, вы должны сбросить обратные вызовы:
User.set_callback(:save, :after, :generate_nick_name)
отлично работает для меня на рельсы 3.0.5
вы можете попробовать что-то вроде этого в вашей модели лица:
after_save :something_cool, :unless => :skip_callbacks def skip_callbacks ENV[RAILS_ENV] == 'development' # or something more complicated end
EDIT: after_save-это не символ, но это, по крайней мере, 1000-й раз, когда я пытался сделать его одним.
Если цель состоит в том, чтобы просто вставить запись без обратных вызовов или проверок, и вы хотели бы сделать это, не прибегая к дополнительным драгоценным камням, добавляя условные проверки, используя RAW SQL или futzing с вашим выходящим кодом в любом случае, рассмотрите возможность использования "теневого объекта", указывающего на вашу существующую таблицу БД. Вот так:
class ImportedPerson < ActiveRecord::Base self.table_name = 'people' end
это работает с каждой версией Rails, является потокобезопасным и полностью устраняет все проверки и обратные вызовы без каких-либо изменений в существующих код. Вы можете просто бросить это объявление класса прямо перед вашим фактическим импортом, и вам должно быть хорошо идти. Просто не забудьте использовать новый класс для вставки объекта, например:
ImportedPerson.new( person_attributes )
можно использовать
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 assoc, где вы также делаете accepts_nested_attributes_for), вам нужно будет перезагрузить ассоциация, в случае, если в рамках сохранения был удален один из ее членов.
https://gist.github.com/576546
просто сбросьте этот патч обезьяны в config/initializers / skip_callbacks.РБ
затем
Project.skip_callbacks { @project.save }
или тому подобное.
все заслуги перед автором
самый
up-voted
ответ может показаться запутанным в некоторых случаях.вы можете использовать только простой
if
проверьте, хотите ли вы пропустить обратный вызов, например:after_save :set_title, if: -> { !new_record? && self.name_changed? }
решение, которое должно работать во всех версиях Rails без использования gem или плагина, просто выдает инструкции обновления напрямую. например,
ActiveRecord::Base.connection.execute "update table set foo = bar where id = #{self.id}"
Это может (или не может) быть вариант, в зависимости от сложности вашего обновления. Это хорошо работает, например, обновление флагов на записи из внутри обратный вызов after_save (без повторного запуска обратного вызова).
# 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
ни один из этих пунктов до
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 работает с рельсами 2.x
Я написал плагин, который реализует update_without_callbacks в Rails 3:
http://github.com/dball/skip_activerecord_callbacks
правильным решением, я думаю, является переписать ваши модели, чтобы избежать обратных вызовов в первую очередь, но если это непрактично в ближайшем будущем, этот плагин может помочь.
Если вы используете рельсы 2. Вы можете использовать SQL-запрос для обновления столбца без выполнения обратных вызовов и проверок.
YourModel.connection.execute("UPDATE your_models SET your_models.column_name=#{value} WHERE your_models.id=#{ym.id}")
Я думаю, что он должен работать в любых версиях rails.
когда мне нужен полный контроль над обратным вызовом, я создаю другой атрибут, который используется в качестве коммутатора. Простой и эффективный:
модель:
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
для создания тестовых данных в Rails вы используете этот хак:
record = Something.new(attrs) ActiveRecord::Persistence.instance_method(:create_record).bind(record).call
вы можете использовать sneaky-save gem:https://rubygems.org/gems/sneaky-save.
обратите внимание, что это не может помочь в сохранении ассоциаций вместе без проверки. Он выдает ошибку "created_at не может быть null", поскольку он непосредственно вставляет sql-запрос в отличие от модели. Для реализации этого нам необходимо обновить все автоматически сгенерированные столбцы БД.
мне нужно было решение для Rails 4, поэтому я придумал это:
приложение/модели/проблемы / save_without_callbacks.РБ
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)
зачем вы хотите быть в состоянии сделать это в разработке? Конечно, это будет означать, что вы строите свое приложение с недействительными данными, и поэтому оно будет вести себя странно, а не так, как вы ожидаете в производстве.
Если вы хотите заполнить свою БД dev данными, лучшим подходом было бы построить задачу rake, которая использовала камень faker для создания действительных данных и импорта их в БД, создавая столько или несколько записей, сколько вы хотите, но если вы согнуты пяткой и у вас есть веская причина I угадайте, что update_without_callbacks и create_without_callbacks будут работать нормально, но когда вы пытаетесь согнуть рельсы по своей воле, спросите себя, у вас есть веская причина, и если то, что вы делаете, действительно хорошая идея.
один из вариантов-иметь отдельную модель для таких манипуляций, используя ту же таблицу:
class NoCallbacksModel < ActiveRecord::Base set_table_name 'table_name_of_model_that_has_callbacks' include CommonModelMethods # if there are : : end
(тот же подход может упростить обход проверок)
Стефан
другой способ-использовать крючки проверки вместо обратных вызовов. Например:
class Person < ActiveRecord::Base validate_on_create :do_something def do_something "something clever goes here" end end
таким образом, вы можете получить выполнить_действие по умолчанию, но вы можете легко заменить его:
@person = Person.new @person.save(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" над той же таблицей