【Rails】 DataTables 実装方法

# はじめに
RailsアプリケーションでDataTablesを使っている記事を見かけなかったので、まとめることにしました。
やり方がわかっていないと細かい仕様を変更するのに時間がかかってしまいますが、やり方さえ覚えてしまえばとても使いやすい強力なツールです。
Bootstrap や jQuery UI を使うことによって、時間をかけずに多機能&良いレイアウトを提供してくれるため、爆速で開発できるRailsととても相性がいいと個人的には思っています。

#### 関連リンク
関連リンクを下記に載せておくので、必要であれば参考にしてください。。

* 【Rails】 DataTables 動的にカラムを変更する方法
    * https://qiita.com/syukan3/items/83c4ecd351db57911943
* 【Rails】 DataTables 検索結果の保持方法
    * https://qiita.com/syukan3/items/4be6ba8ef22aff189fc4

# DataTables
DataTables は、HTMLのテーブルに、__ページ切り替え、ページ当たりの件数設定、ソート、フィルタなどの機能を簡単に追加できるjQuery プラグインのライブラリ__。

# 使い方
## 設定
```Ruby:GemFile
# ページネーションにはkaminariを使用するため
gem 'kaminari'
gem 'jquery-datatables-rails'
```

```
$ bundle install
```

```
$ rails g jquery:datatables:install
$ rails g jquery:datatables:install bootstrap3
```

```javascript:app/assets/javascripts/application.js
//= require dataTables/jquery.dataTables
//= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
```

```CSS:app/assets/stylesheets/application.css
*= require dataTables/jquery.dataTables
*= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
```


* Bootstrap
    * https://getbootstrap.com/

※ version3系をダウンロードする。（GitHubにBootstrap4の記載がないため念のため。。）
4系でも使えると知っている方がいればコメントください。。

```
# 下記のようにファイルを配置する
app/assets/javascripts/bootstrap.min.js
app/assets/stylesheets/bootstrap.min.css
```


## アプリケーション実装
### 前提条件
Userモデルを scaffold で作成していること。

### 全体像

厳密には異なるところもありますが、ざっくりと全体像を示すとこんな感じになります。

① id が users の要素を持った table を作成（table#users）し、CoffeeScript にて読み込み
② ajax で CoffeeScript から Rails 側へ、データの算出を要求する
③ コントローラーにて ajax を受け取り、 UserDatatable モデルへデータの算出を要求する
④ 要求を満たすデータを返す
⑤ ④のデータを json ファイルにして返す
⑥ CoffeeScript にて実装したテーブルを HTML にて表示する


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308256/3849a34f-9646-08c1-ffe0-e0ca481f36e9.png)


### ビュー

slim だと本当にシンプルに書くことができます。

```slim:app/views/users/index.html.slim
// 検索項目
button#search_btn type="button"
  |詳細検索
button#search_exec_btn type="button"
  |検索
button#search_clear_btn type="button"
  |クリア
table
  tr
    td = label :user, :id
    td = text_field_tag :id, '', id: "search_id"
    td = label :user, :username
    td = text_field_tag :username, '', id: "search_username"
    td = label :user, :name
    td = text_field_tag :name, '', id: "search_name"

// DataTables のテーブル表示
table#incidents
```

### ルーティング
jsonファイルを dataTables 側に渡すときに使用するルーティング。
のちほど、Javascriptにて使用する。

```Ruby:config/routes.rb
resources :users do
  collection do
    post 'ajax_data'
  end
end
```

### コントローラー

UserDatatableのインスタンスをjsonファイルとして返す。

```Ruby:app/controllers/users_controller.rb
def ajax_data
  respond_to do |format|
    format.html
    format.json {render json: UsersDatatable.new(params) }
  end
end
```

UserDatatableはパラメーターを受け取って、SQL を実行し JSON 形式に変換するクラス。
テーブルに表示させたい項目や検索結果などをこちらのファイルにて割り出している。

