RailsでjQueryUIのsortableとranked-model使ったらハマりました。
使ったもの
- Rails 5.2
- jueryUI sortable 日本語リファレンス
- ranked-model
経緯
タスク管理アプリを作ってました。その中で、タスクをドラッグ&ドロップで並び替えたかったんですね。
機能実装はこちらの記事を参考にさせていただいてます。
さらに、タスクを種類ごとに管理したかったのでTaskモデルを継承した ImportantTask
, OtherTask
みたいなものを作り、そのクラス毎に並び順を保持するようにしました。内容は少しでもわかりやすくしようと思って適当にダミー入れてます。
class Task < ApplicationRecord
include RankedModel
ranks :row_order
end
class ImportantTask < Task
end
class OtherTask < Task
end
で、.sortable-important
と.sortable-other
クラスを付与したテーブル間でのsortableを実装しました。jsはこんな感じ。
$(document).on('turbolinks:load', function () {
sortableFunction('.sortable-important', 'ImportantTask');
sortableFunction('.sortable-other', 'OtherTask');
});
function sortableFunction(klass, taskType) {
var targetClasses = [
'.sortable-important',
'.sortable-other'
];
$(klass).sortable({
items: '.item',
handle: '.draggable_icon',
connectWith: targetClasses,
update: function (e, ui) {
if (this === ui.item.parent()[0]) {
doUpdate(e, ui, taskType);
}
}
});
}
function doUpdate(e, ui, taskType) {
var item = ui.item;
var item_data = item.data();
var params = {_method: 'put'};
params[item_data.modelName] = {
row_order_position: item.index(),
type: taskType
};
$.ajax({
type: 'POST',
url: item_data.updateUrl,
dataType: 'json',
data: params
});
}
呼ばれるactionはこちら。
def sort
task = Task.find_by(id: params[:task_id])
task.update!(task_params)
render json: task
end
ですが、important -> otherやその逆の移動時に、row_orderが想定通りに設定されず、d&d > ページのリロード すると表示される並び順が変わってしまう、なんて現象が起きました。
原因と解決方法
ranked-modelはSTIに則って、そのクラス内での並び順を設定してくれます。
なんですが、簡単にranked-modelを追ってみたところ、row_order更新時に見ているのは、 type
の値ではなくクラス名みたいです。
# クラス名セットしてるところ
# パラメータが渡されていればそれをセットし、なければupdate対象のインスタンスのクラスをセットする
def instance_class
ranker.class_name.nil? ? instance.class : ranker.class_name.constantize
end
def rearrange_ranks
# 略
if current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank == RankedModel::MAX_RANK_VALUE
_scope.
where( instance_class.arel_table[ranker.column].lteq(rank) ).
# 以下略
Rails側でモデルをupdateする時にはtypeを変更しますが、その時点のインスタンスのクラスは移動前のものであるため、「移動前のクラス内での並び順」を算出してしまいます。
ですので、type間の移動&並び順変更がしたい場合は一旦typeを更新して、該当レコードを更新後のクラスのインスタンスとして再度取得し、row_orderを更新するという2ステップを踏む必要があります。
def sort
task = Task.find_by(id: params[:task_id])
task.update!(type: task_params[:type])
task = Task.find_by(id: params[:task_id])
task.update!(task_params)
render json: task
end
なんか嫌ですね。この程度の切り分けであればranked-modelのwith_sameオプションでまかなえそうな気もするので、STIを使う必要があるかってところから考え直すべきなんでしょうが、一応これでテーブル間移動の際も正しく並び順が算出できます。
結論
STI使ってる時のranked-modelでの更新時はインスタンスのクラス名に気をつけよう。