[Rails+Vue.js]に係るCRUD入門〜Part２: Read編〜

Rails+Vue.js+Webpackerによる「Create, Read, Update, Destroy」処理のチュートリアルを記述する。
なお，前提知識として，Railsチュートリアル終了程度を想定する。
# ＜概要＞
## ■ 本記事の内容
- ViewをERB，API通信をVue.jsにより，本の詳細情報に係る表示機能を実装する。
- 今回のコードは，[GitHubのコミット履歴](https://github.com/masayuki-0319/rails-vue-bookshelf/commits/02_Read)で確認可能である。
    - [Bookモデル及びデータベースを生成]から[APIを使用したRead機能を実装]まで

## ■ 記事共通
- 目次
    - [ソースコード(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/)

# ＜本文＞
## ■ Bookモデル及びデータベースを生成

```.bash
$ rails g model Book title:string author:string publisher:string genre:string
```

```db/migrate/[time_stump]_create_books.rb
class CreateBooks < ActiveRecord::Migration[5.2]
  def change
    create_table :books do |t|
      t.string :title, null: false
      t.string :author, null: false
      t.string :publisher, null: false
      t.string :genre, null: false

      t.timestamps
    end
  end
end
```

```app/models/book.rb
class Book < ApplicationRecord
  validates :title, presence: true, length: { maximum: 100 }
  validates :author, presence: true, length: { maximum: 100 }
  validates :publisher, presence: true, length: { maximum: 50 }
  validates :genre, presence: true, length: { maximum: 50 }
end
```

```.bash
$ rails db:migrate
```
- 本の登録情報として，タイトル，著者，出版社，ジャンルを登録する。
- テーブルにバリデーションを設定しておく。
- 参考URL
    - [Railsバリデーションまとめ](https://qiita.com/shunhikita/items/772b81a1cc066e67930e)


## ■ 本のサンプルデータを作成

**○１： Gem[faker]をGemfileに追加**

```sh:Gemfile
...
gem 'materialize-sass'
gem 'material_icons'
gem 'faker'
...
```

```.bash
$ bundle install
```

**○２： サンプルデータを登録**

```config/seeds.rb
20.times do
  Book.create!(
    title: Faker::Book.title,
    author: Faker::Book.author,
    publisher: Faker::Book.publisher,
    genre: Faker::Book.genre
  )
end
```

```.bash
$ rails db:migrate
```

- ※追記：[seeds.rbでデータ投入する際、createよりもcreate!を使うべき？](http://yoshi10.hatenablog.com/entry/2016/06/24/081018)
    - [rails db:seed]で登録失敗時に例外処理が戻り，エラーの検知が容易になる。


## ■ Booksコントローラを生成し，本情報を表示

```.bash
$ rails g controller Books index
```

```config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'
  resources :books, only: [:index]
end
```

```app/controllers/books_controller.rb
class BooksController < ApplicationController
  def index
    @books = Book.all
  end
end
```

```html:app/views/books/index.html.erb
<div class="container">
  <h1 class="#f3e5f5 purple lighten-5 center">[Rails+Vue.js]~Bookshelf~</h1>
  <div class="row #e3f2fd blue lighten-5">
    <% @books.each do |book| %>
      <div class="col s4 m6">
        <div class="card">
          <span class="card-title">
            <%= book.title %>
          </span>
        </div>
      </div>
    <% end %>
  </div>
</div>
```

- この時点で次の画像のように表示されると成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/355926/37d60ac3-0465-30cb-a781-a58470ce3bc5.png)

## ■ APIの生成

**○１： APIの送信元を作成**

```.bash
$ rails g controller api::books
```

```config/routes.rb
Rails.application.routes.draw do
...
  namespace :api do
    resources :books, only: [:show]
  end
end
```

```app/controllers/api/books_controller.rb
class Api::BooksController < ApplicationController
  def show
    @book = Book.find(params[:id])
    render 'show', formats: 'json', handlers: 'jbuilder'
  end
end
```

```app/views/api/books/show.json.jbuilder
json.id        @book.id
json.title     @book.title
json.author    @book.author
json.publisher @book.publisher
json.genre     @book.genre

# 次のコードと同等です。
# json.extract! @book, :id, :title, :author, :publisher, :genre
```

**○２： 実装を確認**

```
{"id":1,"title":"ヤマノススメ","author":"しろ","publisher":"アース・スター エンターテイメント","genre":"アウトドア"}
```

- http://localhost:5000/api/books/1.json で上記のように表示されたら成功です。


## ■ API通信を実現するライブラリを導入

```.bash
$ yarn add axios
```

- 参考URL
    - [公式：axios を利用したAPIの使用](https://jp.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html)

## ■ APIを使用したRead機能を実装

**○１： Vue.js側のエントリーポイントを作成**

```app/javascript/packs/book.js
import Vue from 'vue/dist/vue.esm';
import axios from 'axios';

new Vue({
  el: '.js-booksIndex',
  data: {
    bookInfo: {},
    bookInfoBool: false
  },
  methods: {
    setBookInfo(id){
      axios.get(`api/books/${id}.json`).then(res => {
        this.bookInfo = res.data;
        this.bookInfoBool = true;
      });
    }
  }
});
```

- [el: '.js-booksIndex']：book.jsの対象範囲
- [data.bookInfo]：表示する本の詳細情報
- [data.bookInfoBool]：情報の表示有無を判定（デフォルトで非表示設定）
- [setBookInfo(id)：詳細情報を表示するメソッド
    1. [axios.get(`api/books/${id}.json`)]により，APIを受け取り，
    2. [this.bookInfo = res.data;]により，本情報をdataに格納し，
    3. [this.bookInfoBool = true;]により，非表示を表示に変更して，詳細情報が表示される。

**○２： Rails側のViewを作成**

```html:app/views/books/index.html.erb
+ <div class="container js-booksIndex">
  <h1 class="#f3e5f5 purple lighten-5 center">[Rails+Vue.js]~Bookshelf~</h1>
  <div class="row #e3f2fd blue lighten-5">
    <% @books.each do |book| %>
      <div class="col s4 m6">
+         <div class="card btn">
+           <span class="card-title" v-on:click="setBookInfo(<%= book.id %>)">
            <%= book.title %>
          </span>
        </div>
      </div>
    <% end %>
  </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>
+ <%= javascript_pack_tag 'book' %>
```
- [v-on:click="setBookInfo(<%= book.id %>)]
    - [v-on:click="~"]により，クリックした際の動作を指定する。
    - この場合，[book.js]の[setBookInfo(id)]が動作して，[data.bookInfo]にクリックした本の情報が格納される。
    - なお，[v-on:click]は，[@click]で省略可能である。
- [v-show="bookInfoBool"]
    - [v-show="~"]により，中身がTrueの場合，要素が表示される。
    - この場合，[book.jsの[data.bookInfoBool]が参照され，表示有無が判定される。
- 参考URL
    - [公式:v-on](https://jp.vuejs.org/v2/api/#v-on)
    - [公式：v-show](https://jp.vuejs.org/v2/guide/conditional.html#v-show)

**○３： 実装を確認**
![02_Read：動作確認.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/355926/1c95efbe-493c-9fdf-5d67-42be9edf41e8.gif)

- 上記のように動作したら成功である。
- なお，現在のビューは，ERBを使用しているが，SPAによる実装を目指すため，次の記事で全てVueのコンポーネントに置き換える。

**〜Part２：Read編終了〜**
**[〜Part３：Vue Router設定編へ進む〜](https://qiita.com/Moo_Moo_Farm/items/075e970287cb1aaf52c1)**