[Rails]ActiveRecord::Dirtyモジュール
はじめに
データ変更に関連する処理を実装したいのでActiveRecord::Dirty
についてまとめてみました。
ActiveRecord::Dirty
とは
ActiveRecord::Dirty
モジュールは、RailsのActive Recordライブラリに組み込まれたモジュールの一つであり、データベースレコードの変更状態を追跡するために使用されます。
データベースレコードが変更されたかどうかを検出し、変更内容を追跡するための便利なメソッドとイベントを提供しています。
ActiveRecord::Dirty
モジュールの主な機能:
- 変更の追跡: データベースレコードが変更された場合、どの属性が変更されたかを知ることができます。
- 変更内容の取得: レコードの変更された属性の以前の値と新しい値を取得するためのメソッドが提供されます。
- 変更イベント: データベースレコードの特定の属性が変更された場合に、カスタムメソッドをトリガーすることができます。変更時のカスタム処理を実装するには便利です。
ActiveRecord::AttributeMethods::Dirty
モジュールで提供される主要なメソッド:
メソッド | 説明 |
---|---|
changed_attribute_names_to_save? |
変更されたすべての属性の配列を返します。 (changed から) |
has_changes_to_save? |
1つ以上の属性が変更された場合にtrue を返します。 (changed? から) |
changes_to_save |
変更された属性とその変更前後の値を含むハッシュを返します。(changes から) |
<attribute>_change_to_be_saved |
特定のカラムの変更前後の値を含むハッシュを返します。(<attribute>_changes から) |
will_save_change_to_attribute? |
保存予定の変更があるか判定しtrue/false を返します。変更前後の値を指定できますます。(<attribute>_changed? から) |
saved_changes |
最後の保存から変更された属性とその変更前後の値を含むハッシュを返します(previous_changes から)。 |
restore_attributes |
変更前の値で属性を復元します。 |
previous_changes |
最後の保存から変更された属性とその変更前後の値を含むハッシュを返します。 |
clear_changes_information |
属性の変更情報をクリアします。 |
clear_attribute_changes |
特定の属性の変更情報をクリアします。 |
attribute_in_database |
変更された属性とその変更前後の値を含むハッシュを返します(changed_attributes から)。 |
changes_applied |
属性変更を適用し、変更情報をクリアします。 |
reset_attribute |
特定の属性の変更情報をリセットします。 |
reset_changes |
すべての属性の変更情報をリセットします。 |
これらのメソッドは、Active Recordモデル内でデータの変更状態を追跡し、属性の変更前後の値を取得したり、変更があったかどうかを確認したりするのに使えます。
ActiveRecord::Dirty
モジュールのクラスメソッド一覧
早速ActiveRecord::Dirty
を使ってみます。
ActiveRecord::Dirty
モジュールをincludeする
モデルにTodoモデルがあるとします。
一つのTodoにステータスをつけられます、ActiveRecord::Dirty
モジュールを導入しステータスの変更を記録する例です。
また、before_save
コールバックを使用して、status
属性が変更されたときにカスタム処理を実行させることもできます。
class Todo < ApplicationRecord
enum status: { planned: 0, started: 1, completed: 2, archived: 3 }
# includeする
include ActiveModel::Dirty
before_save :announce_status_changes
private
def announce_status_changes
if status_changed?
# status属性が変更された場合に実行する処理
puts "ステータスが #{name_was} から #{name}"に変更されました。
end
end
end
ステータスを変更する
irb(main):016:0> todo = Todo.last
Todo Load (0.3ms) SELECT "todos".* FROM "todos" ORDER BY "todos"."id" DESC LIMIT $1 [["LIMIT", 1]]
=>
#<Todo:0x00000001122927d8
...
irb(main):017:0> todo.status = 2
=> 2
irb(main):018:0> todo.changed?
=> true
irb(main):019:0> todo.status_changed?
=> true
irb(main):020:0> todo.status_was
=> "planned"
irb(main):021:0> todo.status_change
=> ["planned", "started"]
irb(main):022:0> todo.status = 3
=> 3
irb(main):023:0> todo.status_change
=> ["planned", "archived"]
変更を取得する
irb(main):001:0> todo.has_changes_to_save?
=> true
irb(main):002:0> todo.changed
=> ["status"]
irb(main):003:0> todo.changed_attributes
=> {"status"=>"planned"}
irb(main):004:0> todo.changes
=> {"status"=>["planned", "done"]}
irb(main):005:0> todo.changes_to_save
=> {"status"=>["planned", "done"]}
同じステータスで更新しても変更にならない
irb(main):048:0> todo.status = 2
=> 2
# 同じステータスで更新する
irb(main):049:0> todo.status = 2
=> 2
irb(main):050:0> todo.status_changed?
=> false
irb(main):051:0> todo.status_change
=> nil
変更をトラッキングする
irb(main):052:0> todo.status_will_change!
=> "done"
irb(main):053:0> todo.status_change
=> ["done", "done"]
status_will_change!
メソッドは、Active Recordモデル内で属性の変更をトラッキングするために使用されるメソッドの一つです。属性の変更をトラッキングするためのフラグを立てます。
このメソッドを呼び出すことで、Active Recordは指定した属性が変更されたことを認識し、後でその変更をデータベースに反映できるようになります。
class Todo < ApplicationRecord
# status属性が変更される前にstatus_will_change!メソッドを呼び出す
def mark_as_completed
status_will_change!
self.status = 2 # ステータスを「完了」に変更
save
end
end
mark_as_completed
メソッド内でstatus_will_change!
メソッドを呼び出して、status
属性が変更されたことをActive Recordに通知しています。その後、status
属性を変更してレコードを保存します。
変更をリセットする
手動で変更をリセットします。
irb(main):059:0> todo.changes_applied
=> nil
irb(main):060:0> todo.changes
=> {}
irb(main):061:0> todo.changed
=> []
irb(main):062:0> todo.has_changes_to_save?
=> false
# 全ての変更を消す
irb(main):063:0> todo.clear_changes_information
=> nil
ステータスを保存する
ステータスを保存すると変更をリセットされます。
irb(main):026:0> todo.save
TRANSACTION (0.2ms) BEGIN
Todo Update (0.4ms) UPDATE "todos" SET "status" = $1, "updated_at" = $2 WHERE "todos"."id" = $3 [["status", 3], ["updated_at", "2023-09-26 15:18:08.129468"], ["id", 4]]
TRANSACTION (0.5ms) COMMIT
=> true
irb(main):027:0> todo.changed?
=> false
irb(main):028:0> todo.status_changed?
=> false
irb(main):029:0> todo.status_was
=> "planned"
irb(main):030:0> todo.status_for_database
=> 1
ステータスが保存された後に変更を取得する
irb(main):001:0> todo.status = 1
=> 1
irb(main):002:0> todo.status
=> "started"
irb(main):003:0> todo.save
TRANSACTION (0.2ms) BEGIN
Todo Update (1.7ms) UPDATE "todos" SET "status" = $1, "updated_at" = $2 WHERE "todos"."id" = $3 [["status", 1], ["updated_at", "2023-09-26 15:44:21.531908"], ["id", 4]]
TRANSACTION (0.6ms) COMMIT
=> true
irb(main):004:0> todo.previous_changes
=>
{"status"=>["planned", "started"],
"updated_at"=>
[Tue, 26 Sep 2023 15:24:44.736906000 UTC +00:00, Tue, 26 Sep 2023 15:44:21.531908000 UTC +00:00]}
irb(main):005:0> todo.status_before_last_save
=> "planned"
終わりに
RailsのActiveRecord::Dirty
モジュールでした。
ActiveRecordのコールバックと組み合わせて使うことでTodoの変更状態に応じて特定のメソッドを実行できます。状態変更の通知などを送れると良さそうです。
Discussion