LoginSignup
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-11-18

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
  3. You can use dark theme
What you can do with signing up
3