[Rails+Vue.js]に係るCRUD入門〜Part３: Vue Router設定編〜

Rails+Vue.js+Webpackerによる「Create, Read, Update, Destroy」処理のチュートリアルを記述する。
なお，前提知識として，Railsチュートリアル終了程度を想定する。
# ＜概要＞
## ■ 本記事の内容
- 前回作成したViewをVueのコンポーネントへ移行する。
- SPAに必要なVue Routerを設定する。
- ルーティング用のヘッダーを作成する。
- 今回のコードは，[GitHubのコミット履歴](https://github.com/masayuki-0319/rails-vue-bookshelf/commits/03_vue_router)で確認可能である。
    - [前回分の修正]から[不要データの削除]まで
- 本記事の参考URL
    - [Vue Router公式ドキュメント(日本語版)](https://router.vuejs.org/ja/)

## ■ 記事共通
- 目次
    - [ソースコード(GitHubへリンク)](https://github.com/masayuki-0319/rails-vue-bookshelf)
    - [〜Part１: 環境構築編](https://qiita.com/Moo_Moo_Farm/items/afacfe4349af6a106253)
    - [〜Part２: Read編](https://qiita.com/Moo_Moo_Farm/items/fdf133d4c2bcf6d207b6)
    - [▲ Part３: Vue Router設定編](https://qiita.com/Moo_Moo_Farm/items/075e970287cb1aaf52c1)：本記事
    - [〜Part４: Create, Update, Delete編](https://qiita.com/Moo_Moo_Farm/items/8cf46e228d59417550b1)
    - [〜Part５: Vuex設定編](https://qiita.com/Moo_Moo_Farm/items/fbc2e8c8f971dc5fe4bb)
    - [〜Part６: ユーザー登録&ログイン編](https://qiita.com/Moo_Moo_Farm/items/65aaa2ac7f8cef50fca6)
   - [〜Part Last: Docker化編](https://qiita.com/Moo_Moo_Farm/items/5975cd9d2d48ee35d6d6)

- 実装機能
    - お気に入りの本に係る登録，参照，編集，削除処理
    - 非同期通信（Ajax）による[Rails+Vue.js]のCRUD処理
    - SinglePageApplication（SPA）
    - ユーザー登録機能（JWTによるトークン認証）

- 開発環境
    - MacOS Mojave
    - Ruby(2.5.1)
    - Ruby on Rails(5.2.1)
    - Vue.js(2.6.10)
    - Yarn(1.17.0)
    - Webpack(4.39.2)

- 学習情報URL
    - [Vue.js公式](https://jp.vuejs.org/)
    - [Vue Mastery(The Vue Instance)](https://www.vuemastery.com/)

# ＜本文＞
## ■ 前回分の修正
前回のビューはERBで実装したが，今後はSPAによる実装を進めるため，ERBによる実装を削除し，ERBによるViewをVue.jsに置き換える作業を実施する。

**○１：[App.vue]にビューの情報を転記**

```app/javascript/packs/App.vue
<template>
  <div class="container">
    <h1 class="#f3e5f5 purple lighten-5 center">[Rails+Vue.js]~Bookshelf~</h1>
    <div class="row #e3f2fd blue lighten-5">
      <div class="col s4 m6" v-for="book in books">
        <div class="card btn">
          <span class="card-title" v-on:click="setBookInfo(book.id)">
            {{ book.title }}
          </span>
        </div>
      </div>
    </div>
    <div class="row" v-show="bookInfoBool">
      <div class="col s12 m12">
        <div class="card blue-grey darken-1">
          <div class="card-content white-text">
            <span class="card-title">
              【{{ bookInfo.title }}】
            </span>
            <div class="detail">
              ・著者：{{ bookInfo.author }}
            </div>
            <div class="detail">
              ・出版社：{{ bookInfo.publisher }}
            </div>
            <div class="detail">
              ・ジャンル：{{ bookInfo.genre }}
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import Vue from 'vue'
  import axios from 'axios'

  export default {
    name: 'BookHome',
    data: function() {
      return {
        bookInfo: {},
        bookInfoBool: false,
        books: [],
      }
    },
    mounted: function() {
      this.fetchBooks();
    },
    methods: {
      fetchBooks() {
        axios.get('/api/books').then((res) => {
          for(var i = 0; i < res.data.books.length; i++) {
            this.books.push(res.data.books[i]);
          }
        }, (error) => {
          console.log(error);
        });
      },
      setBookInfo(id){
        axios.get(`api/books/${id}.json`).then(res => {
          this.bookInfo = res.data;
          this.bookInfoBool = true;
        });
      }
    }
  }
</script>

<style scoped></style>
```

- Rubyのコードも含まれているため，該当部分を次の事項のとおり修正
- [v-for="book in books"]
    - [books]に格納された各情報について，[book]にそれぞれ格納し，定義した要素内で展開する。
    - Rubyだと[books.each { |book| book.title }]と同値である。
- [mounted: function(){}]
    - Vueインスタンスのマウント直後に実行される。
- [fetchBooks(){}]
    1. [axios.get('/api/books')]により，Railsから情報を受け取り，
    2. [for(var i = 0; i < res.data.books.length; i++) { this.books.push(res.data.books[i]) }]で，受け取った情報をbooksに順次格納する。


- 参考URL
    - [公式：v-for](https://jp.vuejs.org/v2/guide/list.html#v-for-%E3%81%A7%E9%85%8D%E5%88%97%E3%81%AB%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0%E3%81%99%E3%82%8B)
    - [公式：mounted](https://jp.vuejs.org/v2/api/#mounted)

**○２：APIの作成**

```app/controllers/api/books_controller.rb
class Api::BooksController < ApplicationController
  def index
    @books = Book.all
    render 'index', formats: 'json', handlers: 'jbuilder'
  end
...
end
```

```app/views/api/books/index.json.jbuilder
json.set! :books do
  json.array! @books do |book|
    json.extract! book, :id, :title
  end
end
```

**○３：ルーティングの修正**

```config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'

  namespace :api do
    resources :books, only: [:index, :show]
  end
end
```

- ERBのルーティングを削除

**○４：動作確認**

- ここまでで，前回と同じ動作になると成功です。

## ■ Vue Routerの設定

**○１：Vue Routerを導入**
 Vue Routerにより，登録されたパスとコンポーネントで，画面内を差し替えること可能にするSPAの要となるライブラリである。

```.bash
$ yarn add vue-router
```

**○２：[App.vue]を修正**

```app/javascript/packs/App.vue
<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
  export default {
    name: 'App',
  }
</script>

<style scoped></style>
```

- [router-view]：router.jsで登録されたコンポーネントが表示される場所
- [name: 'App']：VueDevtoolsにおけるコンポーネントの識別名称
- 参考URL
    - [公式：router-view](https://router.vuejs.org/ja/api/#router-view)

**○３：[router.js]を作成**

```app/javascript/router/router.js
import Vue          from 'vue'
import VueRouter    from 'vue-router'
import BookHome     from '../pages/BookHome.vue'

Vue.use(VueRouter)

const routes = [
  { path: '/',  name: 'BookHome', component: BookHome },
];

export default new VueRouter({ routes });
```

**○４：[application.js]を修正**

```app/javascript/packs/application.js
import Vue    from 'vue'
import App    from './App.vue'
import Router from '../router/router.js'

const app = new Vue({
   el: '#app',
   router: Router,
   render: h => h(App)
})
```
**○５：[BookHome.vue]を作成**

```app/javascript/pages/BookHome.vue
<template>
  <div class="container">
    <h1 class="#f3e5f5 purple lighten-5 center">[Rails+Vue.js]~Bookshelf~</h1>
    <div class="row #e3f2fd blue lighten-5">
      <div class="col s4 m6" v-for="book in books">
        <div class="card btn">
          <span class="card-title" v-on:click="setBookInfo(book.id)">
            {{ book.title }}
          </span>
        </div>
      </div>
    </div>
    <div class="row" v-show="bookInfoBool">
      <div class="col s12 m12">
        <div class="card blue-grey darken-1">
          <div class="card-content white-text">
            <span class="card-title">
              【{{ bookInfo.title }}】
            </span>
            <div class="detail">
              ・著者：{{ bookInfo.author }}
            </div>
            <div class="detail">
              ・出版社：{{ bookInfo.publisher }}
            </div>
            <div class="detail">
              ・ジャンル：{{ bookInfo.genre }}
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import axios from 'axios'

  export default {
    name: 'BookHome',
    data: function() {
      return {
        bookInfo: {},
        bookInfoBool: false,
        books: [],
      }
    },
    mounted: function() {
      this.fetchBooks();
    },
    methods: {
      fetchBooks() {
        axios.get('/api/books').then((res) => {
          for(var i = 0; i < res.data.books.length; i++) {
            this.books.push(res.data.books[i]);
          }
        }, (error) => {
          console.log(error);
        });
      },
      setBookInfo(id){
        axios.get(`api/books/${id}.json`).then(res => {
          this.bookInfo = res.data;
          this.bookInfoBool = true;
        });
      }
    }
  }
</script>

<style scoped></style>
```
- 修正前の[App.vue]をそのまま転記
- [router-view]に差し込まれるコンポーネント

## ■ Headerの設定
　次の記事で本の登録機能を作成するため，ここでルーティング用のヘッダーを作成しておく。

**○１：[components]フォルダ及び[Header.vue]を作成**

```app/javascript/components/Header.vue
<template>
   <div>
     <nav>
       <div class="nav-wrapper">
         <router-link to="/" class="brand-logo">Bookshelf</router-link>
         <ul id="nav-mobile" class="right">
           <li><router-link to="">本の登録</router-link></li>
         </ul>
       </div>
     </nav>
   </div>
</template>

<script></script>
<style scoped></style>
```

**○２：[App.vue]を修正**

```app/javascript/packs/App.vue
<template>
  <div id="app">
    <Header/>
    <router-view/>
  </div>
</template>

<script>
  import Header from '../components/Header.vue'
  export default {
    name: 'App',
    components: {
      Header
    },
  }
</script>

<style scoped></style>
```

## ■ 不要データの削除
- [app/javascript/packs/book.js]：削除
- [app/views/books]：削除
- [app/controllers/books_controller.rb]：削除

**〜Part３：Vue Router設定編終了〜**
**[〜Part４：Create, Update, Delete編へ進む〜](https://qiita.com/Moo_Moo_Farm/items/8cf46e228d59417550b1)**