Как обнаружить изменения атрибутов модели?

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"]}

Ой! радости Rails!

Хариш Шетти
источник
6
Это не сработает после сохранения модели, о чем он просил.
Tom Rossi
4
@TomRossi, dirtyвызовы работают after_save(как в Rails 2.3, так и в 3.x). Я использовал его несколько раз.
Harish Shetty
11
@TomRossi, грязные флаги сбрасываются после фиксации, поэтому они не будут доступны в after_commitобратных вызовах, представленных в Rails 3.x. Они обязательно будут работать after_save.
Harish Shetty
Я понятия не имел! Я думал, они были сброшены после сохранения!
Tom Rossi
5
@TomRossi Я начал с того же предположения несколько лет назад. Когда я попытался проверить грязные флаги в after_save, это сработало. По сути, after_saveэто обратный вызов для состояния между after DMLи before_commit. Вы можете завершить всю транзакцию after_save, вызвав исключение. Если вы хотите что-то сделать после сохранения, не влияя на текущую операцию, используйте after_commit:-)
Хариш Шетти
38

вы можете использовать это

self.changed

он возвращает массив всех столбцов, которые изменились в этой записи

вы также можете использовать

self.changes

который возвращает хеш столбцов, которые изменились, до и после результатов в виде массивов

Zeacuss
источник
7
Небольшое замечание, чтобы сказать, что в self.них нет необходимости - вы можете просто сказать changedи changes.
user664833
@ user664833 В частности, вы можете опустить, selfкогда в самой модели, но вы можете вызвать их для любого объекта с помощью object.changedи object.changes. :)
Джошуа Пинтер
4

Я рекомендую вам взглянуть на один из доступных плагинов конечного автомата:

Любой из них позволит вам устанавливать состояния и переходы между состояниями. Очень полезный и простой способ удовлетворить ваши требования.

Тоби Хеде
источник
Я пробую rubyist-aasm. Допустим, у меня есть класс Claim <ActiveRecord :: Base include 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?
Дэвид 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?. Этот метод полезен в обратных вызовах after, чтобы определить, изменил ли вызов для сохранения определенный атрибут.

Параметры

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

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

Ронна
источник
0

Вам будет намного лучше использовать хорошо проверенное решение, такое как гем state_machine .

Пас Арича
источник