【Rails6 + Vue.js】ブックマークを管理する簡単なSPAを作ってみた話（第一章：アプリ作成〜ダミーデータ表示）

先日ドットインストールでVue.jsの学習を一通り終え、何かアウトプットとして個人でSPAを開発したいと考え、Ruby on Rails + Vue.jsで CRUD と検索機能等を持った簡単なブックマークの個人管理アプリケーションを作成したので構築手順や開発中に調べて気づいたことなどを記事にまとめます。

## 前提条件
【バージョンについて】

- Ruby 2.6.5
- Ruby on Rails 6.0.0
- yarn 1.22.5
- Vue.js 2.6.12
- Vuetify 2.4.7

DBはMySQLを使用

【記事内の説明について】
HTML、CSS、Vuetifyの説明は割愛します。
また、今回はVue.jsで初のCRUDを実装することを主な目的としているため、deviseを導入したユーザー管理機能についての説明は割愛します。

## 概要
### 完成版イメージ

トップページの見た目はこのような感じです。
[![Image from Gyazo](https://i.gyazo.com/1b619aae2a50b4c5f37f51364b5a9709.gif)](https://gyazo.com/1b619aae2a50b4c5f37f51364b5a9709)

### 実装機能

- ブックマーク一覧表示
- ブックマーク新規投稿
- ブックマーク編集
- ブックマーク削除
- ブックマーク並び替え
- フリーワード検索
- カテゴリー別検索
- ローディング表示
- ページネーション（kaminari）
- ユーザー管理（devise）

### 記事構成
本アプリケーションについての記事は大きく4部構成で作成します。

- [第一章：本記事（アプリケーション作成〜ダミーデータ表示）]()
- [第二章：非同期によるブックマークの新規投稿、編集、削除、並び替え機能の実装](https://qiita.com/showy/items/5bead2820923ce7ce80a)
- [第三章：フリーワード検索、カテゴリー別絞り込み機能の実装](https://qiita.com/showy/items/7ca2e46e09371c489204)
- [第四章：ローディング表示、ページネーション、ユーザー管理機能の実装](https://qiita.com/showy/items/38747e6cebfd7f89cff3)

### 本記事の参考URL
本記事の作成にあたって以下の記事を参考にさせて頂きました。

- [[Rails+Vue.js]に係るCRUD入門〜Part１：環境構築編〜](https://qiita.com/Moo_Moo_Farm/items/afacfe4349af6a106253)
- [【Rails6】10分でRails + Vue + Vuetifyの環境を構築する](https://qiita.com/Ryoga_aoym/items/e1d91351389904240594)
- [既存のRailsアプリにVue.jsを導入した](https://qiita.com/kyokucho1989/items/5a285256aafe7fee8b5c)

## 構築手順（アプリ作成〜ダミーデータ表示）

### Railsアプリケーションを作成
アプリの名前はbookmark-appで作成します。

```ターミナル
% rails _6.0.0_ new bookmark-app -d mysql
% cd bookmark-app
% bundle install
% rails db:create
```

### Webpackerをインストール

Webpackerは、複数のJavaScriptファイルなどを1つにまとめて出力するツールであるwebpackを簡単にRailsアプリに統合できるGemであり、Rails5.1以降標準でサポートされています。

Gemは既に追加されているため、こちらをターミナルでインストールします。

```
% rails webpacker:install
```

### Vue.jsの導入
Vue.jsをWebpackerで導入するために以下のコマンドを実行します。

```
% rails webpacker:install:vue
```

### ルーティングの設定
今回はhomeコントローラーのindexアクションをページのルーティングとして設定します。

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

### コントローラの作成
上記でルーティングで設定したhomeコントローラーとindexのビューを作成します。

```
% rails g controller home index
```

```app/controlllers/home_controller.rb
class HomeController < ApplicationController
  def index
  end
end
```
作成された `app/views/home/index.html.erb` に `app/javascript/packs/hello_vue.js` ファイルを読み込むコードを記載します。これは、hello_vue.js ファイルを介してjavascript/packs/app.vueファイルを読み込みための記述です。

```app/views/home/index.html.erb
<%= javascript_pack_tag 'hello_vue' %>
```
この時点でサーバーを立ち上げ、http://localhost:3000/home にアクセスすると画面に「Hello Vue!」が表示されます。

### モデルについて
今回のアプリケーションに実装するモデルはUserモデルとBookmarkモデルの2つです。
しかし、Usersテーブルを先に作ってしまうと処理の確認等開発の効率が悪くなると考えたため自分はまずBookmarkモデルを作成し一通りの処理を完成させてからUsersモデルをdeviseで実装しました。
本記事でも最初にBookmarkモデルを作り、アプリの主要な機能の実装が完了してからUserモデルの実装を進めることにします。

【ER図】
[![Image from Gyazo](https://i.gyazo.com/d3a9c185be6788fb56367da23fc9adc2.png)](https://gyazo.com/d3a9c185be6788fb56367da23fc9adc2)

### Bookmarkモデルの作成
Bookmarkモデルを作成します。

```
% rails g model Bookmark
```
Bookmarkテーブルにはそれぞれタイトル、URL、カテゴリーを保存すると設計したため、作成されたマイグレーションファイルに以下の設定を記述します。

| Column    | Type     | Options     |
| --------- | -------- | ----------- |
| title     | string   | null: false |
| url       | string   | null: false |
| category  | string   | null: false |

```db/migrate/xxxxxx_create_bookmarks.rb
class CreateBookmarks < ActiveRecord::Migration[6.0]
  def change
    create_table :bookmarks do |t|
      t.string :title,    null: false
      t.string :url,      null: false
      t.string :category, null: false
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end
```

記述が完了し保存したらマイグレーションを実行します。

```
% rails db:migrate
```

次に、Bookmarkモデルにバリデーションを設定します。
タイトル、URL、カテゴリーは全て空のデータが保存されないようにし、タイトルとカテゴリーの文字数は最大50文字、URLは正しいURLが入力されていなければ保存できないように設定しました。

```app/models/bookmark.rb
class Bookmark < ApplicationRecord
  validates :title, presence: true, length: { maximum: 50 }
  validates :category, presence: true, length: { maximum: 50 }

  with_options presence: true do
    validates :url, format: {with: /\A#{URI::regexp(%w(http https))}\z/}
  end
end
```
以上で、Bookmarkモデルの作成は完了です。

### ダミーデータの作成（Fakerの導入）

ブックマークの一覧表示機能の実装にあたって、あらかじめデータがちゃんと表示されるかどうかを確認する初期データが必要です。
手動でデータを挿入してもいいのですが手間がかかるため、今回はGemでFakerを導入して簡単にダミーデータを作成しそちらを表示させます。

まず、GemfileにFakerを記載

```
gem 'faker'
```

そして、ターミナルでbundle installを実行します。

```
% bundle install
```

### ダミーデータの作成（seeds.rbの編集）

seedファイルは、データベースにあらかじめ入れておきたいデータを定義しておくものです。
データをあらかじめ定義することで、コマンドで簡単にDBへデータを投入できるため、先程導入したFakerを使ってダミーデータを作成しbookmarksテーブルにデータを入れていきます。

参考URL : [faker-ruby](https://github.com/faker-ruby/faker)

```db/seeds.rb
10.times do
  Bookmark.create(
    title: Faker::Lorem.word,
    url: Faker::Internet.url,
    category: Faker::Lorem.word,
  )
end
```

 上記の記述でタイトル、URL、カテゴリーそれぞれにFakerでダミーデータを作成し、それを繰り返し処理で10個分のブックマークのデータを作成しています。

記載が完了したらターミナルでseeds.rbファイルを実行します。

```
% rails db:seed
```

以上でダミーデータの作成は完了です。


### Bookmarksコントローラーの作成（API）
作成したダミーデータを取得してデータを渡すためにAPIの送信元を作成します。

```
% rails g controller api::bookmarks
```
### Bookmarksコントローラーのルーティングを設定

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

  namespace :api, format: 'json' do
    resources :bookmarks, only: [:index]
  end
end
```
作成したコントローラーでBookmarksテーブルからデータを取得
作成時間順で新しい投稿が上に来るように取得します。

```app/controllers/api/bookmarks_controller.rb
class Api::BookmarksController < ApplicationController

  def index
    @bookmarks = Bookmark.order('created_at DESC')
    render "index", formats: :json, handlers: "jbuilder"
  end

end
```
因みにRails6以降では、`formats: :json`の部分を`formats: 'json'`のようにシンボルではなくシングルクオーテーションで書いてしまうとエラーになってしまうようです。

参考：[レイアウトとレンダリング -Railsガイド](https://railsguides.jp/layouts_and_rendering.html#formats%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3)


### jbuilderでjsonを返す
APIとしてデータを返す時、ほとんどの場合json形式でデータを渡すことが多いです。
Railsには、そのjsonの作成を簡単にしてくれるjbuilderという機能があります。
そして今回はそのjbuilderを使って、bookmarksコントローラーからAPIとしてデータを返していきます。


まず`app/views/api/bookmarks`配下に`index.json.jbuilder`ファイルを作成します。

```
% touch app/views/api/bookmarks/index.json.jbuilder
```
作成したファイルに以下のコードを記述します。

```app/views/api/bookmarks/index.json.jbuilder
json.array! @bookmarks, :id, :title, :url, :category
```
コントローラーから渡されてきたインスタンス変数`@bookmarks`とキー`:id, :title, :url, :category`を`array!`メソッドを使って配列を回しデータを返します。

一覧表示させるだけなら`:id`は必要ないのですが、今回はCRUDを実装するためIDも渡します。


以上でサーバーサイドの実装はひとまず完成です。

### axiosの導入
APIの非同期通信を行うために、それを実現するライブラリのaxiosを導入します。

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

```
% yarn add axios
```

### Vuetifyの導入
Vue.jsのUIライブラリであるVuetifyを導入します。
[Vuetify](https://vuetifyjs.com/ja/)

```zsh
% yarn add vuetify
```

ここで今回自分はVue.jsを導入した時デフォルトで生成された`app/javascript/packs/hello_vue.js`ファイルを削除し、新たに`app/javascript/packs/top_vue.js`を作成して実装しました。
コードをスッキリさせるためです。

`hello_vue.js`を削除したので`index.html.erb`の内容も忘れずに変更。
`app/javascript/packs/top_vue.js`を読み込むために以下のコードを記述します。

```app/views/home/index.html.erb
<%= javascript_pack_tag 'top_vue' %>
<%= stylesheet_pack_tag 'top_vue'  %>
```

`top_vue.js`で先程導入したVuetifyを読み込ませます。

```app/javascript/packs/top_vue.js
import Vue from 'vue'
import Vuetify from "vuetify";
import "vuetify/dist/vuetify.min.css";
import App from '../app.vue'

Vue.use(Vuetify);
const vuetify = new Vuetify();

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    vuetify,
    render: h => h(App)
  }).$mount()
  document.body.appendChild(app.$el)

  console.log(app)
})
```
以上でVuetifyの導入は完了です。

参考にさせて頂いた記事：[【Rails6】10分でRails + Vue + Vuetifyの環境を構築する](https://qiita.com/Ryoga_aoym/items/e1d91351389904240594)
- [Vuetifyjs.com -Webpack でのインストール](https://vuetifyjs.com/ja/getting-started/installation/#webpack-3067306e30a430f330b930c830fc30eb)

### ダミーデータの一覧表示機能の実装
いよいよ上で作成したダミーデータを表示させます。

まずはVue.js導入時にデフォルトで生成されている`app/javascript/app.vue`ファイルを確認します。

```app/javascript/app.vue
<template>
  <div id="app">
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      message: "Hello Vue!"
    };
  }
};
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>
```

今回このアプリケーションでは基本的にこの`app/javascript/app.vue`ファイルを編集してフロント機能の実装を進めていきます。

スタイルは全てstyle属性で指定します。

```app/javascript/app.vue
<template>
  <v-app id="app">
    <v-container style="height: 1000px; max-width: 2400px; padding: 0 20px;">
      <v-layout>
        <v-flex xs2 style="justify-content: center; padding: 20px 5px 0 5px">
          <h3>サイドメニュー表示</h3>
        </v-flex>

        <v-flex xs8>
          <div style="width: 100%; margin: 5px 0 20px 0; display: flex; justify-content: center;">
            <h1>Bookmark 一覧</h1>
          </div>
          <v-layout>
            <v-flex row wrap style="justify-content: center;">

            <!-- v-forで bookmarkList の配列の中身を取り出してbookmarkに代入し繰り返す -->
              <v-card v-for="bookmark in bookmarkList" :key="bookmark.id" style="width: 100%">
                <v-card-title primary-title style="margin-bottom: 15px; width: 100%; padding-bottom: 10px;">
                  <div style="width: 100%;">
                    <div class="headline mb-0" style="display: flex; justify-content: space-between; width: 100%">
                      <p style="font-size: 18px;">
                        {{ bookmark.title }}  <!-- タイトルを表示 -->
                      </p>
                    </div>
                    <v-divider></v-divider>
                    <div style="font-size: 16px; display: flex; justify-content: space-between; width: 100%">
                      <div>
                        #{{ bookmark.category }}  <!-- カテゴリーを表示 -->
                      </div>
                    </div>
                  </div>
                </v-card-title>
              </v-card>

            </v-flex>
          </v-layout>
        </v-flex>

        <v-flex xs2>
          <v-btn style="margin: 20px 0 40px 0;">
            Bookmarkを追加する  <!-- 追加するモーダルを表示させるボタン（後に実装） -->
          </v-btn>
          <p style="margin-right: 30px">- Bookmark List -</p>
          <!-- 右側にもリストとしてブックマークをタイトルのみ一覧表示させる -->
          <ul v-for="bookmark in bookmarkList" :key="bookmark.id" style="list-style: none; margin-right: 30px">
            <li style="margin-top: 10px;">
              <p>{{ bookmark.title }}</p>  <!-- タイトルを表示させる -->
            </li>
            <hr>
          </ul>

        </v-flex>
      </v-layout>
    </v-container>
  </v-app>
</template>

<script>
import axios from 'axios';

export default {
  data: function () {
    return {
      bookmarkList: ['',''],  // 空配列を用意
    }
  },
  mounted () {
    this.setBookmark(); // setBookmark() を呼び出す
  },
  methods: {
    setBookmark: function () {
      axios.get('/api/bookmarks') // axiosを使ってデータを取得
      .then(response => {
        this.bookmarkList = response.data // axiosで呼び出したAPIの情報をbookmarkListに代入
        }
      );
    },
  }
}
</script>
```


以上で本記事【アプリ作成〜ダミーデータ表示】は終了です。

次章はこちら↓

https://qiita.com/showy/items/5bead2820923ce7ce80a