はじめに
Rails 5.1ではJavaScript/index.html.erb周りのサポートが大きく改善されました。
これにより、Vue.jsやReactといったモダンなJSフレームワークをRails内で非常に扱いやすくなっています。
僕も実際に試してみましたが、本当にびっくりするぐらい簡単にVue.jsやReactを動かすことができました。
そこでこの記事ではRails 5.1とVue.jsを組み合わせたサンプルアプリケーションの作成方法をチュートリアル形式で、できるだけ詳しく説明します。
また、ローカルで動かしておしまい、ではなく、Herokuにデプロイしたり、テストコードを書いたりするところまでカバーします。
この記事自体は長いですが、実際に手を動かすと(スムーズに進んだ場合)30分以内で終わらせることができるはずです!
今回作成するサンプルアプリケーション
今回は以下のリンク先にあるVue.jsのアプリケーションをRails上で作成します。
このサンプルアプリケーションではテーブルのソートや、データの絞り込みがVue.jsの機能を使って実装されています。
この記事の解説動画
この記事の内容はYouTubeでも解説しています。
実際にコードを書いたり、画面を動かしたりする様子が動画で確認できるので、さらに内容が理解しやすくなるはずです。
https://www.youtube.com/watch?v=ycOeM2umXkY
ちなみに、動画の長さは約40分です。
1.5倍速~2倍速ぐらいで再生すると、サクサクっと視聴できると思います。
この記事で作成したコード
この記事で作成したコードはGitHubにアップしてあります。
コミットログを追いかけていくと、どこで何が変わったのか把握しやすくなると思います。
こちらも参考にしてください。
それでは以下が本編です!
Rubyバージョンの確認
この記事ではRuby 2.4.0を使います。
$ ruby -v
ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-darwin16]
ですが、おそらくRuby 2.2.2以上なら動作するはずです。
Railsバージョンの確認
この記事ではRails 5.1.0.beta1を使います。
(なので、正式リリース時には多少仕様が変わる可能性があります)
$ rails -v
Rails 5.1.0.beta1
なお、gem install rails -v 5.1.0.beta1
でインストールできない場合は、以下のコマンドを試してみてください。
$ ruby -rbundler/inline -e "gemfile(true) do; source 'https://rubygems.org/'; gem 'rails', '5.1.0.beta1'; end"
新規Railsアプリを作成する
Rails 5.1でVue.jsを使う場合、3通りの方法があります。
-
rails new
に--webpack=vue
オプションを付けて、プロジェクトの作成時にVue.jsもインストールする -
rails new
に--webpack
オプションを付ける。その後、Vue.jsをインストールする - 普通に
rails new
して、それからWebpackerとVue.jsをインストールする
一番手軽なのは1の方法ですが、このチュートリアルではWebpackerやVue.jsのインストール時にどこが変わるのか確認するため、あえて3の方法で進めます。
というわけで、以下のコマンドを実行してRailsアプリを作成してください。
$ rails new rails-vue-sandbox
Commitログ
Initial commit · JunichiIto/rails-vue-sandbox@281aa22
Webpacker gemのインストール
続いて、Webpacker gemをインストールします。
Gemfileに以下の行を追加し、bundle install
してください。
gem 'webpacker', github: 'rails/webpacker'
それからbin/rails webpacker:install
というコマンドを実行するのですが、その前にまず、Yarnがインストールされていることを確認してください。
$ yarn -v
yarn install v0.20.3
[1/4] 🔍 Resolving packages...
success Already up-to-date.
✨ Done in 0.42s.
インストールされていなければ、以下のページを参考にしてインストールしておきましょう。
Yarnのインストールが済んだら、bin/rails webpacker:install
を実行します。
$ bin/rails webpacker:install
これでWebpack関連のファイルや設定が追加されます。
新規追加
app/javascript/packs/application.js
bin/webpack
bin/webpack-dev-server
bin/webpack-watcher
config/webpack/development.js
config/webpack/production.js
config/webpack/shared.js
yarn.lock
変更
.gitignore
config/environments/development.rb
config/environments/production.rb
package.json
Commitログ
Install webpacker · JunichiIto/rails-vue-sandbox@b4b6a58
Vue.jsのインストール
今度はVue.jsのインストールです。
Webpackerをインストールしていれば、コマンド一発でVue.jsがインストールできます。
$ rails webpacker:install:vue
これでVue.js用のファイルや設定が追加されます。
新規追加
app/javascript/packs/app.vue
<template>
<div id="app">
Hello Vue!
</div>
</template>
app/javascript/packs/hello_vue.js
// Run this example by adding <%= javascript_pack_tag 'hello_vue' %> to the head of your layout file,
// like app/views/layouts/application.html.erb.
// All it does is render <div>Hello Vue</div> at the bottom of the page.
import Vue from 'vue'
import App from './app.vue'
document.body.appendChild(document.createElement('hello'))
new Vue({
el: 'hello',
template: '<App/>',
components: { App }
})
変更
config/webpack/shared.js
package.json
yarn.lock
hello_vue.jsを表示する画面を作成する
次に、hello_vue.js
を表示する画面を作成します。
まず、コントローラを作成します。
$ rails g controller Home index
コントローラの中身はそのままでOKです。
class HomeController < ApplicationController
def index
end
end
Viewは空っぽにしておきます。
app/views/layouts/application.html.erb
には<%= javascript_pack_tag 'hello_vue' %>
を追加します。
<!DOCTYPE html>
<html>
<head>
<title>RailsVueSandbox</title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= yield %>
+ <%= javascript_pack_tag 'hello_vue' %>
</body>
</html>
routes.rb
では、home#index
をrootページに指定します。
Rails.application.routes.draw do
root to: 'home#index'
end
JSのコンパイル
次に、Vue.js関連のJavaScriptをコンパイルします。
JSのコンパイルにはbin/webpack
を使います。
ただし、2017年2月27日時点では、このコマンドを実行するとエラーが出ます。
$ bin/webpack
(node:41610) DeprecationWarning: loaderUtils.parseQuery() received a non-string value which can be problematic, see https://github.com/webpack/loader-utils/issues/56
parseQuery() will be replaced with getOptions() in the next major version of loader-utils.
Hash: c4d67f31e483f6e6e58e
Version: webpack 2.2.1
Time: 752ms
Asset Size Chunks Chunk Names
hello_vue.js 232 kB 0 [emitted] hello_vue
application.js 3.37 kB 1 [emitted] application
hello_vue.js.map 285 kB 0 [emitted] hello_vue
application.js.map 3.33 kB 1 [emitted] application
[0] ./app/javascript/packs/app.vue 2.05 kB {0} [built] [failed] [1 error]
[1] ./~/vue/dist/vue.common.js 226 kB {0} [built]
[2] (webpack)/buildin/global.js 509 bytes {0} [built]
[3] ./app/javascript/packs/application.js 489 bytes {1} [built]
[4] ./app/javascript/packs/hello_vue.js 415 bytes {0} [built]
ERROR in ./app/javascript/packs/app.vue
Module build failed: Error:
Vue packages version mismatch:
- vue@2.1.10
- vue-template-compiler@2.2.1
This may cause things to work incorrectly. Make sure to use the same version for both.
If you are using vue-loader@>=10.0, simply update vue-template-compiler.
If you are using vue-loader@<10.0 or vueify, re-installing vue-loader/vueify should bump vue-template-compiler to the latest.
at Object.<anonymous> (/Users/jit/dev/sandbox/rails-vue-sandbox-2/node_modules/vue-template-compiler/index.js:8:9)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Module.require (module.js:498:17)
at require (internal/module.js:20:19)
at Object.<anonymous> (/Users/jit/dev/sandbox/rails-vue-sandbox-2/node_modules/vue-loader/lib/parser.js:1:78)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Module.require (module.js:498:17)
at require (internal/module.js:20:19)
at Object.<anonymous> (/Users/jit/dev/sandbox/rails-vue-sandbox-2/node_modules/vue-loader/lib/loader.js:2:13)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Module.require (module.js:498:17)
at require (internal/module.js:20:19)
at Object.<anonymous> (/Users/jit/dev/sandbox/rails-vue-sandbox-2/node_modules/vue-loader/index.js:1:80)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
このエラーを解消するために、yarn upgrade vue
でVue.jsのバージョンを上げてください。
$ yarn upgrade vue
yarn upgrade v0.20.3
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
warning "url-loader@0.5.8" has unmet peer dependency "file-loader@*".
[4/4] 📃 Building fresh packages...
success Saved 1 new dependency.
└─ vue@2.2.1
✨ Done in 4.36s.
こうすると、bin/webpack
の実行に成功するはずです。
$ bin/webpack
bin/webpack
(node:60134) DeprecationWarning: loaderUtils.parseQuery() received a non-string value which can be problematic, see https://github.com/webpack/loader-utils/issues/56
parseQuery() will be replaced with getOptions() in the next major version of loader-utils.
Hash: 5324517c32736f3529f8
Version: webpack 2.2.1
Time: 1043ms
Asset Size Chunks Chunk Names
hello_vue.js 249 kB 0 [emitted] hello_vue
application.js 3.37 kB 1 [emitted] application
hello_vue.js.map 309 kB 0 [emitted] hello_vue
application.js.map 3.33 kB 1 [emitted] application
[0] ./app/javascript/packs/app.vue 1.21 kB {0} [built]
[1] ./~/vue/dist/vue.common.js 242 kB {0} [built]
[2] ./~/vue-loader/lib/component-normalizer.js 1.15 kB {0} [built]
[3] ./~/vue-loader/lib/template-compiler.js?{"id":"data-v-20bbc081"}!./~/vue-loader/lib/selector.js?type=template&index=0!./app/javascript/packs/app.vue 407 bytes {0} [built]
[4] (webpack)/buildin/global.js 509 bytes {0} [built]
[5] ./app/javascript/packs/application.js 489 bytes {1} [built]
[6] ./app/javascript/packs/hello_vue.js 415 bytes {0} [built]
hello_vue.jsの動作確認
それではrails serverを起動しましょう。
$ rails s
http://localhost:3000 にアクセスして、次のような画面が表示されればOKです。
(Rails上でVue.jsが実行できました。おめでとうございます!)
Commitログ
Create home#index · JunichiIto/rails-vue-sandbox@470fc67
Vue.jsサンプルアプリケーションの作成
さて、Vue.jsが動作する環境が整ったので、サンプルアプリケーションを作成に移りましょう。
今回はこちらのページにあるコードを使わせてもらいます。
View、JS、CSSをそれぞれ次のように書き換えてください。
<!-- component template -->
<script type="text/x-template" id="grid-template">
<table>
<thead>
<tr>
<th v-for="key in columns"
@click="sortBy(key)"
:class="{ active: sortKey == key }">
{{ key | capitalize }}
<span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in filteredData">
<td v-for="key in columns">
{{entry[key]}}
</td>
</tr>
</tbody>
</table>
</script>
<!-- demo root element -->
<div id="demo">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<demo-grid
:data="gridData"
:columns="gridColumns"
:filter-key="searchQuery">
</demo-grid>
</div>
import Vue from 'vue'
import App from './app.vue'
// register the grid component
Vue.component('demo-grid', {
template: '#grid-template',
replace: true,
props: {
data: Array,
columns: Array,
filterKey: String
},
data: function () {
var sortOrders = {}
this.columns.forEach(function (key) {
sortOrders[key] = 1
})
return {
sortKey: '',
sortOrders: sortOrders
}
},
computed: {
filteredData: function () {
var sortKey = this.sortKey
var filterKey = this.filterKey && this.filterKey.toLowerCase()
var order = this.sortOrders[sortKey] || 1
var data = this.data
if (filterKey) {
data = data.filter(function (row) {
return Object.keys(row).some(function (key) {
return String(row[key]).toLowerCase().indexOf(filterKey) > -1
})
})
}
if (sortKey) {
data = data.slice().sort(function (a, b) {
a = a[sortKey]
b = b[sortKey]
return (a === b ? 0 : a > b ? 1 : -1) * order
})
}
return data
}
},
filters: {
capitalize: function (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
},
methods: {
sortBy: function (key) {
this.sortKey = key
this.sortOrders[key] = this.sortOrders[key] * -1
}
}
})
// bootstrap the demo
var demo = new Vue({
el: '#demo',
data: {
searchQuery: '',
gridColumns: ['name', 'power'],
gridData: [
{ name: 'Chuck Norris', power: Infinity },
{ name: 'Bruce Lee', power: 9000 },
{ name: 'Jackie Chan', power: 7000 },
{ name: 'Jet Li', power: 8000 }
]
}
})
body {
font-family: Helvetica Neue, Arial, sans-serif;
font-size: 14px;
color: #444;
}
table {
border: 2px solid #42b983;
border-radius: 3px;
background-color: #fff;
}
th {
background-color: #42b983;
color: rgba(255,255,255,0.66);
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
td {
background-color: #f9f9f9;
}
th, td {
min-width: 120px;
padding: 10px 20px;
}
th.active {
color: #fff;
}
th.active .arrow {
opacity: 1;
}
.arrow {
display: inline-block;
vertical-align: middle;
width: 0;
height: 0;
margin-left: 5px;
opacity: 0.66;
}
.arrow.asc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid #fff;
}
.arrow.dsc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #fff;
}
これらのコードを組み込んだら、JSをコンパイルします。
$ bin/webpack
そしてブラウザをリロードすると、jsfiddleで表示されていた画面と同じ画面が表示されるはずです。
ソートや検索も動作することを確認しておきましょう。
Commitログ
Create sample app · JunichiIto/rails-vue-sandbox@a46704f
Herokuへのデプロイ
ローカルで動かしておしまい、ではなく、本番環境でも使えるようにしましょう。
今回はHerokuにデプロイしてみます。
(HerokuのアカウントやCLIツールは事前にセットアップしておいてください)
コードの変更
まず、Gemfileを変更します。変更箇所は以下の3点です。
- Rubyのバージョンを指定する
- pg gemをインストールする
- sqlite3 gemを
:development, :test
グループに移動させる
source 'https://rubygems.org'
+ruby '2.4.0'
+
git_source(:github) do |repo_name|
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
"https://github.com/#{repo_name}.git"
@@ -8,8 +10,7 @@ end
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.1.0.beta1'
-# Use sqlite3 as the database for Active Record
-gem 'sqlite3'
+gem 'pg'
# Use Puma as the app server
gem 'puma', '~> 3.7'
# Use SCSS for stylesheets
@@ -35,6 +36,7 @@ gem 'jbuilder', '~> 2.5'
# gem 'capistrano-rails', group: :development
group :development, :test do
+ gem 'sqlite3'
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
# Adds support for Capybara system testing and selenium driver
Gemfileを変更したらbundle install
を実行します。
続いて、プロジェクトのルートディレクトリにProcfile
を作成します。
web: bundle exec puma -p $PORT
Commitログ
Setup for Heroku · JunichiIto/rails-vue-sandbox@8c1827d
Herokuインスタンスの作成とデプロイ
Herokuへのデプロイは次のようにコマンドを入力していってください。
# インスタンスの作成
$ heroku create
# add-onの追加
$ heroku addons:create heroku-postgresql:hobby-dev
# ビルドパックの追加
$ heroku buildpacks:add --index 1 heroku/nodejs
$ heroku buildpacks:add --index 2 heroku/ruby
# デプロイとマイグレーション実行
$ git push heroku master
$ heroku run rake db:migrate
# Heroku上のアプリケーションを開く
$ heroku open
通常のデプロイと変わっている点はビルドパックを追加するところです。
これを追加しないと、デプロイ時にエラーが発生します。
参考:Introducing Webpacker – Statuscode – Medium
Heroku上のアプリケーションを開いたときに、ローカルと同じように動作すればOKです!
webpack-dev-serverを有効にする
さて、今のままだと、Vue.js用のJSを変更するたびに毎回bin/webpack
を実行しなければいけません。
これはちょっと面倒なので、webpack-dev-serverという機能を利用して、JSを変更したらすぐに変更が反映されるようにしましょう。
まず、ルートディレクトリにProcfile.dev
というファイルを作成します。
web: bundle exec rails s
# watcher: ./bin/webpack-watcher
hot: ./bin/webpack-dev-server
それから、bin
ディレクトリの下にserver
という名前のファイルを作成します。
#!/bin/bash -i
bundle install
bundle exec foreman start -f Procfile.dev
server
は実行可能なファイルにしておきます。
$ chmod 777 bin/server
また、server
ではforeman
を使うので、Gemfileに追加してbundle install
します。
group :development do
# ...
+ gem 'foreman'
end
最後に、config/environments/development.rb
にある、config.x.webpacker[:dev_server_host] = "http://localhost:8080"
のコメントを外します。
Rails.application.configure do
# Make javascript_pack_tag load assets from webpack-dev-server.
- # config.x.webpacker[:dev_server_host] = "http://localhost:8080"
+ config.x.webpacker[:dev_server_host] = "http://localhost:8080"
rails s
でサーバーを起動していたら停止し、bin/server
でサーバーを起動してください。
サーバーが起動したら http://localhost:5000/ にアクセスします。
こうすると、hello_vue.js
への変更が、即座に画面に反映されるようになります。
Commitログ
Setup for webpack-dev-server · JunichiIto/rails-vue-sandbox@07d8a79
Vue.js用のシステムテストを書く
最後に、この画面のテストコードも書いておきましょう。
Rails 5.1ではSystemTestCaseという新しいテストケースクラスが導入されました。
これを使うと、JavaScriptを利用する画面のテストが書けます。
SystemTestCaseではChromeDriverというドライバを使います。
これは事前にセットアップが必要です。
以下の記事を参考にしてセットアップしてください。
Rails 5.1のSystemTestCaseを試してみた - Qiita
ChromeDriverをセットアップしたら、テストコードを作成します。
今回はtest/system/home_test.rb
というファイルを作成します。
require "application_system_test_case"
class HomeTest < ApplicationSystemTestCase
test "visiting the index" do
# トップページを開く
visit root_path
# 4人とも表示される
assert_text 'Chuck Norris'
assert_text 'Bruce Lee'
assert_text 'Jackie Chan'
assert_text 'Jet Li'
# "J"でフィルタリングする(2人表示)
fill_in 'query', with: 'J'
assert_no_text 'Chuck Norris'
assert_no_text 'Bruce Lee'
assert_text 'Jackie Chan'
assert_text 'Jet Li'
# "Jet"でフィルタリングする(1人だけ表示)
fill_in 'query', with: 'Jet'
assert_no_text 'Chuck Norris'
assert_no_text 'Bruce Lee'
assert_no_text 'Jackie Chan'
assert_text 'Jet Li'
end
end
なお、コントローラのテストは使わないので中身を消しておきます。
require 'test_helper'
class HomeControllerTest < ActionDispatch::IntegrationTest
# 削除
end
テストコードが書けたら、テストを実行します。
$ rake
Run options: --seed 19056
# Running:
Puma starting in single mode...
* Version 3.7.1 (ruby 2.4.0-p0), codename: Snowy Sagebrush
* Min threads: 0, max threads: 1
* Environment: test
* Listening on tcp://0.0.0.0:64816
Use Ctrl-C to stop
.
Finished in 3.184606s, 0.3140 runs/s, 0.0000 assertions/s.
1 runs, 0 assertions, 0 failures, 0 errors, 0 skips
このようにすると、モダンなJavaScriptフレームワークを使っている場合でもちゃんとテストを書くことができます。
Commitログ
Test for home#index · JunichiIto/rails-vue-sandbox@caebff1
応用:Vue.jsではなくReactやAngularを使う
今回はVue.jsを使いましたが、ReactやAngular(+TypeScript)を使う場合も基本的な流れは同じです。
rails webpacker:install:vue
の代わりに、rails webpacker:install:react
やrails webpacker:install:angular
を実行するとReactやAngular用のセットアップが完了します。
詳しくはWebpacker gemのREADMEをご覧ください。
参考:おおまかなディレクトリ&ファイル構成
今回のVue.jsアプリケーションに関連しそうなディレクトリとファイルだけを、独断でピックアップしてtree表示してみました。
各ファイルはこんな感じで配置されています。
.
├── app
│ └── javascript
│ └── packs
│ ├── app.vue
│ ├── application.js
│ └── hello_vue.js
├── bin
│ ├── webpack
│ ├── webpack-dev-server
│ ├── webpack-watcher
│ └── yarn
├── config
│ └── webpack
│ ├── development.js
│ ├── production.js
│ └── shared.js
├── node_modules
│ └── vue
├── package.json
├── public
│ └── packs
│ ├── application.js
│ ├── application.js.map
│ ├── hello_vue.js
│ └── hello_vue.js.map
└── yarn.lock
まとめ
というわけで、この記事ではRails 5.1でVue.jsのサンプルアプリケーションを作成する手順を説明しました。
これまではモダンなJavaScriptフレームワークに対して「いっぱいツールがあって、スタートラインに立つまでが大変」という印象を持っていました。
しかし、Rails 5.1を使うとあっという間にVue.jsやReactの開発環境を構築することができました。
これはとてもすごいことだと思います。
もちろん、ここで作成したのはHello, worldレベルのサンプルアプリケーションの作成です。
実務で使うためにはもっといろんな知識が必要になるはずです。
とはいえ、「とりあえず動かして試せる環境」があるのとないのでは大違いです。
動かせる環境があれば、「何をどうすればどう変わるのか」が理解しやすくなるので、漠然と技術記事を眺めるよりもぐんと学習効率がアップすると思います。
この記事を参考にして、みなさんもぜひ手元でモダンなJSフレームワーク環境を構築してみてください!
あわせて読みたい
僕が書いたRails 5.1関連のQiita記事です。
こちらもあわせてどうぞ。
Special thanks
Rails 5.1で導入されたJSの新機能については、以下の記事を参考にさせてもらいました。
どうもありがとうございました。