collection_check_boxes を用いて中間テーブルを削除した際に、中間モデルの after_destroy のコールバックを実行したかったが上手くいきませんでした。
モデル
class Route < ApplicationRecord
belongs_to :user
has_many :route_spots, dependent: :destroy
has_many :spots, through: :route_spots
end
class Spot < ApplicationRecord
belongs_to :user
has_many :route_spots, dependent: :destroy
has_many :routes, through: :route_spots
end
class RouteSpot < ApplicationRecord
belongs_to :route
belongs_to :spot
after_destroy :align_sequence
private
def align_sequence
self.route.route_spots.order(sequence: "ASC").each_with_index{ |object, index|
object.sequence = index + 1
object.save
}
end
end
コントローラ
class RoutesController < ApplicationController
before_action :authenticate_user!
before_action :set_id, only: %i[edit destroy update show]
...省略
def edit
@my_spots = current_user.spots
end
def update
if @route.update(route_params)
redirect_to routes_path(anchor: "id_#{@route.id}"), notice: "「#{@route.name}」を編集しました"
else
render :edit
end
end
...省略
private
def route_params
params.require(:route).permit(:id, :name, :memo, :sequence, spot_ids:[])
end
def set_id
@route = Route.find(params[:id])
end
end
view
= form_with model: route, local:true, id:"route_form" do |f|
= f.label :name
= f.text_field :name, id: 'route_name'
= f.label :memo
= f.text_area :memo, id: 'route_memo'
= f.collection_check_boxes(:spot_ids, @my_spots, :id, :name)
= f.submit '登録する' , class:'button-primary'
やりたかったこと
動作自体は、Route の edit 時に、has_may な Spots の登録/登録解除を、collection_check_boxes で簡単に実装しただけのなんてことのないもの。
やりたかったのは、collection_check_boxes でチェックを外して route を update (routeのupdate と同時に route.route_spots が destroy)した際に、RouteSpotモデルの after_destroy で メソッドalign_sequence を実行したかった。
しかし、after_destroy、before_destroy、どちらでも動作せず、ただ RouteSpot の destroy が完了するだけでした。
- 下記の記事を辿ると、どうにも Rails が持っているバグとのこと。
- githubのissueを見ると、どうも放置されて close しているっぽいです。状況が限定的すぎだからでしょうか。
- RouteSpot の after_destroy、before_destroyは動作しなかったが、試しにafter_createを書いてみたら、そちらは問題なく動いたので、やはりコールバック全てが動作しない訳ではなく、destroy関連のみが動かないようです。
結論
あまり良いとは言えないかもしれないですが、RouteSpot のコールバック(after_destroy)ではなく、Route のコールバック(after_update)ので処理することにしました。
もともと、route.route_spots に対し一括で連番を振り直すというものだったので、ひとまずやりたいことは問題なく動作しますので、今回はこれで妥協します。
仕様と言われたら納得いきますが、バグ、しかも長期にわたって放置されているものとだと思うとちょっと微妙な気持ちです。