Як виявити зміни атрибутів у моделі?


85

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

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

База даних має "стан" із типовим значенням "очікує на розгляд".

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

Моя ідея полягає в тому, щоб мати функцію в моделі:

    after_save :check_state

    def check_state
      # if status changed from nil to pending (created)
      do this

      # if status changed from pending to approved
      performthistask
     end

Моє питання полягає в тому, як мені перевірити попереднє значення перед зміною в моделі?

Відповіді:


173

Ви повинні переглянути модуль ActiveModel :: Dirty : Ви повинні мати можливість виконувати наступні дії щодо вашої моделі претензії:

claim.status_changed?  # returns true if 'status' attribute has changed
claim.status_was       # returns the previous value of 'status' attribute
claim.status_change    # => ['old value', 'new value'] returns the old and 
                       # new value for 'status' attribute

claim.name = 'Bob'
claim.changed # => ["name"]
claim.changes # => {"name" => ["Bill", "Bob"]}

О! радості Рейків!


6
Це не буде працювати після збереження моделі, про що він просив.
Том Россі,

4
@TomRossi, dirtyдзвінки працюють after_save(як у Rails 2.3, так і у 3.x). Я використовував його кілька разів.
Harish Shetty

11
@TomRossi, брудні прапори скидаються після коміту, тому вони не будуть доступні у after_commitзворотних викликах, представлених у Rails 3.x. Вони точно працюватимуть у after_save.
Harish Shetty

Я поняття не мав! Я думав, що вони були скинуті, як тільки це було збережено!
Том Россі,

5
@TomRossi Я почав з того самого припущення кілька років тому. Коли я намагався перевірити брудні прапори в after_save, це спрацювало. По суті, after_saveце зворотний виклик для стану між after DMLі before_commit. Ви можете завершити всю транзакцію after_save, додавши виняток. Якщо ви хочете щось зробити після збереження, не впливаючи на поточну операцію, використовуйте after_commit:-)
Харіш Шетті

38

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

self.changed

він повертає масив усіх стовпців, які змінилися в цьому записі

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

self.changes

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


7
Просто незначна примітка, щоб сказати, що використовувати їх self.на них непотрібно - ви можете просто сказати changedі changes.
user664833

@ user664833 Більш конкретно, ви можете пропустити, selfколи знаходиться в самій моделі, але ви можете викликати їх на будь-якому об'єкті з object.changedі object.changes. :)
Джошуа Пінтер

4

Рекомендую переглянути один із доступних плагінів стану машини:

Будь-яка з них дозволить вам налаштувати стани та переходи між станами. Дуже корисний та простий спосіб задоволення ваших вимог.


Я намагаюся спробувати рубіст-аазм. Скажімо, у мене є клас Claim <ActiveRecord :: Base включає AASM aasm_column: status aasm_initial_state: pending aasm_state: pending,: enter =>: enter_pending def enter_pending Notifier.deliver_pending_notification (self) end end І поле мого статусу в базі даних має значення за замовчуванням "в очікуванні". Якщо я мав би зробити Claim.create без заповнення поля стану (так що воно буде працювати "в очікуванні"), чи буде AASM запускати метод "enter_pending"?
David C

2

Для Rails 5.1+ слід використовувати активний метод атрибуту запису: saved_change_to_attribute?

save_change_to_attribute? (attr_name, ** параметри) `

Чи змінився цей атрибут, коли ми востаннє зберігали? Цей метод можна викликати як saved_change_to_name?замість saved_change_to_attribute?("name"). Поводиться подібно до attribute_changed?. Цей метод корисний при зворотних викликах, щоб визначити, чи змінив виклик для збереження певний атрибут.

Варіанти

from Після передачі цей метод поверне значення false, якщо початкове значення не дорівнює заданому параметру

to Після передачі цей метод поверне значення false, якщо значення не було змінено на задане значення

Отже, ваша модель буде виглядати так, якщо ви хочете викликати якийсь метод на основі зміни значення атрибута:

class Claim < ApplicationRecord
  
  after_save :do_this, if: Proc.new { saved_change_to_status?(from: nil, to: 'pending') }

  after_save :do_that, if: Proc.new { saved_change_to_status?(from: 'pending', to: 'approved') }

  
  def do_this
    ..
    ..
  end

  def do_that
    ..
    ..
  end

end

І якщо ви не хочете перевіряти зміну значення в зворотному виклику, ви можете зробити наступне:

class Claim < ApplicationRecord

  after_save: :do_this, if: saved_change_to_status?


  def do_this
    ..
    ..
  end

end

0

Я бачив, як питання піднімається в багатьох місцях, тому я написав для нього крихітний рубім, щоб зробити код трохи приємнішим (і уникати мільйонів тверджень if / else скрізь): https://github.com/ronna-s / on_change . Я сподіваюся, що це допоможе.


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