【Rails/jQuery/haml】ドラッグ＆ドロップでの並び替え機能の実装

##はじめに
アプリケーションを「使う側」としては、ドラッグ＆ドロップで自由に並び順を入れ替えるなんてめちゃめちゃ普通に使っていますが、「つくる側」としてはどのようにすれば自由な並び順の入れ替えを実現できるのか気になったので、試しに実装してみました。

普段当たり前に使いすぎていて、逆にないと不便な機能だと思いますので、もしよかったら参考にしてみてください。

ということで、まずは完成形の動画をご覧ください↓
![alt](https://gyazo.com/771e6deb42617529e7fd69a06ed53866/raw)

きちんとドラッグ＆ドロップでの並び替え機能が実装されていますね。
※個人的には、若干の挙動不備がある（並び替えをするとリスト全体が動いてしまうときがある）ので、原因がわかる方がいれば教えて欲しいと思っていますが、、

では、実際の機能の実装手順をお伝えします。

##＜ドラッグ＆ドロップ機能の実装手順＞
**①事前準備**
※事前準備についてはあくまで今回作成したアプリケーションに必要な手順なので、単にドラッグ＆ドロップの実装の仕方だけ知りたい方は、②へお進みください。

**1．scaffoldでベースとなるアプリケーションを作成する**
この項目については先日記事を書かせていただいたので以下をご確認ください。

【Rails】scaffoldを使用したアプリケーションの作成手順
https://qiita.com/tsubasaozawa/items/98f3e64a450d4304fc84

**2．必要なGemをインストールする**
**⑴ jQuery（rails用）のインストール**

```ruby:Gemfile
gem 'jquery-rails'
```

```javascript:javascript/application.js
//= require jquery
```

```:ターミナル
$ bundle install
```

**⑵ hamlのインストール**

```ruby:Gemfile
gem 'haml-rails'
```

```:ターミナル
$ bundle install
```

今のままだと、元からあるerbファイルはそのままなので、以下のコマンドを使って、すべてhamlのファイルに変換しましょう。

```:ターミナル
$ rails haml:erb2haml
```
rails haml:erb2hamlのコマンドを入力した際、途中で以下の表示が出ますが、ただの確認ですので「y」と入力し、enterキーを押してください。

```:ターミナル
Would you like to delete the original .erb files? (This is not recommended unless you are under version control.) (y/n)
```

以下の表記が出たらerbファイルのhaml変換完了です。

```:ターミナル
Task complete!
No .erb files found. Task will now exit.
```

ここまでが事前準備になります。
以下、いよいよドラッグ＆ドロップ機能の実装を進めていきます。

**②ドラッグ＆ドロップ機能の実装手順**
**1．jQuery UIをインストールする**
ドラッグ&ドロップを実現するためにjQuery UIを導入します。

```ruby:Gemfile
gem 'jquery-ui-rails'
```
```javascript:javascript/application.js
//= require jquery-ui/widgets/sortable
```
※上記については、バージョンによって参照パスが異なるようなので、以下の記事を参考に記述をお願いします。
https://qiita.com/jnchito/items/391fb16d3f69fda9bdae

```:ターミナル
$ bundle install
```

**2．ranked-modelを使ってRailsで順番を管理できるようにする**
Railsで順番を管理するためには、まずranked-modelというGemをインストールします。

```ruby:Gemfile
gem 'ranked-model'
```

```:ターミナル
$ bundle install
```

次に、コントローラでrankメソッド（row_order順の並び替えを実行する）が使えるよう、モデルの設定とテーブルへのカラムの追加を行います。

```ruby:models/todo.rb
class Todo < ApplicationRecord
  include RankedModel
  ranks :row_order
end
```

```:ターミナル
$ rails g migration AddRowOrderToTodos row_order:integer
$ rake db:migrate
```


```ruby:todos_controller.rb
 def index
   @todos = Todo.all
 end

  ↓変更

 def index
   @todos = Todo.rank(:row_order)
 end
```

最後に、コントローラにrankメソッドの記載をして、ranked-modelを使用した順番を管理するための設定は完了です。

**3．sortアクションを設定する**
Ajaxでデータを更新できるよう、sort アクションを設定します。
まずは、ルーティングを以下のように設定することで、「PUT /todos/:todo_id/sort」で「todos#sort」が呼ばれるようにします。

```ruby:routes.rb
 Rails.application.routes.draw do
   resources :todos
   root to: "todos#index"
 end

  ↓変更

 Rails.application.routes.draw do
   resources :todos do
     put :sort
   end
   root to: "todos#index"
 end
```

次に、並べ替えが行われた際に実行されるsortアクションの定義を行います。

```ruby:todos_controller.rb
  def sort
    todo = Todo.find(params[:todo_id])
    todo.update(fruit_params)
    render body: nil
  end

```
最後に、ストロングパラメーターに:row_order_positionの記述を追加し、並び替えの情報が受け取れるようにします。

```ruby:todos_controller.rb
  private

    # 一部省略

    def todo_params
      params.require(:todo).permit(:content, :priority, :limit)
    end

  ↓変更

  private

    # 一部省略

    def todo_params
      params.require(:todo).permit(:content, :priority, :limit, :row_order_position)
    end
```
※通常であれば、カラム名であるrow_orderを追加すると思いますが、gemの仕様で「_position」をつけなくてはいけないらしいです。


**4．index.html.hamlにカスタムデータ値をセットする**
この後のAjaxリクエストの記述を行うにあたって、index.html.hamlにカスタムデータ値をセットします。
今回は、todoリストの一つ一つを自由に並び替えできるようにしたいので、リストの親要素である「tbody」に以下の記述を追加しています。
`#item{"data-model_name": "todo.class.name.underscore", "data-update_url": "todo_sort_path(todo)"}`

```haml:index.html.haml
%p#notice= notice
%h1 Todos
%table
  %thead
    %tr
      %th Content
      %th Priority
      %th Limit
      %th{:colspan => "3"}
  %tbody#item{"data-model_name": "todo.class.name.underscore", "data-update_url": "todo_sort_path(todo)"}
    - @todos.each do |todo|
      %tr
        %td= todo.content
        %td= todo.priority
        %td= todo.limit
        %td= link_to 'Show', todo
        %td= link_to 'Edit', edit_todo_path(todo)
        %td= link_to 'Destroy', todo, method: :delete, data: { confirm: 'Are you sure?' }
%br/
= link_to 'New Todo', new_todo_path

```

**5．JavascriptでAjaxリクエストを送信する**
新たにjsファイルを作成（ファイル名は任意）し、Ajaxリクエストの設定を行います。

```javascript:table_sort.js
$(function(){
  $('#item').sortable({
    update: function(e, ui){
      var item = ui.item;
      var item_data = item.data();
      var params = {_method: 'put'};
      params[item_data.modelName] = { row_order_position: item.index() }
      $.ajax({
        type: 'POST',
        url: item_data.updateUrl,
        dataType: 'json',
        data: params
      });
    }
  });
});

```

以上で、ドラッグ＆ドロップでの並び替え機能の実装は完了です！

皆さま改めて以下のようなビュー操作ができるようになりましたでしょうか？
![alt](https://gyazo.com/771e6deb42617529e7fd69a06ed53866/raw)

途中途中、もう少し詳しく書きたいところもありましたが、また時間があるときに追記させていただきます。

【参考記事】
Rails 4で作るドラッグアンドドロップで表示順を変更できるサンプルアプリ(スクリーンキャスト付き)
https://qiita.com/jnchito/items/391fb16d3f69fda9bdae