はじめに
最近、Vue.jsとRailsでアプリを作っているのですが、Vue.jsとRailsでアプリを作る記事が少なく、勉強するのに少し不便でした。
Vue.js × Railsの記事が少ないと言っても探せばそれなりに見つかるのですが、私みたいなフロントエンドの事よくわかってない人間には、理解するのに時間がかかったりします。
ネットで記事をあさったり、そもそもJavaScriptが良くわかってないので、JavaScriptから勉強し直してみたり、Vue.js × Railsでアプリを作るだけにしては非常に遠回りしてしまいました。
この記事について
この記事は、私みたいにVue.js × Railsのアプリ作成で遠回りな勉強をしている人をなくす事を目的としています。
初心者向けにVue.js × Railsでアプリを作る記事を書いて、実装のイメージを掴んでもらえれば、私のような遠回りはなくなるはず。。。1度小さいアプリを作ってしまえば、理解度がグッと上がり、他の記事も読みやすくなるのできっと大丈夫!
また、この記事は私がRubyエンジニアなので、Rubyエンジニアから見てVue.jsをどう実装しようかという視点になってます。
この記事を読む対象のレベル
Vue.js × Rails両方ともチュートリアルをやったくらいのレベルを対象としています。
Vue.jsもRailsもだいたいこんな感じというのがわかっていれば作れると思います。
どんなものを作るか? Vue.js × Railsそれぞれの役割とは?
この記事では、Vue.js × RailsでミニマムなToDoリストアプリを作っていきます。定番ですね。
私はToDoアプリを作ろうとした時、Vue.js × Railsがそれぞれどんな役割をしているのかよくわからず、実装のイメージが掴めなかったのですが、いろんな記事を読んだ結果、RailsでAPIを作り、APIへのリクエストと返ってきたデータの表示をVue.jsで行うというのが多かったです。今回もこの役割分担でアプリを作っていきます。
WEB業界での経験が浅い、もしくはこれからWEB業界を目指す方はAPIのイメージがつかみにくいかもしれませんが、一言で言うとURLのリクエストを受けたら、URLに応じたデータを返すものです。
この役割のイメージが分かればRailsの部分をFirebaseに置き換えようとか、Vue.jsをReactに置き換えようとか応用が効くような気がします。
(注)私は現時点でFirebaseもReactも詳しくないので応用が効く気がするというだけ。。。詳しい方がいたら教えて下さると助かります。
完成後のイメージ
- テキストボックスにタスクを入力して追加ボタンを押すとリストに追加されて表示される。
- チェックボックスをチェックすると取り消し線が引かれる
- 削除ボタンを押すと削除
実際に作ってみよう
RailsでAPIを作る
では、実際にアプリを作ってきます。まずはRailsでAPIを作るところから。
以下のコマンドで、Railsアプリを作ります。--webpack=vue
をするとwebpackとVue.jsのインストールが出来ます。
rails new todo_list --webpack=vue
「webpackとかまた新しい言葉出すなよ!」って方は以下のリンクを見てください!
【5分でなんとなく理解!】Webpack入門
Webpackとは、js、cssなどフロントで作るファイルをバンドリングしてくれるものです。
ToDoリストを表示するHome画面を作成
ToDoを表示するHome画面を作るため、コントローラーを作成します。
rails g controller home
作成したコントローラーにindexだけ追加しておきましょう。
class HomeController < ApplicationController
def index
end
end
viewからVue.jsが呼び出せるか試しますために追加します。
<%= javascript_pack_tag 'hello_vue' %>
routes.rbに以下を追加。
Rails.application.routes.draw do
root to: 'home#index'
end
rails s
してlocalhost:3000
にアクセスします。
以下のような画面が表示されてればOKです。
ちなみにVue.jsを変更したら
bin/webpack
で更新してあげる必要があります。(重要)
APIの処理を作る
まずは、ToDoリストにタスクを追加するためにモデルを作っていきます。
rails generate model Task name:string is_done:boolean
ルーティングに以下を追加します。
表示用のhomeとデータを返すAPI用のapi::tasksを追加。
Rails.application.routes.draw do
root to: 'home#index'
namespace :api, format: 'json' do
resources :tasks, only: [:index, :create, :destroy, :update]
end
end
リクエストを受けたらデータを返すため、Tasksのコントローラーを作ります。
rails g controller Api::Tasks
マイグレーションします。
rails db:migrate
コントローラーの中身は以下のような感じ。
module Api
class TasksController < ApplicationController
skip_before_action :verify_authenticity_token
def index
@tasks = Task.order('created_at DESC')
end
def create
@task = Task.new(task_params)
if @task.save
render json: @task, status: :created
else
render json: @task.errors, status: :unprocessable_entity
end
end
def destroy
Task.find(params[:id]).destroy!
end
def update
Task.find(params[:id]).toggle!(:is_done)
end
private def task_params
params.require(:task).permit(:name, :is_done)
end
end
end
APIを返す時は、htmlではなくJSONで返してあげたいので以下を追加します。
自分は実際にWEB業界に入るまでJSONに馴染みがなかったのですが、以下の形で書きます。
json.set! :tasks do
json.array! @tasks do |task|
json.extract! task, :id, :name, :is_done, :created_at, :updated_at
end
end
APIの動作確認
DBにデータを入れて確認してみましょう。
コンソールを立ち上げます。
rails c
Taskモデルにデータを追加してみましょう。
Task.create(name: 'テスト用タスク')
もう一度サーバー立ち上げ
rails s
以下のアドレスで追加したデータがJSONでデータが返ればOK。
Vue.jsでフロント作成
コンポーネント
コンポーネントとは、名前付きの再利用可能な Vue インスタンスです。
再利用出来そうなパーツごとにコンポーネントを区切って実装するのが、どうやら重要らしい。
今回、最小限の構成でアプリを構成するためコンポーネントについては省こうか迷ったのですが、重要なので組み込みます。
わかりやすいイメージで言うと、ヘッダー、フッター、サイドナビ等は色んなページで再利用するのでコンポーネントを分けて実装するといった感じでしょうか。
今回もヘッダーとToDoリストを表示するボディ部分でコンポーネントを分けて実装したいと思います。
では、まず以下のようにToDoリスト表示部分を作って下さい。
<div id="app">
<navbar></navbar>
</div>
<%= javascript_pack_tag 'todo' %> # todoに変更する事に注意
はヘッダーのコンポーネントを表示します。
<%= javascript_pack_tag 'todo' %>でapp/javascript/packs配下のtodo.jsファイルを読み込みます。
ヘッダーの作成
まずはヘッダー部分のコンポーネントを用意します。
<template>
<h1>ToDoリスト</h1>
</template>
次に/app/views/home/index.erb
から呼び出されているapp/javascript/packs/todo.js
にコンポーネントの設定をしていきます。
以下のように書くとapp/views/home/index.erb
の<navbar></navbar>
に/app/javascript/packs/components/header.vue
をマウントして表示してくれるようです。
import Vue from 'vue/dist/vue.esm.js'
import Header from './components/header.vue'
var app = new Vue({
el: '#app',
components: {
'navbar': Header
}
});
Vue.jsの読み込み設定
webpackはVue.jsの読み込み方がわからないので以下を実行します(重要)
module.exports = {
test: /\.styl$/,
use: [
'style-loader', 'css-loader', 'stylus-loader'
]
}
const { environment } = require('@rails/webpacker')
const { VueLoaderPlugin } = require('vue-loader')
const vue = require('./loaders/vue')
const stylus = require('../loaders/stylus') // 作ったstylusをrequireする
environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin())
environment.loaders.prepend('vue', vue)
module.exports = environment
environment.loaders.prepend('stylus', stylus) // 作ったstylusをロード
webpackを再読み込みしてからサーバーを再起動しましょう(重要)
bin/webpack
rails s
rails sしてヘッダーが表示されて入ればOKです
ToDoリストを表示するボディ部分
axiosというライブラリを使って、フロントエンドからHTTPリクエストをします。
以下のコマンドでyarnでaxiosを追加して下さい。
yarn add axios
ToDoアプリのメイン部分の実装です。解説は後ほど詳しく説明します。
<template>
<div>
<div>
<input v-model="newTask" placeholder="to doを追加して下さい">
<div v-on:click="createTask">
<i>追加</i>
</div>
</div>
<ul>
<li v-for="(task, index) in tasks">
<input type="checkbox" v-model="task.is_done" v-on:click="update(task.id, index)">
<span v-bind:class="{done: task.is_done}">{{ task.name }}</span>
<button v-on:click="deleteTask(task.id, index)">削除</button>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
export default {
data: function () {
return {
tasks: [],
newTask: ''
}
},
mounted: function () {
this.fetchTasks();
},
methods: {
fetchTasks: function () {
axios.get('/api/tasks').then((response) => {
for(let i = 0; i < response.data.tasks.length; i++) {
this.tasks.push(response.data.tasks[i]);
}
}, (error) => {
console.log(error, response);
});
},
createTask: function () {
if(this.newTask == '') return;
axios.post('/api/tasks', { task: { name: this.newTask } }).then((response) => {
this.tasks.unshift(response.data);
this.newTask = '';
}, (error) => {
console.log(error, response);
});
},
deleteTask: function (task_id, index) {
axios.delete('/api/tasks/' + task_id).then((response) => {
this.tasks.splice(index, 1);
}, (error) => {
console.log(error, response);
});
},
update: function (task_id) {
axios.put('/api/tasks/' + task_id).then((response) => {
}, (error) => {
console.log(error);
});
}
}
}
</script>
作ったindex.vueを読み込んであげましょう。
import Vue from 'vue/dist/vue.esm.js'
import Header from './components/header.vue'
import Index from './components/index.vue' // 追加
var app = new Vue({
el: '#app',
components: {
'navbar': Header,
'contents' : Index // 追加
}
});
ヘッダーのしたにindex.vueを表示するため、<navbar></navbar>
の下に<contents></contents>
を追加します。
<div id="app">
<navbar></navbar>
<contents></contents>
</div>
<%= javascript_pack_tag 'todo' %>
チェックボックスが押されたら取り消し線を表示するためcss追加。
#app li > span.done {
text-decoration: line-through;
}
rails s
して動くか確認してみて下さい。
実際にToDoリストを追加してみましょう。
ToDoアプリのコード解説メモ
学習し始めだと、Vue.jsのどの行が何をやっているのかわからない事があったのでメモ付きのコードをのせます。
まずはtemplate
<template>
<div>
<div>
<input v-model="newTask" placeholder="to doを追加して下さい">
# 追加ボタンを押すとcreateTaskを実行する
<div v-on:click="createTask">
<i>追加</i>
</div>
</div>
<ul>
# fetchしたタスク一覧(tasks)から一つずつtaskとindexを取り出す処理
<li v-for="(task, index) in tasks">
# チェックボックスが押されたらv-modelのis_doneを変更して取り消し線を引く
# updateでAPI側のデータも更新
<input type="checkbox" v-model="task.is_done" v-on:click="update(task.id, index)">
# タスクの表示。v-bind:classでis_doneを参照して取り消し線が引かれるかどうか判定
<span v-bind:class="{done: task.is_done}">{{ task.name }}</span>
# 削除ボタンを押すとdeleteTaskを実行
<button v-on:click="deleteTask(task.id, index)">削除</button>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
export default {
data: function () {
return {
tasks: [],
newTask: ''
}
},
mounted: function () {
this.fetchTasks();
},
methods: {
// APIからタスク一覧を取得
fetchTasks: function () {
axios.get('/api/tasks').then((response) => {
for(let i = 0; i < response.data.tasks.length; i++) {
this.tasks.push(response.data.tasks[i]);
}
}, (error) => {
console.log(error, response);
});
},
// 新しいタスク作成
createTask: function () {
// テキストボックスが空の場合はreturnして終了
if(this.newTask == '') return;
// apiへ追加リクエスト
axios.post('/api/tasks', { task: { name: this.newTask } }).then((response) => {
// unshiftで現在のtasksの先頭にタスクを追加
this.tasks.unshift(response.data);
// 追加したらテキストボックスを空にする
this.newTask = '';
}, (error) => {
console.log(error, response);
});
},
// タスク削除
deleteTask: function (task_id, index) {
// apiへ削除リクエスト
axios.delete('/api/tasks/' + task_id).then((response) => {
this.tasks.splice(index, 1);
}, (error) => {
console.log(error, response);
});
},
// タスク更新。今回はis_doneのみ更新だが、タスク名とか色々更新するようカスタムしても良いと思う
update: function (task_id) {
// apiへ更新リクエスト
axios.put('/api/tasks/' + task_id).then((response) => {
}, (error) => {
console.log(error);
});
}
}
}
</script>
まとめ
小さいアプリをとりあえず作ってみると理解度がかなり深まると思うので、今回のようなToDoアプリを作ってみると良いと思います。
かけ足で記事を書いてしまったのですが、これで私みたいな人間を救えるのか...???
今後も私のようにVue.js × Railsでアプリを作りたい人向けに記事を改訂して行きたいです。