Як я можу уникнути запуску зворотних викликів ActiveRecord?


140

У мене є кілька моделей, у яких є зворотні виклики after_save. Зазвичай це нормально, але в деяких ситуаціях, як, наприклад, при створенні даних про розробку, я хочу зберегти моделі, не запускаючи зворотні дзвінки. Чи є простий спосіб це зробити? Щось схоже на ...

Person#save( :run_callbacks => false )

або

Person#save_without_callbacks

Я заглянув у документи Rails і нічого не знайшов. Однак, на мій досвід, документи Rails не завжди розповідають всю історію.

ОНОВЛЕННЯ

Я знайшов повідомлення в блозі, в якому пояснюється, як можна видалити зворотні дзвінки з такої моделі:

Foo.after_save.clear

Я не міг знайти, де цей метод документально підтверджений, але він, здається, працює.


8
Якщо ви робите щось руйнівне або дороге (наприклад, надсилання електронної пошти) у зворотному дзвінку, рекомендую перенести це та запустити його окремо від контролера чи деінде. Таким чином, ви не «випадково» спровокуєте його у розвитку тощо
ryanb

2
прийняте вами рішення не працює для мене. Я використовую рейки 3. Я отримую помилку на зразок цієї: - не визначений метод `update_without_callbacks 'для # <Користувача: 0x10ae9b848>
Mohit Jain

yaa, що повідомлення в блозі працювало ....
Mohit Jain

1
Пов'язані питання: stackoverflow.com/questions/19449019 / ...
Allerin

Не вдалося Foo.after_save.clearзняти зворотні дзвінки для всієї моделі? І як тоді ви пропонуєте їх відновити?
Джошуа Пінтер

Відповіді:


72

Це рішення лише для 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)

Це, безумовно, те, що ви дійсно хочете використовувати в консолі або роблячи деякі випадкові тести. Сподіваюся, це допомагає!


7
це не працює для мене. Я використовую рейки 3. Я отримую помилку на зразок цієї: - не визначений метод `update_without_callbacks 'для # <Користувача: 0x10ae9b848>
Mohit Jain

Ваша пропозиція не працює, але повідомлення в блозі, згадане в частині оновлення, працює ..
Mohit Jain

Це також пропустить перевірки.
Даніель Піцш

У мене є інше рішення для будь-якої версії Rails. Це працює на нас добре. Перевірте це у своєму дописі в блозі: railsguides.net/2014/03/25/skip-callbacks-in-tests
ka8725

224

Використовуйте 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

2
схоже, що він працює і з 2.x, і існує маса інших методів, які діють аналогічно: guides.rubyonrails.org/…
rogerdpack

