RailsにWebpack+Vue.jsを、出来るだけ気楽に導入してみる

# はじめに
Vue.jsにはHTML/CSS/Javascriptを単一のファイルにまとめることのできる、コンポーネント機能があります。
しかし、コンポーネントを使用するためにはトランスパイラが必要となり、Rails標準のアセットパイプラインでは処理できません。
そのため、RailsにWebpackを導入することになります。
Webpackの導入方法は多くありますが、極力Railsに手を入れない方法で導入してみようと思います。

# 動作環境
動作に必要なパッケージ（カッコ内は確認したバージョン）
これらのインストールについては本記事では割愛します。
（確認日：2019/7/17）

* Linux (Debian Stretch)
* Ruby (2.5.5)
* npm (6.4.1)
* Bundler (2.0.1)
* MariaDB (mysql Ver 15.1 Distrib 10.1.38-MariaDB)(Railsが対応していれば別のDBでも可)

この記事でgemやnpmから導入するパッケージの動作確認バージョンは以下になります。

* Ruby on Rails (5.2.3)
* Webpack (4.35.0)
* Vue.js (2.6.10)
* axios (0.19.0)

#参考資料
基本的に以下の２つの記事をまとめた形になります。

* [WebPackを使ってRailsからJavaScriptを楽に良い感じに分離する(Qiita)](https://qiita.com/necojackarc/items/afa674ab10aafa9784eb)
* [Webpackで始めるVue.js(別サイトにリンク)](https://log.pocka.io/posts/vue-webpack-tutorial/)
 
この他、`基礎から学ぶ Vue.js`（所謂猫本）を参考にしています。

#開発方針

* **Sprocketsは残したままWebpackを導入する**
* Railsプロジェクトに`/frontend`ディレクトリを作成し、Webpackを導入する
* Webpackは`/app/assets/javascript/webpack.js`にスクリプトを出力する。
* **Webpackで処理したファイルをSprocketsで再度処理する。**そういった不合理さは気にしない人向け。
* jQueryはwebpackで管理せず、Rails側で用意するかCDNを用いるかのどちらかとする(本記事ではjQueryを使用していない)

`/frontend`以下のディレクトリ構成

```
/frontend
|- config
|  |- production
|  |  |- webpack.config.js
|  |- development
|     |- webpack.config.js
|- node_modules
|- src
|  |- javascripts
|     |- components
|     |  |- vuesample.vue
|     |  |- vuesampleItem.vue
|     |- entry.js
|- package.json
```

#Railsのインストール
project_nameというRailsプロジェクトを作成するとします。

```:Shell
mkdir project_name
cd project_name
bundle init
```
`bundle init`でGemfileを作成します。
今回は5.2系最新版をインストールします。
今後Rails6以降を使いたい場合はバージョンを変更します。

```:Gemfile
gem "rails" , "~> 5.2"
```
`vender/bundle`以下にRailsをインストールします。

```:Shell
bundle install --path=vendor/bundle
```
`rails new`します。Turbolinksは無効にします。
DBはMySQL(MariaDB)を指定していますが、任意のDBで問題ありません。
-Bオプションを指定しているため、bundleは行われません。
実行すると、上書きするかを聞かれるので上書きします。

```:Shell
bundle exec rails new . -B -d mysql --skip-turbolinks
```
Gemfileを編集して`pry`をインストールします。

```:Gemfile
group :development do
  # ↓追加
  gem 'pry-byebug'
end
```
`bundle update`でインストールします。

#Webpackを導入する
##Webpackなどパッケージのインストール
Railsのルートディレクトリに`/frontend`ディレクトリを作成し、Webpackをインストールします。

```:Shell
mkdir frontend
cd frontend
```
`package.json`を作成します。

```:Shell
npm init
```
色々質問されますが、デフォルト値で構いません。
質問が終わったら、必要なパッケージをインストールします。

```:Shell
npm i --save vue webpack webpack-cli vue-loader vue-template-compiler css-loader style-loader babel-loader @babel/core @babel/preset-env sass-loader node-sass
```
##webpackの設定ファイルを作成
`/frontend/config/development/webpack.config.js`
`/frontend/config/production/webpack.config.js`
を作成します。

```Shell
mkdir config
mkdir config/development
mkdir config/production
touch config/development/webpack.config.js
touch config/production/webpack.config.js
```

今回はWebpack4用の設定ファイルを作ります。
Webpackの設定ファイルはバージョンにより異なるため注意が必要です。

Webpackの出力先を`/app/assets/javascripts`にするためには絶対パスでの指定が必要です。
`/home/vagrant/projects/project_name/app/assets/javascripts`の部分は開発環境のパスに書き換える必要があります。

`devtool: 'inline-source-map'`を指定することで、ブラウザからのデバッグが可能になります。

```javascript:/frontend/config/development/webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  devtool: 'inline-source-map', //デバッグに必要
  mode: 'development', // Webpack4では必須
  entry: {
    webpack: './src/javascripts/entry.js' //Keyがファイル名になる
  },
  output: {
    // /app/assets以下に出力（フルパスで記述）
    // ここを書き換え
    path: '/home/vagrant/projects/project_name/app/assets/javascripts',
    filename: '[name].js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.css$/,
        use: ['vue-style-loader', 'css-loader'] // css-loader -> vue-style-loaderの順で通していく
      },
      {
        test: /\.scss$/,
        use: [
          'vue-style-loader',
          'css-loader',
          {
            loader: 'sass-loader',
          },
        ],
      }
    ]
  },
  resolve: {
    // import './foo.vue' の代わりに import './foo' と書けるようになる(拡張子省略)
    extensions: ['.js', '.vue'],
    alias: {
      // vue-template-compilerに読ませてコンパイルするために必要
      vue$: 'vue/dist/vue.esm.js',
    },
  },
  plugins: [
    new VueLoaderPlugin()
  ],
  // jQueryはRails側で用意するか、CDNを使用
  // (CoffeeScriptからjQueryを呼び出す可能性を想定)
  // 記事内ではjQueryを使用していない。読み込まなくてもエラーにならない。
  externals: {
    jquery: 'jQuery'
  }
}
```
production環境用のconfigも同様に作ります。

* `mode: 'production'`を指定してコードを圧縮すること
* `devtool: 'inline-source-map'`を削除していること
* production環境でのアセットプリコンパイルを通すため、ES6以降のJavaScriptをES5に変換するように指定すること

が、developmentとの違いになります。

```javascript:/frontend/config/production/webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  mode: 'production', // Webpack4では必須
  entry: {
    webpack: './src/javascripts/entry.js' //Keyがファイル名になる
  },
  output: {
    // /app/assets以下に出力（フルパスで記述）
    // ここを書き換え
    path: '/home/vagrant/projects/project_name/app/assets/javascripts',
    filename: '[name].js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: [
            // ES5に変換するように指定
            "@babel/preset-env"
          ]
        }
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.css$/,
        use: ['vue-style-loader', 'css-loader'] // css-loader -> vue-style-loaderの順で通していく
      },
      {
        test: /\.scss$/,
        use: [
          'vue-style-loader',
          'css-loader',
          {
            loader: 'sass-loader',
          },
        ],
      }
    ]
  },
  resolve: {
    // import './foo.vue' の代わりに import './foo' と書けるようになる(拡張子省略)
    extensions: ['.js', '.vue'],
    alias: {
      // vue-template-compilerに読ませてコンパイルするために必要
      vue$: 'vue/dist/vue.esm.js',
    },
  },
  plugins: [
    new VueLoaderPlugin()
  ],
  // jQueryはRails側で用意するか、CDNを使用
  externals: {
    jquery: 'jQuery'
  }
}
```
`package.json`を編集して、`npm run`から実行できるようにします。

```json:/frontend/package.json
  "scripts": {                                                                     
    "test": "echo \"Error: no test specified\" && exit 1",
    // ↓下の３つを追加                         
    "release": "webpack --config config/production/webpack.config.js",             
    "build": "webpack --config config/development/webpack.config.js",              
    "watch": "webpack --watch --config config/development/webpack.config.js"       
  },                                                                               
```
`npm run build`でdevelopment環境、`npm run release`でproduction環境での処理が行われます。

# Railsに組み込む
Railsで動作確認をするため、Railsのルートディレクトリに移動して適当なコントローラを作ります。

```:Shell
cd ..
bundle exec rails g controller samples
```

```ruby:/app/controllers/pages_controller.rb
def index
end
```

```ruby:/config/routes.rb
Rails.application.routes.draw do
  # ↓追加
  resources :samples
end
```

```erb:/app/views/samples/index.html.erb
<div id="app">
  <p>{{name}}</p>
</div>
```

```javascript:/frontend/src/javascripts/entry.js
import Vue from 'vue';

document.addEventListener("DOMContentLoaded", function(event) {
  new Vue({
    el: '#app',
    data: {
      name: 'aaa'
    }
  });
});
```
`npm run build`や`npm run release`を実行して、
`/app/assets/webpack.js`を生成します。

## DBのパスワードの設定
このままRailsを起動するとDBに接続できずにエラーが起きるのでパスワードを設定します。
`/config/database.yml`を編集してDBのユーザ名とパスワードを設定します。

```yml:/config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: yourusername #ユーザ名を入力
  password: yourpassword #パスワードを入力
  socket: /var/run/mysqld/mysqld.sock
```
パスワードを指定したら、DBを作成します。

```Shell
bundle exec rails db:create
```
DBの作成が成功したら`rails s`でサーバーを起動。
もしサーバーのアドレスがlocalhostでない場合、IPアドレスを-bオプションで指定しましょう。

```Shell
bundle exec rails s -b サーバーのIPアドレス
```
`/sample`にアクセスして、Vueで指定したnameが表示されていれば、成功です。

#コンポーネントを導入する
Vue.jsの導入に成功したので、次はコンポーネントを導入します。

`entry.js`をできるだけ汚したくないため、親となるコンポーネント`vueSample.vue`を呼び出し、親コンポーネントから`vueSampleItem.vue`を子コンポーネントとして呼び出します。

データは親コンポーネントで保持し、データが変更されると自動的に子コンポーネントに反映されるように実装します。

```vue:/frontend/src/javascripts/components/vueSample.vue
<template>
  <div>
    <div>{{message}}</div>
    <div>
      <!-- samplesに格納された配列を元に表示する -->
      <!-- idとnameを子コンポーネントに渡す -->
      <!-- keyはあったほうがいいと思われる -->
      <vue-sample-item v-for="sample in samples" 
        v-bind:key="sample.id"
        v-bind:name="sample.name"
        v-bind:id="sample.id" 
      >
      </vue-sample-item>
    </div>
  </div>
</template>
<script>
import vueSampleItem from './vueSampleItem';

export default {
  name: 'vueSample',
  data: function(){
    return {
      //samplesがデータを入れる配列
      //この配列を編集すると自動的に表示に反映される
      samples: [
        {
          id: 1,
          name: 'sample1'
        },
        {
          id: 2,
          name: 'sample2'
        },
        {
          id: 3,
          name: 'Sample3'
        }
     ],
      //表示したいメッセージ
      message: 'Message'
    }
  },
  //子コンポーネントを指定する
  components: {
    'vue-sample-item': vueSampleItem
  }
}
</script>
```

```javascript:/frontend/src/javascripts/components/vueSampleItem.vue
<template>
  <div class="vuesampleItem" v-bind:class="{active: isActive}">
    <p v-on:click="toggleActive();">
      {{name}}
    </p>
  </div>
</template>

<script>
export default {
  name: 'vueSampleItem',
  //親コンポーネントから受け取るプロパティ
  //型はできるだけ指定した方がよいとされる
  props: {
    id: Number, 
    name: String 
  },
  data: function(){
    return {
      isActive: false
    } 
  },
  methods: {
    toggleActive: function(){
      this.isActive = !this.isActive;
    },
  }
}
</script>

<style lang="scss" scoped>
  .vuesampleItem {
    border: 1px dashed #abcdef;
    margin: 4px 0; 

    &.active {
      border-left: 10px solid #abcdef;
    }

    &:hover {
      background-color: #fafafa;
    }

    p {
      margin: 0;
      padding: 4px;
    }
  }
</style>
```
コンポーネントごとに、HTML/CSS/JavaScriptをまとめて記述できました。

コンポーネントの詳しい解説は、`基礎から学ぶ Vue.js`などの資料を参考してもらえればと思います。
（コンポーネントの解説だけで大きな分量になります。）

コンポーネントを読み込むため、`entry.js`を修正します。

```javascript:/frontend/src/javascripts/entry.js
import Vue from 'vue';

//↓追加
import vueSample from './components/vueSample';

document.addEventListener("DOMContentLoaded", function(event) {
  new Vue({
    el: '#app',
    data: {
      name: 'aaa',
    },
    //↓下を追加。上のカンマも忘れずに。
    components: {
      'vue-sample': vueSample
    }
  });
});
```
最後にビューを修正して親コンポーネントを配置します。

```erb:/app/views/samples/index.html.erb
<div id="app">
  <%# 親となるコンポーネントを指定する %>
  <vue-sample></vue-sample>
</div>
```
`npm run build`で更新します。
成功していれば、samplesに格納した配列の通りに表示されているはずです。
名前をクリックすると、`class`を切り替えることができます。

# Railsと通信してDBと連携する
ここまでは配列に直接入力したデータを参照していました。
次は、Railsと通信してDBのデータを取得します。

データ一覧の表示、データの作成、更新、削除の機能を実装していきます。

##DBの設定
Railsのモデルを作成します。

```:Shell
bundle exec rails g model sample
```
成功するとメッセージが表示されます。

```
invoke    active_record
create    db/migrate/20190727062002_create_samples.rb
create    app/models/sample.rb
invoke    test_unit
create    test/models/sample_test.rb
create    test/fixtures/samples.yml
```
`/db/migrate/`以下に新しいマイグレーションファイルができているので、編集します。
名前を保存するため、`string`型で`name`カラムを追加します。

```ruby:/db/migrate/[日付]_create_samples.rb
class CreateSamples < ActiveRecord::Migration[5.2]
  def change
    create_table :samples do |t|
      t.timestamps
      # ↓追加
      t.string :name, default: ""
    end
  end
end

```

成功したら、`name`カラムを追加するためマイグレーションを追加します。

```:Shell
bundle exec rails db:migrate
```
成功すると`name`カラムが追加されます。

##DBの情報を取得するためのアクションを追加
Rails側からDBの情報を送信するためのアクションを作ります。
作成するアクションは下の通りになります

| アクション名 | 役割 |
|:-:|:-:|
| index  | レコード一覧の表示 |
| create | レコードの新規作成 |
| update | 指定したidのレコードの更新  |
| destroy| 指定したidのレコードを削除  |
| all  | 全レコードの取得  |

`all`以外は`resources`から自動生成されます。

ルーティングを変更するため、`routes.rb`を編集します。

```ruby:/config/routes.rb
Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  resources :samples, only: [:index, :create, :update, :destroy] do
    collection do
      get "/all", to: "samples#all"
    end
  end
end
```
次に、コントローラを編集します。

`all` `create` `update` の各メソッドは、`id`カラムと`name`カラムの情報を連想配列で返します。
`destroy`メソッドは`id`カラムのみを返します。
失敗した場合、例外を起こす（update!などのメソッドを使用する）ようにして、失敗をクライアント側で検出できるようにしました。
（失敗した場合、Internal Server Errorになります）

`getsample`メソッドは、インスタンス変数`@sample`に指定した`id`を自動的に格納します。
`sample_to_hash`メソッドは、取得したデータから必要な情報（`id`と`name`）を抜き出すメソッドになります。

```ruby:/app/controllers/samples_controller.rb
class SamplesController < ApplicationController
  before_action :getsample, only: [:update, :destroy]

  def index
  end

  def all
    samples = Sample.all
    result = []
    samples.each do |sample|
      hash = sample_to_hash(sample)
      result << hash
    end
    render :json => result
  end

  def create
    name = params[:name]
    sample = Sample.new({
      name: name,
    })
    sample.save!

    result = sample_to_hash(sample)
    render :json => result
  end

  def update
    @sample.update!({
      name: params[:name],
    })
    result = sample_to_hash(@sample)
    render :json => result
  end

  def destroy
    @sample.destroy!

    result = {
      id: params[:id],
    }
    render :json => result
  end

  private

  def getsample
    @sample = Sample.find(params[:id])
  end

  def sample_to_hash(sample)
    hash = {}
    hash[:id] = sample.id
    hash[:name] = sample.name
    return hash
  end
end
```
## コンポーネントからRailsに通信する
###axiosのインストール
Rails側の準備ができたので、コンポーネントから通信してデータを読み込みましょう。

Vue.jsからの通信クライアントは`axios`が使われることが多いようです。
axiosをインストールします。

```:Shell
cd ./frontend
npm i --save axios
```
###一覧の表示と、レコードの追加
最初に、レコードの一覧表示と新規追加を作ります。

レコードの一覧表示は、`samples`の配列にデータをロードすることで自動的に作成できます。

新規レコードを追加するため、一覧ページの一番上にテキストフィールドと送信ボタンを配置します。
テキストフィールドに名前を入力し、ボタンを押すとpostで送信します。
テキストフィールドの内容を、`newname`にバインディングし、送信ボタンがクリックされると`sendCreate()`メソッドが呼び出されるようにします。
`sendCreate()`メソッドは`newname`を参照してpostを送信します。

```vue:/frontend/src/javascripts/components/vueSample.vue
<template>
  <div>
    <div>{{message}}</div>
    <!-- ↓下のdivタグを追加 -->
    <!-- 新規作成フォーム -->
    <div>
      <label>Name<input v-model="newname"></label>
      <button v-on:click="sendCreate();">Create</button>
    </div>
    <!-- 省略 -->
  </div>
</template>
```
スクリプトも変更します。
`axios`を使用するため、インポートします。
railsとの通信のためCSRFトークンを用意する必要があるため、`axios`にトークンを設定します。

```vue:/frontend/src/javascripts/components/vueSample.vue
<script>
import axios from 'axios';

import vueSampleItem from './vueSampleItem';

// set CSRF token
axios.defaults.headers.common = {
  'X-Requested-With': 'XMLHttpRequest',
  'X-CSRF-TOKEN' : document.querySelector('meta[name="csrf-token"]').getAttribute('content')
};
// 省略
</script>
```
次にコンポーネント本体のスクリプトを修正します。

配列`samples`に入力されていたデータを全て削除します。
変数`newname`を追加します。

メソッドを追加します。

| メソッド名 | 概要 |
|:-:|:-:|
| getAllSamples()  | 全データの取得をするメソッド  |
| sendCreate()  | レコードの新規作成のため、名前をRailsに送るメソッド  |
| getLocation()  | 必ず最後に/が付くように自ページのURLを取得する |



```vue:/frontend/src/javascripts/components/vueSample.vue
<script>
//省略
export default {
  name: 'vueSample',
  data: function(){
    return {
      //内容を空にする
      samples: [],
      //newnameを追加
      newname: '',
      message: 'Message'
    }
  },
  methods: {
    getAllSamples: function(){
      axios.get( this.getLocation() + 'all' )
      //成功したときのコールバック
      .then( function(response){
        this.samples = response.data;
        this.message = '取得に成功しました。'
      }.bind(this) )
      //失敗したときのコールバック
      .catch( () => this.message = '取得に失敗しました。');
    },
    sendCreate: function(){
      axios.post( this.getLocation(), {
        name: this.newname
      } )
      .then( function(response){
        //成功したらsamplesに項目を追加
        this.samples.push(response.data);
        this.message = '追加に成功しました。'
      }.bind(this))
      .catch( () => this.message = '追加に失敗しました。');
      //テキストフィールドを空にする
      this.newname = '';
    },
    getLocation: function(){
      let result = location.href;
      //最後に/がついていなかったら追加する
      if( !result.match(/\/$/) ){
        result += '/'
      }
      return result;
    }
  },
  //コンポーネントがマウントされると、getAllSamples()が呼ばれる
  mounted: function(){
    this.getAllSamples();
  },
  components: {
    'vue-sample-item': vueSampleItem
  }
}
</script>
```
完成したら、`npm run build`を実行して`webpack.js`を更新します。
テキストフィールドに名前を入力して送信すると、要素が追加されるはずです。

###データの編集
次にデータの編集ができるようにしましょう。
`sendUpdate`メソッドを作り、patchで更新します。

```vue:/frontend/src/javascripts/components/vueSample.vue
<script>
//省略
  methods: {
    //↓下を追加
    sendUpdate: function(_id, _name){
      axios.patch( this.getLocation() + _id, {
        id: _id,
        name: _name
      } )
      .then(function(response){
        this.message = '更新に成功しました。';
      }.bind(this))
      .catch( () => this.message = '更新に失敗しました。');
      // nameを書き換え
      // findメソッドで配列内を検索し、idが一致した要素の名前を変更する
      let target = this.samples.find( item => item.id == _id );
      target.name = _name;
    },
    //省略
  }
</script>
```
メソッドを追加しました。
しかし、これだけだとメソッドは実行されません。
子コンポーネントに更新のためのフォームを作り、メソッドを実行しないといけません。

`vueSampleItem.vue`を修正します。

テキストフィールドを作り、算出パラメータ`myName`とバインディングします。
`name`は親コンポーネントのデータなので、変更してはいけません。
代わりに`myName`が変更された際には親コンポーネントの`sendUpdate`メソッドを実行するように設定します。
`sendUpdate`メソッドは親コンポーネントのデータを変更することができます。

`myName`は`data`に追加せず、算出パラメータとして設定します。
`myName`にデータが書き込まれる際に、`sample-update`イベントを自分自身に発生させます。
`sample-update`イベントが発生した際に、親コンポーネントの`sendUpdate`メソッドが実行されるように設定します。
子コンポーネントから`sample-update`イベントを起こすためには、`$emit`メソッドを使用します。

```vue:/frontend/src/javascripts/components/vueSampleItem.vue
<template>
  <div class="vuesampleItem" v-bind:class="{active: isActive}">
    <p v-on:click="toggleActive();">
      {{name}}
    </p>
    <!-- ↓下のpタグを追加 -->
    <p>
      <input v-model="myName">
    </p>
  </div>
</template>
<script>

export default {
  name: 'vueSampleItem',
  props: {
    id: Number, 
    name: String 
  },
  data: function(){
    return {
      isActive: false
    } 
  },
  //computedを追加
  //myNameは算出パラメータとして設定
  computed: {
    myName: {
      //読み取りはnameを参照
      get: function(){
        return this.name
      },
      //書き込みはsample-updateイベントを自分自身に送信する。
      //sample-updateイベントが発生すると、
      //親コンポーネントのsendUpdateメソッドが発生するように設定する
      set: function(val){
        if (this.name !== val){
          this.$emit('sample-update', this.id, val);
        }
      }
    }
  },
  //ここまで追加
  methods: {
    toggleActive: function(){
      this.isActive = !this.isActive;
    },
  }
}
</script>
```
`sample-update`イベントが発生した際に、`sendUpdate`メソッドが呼び出されるように親コンポーネントを設定します。

```vue:/frontend/src/javascripts/components/vueSample.vue
<template>
  <!-- 省略 -->
  <!-- v-on:sample-updateイベントが発生すると、sendUpdateメソッドが呼び出される -->
  <vue-sample-item v-for="sample in samples" 
    v-bind:key="sample.id"
    v-bind:name="sample.name"
    v-bind:id="sample.id" 
    v-on:sample-update="sendUpdate"
  >
  </vue-sample-item>
  <!-- 省略 -->
</template>
```
`npm run build`でビルドします。
テキストフィールドの内容を変更すると、DBが更新されます。

###レコードの削除
最後にレコードの削除を実装します。

子コンポーネントに削除ボタンを作り、クリックすると削除されるように実装します。

```vue:/frontend/src/javascripts/components/vueSampleItem.vue
<template>
  <!-- 省略 -->
  <!-- ↓下を追加 -->
  <p>
    <button v-on:click="sendDelete();">delete</button>
  </p>
  <!-- 省略 -->
</template>
```
`sendDelete`メソッドを追加します。
データを更新した時と同じように、`sample-delete`イベントを発生させます。

```vue:/frontend/src/javascripts/components/vueSampleItem.vue
<script>
  //省略
  methods: {
    toggleActive: function(){
      this.isActive = !this.isActive;
    },
    // ↓下を追加
    sendDelete: function(){
      this.$emit('sample-delete', this.id);
    },
  }
  //省略
</script>
```
更新の時と同じように親コンポーネントも変更します。

```vue:/frontend/src/javascripts/components/vueSample.vue
<template>
  <!-- 省略 -->
  <!-- sample-deleteイベント発生時にsendDelete()メソッドを実行する -->
  <vue-sample-item v-for="sample in samples" 
    v-bind:key="sample.id"
    v-bind:name="sample.name"
    v-bind:id="sample.id" 
    v-on:sample-update="sendUpdate"
    v-on:sample-delete="sendDelete">
  </vue-sample-item>
  <!-- 省略 -->
</template>
<script>
  // 省略
  methods:{
    //sendDeleteメソッドを追加
    sendDelete: function(_id){
      axios.delete( this.getLocation() + _id )
      .then(function(response){
        this.message = '削除に成功しました。';
      }.bind(this) )
      .catch( () => this.message = '削除に失敗しました。');
    
      //配列を更新
      //idが一致しない要素だけを抜き出して新しい配列を作る
      this.samples = this.samples.filter(item => item.id !== _id);
    },
  },
  //省略
</script>
```
`npm run build`で更新します。
成功していれば、削除ボタンでデータを削除することができるはずです。

# さいごに
`Rails`と`Vue.js`を組み合わせて、CRUDを実現することができました。

最初に`Vue.js`を導入しようとしたきっかけは`React`と違いトランスパイルが不要、ということでRailsに組み込みやすいかなあと思ったためでした。
しかし、結局のところ単一ファイルコンポーネントが使いたくなったため、Webpackの導入から始める必要が出てきてしまいました。

Railsのアセットパイプラインは、最近では欠点といわれることが多いようです。
それでもRailsのActiveRecordは評価が高く、DB周りでは今後もRailsが多く使われていくかと思います。

RailsにWebpackを導入してVue.jsを利用することは、Rails初心者には少し敷居が高いかなあと思っています。
しかし、この記事をきっかけにして少しでもVue.jsを触ってみたいと思ってもらえたら、動かしながら学んでもらえたら、嬉しいと思います。

# サンプルコードについて
サンプルコードを、[GitHub](https://github.com/okabee-dic/vuetest)にアップロードしましたので、ご自由にご利用ください。
ただし、利用した結果については、責任を負いかねます。

パッケージについてはインストールされていないので更新が必要です。
DBの設定を変更した後、

```
bundle install --path=vendor/bundle
cd frontend
npm install
cd ..
bundle exec rails db:create
bundle exec rails db:migrate
```
といった感じで実行できると思います。