モデルから属性の変更を検出する方法は?


85

モデルが保存された後に実行されるコールバック関数をrailsに作成したいと思います。

私はこのモデルを持っています、クレームの状態に応じて変化する属性「ステータス」を持つクレーム、可能な値は保留中、承認済み、承認済み、拒否済みです

データベースには「state」があり、デフォルト値は「pending」です。

モデルが最初に作成された後、またはモデルがどの状態から変化するかに応じて、ある状態から別の状態に更新された後に、特定のタスクを実行したいと思います。

私の考えは、モデルに関数を含めることです。

    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 ::ダーティモジュールを:あなたはあなたの主張のモデルに対して次のアクションを実行することができるはずです。

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
これは、彼が要求したモデルが保存された後は機能しません。
トム・ロッシ

4
@TomRossi、dirty呼び出しはafter_save(Rails 2.3と3.xの両方で)機能します。私はそれを数回使用しました。
Harish Shetty 2012年

11
@TomRossi、ダーティフラグはコミット後にリセットされるため、Rails3.xでafter_commit導入されたコールバックでは使用できません。彼らは確かにで動作しafter_saveます。
Harish Shetty 2012年

私は今まで知らなかった!保存したらリセットされたと思いました!
トム・ロッシ

5
@TomRossi私は数年前に同じ仮定から始めました。after_saveのダーティフラグをチェックしようとすると、うまくいきました。本質的にafter_saveは、after DMLとの間の状態のコールバックですbefore_commitafter_save例外をスローすることで、トランザクション全体を終了できます。現在の操作に影響を与えずに保存後に何かを実行したい場合は、次を使用してくださいafter_commit:-)
Harish Shetty 2012年

38

あなたはこれを使うことができます

self.changed

このレコードで変更されたすべての列の配列を返します

あなたも使うことができます

self.changes

変更された列のハッシュと結果の前後を配列として返します


7
使用する必要がないと言うことだけでマイナーノートself.これらに-あなただけ言うことができるchangedchanges
user664833 2014

@ user664833は具体的には、あなたは、省略することができselfたときにモデル自体ではなく、あなたが任意のオブジェクト上でこれらを呼び出すことができますobject.changedし、object.changes。:)
Joshua Pinter 2018

4

利用可能なステートマシンプラグインの1つをご覧になることをお勧めします。

どちらでも、状態と状態間の遷移を設定できます。要件を処理するための非常に便利で簡単な方法。


私は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)endendそしてデータベースのステータスフィールドにはデフォルト値があります「保留中」の。ステータスフィールドに入力せずにClaim.createを実行した場合(「pending」が実行されるように)、AASMは「enter_pending」メソッドを実行しますか?
David C

2

Rails 5.1以降では、アクティブレコード属性メソッドを使用する必要があります:saved_change_to_attribute?

saved_change_to_attribute?(attr_name、** options) `

この属性は、最後に保存したときに変更されましたか?このメソッドは、の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

私は多くの場所で質問が増えるのを見てきましたので、コードを少し良くするために(そしてどこでも100万のif / elseステートメントを避けるために)小さなrubygemを書きました:https//github.com/ronna-s / on_change。それがお役に立てば幸いです。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.