```Ruby:app/datatables/users_datatable.rb
class UsersDatatable
  attr_accessor :params

  def initialize(params)
    @params = params
  end

  # jQuery DataTables へ渡すためのハッシュを作る
  # 補足：コントローラーの render json: で指定したオブジェクトに対して as_json が呼び出される
  def as_json(options = {})
    {
        recordsTotal: User.count, # 取得件数
        recordsFiltered: users.total_count, # フィルター前の全件数
        data: users, # 表データ
    }
  end

  def users
    @users ||= fetch_users
  end

  # 検索条件や件数を指定してデータを取得
  def fetch_users
    User.where(search_sql).page(page).per(per)
  end

  # カラム情報を配列にする
  def columns
    return [] if params["columns"].blank?
    params["columns"].to_unsafe_h.map{|_,v| v["data"]}
  end

  # 検索ワードが指定されたとき
  def search_sql
    search_sql = []
    for column, search_params in params["columns"] do
      search_sql.push("#{search_params["data"]} like '%#{search_params["search"]["value"]}%'") if search_params["search"]["value"].present?
    end
    search_sql.join(" and ")
  end

  # ソート順
  def order_sql
    return "" if params["order"]["0"].blank?
    order_data = params["order"]["0"]
    order_column = columns[order_data["column"].to_i]
    # "id desc" のようにSQLの一部を作る
    "#{order_column} #{order_data["dir"]}"
  end

  # kaminari 向け、ページ数
  def page
    params["start"].to_i / per + 1
  end

  # kaminari 向け、1ページで取得する件数
  def per
    params["length"].to_i > 0 ? params["length"].to_i : 10
  end
end

```


### Javascript

```coffeescript:app/assets/javascripts/users.coffee
$ ->
  # DataTables オブジェクト作成（ここではhtmlのid=users）
  user_table = new DataTables($('#users'))
  # ajax にて、 json ファイルを読み込み。作成したルーティングを引数に入れる。
  user_table.setAjax("/users/ajax_data")
  # user_table へカラムを追加する
  user_table.setColumns([
    { data: 'id',         title: 'ユーザID', width: '5%' },
    { data: 'username',   title: 'ユーザ名', width: '25%' },
    { data: 'name',       title: '名前',    width: '30%' },
    { data: 'created_at', title: '登録日時', width: '20%' },
    { data: 'updated_at', title: '更新日時', width: '20%' },
  ])
  user_table.setOption(false, true, true)
  # 表示順番を0番目のカラムを昇順で表示（ここではidを昇順で表示）
  user_table.setOrders([[0,'asc']])
  # #users へ user_table を描写する。
  user_table.drawTable()

  # 各行をクリックすると詳細画面へ遷移するように設定
  $('#users tbody').on 'click', 'tr', ->
    data = $('#users').dataTable().fnGetData(this);
    document.location = "/users/#{data.id}"

  # 詳細検索ボタンを押すと、検索ボタンおよびカラムが表示されるように設定
  $('#search_btn').on 'click', ->
    $('.search-body').slideToggle(200)
    $('#search_exec_btn').slideToggle(200)
    $('#search_clear_btn').slideToggle(200)

  # 検索ボタンを押すと、表示されているテーブルのカラムが検索条件に合致したものだけを表示するように設定。
  $('#search_exec_btn').on 'click', ->
    # dataTable の API を利用する。
    user_table_api = $('#users').dataTable().api()
    # 各検索カラムの値を読み込み
    search_id =       $('#search_id').val()
    search_username = $('#search_username').val()
    search_name =     $('#search_name').val()
    # 検索結果をテーブルに描画する
    user_table_api.columns(0).search(search_id).draw()
    user_table_api.columns(1).search(search_username).draw()
    user_table_api.columns(2).search(search_name).draw()

  $('#search_clear_btn').on 'click', ->
    # 検索欄 入力内容 初期化
    tmp_array = ["id", "username", "name"]
    tmp_array.forEach (key) ->
      $("#search_#{key}").val('').trigger('change')
```


# まとめ
少し複雑な実装でしたので、Keynoteで作った全体像を用いての説明でした。
いかがでしたでしょうか。少しでも理解の助けになれればなと思います。

また、DataTables は高機能なライブラリですので、他にもできることを紹介していきたいと思います。


# 参考
* DataTables（公式ページ）
    * https://datatables.net/
* GitHub（rweng/jquery-datatables-rails）
    * https://github.com/rweng/jquery-datatables-rails
* Rails | Ajax で動的な検索・ソート機能付きページネーションを実装する（jQuery DataTablesプラグイン連動）
    * http://jetglass.hatenablog.jp/entry/2015/05/27/172831
* DataTablesの使い方
    * https://qiita.com/nissuk/items/7ac59af5de427c0585c5
* DataTablesでページが切り替わっても、ソートやページャや検索結果を保持しておくようにしたい。
    * https://tech-tokyobay.manju.tokyo/archives/889
* javascript でテキストをクリップボードにコピーする２つの方法
    * https://yaruzou.net/category/web-site/java-script

---
### 会社紹介

**株式会社 Mosaica**  
最先端テクノロジーで社会課題を解決し、持続可能な未来を創造する IT カンパニー。
AI ソリューション、クラウド統合、DX 推進、経営コンサルティングなど包括的なサービスでビジネス変革を支援しています。  

詳しくは [公式サイト](https://mosaica.co.jp/) までお気軽にご相談ください。
公式サイト： https://mosaica.co.jp/