Ruby on Railsアクティブレコード更新メソッドの比較
Ruby on RailsのActiveRecord::Persistenceモジュールにはアクティブレコード更新のために様々なメソッドが用意されていますが、似たような名前のメソッドが多く、それぞれの違いがわからなかったり、どれを使うのが良いのか迷ったりすることがあります。
そこで、更新メソッドの違いを比較することで、それぞれをどのような場面で使うのが適切なのかを見ていきたいと思います。
update
更新メソッドの中で最も頻繁に使うのはupdateでしょう。
@thing.update(thing_params)
のようにコントローラーのupdateアクションで使うのが一般的です。
モデルのバリデーション、コールバック共に実行されます。
もしバリデーションでエラーになった場合はfalse、データベースに無事に保存されればtrueが戻り値になりますので、if..elseで使うのが適しています。
class ThingsController < ApplicationController
def update
if @thing.update thing_params
redirect_to @thing
else
render 'edit'
end
end
end
update!
update!ではupdateと同じくバリデーション、コールバック共に実行されます。但しバリデーションがエラーになった場合は、例外となります。
def update
@thing.update! thing_params
redirect_to @thing
rescue => e
render 'edit'
end
updateではなくupdate!を使用すべき場面としては、複数の異なるモデルの保存を一括で行わなければならず、どれか一つでもモデル保存がエラーになったら例外を投げてデータベーストランザクションをロールバックしたい時が当てはまります。
def update
@thing.transaction do
@thing.update!
@foo.update!
end
redirect_to @thing
rescue
render 'edit'
end
同じコードをupdateを使って書くと以下のように自身で例外を起こさなければなりません。
def update
@thing.transaction do
raise ActiveRecord::RecordInvalid.new 'thing failed' unless @thing.update
raise ActiveRecord::RecordInvalid.new 'foo failed' unless @foo.update
end
redirect_to @thing
rescue ActiveRecord::RecordInvalid => e
render 'edit'
end
update_attribute
update_attributeは1つのモデルattributeのみを更新します。バリデーションはスキップされますが、コールバックは実行されます。
使用例としては、データベースレコードの論理削除などがあります。バリデーションを気にせず確実にdeleted_atにタイムスタンプを記録した後、コールバックで処理完了の通知を送りたい場合などです。
updateを使うとバリデーションで引っかかってしまうリスクがありますし、後述のupdate_columnだとコールバックが呼ばれません。ソースコードの裏側ではsave(validation: false)が実行されます。なので、もしメソッド実行以前に他のattributeがdirtyになっている場合は一緒に更新されてしまうので、注意が必要です。
@user.update_attribute deleted_at: Time.current
update_attributes
update_attributesは、一見update_attributeの複数形なので、複数のattributesをバリデーションなしで更新するためのメソッドに見えますが、実はupdateのエイリアスです。updateとどちらを使うかはエンジニアの好み、もしくは文脈により意図がよりはっきり伝わる方を選べば良いかと思います。
save
saveでupdateと同じことをしようとすると、以下のようにアサインと保存を分けることになります。実際、updateのソースコードではassign_attributesとsaveが呼ばれています。
@thing.assign_attributes thing_params
@thing.save
通常はupdateを使って一行で済ませますが、アサインメントと保存の間に処理を加えたい場合は、saveを使います。例えば@thingにparamsをアサイン後、@thingインスタンスのプロパティ値を変更したりする場合です。
@thing.assign_attributes thing_params
@thing.modify_some_attributes
@thing.save
バリデーションエラーやコールバックでの例外が起こった場合はfalse, データベースで無事に更新された場合はtrueが戻り値になります。
save!
save!はsaveと似ていますが、バリデーションエラーやコールバック中に例外が起こると、例外が起こります。もしエラーがない場合、trueを返します。
def update
@thing.transaction do
@thing.assign_attributes thing_params
@thing.modify_some_attributes
@thing.save!
@foo.save!
end
redirect_to @thing
rescue
render 'edit'
end
toggle
toggleはブーリアン型のattributeを反対の値にします。つまり、元の値がtrueだったらfalse, falseならtrueになります。データベースへの保存は行われず、オブジェクトメモリーの値が変化します。
戻り値はselfのため、メソッドチェインが可能です。
def toggle_enabled
if @thing.toggle(:enabled).save
redirect_to @thing
else
render 'edit'
end
end
toggle!
toggle!はtoggleと同じようにブーリアン型のattributeを反対の値にしますが、データベースへの保存がされます。ソースコードでは、toggleとupdate_attributeのメソッドチェインとなっています。ゆえに、戻り値もselfではなくブーリアンとなります。
def toggle_enabled
if @thing.toggle!(:enabled)
redirect_to @thing
else
render 'edit'
end
end
touch
touchはupdated_at/updated_on、及び任意のタイムスタンプ・カラムを現在時刻で更新します。
after_touch, after_commit/after_rollbackののコールバックしか実行されないので、 タイムスタンプカラムの更新で通常のafter_saveなどのコールバックを実行したくない時に使えます。
@user.touch(:deleted_at)
update_column
update_columnは後述のupdate_columnsと挙動は一緒ですが、複数ではなく単数のattributeを更新したい場合に使用します。
@thing.update_column :status, 'active'
以下のようにupdate_columnsを使って単数のattributeを更新しても挙動は同じですが、update_columnを使ったほうが単数カラムの更新というコードの意図がよりはっきりします。
@thing.update_columns status: 'active'
update_columns
update_columnsは複数のattributesを更新します。バリデーション、コールバック共にスキップされ、updated_atも更新されないため、生のUPDATE SQLと同義です。
使用例としては、新規レコード作成後のActiveRecordコールバックの中で、アサインされたプライマリーidを基に他のコラムを更新したい時があります。また、バグなどにより本番データベースレコードの整合性を修正したいが、生のSQLで間違って別のレコードを更新するようなミスを防ぎたいようなケースが該当します。
@thing.update_columns status: 'active', amount: 100
どのメソッドを使うべきかの判断
前述の各更新メソッドの特徴をまとめると、以下の表の通りとなります。
メソッド | バリデーション | コールバック実行 | 戻り値 | エラーの場合 | 更新されるattributeの数 | updated_at更新有無 |
---|---|---|---|---|---|---|
update (update_attributes) |
あり | あり | ブーリアン | 戻り値がfalse | 複数 | あり |
update! | あり | あり | ブーリアン | 例外 | 複数 | あり |
update_attribute | なし | あり | ブーリアン | 戻り値がfalse | 単数 | あり |
save | あり | あり | ブーリアン | 戻り値がfalse | 複数 | あり |
save! | あり | あり | ブーリアン | 例外 | 複数 | あり |
toggle | なし | なし | self | n/a | 単数 | なし |
toggle! | なし | あり | ブーリアン | 戻り値がfalse | 単数 | あり |
touch | なし | あり | ブーリアン | n/a | 複数 | あり |
update_column | なし | なし | ブーリアン | n/a | 単数 | なし |
update_columns | なし | なし | ブーリアン | n/a | 複数 | なし |
上記の表で条件にあうメソッドを絞り込んで決定しても良いですし、以下のようなディシジョンツリーを使ってどのメソッドを使うべきかを判断しても良いかと思います。
補足:更新メソッド間の依存関係
各メソッドのソースコードを読むと、いくつかのメソッドは他のメソッドのラッパーメソッドであることが確認出来ます。例えば、updateのソースコードではsaveが呼ばれているため、updateの基本的な挙動はsaveに準拠することがわかります。以下の図は、どのメソッドがどのメソッドに依存しているかをまとめたものですが、この依存関係を頭に入れておくと、saveやupdate_columnsなどベースとなる数個のメソッドの特徴を覚えるだけで、他のメソッドの動作も理解出来るはずです。
最後に
このように、Ruby on Railsではモデルの更新を行うのに似たようなメソッドがいくつもあるので混乱しがちですが、異なった文脈に応じて適切なメソッドを使うことでrubyらしく人間に取って読みやすいコードを書くことが出来ます。