15
Це не стосується :create_without_callbacks:( Як я можу запустити щось подібне до цього? (Працював у Rails2, видалено в Rails3).
nzifnab

Якщо припустити, що @personдесь є контролером у контролері, це рішення означає, що люди, які читають ваш клас моделі, не зможуть зрозуміти зворотні дзвінки. Вони побачать after_create :something_coolі подумають "чудово, щось круте станеться після створення!". Щоб насправді зрозуміти ваш модельний клас, їм доведеться проглядати всі ваші контролери, шукаючи всі маленькі місця, куди ви вирішили ввести логіку. Мені це не подобається> o <;;
Ziggy

1
замінити skip_callback ..., if: :skip_some_callbacksна, after_create ..., unless: :skip_some_callbacksщоб запустити це належним чином з after_create.
sakurashinken

28

Оновлено:

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

добре працює для мене на рейках 3.0.5


20

рейки 3:

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

11
Приємно. Також MyModel.skip_callback (: create,: after,: my_callback) для точного контролю .. див. ActiveSupport :: Callbacks :: ClassMethods документи для всіх лобанг
tardate

4
Корисна інформація: "символ" reset_callbacksне є :after_save, а скоріше :save. apidock.com/rails/v3.0.9/ActiveSupport/Callbacks/ClassMethods/…
nessur

19

Якщо мета - просто вставити запис без зворотних викликів чи перевірок, і ви хотіли б це зробити, не вдаючись до додаткових дорогоцінних каменів, додаючи умовні чеки, використовуючи RAW SQL або будь-яким чином збиваючись зі своїм вихідним кодом, подумайте про використання "тіні об'єкт ", що вказує на вашу існуючу таблицю db. Так:

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

Це працює з будь-якою версією Rails, є безпечною для потоків і повністю виключає всі перевірки та зворотні виклики, не змінюючи існуючий код. Ви можете просто кинути цю декларацію класу прямо перед вашим фактичним імпортом, і вам слід погодитися. Просто пам’ятайте, щоб використовувати новий клас для вставки об’єкта, наприклад:

ImportedPerson.new( person_attributes )

4
Найкраще рішення. Елегантний і простий!
Рафаель Олівейра

1
Це спрацювало дуже добре для мене, тому що це було те, що я хотів зробити лише в тесті, щоб імітувати стан бази даних "до", не забруднюючи свій виробничий модельний об'єкт машинами, щоб необов'язково пропустити зворотні дзвінки.
Дуглас Ловелл

1
На сьогодні найкраща відповідь
robomc

1
Оновлений, оскільки він показує, як обходити існуючі обмеження рейок, і допоміг мені зрозуміти, як працює весь об'єкт MVC. Так просто і чисто.
Майкл Шмітц

17

Ви можете спробувати щось подібне у вашій моделі Person:

after_save :something_cool, :unless => :skip_callbacks

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

EDIT: after_save - це не символ, але це, принаймні, 1000-й раз, коли я намагався зробити його одним.


1
Я дійсно думаю, що це найкраща відповідь тут. Таким чином, логіка, яка визначає час пропуску зворотного дзвінка, доступна у моделі, і у вас немає повних фрагментів коду скрізь, що стримує логіку бізнесу або обходить інкапсуляцію send. KOODOS
Ziggy

10

Ви можете використовувати update_columns:

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

Оновляє задані атрибути об'єкта, не викликаючи збереження, отже, пропускаючи перевірки та зворотні виклики.


7

Єдиний спосіб запобігти всім зворотним викликам after_save - це перше повернення помилково.

Можливо, ви могли б спробувати щось на кшталт (неперевірене):

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

1
Я люблю пробувати (неперевірено). Тривожна їзда.
Адамантиш

Випробуваний і він працює. Я думаю, що це дуже гарне та чисте рішення, дякую!
kernification

5

Схоже, одним із способів вирішити це в Rails 2.3 (оскільки оновлення_without_callbacks відсутнє тощо) було б використовувати update_all, який є одним із методів, який пропускає зворотні виклики згідно з розділом 12 Керівництва Rails до перевірок та зворотних викликів .

Також зауважте, що якщо ви робите щось у своєму зворотному виклику after_, це робить обчислення, засноване на багатьох асоціаціях (тобто, assoc has_many, де ви також приймаєте_nested_attributes_for), вам потрібно буде перезавантажити асоціацію, на випадок як частина збереження , один з її членів був видалений.



4

Найбільший up-voted відповідь може здатися заплутаною в деяких випадках.

ifЯкщо ви хочете пропустити зворотний дзвінок, ви можете просто перевірити, як це:

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

3

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

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

Це може (а може і не бути) варіантом залежно від того, наскільки складним є ваше оновлення. Це добре працює , наприклад , для прапорів коригування на запис з в якості after_save зворотного виклику (без зворотного виклику перезапуску).


Не впевнений, чому потік, але я все ж вважаю, що наведена відповідь є правомірною. Іноді найкращий спосіб уникнути проблем з поведінкою ActiveRecord - це уникати використання ActiveRecord.
Дейв Смілі

Принципово запрошено протидіяти -1. У нас щойно виник виробничий випуск (з довгим сюжетом), який вимагав від нас створення нового запису (а не оновлення), і відкликання зворотних викликів було б катастрофічно. Усі вищезазначені відповіді є хакерськими, визнають вони це чи ні, і перехід до БД було найкращим рішенням. Для цього існують законні умови. Хоча слід остерігатися ін'єкцій SQL із #{...}.
sinisterchipmunk

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

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


1

Я написав плагін, який реалізує update_without_callbacks в Rails 3:

http://github.com/dball/skip_activerecord_callbacks

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


1

Якщо ви використовуєте Rails 2. Ви можете використовувати SQL-запит для оновлення стовпця без запуску зворотних викликів та перевірок.

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

Я думаю, що це має працювати в будь-яких версіях рейок.


1

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

Модель:

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


1

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

Зверніть увагу, це не може допомогти зберегти асоціації без перевірки. Він видає помилку 'created_at не може бути нульовою', оскільки він безпосередньо вставляє запит sql на відміну від моделі. Для цього нам потрібно оновити всі автоматично створені стовпці db.


1

Мені було потрібно рішення для 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)

0

Чому б ви хотіли зробити це в розвитку? Звичайно, це означає, що ви будуєте свою програму з невірними даними, і як така вона буде вести себе дивно, а не так, як ви очікуєте на виробництві.

Якщо ви хочете заповнити свій dev db даними, кращим підходом було б побудувати завдання граблі, яке використовувало дорогоцінний камінь, щоб створити дійсні дані та імпортувати їх у db, створивши стільки чи мало записів, скільки ви хочете, але якщо ви є п'ятою нахилившись на цьому, і є вагома причина, я думаю, що update_without_callbacks та create_without_callbacks спрацюють нормально, але коли ви намагаєтесь зігнути рейки за своєю волею, запитайте себе, чи є у вас вагомі причини, і якщо те, що ви робите, справді хороша ідея.


Я не намагаюся економити без перевірок, просто без зворотних викликів. Мій додаток використовує зворотні дзвінки для запису статичного HTML у файлову систему (на зразок CMS). Я не хочу цього робити під час завантаження даних про розробку.
Етан

Це була лише думка, я думаю, колись у минулому я бачив подібне питання, яке намагається обійти речі з поганих причин.
nitecoder

0

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

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(Один і той же підхід може полегшити ситуацію для обходу перевірок)

Стефан


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)

3
Це здається поганою ідеєю - ви повинні використовувати речі за призначенням. Останнє, що вам потрібно, це ваші перевірки на наявність побічних ефектів.
chug2k

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: використовуйте "іншу модель активних записів" над тією ж таблицею


0

Для користувацьких зворотних дзвінків використовуйте 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.

-5

Не найчистіший спосіб, але ви можете загорнути код зворотного виклику в умові, що перевіряє середовище Rails.

if Rails.env == 'production'
  ...
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.