LoginSignup
3

More than 3 years have passed since last update.

posted at

updated at

Organization

ranked-model × sortable、STIのtype変更を伴うupdateでrow_orderが想定通りに変わらない問題対処

RailsでjQueryUIのsortableとranked-model使ったらハマりました。

使ったもの

経緯

タスク管理アプリを作ってました。その中で、タスクをドラッグ&ドロップで並び替えたかったんですね。
機能実装はこちらの記事を参考にさせていただいてます。
さらに、タスクを種類ごとに管理したかったのでTaskモデルを継承した ImportantTask, OtherTaskみたいなものを作り、そのクラス毎に並び順を保持するようにしました。内容は少しでもわかりやすくしようと思って適当にダミー入れてます。

task.rb
class Task < ApplicationRecord
  include RankedModel
  ranks :row_order
end

important_task.rb
class ImportantTask < Task
end
other_task.rb
class OtherTask < Task
end

で、.sortable-important.sortable-otherクラスを付与したテーブル間でのsortableを実装しました。jsはこんな感じ。

tasks.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はこちら。

tasks_controller.rb

  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の値ではなくクラス名みたいです。

ranked-modelのranker(github)

ranker.rb

# クラス名セットしてるところ
# パラメータが渡されていればそれをセットし、なければ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ステップを踏む必要があります。

task.rb
  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での更新時はインスタンスのクラス名に気をつけよう。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3