DockerをつかったVueとRailsの環境にAPIを実装する

この記事は[DockerをつかってVueとRailsの開発環境にユーザー認証機能を実装する](https://qiita.com/harachan/items/24f7fbda7c41a4115bf5)の続編です。

# 一連の記事
1. [DockerをつかってVueとRailsの開発環境をつくる](https://qiita.com/harachan/items/4561d54948fe650cf611)
2. [DockerをつかってVueとRailsの開発環境にユーザー認証機能を実装する](https://qiita.com/harachan/items/24f7fbda7c41a4115bf5)
3. [DockerをつかったVueとRailsの環境にAPIを実装する](https://qiita.com/harachan/items/ad9fce3f4b84f49d583a)この記事！！

# この記事の目指すところ
- 前回まで作ってきた環境に新しいAPIを追加する
    - 今回はユーザーが出会ったことのある人を登録する機能を作ってみます

# サーバー側
## テーブルの作成
今回は前回までに作ったuserテーブルのIDに対し外部キーを設定します。
テーブルの構造はこんな感じ
テーブル名：acquaintance

| カラム名 | オプション | 説明 | 
| ---- | ---- | ---- |
| user_id | not null, foreign_key | UserテーブルのIDを参照する外部キー|
| id | not null | ID |
| name | | 名前|
| nickname | | あだ名|

早速コマンドを実行してテーブルを作成しましょう

```bash:bash
rails g model acquaintance user:references name:string nickname:string
rails db:migrate
```

## コントローラーの作成
テーブルができたので、コントローラーを作っていきましょう

```bash:bash
rails g controller acquaintances
```

コントローラーが生成されたので、動作を書いていきましょう。
今回書く動作は以下のとおりです。

- create
    - 知り合い情報を登録するAPI
- search 
    - 知り合いを探すAPI

```ruby:server/app/controllers/acquaintances_controller.rb
class AcquaintancesController < ApplicationController

  def create
    @acquaintance = Acquaintance.new(user_id: params[:user_id], name: params[:name], nickname: params[:nickname])

    if @acquaintance.save
      render json: @acquaintance
    else
      render json: { errors: @acquaintance.errors.full_messages }, status: 400
    end
  end

  def search
    @acquaintance = Acquaintance.where(user_id: params[:user_id]).where(name: params[:name]).or(Acquaintance.where(user_id: params[:user_id]).where(nickname: params[:nickname]))
    if @acquaintance != []
      render json: @acquaintance
    else
      render json: { errors: ['ユーザーが見つかりません'] }, status: 404
    end
  end
end

```

## ルーティング情報の設定変更
コントローラーで動作をかいたので、これを使えるようにルーティング情報を記入しましょう。

```ruby:server/config/routers.rb
Rails.application.routes.draw do
  resources :users, only: [ :create ] do
    collection do
      post 'sign_in'
      get 'me'
    end
  end

  post 'login/login'

  get 'acquaintance/search' => 'acquaintances#search'
  resources :acquaintances do
    collection do
      post 'create'
      get 'index'
    end
  end
end

```

# フロント側
## 検索画面を作る
Vueでログイン後の検索画面を作ります。

```vuejs:front/src/views/search.vue
<template>
  <div class="search">
    <table>
      <tr>
        <td>
          <el-input
            placeholder="名前か、あだ名で検索"
            v-model="searchtext"
            clearable>
          </el-input>
        </td>
        <td>
          <el-button type="primary" icon="el-icon-search" @click="send">検索</el-button>
        </td>
        <td>
          <router-link to="/new_acquaintance">
            <el-button type="warning"><i class="el-icon-plus">登録</i></el-button>
          </router-link>
        </td>
      </tr>
    </table>
    <el-table
       :data="acqdata"
       stripe
       @row-click="user"
       style="width: 100%">
       <el-table-column
         prop="name"
         label="名前"
         width="180">
       </el-table-column>
       <el-table-column
         prop="nickname"
         label="あだ名"
         width="180">
       </el-table-column>
     </el-table>
  </div>
</template>

<script>

export default {
  name: 'search',
  data () {
    return {
      searchtext: '',
      acqdata: {
        name: '',
        nickname: ''
      }
    }
  },
  components: {
  },
  mounted () {
    this.axios.get('http://0.0.0.0:3000/acquaintances/', {
      params: {
        user_id: this.$store.state.userinfo.id
      },
      headers: {
        'Content-Type': 'application/json',
        Authorization: 'Bearer ' + this.$store.state.userinfo.token
      }
    })
      .then((response) => {
        this.acqdata = response.data
      })
      .catch((e) => {
        console.log(e)
        this.$message.error('情報取得に失敗しました！' + e)
      })
  },
  methods: {
    user (val) {
      this.$store.dispatch('doRegistrationAcqUser', val)
      this.$router.push('/acquaintanceInfo')
    },
    send () {
      this.axios.get('http://0.0.0.0:3000/acquaintance/search', {
        params: {
          user_id: this.$store.state.userinfo.id,
          name: this.searchtext,
          nickname: this.searchtext
        },
        headers: {
          'Content-Type': 'application/json',
          Authorization: 'Bearer ' + this.$store.state.userinfo.token
        }
      })
        .then((response) => {
          this.acqdata = response.data
        })
        .catch((e) => {
          console.log(e)
          this.$confirm('新しく登録しますか？', 'ユーザーが見つかりませんでした。', {
            confirmButtonText: 'OK',
            cancelButtonText: 'Cancel',
            type: 'warning'
          }).then(() => {
            this.$router.push('new_acquaintance')
          }).catch(() => {
          })
        })
    }
  }
}
</script>
<style scoped>
  .search{
    width: 70%;
    margin: 3vh auto;
  }
  table{
    width: 100%;
  }
</style>
```

前回からStateの情報も少し変えているので、その修正点も変更していきます。

```javascript:front/src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    userinfo: ''
  },
  mutations: {
    RegistrationToken (state, payload) {
      state.userinfo = payload.userinfo
    },
  },
  actions: {
    doRegistrationToken ({ commit }, userinfo) {
      commit('RegistrationToken', { userinfo })
    }
  },
  modules: {
  }
})
```
## 知り合いの登録画面の作成
知り合いの登録画面を作成します。

```vue:front/src/views/new_acquaintance.vue
<template>
  <div class="new_acquaintance">
    <h1>知り合い登録</h1>
    <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="120px" class="demo-ruleForm">
      <el-form-item label="氏名" prop="name">
        <el-input v-model="ruleForm.name"></el-input>
      </el-form-item>
      <el-form-item label="あだ名" prop="nickname">
        <el-input v-model="ruleForm.nickname"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="submitForm('ruleForm')">Create</el-button>
        <el-button @click="resetForm('ruleForm')">Reset</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>

export default {
  name: 'new_acquaintance',
  data () {
    return {
      ruleForm: {
        user_id: this.$store.state.userinfo.id,
        name: '',
        nickname: ''
      },
      rules: {
        name: [
          { required: true, message: '名前を入力してください', trigger: 'blur' },
          { min: 1, max: 20, message: '1文字から20文字で入力してください', trigger: 'blur' }
        ]
      }
    }
  },
  components: {
  },
  methods: {
    submitForm (formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          this.axios.post('http://0.0.0.0:3000/acquaintances/', {
            user_id: this.ruleForm.user_id,
            name: this.ruleForm.name,
            nickname: this.ruleForm.nickname
          },
          {
            headers: {
              'Content-Type': 'application/json',
              Authorization: 'Bearer ' + this.$store.state.userinfo.token
            }
          })
            .then((response) => {
              this.$notify({
                title: 'Success',
                message: '登録にに成功しました！',
                type: 'success'
              })
            })
            .catch((e) => {
              this.$message.error('登録に失敗しました！' + e)
            })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },
    resetForm (formName) {
      this.$refs[formName].resetFields()
    }
  }
}
</script>
<style scoped>
</style>
```

## ルーティング情報の修正
ここまで作ったviewのルーティング情報を追記していきます。
また、ログインしていない場合、Homeにリダイレクトされる処理も書き加えています。

```js:front/src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Store from '@/store/index.js'
import Home from '../views/Home.vue'
import Login from '../views/login.vue'
import Signup from '../views/signup.vue'
import Search from '../views/search.vue'
import Newacquaintance from '../views/new_acquaintance.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    meta: {
      isPublic: true
    },
    component: Home
  },
  {
    path: '/login',
    name: 'login',
    meta: {
      isPublic: true
    },
    component: Login
  },
  {
    path: '/signup',
    name: 'signup',
    meta: {
      isPublic: true
    },
    component: Signup
  },
  {
    path: '/search',
    name: 'search',
    component: Search
  },
  {
    path: '/new_acquaintance',
    name: 'new_acquaintance',
    component: Newacquaintance
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})
router.beforeEach((to, from, next) => {
  if (to.matched.some(page => page.meta.isPublic) || Store.state.userinfo.token) {
    next()
  } else {
    next('/')
  }
})
export default router

```

# 確認
ログイン後のSearch.vueの画面はこんな感じになると思います。
あだ名か、名前を入力するとどちらかが一致するものが帰ってくる感じです。
![FireShot Capture 009 - mybutler - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323051/4091330d-db01-8f81-dad9-8a53b727ea05.png)
新規追加画面の様子です。
![FireShot Capture 010 - mybutler - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323051/fb20a0e7-5f99-b425-bd84-5c9f9465a188.png)

# おわりに
今回はログイン後にAPIをつかいデータの登録と取得の操作をしました。
次回は、これに紐づけたテーブルをもう一つ作り知り合いとの経歴を保存できるようにしていきます。

# 参考にさせていただいた記事

[【Rails5】外部キーの設定メモ](https://qiita.com/Ryoga_aoym/items/763982c750ba88c6b3c6)