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

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

## 使ったもの
- Rails 5.2
- jueryUI sortable [日本語リファレンス](http://js.studio-kingdom.com/jqueryui/interactions/sortable)
- [ranked-model](https://github.com/mixonic/ranked-model)

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

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

```

```ruby:important_task.rb
class ImportantTask < Task
end
```

```ruby:other_task.rb
class OtherTask < Task
end
```

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

```javascript: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はこちら。

```ruby: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)](https://github.com/mixonic/ranked-model/blob/master/lib/ranked-model/ranker.rb)

```ruby: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を更新するという２ステップを踏む必要があります。

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