Rails 5.1 + Vue.js で開発を行う - part2 Ajax で GET しよう！


# 概要

- Rails 5.1 + Vue.js + Webpacker を使ってフロント開発を行う
	- 前回の記事では開発環境の構築を行った
	- [Rails 5.1 + Vue.js で開発を行う - part1 環境構築](http://qiita.com/cohki0305/items/582c0f5ed0750e60c951)
- 本記事では、Ajax 通信で、GET して Rails の View にいろいろ表示していきたい。
- 全コードは[こちら](https://github.com/cohki0305/webpacker-and-vue)より確認いただけます


# 作るアプリ

- 図書管理アプリを作ることにする
	- 本を保存したり、情報を参照するようなアプリを作る

## 開発の流れ

実際に開発する前に開発の流れを紹介しておきます。

- Ajax で GET する方法を実装したいので、はじめに seed で DB に本の各種情報を保存しておきます
- その後に、Vue を使って本の情報を取得したいと思います。

# 準備 | DB にデータを保存する

書籍のデータを作成していきたいと思います。

## Book モデルの作成

Book モデルを作成します。
DB には、タイトル、筆者、出版社、ジャンルを保存しようと思います。

```
$ rails g model Book title:string author:string publisher:string genre:string
$ rake db:migrate
```

## ダミーデータの作成


```Gemfile

gem 'faker'

```

```
$ bundle install
```

```ruby:seed.rb

20.times do
  Book.create(
    title: Faker::Book.title,
    author: Faker::Book.author,
    publisher: Faker::Book.publisher,
    genre: Faker::Book.genre
  )
end

```

```
$ rake db:seed
```

## Rails View で表示する

Rails の View で先程の情報を表示します。

```ruby:app/controllers/books_controller.rb

class BooksController < ApplicationController
  def index
    @books = Book.all
  end
end


```

```html:app/views/books/index.html.haml

.container
  .row
    - @books.each do |book|
      .col.s4.m6
        .card
          %span.card-title
            = book.title

```

こんな感じで表示されます
（Materialize csss を使っていると...）

![スクリーンショット 2017-06-25 10.53.33.png](https://qiita-image-store.s3.amazonaws.com/0/100997/87c99d69-27de-9353-f8bf-c2ae4fed7692.png "スクリーンショット 2017-06-25 10.53.33.png")



# Ajax で本の情報を取得する

Ajax で情報を取得する際にどのように取得するのか考える必要がある。

選択肢は

1. ページローディング時にすべての情報を取得する
2. ページローディング後にユーザのアクションがあったときに情報を取得する

> 1. ページローディング時にすべての情報を取得する

の方法は一番楽な手段だが、2 のほうが動作が軽く、ユーザが必要な情報だけを表示できる合理的な手段である。
そして、1 に関しては Rails で十分に表現できる範囲なので、今回はせっかくなので 2 で行いたいと思う。

具体的には、ページローディング時には、Rails 側で本のタイトルだけを表示しておき、 `詳細` ボタンをユーザが押すと、Ajax で本の詳細情報を取得して表示する、みたいなことをやっていきたい。

## API の作成

Ajax で GET しようと思うと、API を作成して、そこにリクエストを投げて JSON を取得することになります。
なので、はじめに API を作成しましょう。


各本の情報を取得するような API を作成していきます。
今回は jbuilder を使って API を作ろうと思います。
正直なところ、この程度のデータであれば jbuilder は不要なのですが複雑になってくると必要なので覚えておくと良かったりします。

**routes**

エンドポイントをわけるために、別のコントローラーを作成していきます。

```diff:routes.rb

Rails.application.routes.draw do
  root to: 'page#home'

  resources :books, only: %i(index)
  
+ namespace :api do
+   resources :books, only: %i(show)
+ end
end

```


**controller**

```ruby: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

```

**view**

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

```

※ `json.extract!` を使うほうがキレイだが、わかりやすくするために上記のようにした。

この状態で、ちゃんと json が返っているか確認しましょう。
`api/books/1.json` にアクセスしてみてください。以下のような表示になったら、json が返ってきている証拠です。

![スクリーンショット 2017-06-25 11.08.05.png](https://qiita-image-store.s3.amazonaws.com/0/100997/c74d877e-ea24-879f-8e77-28d7a09d5ebd.png "スクリーンショット 2017-06-25 11.08.05.png")

もし返ってきていなければ、コンソールにてネットワークを確認してください。
エラー内容が表示されているかもしれません。

以下のような json が返っていれば問題ないです。

![スクリーンショット 2017-06-25 11.12.13.png](https://qiita-image-store.s3.amazonaws.com/0/100997/68ab4590-5b94-7e26-e5e4-39786244ce10.png "スクリーンショット 2017-06-25 11.12.13.png")



## Ajax で GET しよう！

Vue で Ajax 通信する際に便利なライブリーである `axios` をインストールします。

```
$ yarn add axios
```

本のタイトルをクリックしたときに、Ajax によって GET して Vue の data に値を代入することにします。
なので、card-title をクリックしたときに、Ajax で GET を行う `setBookInfo` メソッド（後ほど定義します）を実行します。


**View**

```diff:app/views/books/index.html.haml
-.container
+.container.js-booksIndex
  .row
    - @books.each do |book|
      .col.s4.m6
        .card
-         %span.card-title
+         %span.card-title{'v-on:click': "setBookInfo(#{book.id})"}
            = book.title

+ = javascript_pack_tag 'books/index'
```

**Vue**

el には 先程の View で指定した class を指定します。
methods には先程、説明しました `setBookInfo()` を定義していきます。
タイトルをクリックしたときに、id を取得するので、引数には `id` を入れておきます。

次に、axios を使って、get を行います。
すごく簡単です。get したい URL を以下のように指定するだけです。
あとは、返ってきた `response` を Vue の data に代入してあげます。


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

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

```

## GET したデータを Rails View に表示する

タイトルをクリックしたら表示するようにしようと思います。

```diff
.container.js-booksIndex
  .row
    - @books.each do |book|
      .col.s4.m6
        .card
          %span.card-title{'v-on:click': "setBookInfo(#{book.id})"}
            = book.title

+  .row{'v-show': 'bookInfoBool'}
+    .col.s12.m12
+      .card.blue-grey.darken-1
+        .card-content.white-text
+          %span.card-title
+            {{ bookInfo.title }}
+          .detail
+            {{ bookInfo.author }}
+          .detail
+            {{ bookInfo.publisher }}
+          .detail
+            {{ bookInfo.genre }}

= javascript_pack_tag 'books/index'

```

表示条件をコントロールする `bookInfoBool` を定義します。
Rails の View 側にて、 `{'v-show': 'bookInfoBool'}` というふうにしておくと、 `bookInfoBool == true` の場合のみ表示されるようになります。

```diff:app/javascript/packs/books/index.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;
        });
    }
  }
});
```

Ajax で　Get できれば表示されるので、get が成功したときに `bookInfoBool = true` にします。

以上のコードを追加すると、以下のようになると思います。

![Kapture 2017-06-25 at 11.48.33.gif](https://qiita-image-store.s3.amazonaws.com/0/100997/8458a3cc-4f2c-ca21-0a97-88a6046e061b.gif "Kapture 2017-06-25 at 11.48.33.gif")