Railsでのアプリケーション開発で「DBに保存したくないけど値を更新したい」時ってあると思いますが、そんな時に便利なassign_attributesで少し詰まったのでメモ。
update_attributesとassign_attributesの違い
update_attributesとassign_attributesの違いについて言及している記事は結構多いので今回は深掘りしませんが、update_attributesの場合はDBを更新してくれて、assign_attributesはDBには保存せず値のみ更新してくれるというもの。
私の場合もそうでしたが、例えば確認画面とかを実装する時に「まだ保存はしたくないけど更新された値を取得したいなー」みたいな時に使えるかと思います。(もちろんassign_attributesだけではDBへの更新がされないので、そのあとにsaveしてあげる必要があります。)
ちなみにupdateメソッドってupdate_attributesのエイリアスみたいですね。。全然別物だと思ってたわややこしい。
本題:assign_attributesを使ってるのに保存される
今回私は確認画面を実装したかったので、先ほど言及したようにassign_attributesで値を更新したかった。モデルは以下の通りです。
group.rb
class Group < ApplicationRecord has_many :group_users, dependent: :delete_all has_many :users, through: :group_users end
user.rb
class User < ApplicationRecord has_many :group_users, dependent: :delete_all has_many :groups, through: :group_users end
group_user.rb
class GroupUsers < ApplicationRecord belongs_to :group belongs_to :user end
ユーザーとグループが多対多で中間テーブルが存在します。
で、グループを更新するときの確認画面でassign_attributesを使って更新された@ group_userをいったん表示した後保存したくて、コントローラーで以下のように書いたのだけど、確認画面の時点で中間テーブルにのみ保存されてしまう。なぜ。。
group_users_controller.rb
def update @group_user.assign_attributes(group_user_params) if @group_user.save redirect_to root_path notice: 'グループを更新しました' else render :edit end end
原因:中間テーブルには即時で保存されてしまうらしい
調べまくったけどなかなか記事が出てこなくて、やっと出てきたこちら
参考:Railsの罠
has_many throughリレーションをassign_attributesするとその時点でinsertされる
基点となるモデルが保存済みの場合、has_many throughリレーションを代入すると即座にinsertが走る
validationしたいなどの理由によりrollbackする必要がある場合にはtrunsactionを使う必要がある
まさにRailsの罠(笑)
詳しい解説がなくて残念ですが、どうやらassign_attributesを使用した場合でも多対多のアソシエーションの中間テーブルには即時で保存されてしまうらしい。。
解決:トランザクションで処理をまとめる
色々調べたけど、保存されてしまった中間テーブルをロールバックして、さらに途中で処理が終わってしまわないようにトランザクションで囲むしか方法がなさそうだった。
group_users_controller.rb
def update ActiveRecord::Base.transaction do @group_user.assign_attributes(group_user_params) #ここで一旦中間テーブルに保存される raise ActiveRecord::Rollback #手動でロールバック end if @group_user.save redirect_to root_path notice: 'グループを更新しました' else render :edit end end
「手動で」と書いている意図ですが、
トランザクションはひとまとめにする必要のある処理が途中で終わってしまった場合に全てなかったことにするために使用するものですが、例外が起こらない限り(エラーにならない限り)自動的なロールバックはしてくれないため、今回のような場合はきちんと書いてあげなきゃいけないみたいです。
まとめ:assign_attributesは中間テーブルの場合即座にinsertされる
こういうイレギュラーな処理は解決に行きつくまでが大変な分、すごく勉強になりますね。。
特に今回は元々トランザクションを使ったことがなかったので、そちらもかなり勉強になりました。
無事に実装できてめでたし。