Rails×Vue.js axiosとransackで検索フォームを作る

## 概要
+ Rails5.1 + webpacker + vue.jsを使用したアプリケーションを作成します。
+ 本記事は環境構築には触れず、ajaxを使用した検索フォームに焦点を絞ります。
+ 作成したコードは[こちら](https://github.com/schiughi/docker-rails-vue/tree/c5663af2f817f2b28951bdbc1d1adcf7e4014c26)

## サンプルアプリ
+ シンプルな社員管理アプリケーションを作成します。
+ 使用するモデルは社員(staff)のみ。
+ 社員モデルは名前(name)、年齢(age)、入社日(joined_on)の情報を持ちます。

```
$ rails g scaffold staff name:string age:integer joined_on:date
$ rake db:migrate
```

## ルーティング
+ ajax用のエンドポイント `api/staffs/search`を作成します。

```routes.rb
  root 'staffs#index'
  scope :api, { format: 'json' } do
    resources :staffs do
      get :search , on: :collection
    end
  end
  resources :staffs
```

## Ajaxでリクエストパラメータを送る

axiosとqsをインストールしておきます。

```
$ yarn add axios
$ yarn add qs
```

```application.js
import Vue from 'vue/dist/vue.esm'
import axios from 'axios'
import Qs from 'qs'

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    el: '#el-index',
    data: function(){ 
      return {
        staffs: [],
        query: {
          name_cont: null,
          age_gteq: 0,
          age_lteq: 100,
          joined_on_gteq: null,
          joined_on_lteq: null
        }
      }
    },
    created: function(){
      this.search()
    },
    methods:{
      search: function(){
        axios.get('/api/staffs/search',{
          params:{
            q: this.query
          },
          paramsSerializer: function(params) {
            return Qs.stringify(params, {arrayFormat: 'brackets'})
          }
        })
        .then((response) => {
          console.log(response);
          this.staffs = response.data.staffs;
        })
        .catch((error) => {
          console.log(error);
        })
      }
    }
  })
})
```
+ `this.query`がそのままransackの検索クエリーになります。
+ `Qs.stringify`でクエリーを連想配列に変換します。
+ 返却されたresponseの配列をdataに格納することで、画面に自動反映されます。  

+ サーバサイドでは特別なことはせず、そのままransackを使うだけです。
 
```Diff:Gemfile
+ gem 'ransack'
```

```staffs_controller.rb
  # GET /api/staffs/searchs
  def search
    @q = Staff.ransack(search_params)
    @staffs = @q.result(distinct: true)
  end
  private
  def search_params
      params.require(:q).permit(:name_cont , :age_gteq , :age_lteq , :joined_on_gteq, :joined_on_lteq)
  end

```

```staffs/search.json.jbuilder
json.staffs @staffs, partial: 'staffs/staff', as: :staff
```
```staffs/_staff.json.jbuilder
json.extract! staff,
              :id,
              :name,
              :age,
              :joined_on,
              :created_at,
              :updated_at
json.url staff_url(staff, format: :json)
```

## フォームを作る

UIにはElement-uiをつかいます。

```
$ yarn add element-ui
```

```Diff:application.js
import Vue from 'vue/dist/vue.esm'
import axios from 'axios'
import Qs from 'qs'
+import ElementUI from 'element-ui'
+import locale from 'element-ui/lib/locale/lang/ja'
+import 'element-ui/lib/theme-default/index.css'
+
+Vue.use(ElementUI, { locale })
+
document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    el: '#el-index',
    data: function(){ 
      return {
        staffs: [],
        query: {
          name_cont: null,
          age_gteq: 0,
          age_lteq: 100,
          joined_on_gteq: null,
          joined_on_lteq: null
        }
      }
    },
    created: function(){
      this.search()
    },
    methods:{
+      notify: function(msg){
+        this.$notify({
+          type: 'error',
+          title: 'Error',
+          message: msg
+        });
+      },
      search: function(){
        axios.get('/api/staffs/search',{
          params:{
            q: this.query
          },
          paramsSerializer: function(params) {
            return Qs.stringify(params, {arrayFormat: 'brackets'})
          }
        })
        .then((response) => {
          console.log(response);
          this.staffs = response.data.staffs;
        })
        .catch((error) => {
          console.log(error);
+          this.notify(error.message);
        })
      }
    }
  })
})
```

+  日本語を適用するには、`element-ui/lib/locale/lang/ja`でimportしたlocaleをセットすれば良いようです。

```index.html.erb
<div id="el-index">
  <h1>staffs</h1>
  <div class="search-form-static">
  <el-form ref="form" :model="query" label-width="120px">
    <el-form-item label="名前">
      <el-input v-model="query.name_cont" placeholder="input name"></el-input>
    </el-form-item>
    <el-form-item label="年齢">
      <el-col :span="11">
        <el-input-number v-model="query.age_gteq" :min="0" :max="150"></el-input-number>
      </el-col>
      <el-col class="line" :span="2">-</el-col>
      <el-col :span="11">
        <el-input-number v-model="query.age_lteq" :min="0" :max="150"></el-input-number>
      </el-col>
    </el-form-item>
    <el-form-item label="入社日">
      <el-col :span="11">
        <el-date-picker
          v-model="query.joined_on_gteq"
          type="date"
          placeholder="日時を選択してください">
        </el-date-picker>
      </el-col>
      <el-col class="line" :span="2">-</el-col>
      <el-col :span="11">
        <el-date-picker
          v-model="query.joined_on_lteq"
          type="date"
          placeholder="日時を選択してください">
        </el-date-picker>
      </el-col>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="search">search</el-button>
    </el-form-item>
  </el-form>
  </div>
  <el-table
    :data="staffs"
    height="400"
    stripe
    style="width: 100%">
    <el-table-column
      prop="name"
      sortable
      label="名前"
      width="180">
    </el-table-column>
    <el-table-column
      prop="age"
      sortable
      label="年齢"
      width="180">
    </el-table-column>
    <el-table-column
      prop="joined_on"
      sortable
      label="入社日">
    </el-table-column>
  </el-table>
</div>
```

+  参考： http://element.eleme.io/#/en-US/component

## テストデータ
+ fakerを使用してテストデータを作成します。

```Diff:Gemfile
+ gem 'faker'
```

```seeds.rb
10.times do
  Staff.create(
    name: Faker::Name.name,
    age: Faker::Number.between(18, 60),
    joined_on: Faker::Date.between(20.days.ago, Date.today)
  )
end
```

## デモ


![docker-rails-vue.gif](https://qiita-image-store.s3.amazonaws.com/0/176594/3924db5d-2939-1198-9973-9d6ac95bde6a.gif)



## 参考
[How to make AJAX calls in Rails 5.1 with or without jQuery](https://learnetto.com/blog/how-to-make-ajax-calls-in-rails-5-1-with-or-without-jquery)
[Vue.jsのコンポーネント詰め合わせ「Element」がスゴかった](https://s8a.jp/vue-js-library-element)