【Rails】検索結果でCSV出力する方法【Ransack】

# 問題

CSV出力機能を実装した際に、そのページに検索機能（ransack gem）があることに気づいた。
検索結果をCSVファイルに反映する方法をいろいろ調べたのですが、実装するのにけっこう紆余曲折してしまったので記録として残しておきます。

# 前提

## バージョン

Ruby 2.5
Rails 5.1
Ransack 2.1.1

## 実装前のソースコード

```ruby:controllers/items_controller.rb
class ItemsController < ApplicationController

  def index
    @q = Item.ransack(params[:q])
    @items = @q.result
    respond_to do |format|
      format.html
      format.csv do
        send_data @items.export, filename: "items_export.csv"
      end
    end
  end

end
```
コントローラーにメソッドを書くとごちゃごちゃしてしまうので、実装はモデルに書きます。


```ruby:models/item.rb
  def self.export
    CSV.generate(encoding: Encoding::SJIS, row_sep: "\r\n", force_quotes: true) do |csv|
      csv << %w(
        ID 名前 所有者名
      )
      all.each do |item|
        id = item.id
        name = item.name
        user = user.name

        csv << [
          id, name, user
        ]
      end
    end
  end
```
CSV出力の設定に関してはこの記事では触れないので、他の方の記事なども参考にしてください。


```erb:views/items/index.html.erb
<div>
  <h4>アイテム一覧</h4>

  <%= search_form_for(@q, url: items_path) do |f| %>
    <div>
      <%= link_to 'データ出力', purchase_orders_path(format: :csv),
          class: 'uk-button uk-button-secondary uk-button-small create-button' %>
    </div>

    <div>
      <div class="uk-grid-small uk-grid">
        <%= f.label :id_cont, 'ID', class: 'uk-form-label search-label' %>
        <div class="uk-form-controls">
          <%= f.search_field :id_cont, class: 'uk-input uk-form-small' %>
        </div>
        
        <%= f.label :name_cont, '名前', class: 'uk-form-label search-label' %>
        <div class="uk-form-controls">
          <%= f.search_field :name_cont, class: 'uk-input uk-form-small uk-form-width-small' %>
        </div>
        
        <%= f.label :user_cont, '所有者名', class: 'uk-form-label search-label' %>
        <div class="uk-form-controls">
          <%= f.search_field :usere_cont, class: 'uk-input uk-form-small uk-form-width-medium' %>
        </div>

        <%= f.submit '検索', class: 'uk-button uk-button-secondary uk-button-small mr15' %>
        <%= link_to 'クリア', url_for, class: 'uk-button uk-button-default uk-button-small' %>
      </div>
    </div>
  <% end %>

  <div>
    <%= will_paginate(@items, previous_label: ' &lt 前へ', next_label: '次へ &gt') %>
  </div>

  <table class="uk-table uk-table-striped table-th uk-table-small">
    <thead>
      <tr>
        <th><%= sort_link(@q, :id, 'ID') %></th>
        <th><%= sort_link(@q, :name, '名前') %></th>
        <th><%= sort_link(@q, :user, '所有者名') %></th>
      </tr>
    </thead>
    <tbody>
      <% @items.each do |item| %>
        <tr>
          <td><%= item.id %></td>
          <td><%= item.name %></td>
          <td><%= item.user%></td>
        </tr>
      <% end %>
    </tbody>
  </table>
</div>
```

①検索前
![キャプチャ1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/473251/dc616d6a-62a8-8f63-aa39-0b9e98afd68f.jpeg)

②検索後
![キャプチャ2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/473251/ee04ca0c-962f-9a49-43c6-6d421a788466.jpeg)

上記のviewで書いたような出力ボタンの場合、検索前①も検索後②も下記のようにcontrollerのindexで取得していた値が全てCSVに出力されます。つまり、検索結果は反映されません。

出力結果

| ID       | 名前   |    所有者名    |
|:-----------------|:------------------|:------------------|
| 1        |  item1  |user1          |
| 2        |  item1  |user2          |
| 3        |  item1  |user2          |
| 4        |  item1  |user2          |
| 5        |  item1  |user2          |


# 解決策

出力ボタンの書き方を変えます。

```erb:views/items/index.html.erb
<div>
  <h4>アイテム一覧</h4>

  <%= search_form_for(@q, url: items_path) do |f| %>
    <div>
      <button type='submit' name='format' value='csv' class='uk-button uk-button-secondary uk-button-small create-button'>
          データ出力
      </button>

    </div>

～～～～～～～～

</div>
```

これで検索後②の結果を反映したCSVが出力できるようになります。

本当はRailsっぽく `f.submit` を使って実装したかったのですが、これを使う場合`format`属性は`search_for_form`に入れる必要があるんですよね。そうすると、検索機能自体が上手くいかなくなってしまうので、止む無くこの書き方にしました。
もっときれいな書き方を知ってる方がいたらぜひ教えてください。

出力結果

| ID       | 名前   |    所有者名    |
|:-----------------|:------------------|:------------------|
| 1        |  item1  |user1          |


ちなみに、実は検索後のページに遷移してなくても、検索項目に値が入っているだけでそちらを反映したデータが出力されるので注意してください。

検索前（検索項目に入力済み）③
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/473251/415fc4c0-a42e-61dd-b1f0-d1c63cf77557.png)

出力結果

| ID       | 名前   |    所有者名    |
|:-----------------|:------------------|:------------------|
| 2        |  item1  |user2          |

# 検索ボタンをdisableにしない方法

出力ボタンを押した後、検索ボタンが押せないようになっています。
これはRailsの機能で、`submit`ボタンが2回クリックされてしまうとHTTPリクエストが重複してしまい、バックエンド側で検出できなくなる可能性があるため、それを防止するためにある機能です。

参考：railsガイドの「3.4 入力を自動で無効にする」
https://railsguides.jp/working_with_javascript_in_rails.html#入力を自動で無効にする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/473251/347e7865-3aa3-e114-4bfa-503f937e9bb0.png)

データ出力した後に検索機能を使う可能性は低いと思いますが、念のため直しておきましょう。
検索ボタンに`data-disable-with`を追記します。

```erb:views/items/index.html.erb
<div>

～～～～～～～～

        <%= f.submit '検索', class: 'uk-button uk-button-secondary uk-button-small mr15', 'data-disable-with' => false %>
        <%= link_to 'クリア', url_for, class: 'uk-button uk-button-default uk-button-small' %>
      </div>
    </div>
  <% end %>

～～～～～～～～

</div>
```

これでCSV出力後も検索ボタンが非活性にならないようになりました。