🧼

ActiveRecord::AttributeMethods::Dirty のメソッドをまとめてみる

2023/11/14に公開

こんにちは!
ラブグラフエンジニアのひろです!

データの更新を検知したいときに便利な ActiveRecord::AttributeMethods::Dirty のメソッド群ですが、どのメソッドを使うか迷うことが多いので、まとめてみます!

便利なメソッドが多いですが、それぞれどのタイミングで使えるか分かりづらいのがつらいですね...
(だから Dirty???)

どんなメソッドがあるのか

前提となるデータ.rb
user = User.find_by(family_name: "みなせ", given_name: "ひろ")
=> #<User:0x00aaaaa
 id: 123456,
 family_name: "みなせ",
 given_name: "ひろ",
 ...

attribute_before_last_save

直前に更新したカラムの、更新される前の値が取得できる

user.family_name = "水瀬"

=> nil # 更新前は取得できない

user.save!

user.family_name_before_last_save
=> "みなせ" # 更新される前の値

attribute_change_to_be_saved

更新される前の値と更新しようとしている値が取得できる

user.family_name = "水瀬"

user.family_name_change_to_be_saved
=> ["みなせ", "水瀬"] # 更新される前の値と更新しようとしている値

user.save!

user.family_name_change_to_be_saved
=> nil # 保存後は取得できない

attribute_in_database

その時点でDBに保存されている値を取得できる

user.family_name_in_database
=> "みなせ" # 現状の値

user.family_name = "水瀬"

user.family_name_in_database
=> "みなせ" # 更新前なので元の値

user.save!

user.family_name_in_database
=> "水瀬" # 更新後なので新しい値

attributes_in_database

更新しようとしているカラム名と元々の値が取得できる

user.family_name = "水瀬"

user.attributes_in_database
=> {"family_name"=>"みなせ"} # 更新しようとしているカラムと元々の値

user.save!

user.attributes_in_database
=> {} # 更新後は取得できない

changed_attribute_names_to_save

更新しようとしているカラム名が取得できる

user.family_name = "水瀬"
=> "水瀬"

user.changed_attribute_names_to_save
=> ["family_name"] # 更新しようとしているカラム名

user.save!

user.changed_attribute_names_to_save
=> [] # 更新後は取得できない

changes_to_save

更新しようとしているカラム名と、更新前後の値を取得できる

user.family_name = "水瀬"

user.changes_to_save
=> {"family_name"=>["みなせ", "水瀬"]} # 更新しようとしているカラム名と前後の値

user.changes_to_save
=> {} # 更新後は取得できない

has_changes_to_save?

更新される値があるかどうかを返す

user.family_name = "水瀬"

user.has_changes_to_save?
=> true # family_name が更新されうるので true

user.save!

user.has_changes_to_save?
=> false # 保存後は取得できない

reload

DBから取得し直して保存前の変更をクリア

user.family_name = "水瀬"

user.family_name
=> "水瀬"

user.reload

user.family_name
=> "みなせ" # 元々の値に戻る

saved_change_to_attribute

更新後に、特定のカラムの更新前後の値が取得できる

user.family_name = "水瀬"

user.saved_change_to_family_name
=> nil # 更新前は取得できない

user.save!

user.saved_change_to_family_name
=> ["みなせ", "水瀬"] # 更新前後の値

saved_change_to_attribute?

更新後に、特定のカラムが更新されたかどうかを返す

user.family_name = "水瀬"

user.saved_change_to_family_name?
=> false # 更新前なので false

user.save!

user.saved_change_to_family_name?
=> true # 更新されているので true

saved_changes

更新されたカラムの更新前後の値が取得できる

user.family_name = "水瀬"

user.saved_changes
=> {}

user.save!

user.saved_changes
=> {
 "family_name"=>["みなせ", "水瀬"],
 "updated_at"=>
  [Mon, 13 Nov 2023 16:02:00.519392000 JST +09:00,
   Mon, 13 Nov 2023 16:02:24.720393000 JST +09:00]
} # 更新されたカラムの更新前後の値

saved_changes?

更新されたカラムがあるかどうかを返す

user.family_name = "水瀬"

user.saved_changes?
=> false # 更新前なので false

user.save!

user.saved_changes?
=> true # 更新された後なので true

will_save_change_to_attribute?

特定のカラムが更新されるかどうかを返す

user.family_name = "水瀬"

user.will_save_change_to_family_name?
=> true # 更新前なので true

user.save!

user.will_save_change_to_family_name?
=> false # 更新後は false に

利用例

ユーザーネームの変更を検知し、変更履歴テーブルを作ることができますね。

## 更新前におこなうパターン
user.name = "水瀬"

if user.will_save_change_to_name?
  UserNameHistory.create!(
    before_name: user.name_in_database,
    after_name: user.name
  )
end

user.save!

## 更新後におこなうパターン
user.name = "水瀬"

user.save!

if user.saved_change_to_name?
  UserNameHistory.create!(
    before_name: user.name_before_last_save,
    after_name: user.name
  )
end

まとめ

今回は ActiveRecord::AttributeMethods::Dirty で利用できるメソッドを、簡単な使用例とともにまとめてみました!

ActiveRecord のコールバックとの組み合わせで少し複雑になってくるので、次回はその辺りを書いていこうと思います!

https://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Dirty.html

ラブグラフのエンジニアブログ

Discussion