FrontEnd

Vue.jsとRailsでTODOアプリのチュートリアルみたいなものを作ってみた【2018.03.24】

投稿日:2018年4月9日 更新日:

はじめに

以前、Qiitaに書いた記事ですが、いくつか現行バージョンではうまくいかないという指摘があったため、やり直してみました。

各バージョンは、2018年3月24日時点の安定版で試しています。

作るもの

簡単なTODOアプリです。

TODOの管理はRailsのAPIで実施します。

前提条件、及び環境

Ruby、Rails、Node.jsの環境をご用意ください。
下記の記事などが分かりやすいです。

また、Webpack、およびVue.jsは yarn でインストールされるため、 yarn もインストールしておいてください。
yarnを使ってみた

本記事を書くにあたり、下記の環境で実施しました。

  • Ruby 2.5.0
  • Ruby on Rails 5.1.5
  • Node.js 9.9.0
  • yarn 1.5.1
  • rails/webpacker 3.3.1
  • Vue.js 2.5.16

Webpack について

複数のJSファイルをひとまとめにするモジュールバンドラーのことです。
複数ファイルに分割して管理しつつ、サーバーへのリクエスト数を削減することができるようです。
最新版で学ぶwebpack 3入門 – JavaScript開発で人気のバンドルツール

準備

まずはもろもろインストールをしていきます。
なお、こちらの記事が動画つきで非常に分かりやすいので、もし初めて実施される方はご覧になった方が良いかと思います。
【動画付き】Rails 5.1で作るVue.jsアプリケーション ~Herokuデプロイからシステムテストまで~

Rails + Vue.jsのプロジェクトを作成する

はじめから --webpack オプションを使用してプロジェクトを作成します。

rails s でRailsのウェルカムページが表示されれば大丈夫です。

Vue.jsの表示確認

基本的に、Railsで用意するビューファイルは1つのみで、そこを差し替えていきます。
まずは、以下のファイルを作成、編集します。

  • app/controllers/home_controller.rb
  • config/routes.rb
  • app/views/home/index.html.erb

javascript_pack_tag を使用することで、 app/javascript/packs 以下にあるJSファイルを探してくれます。
インストール時に hello_vue.js というファイルが生成されているので、これを index にて読み込ませます。
これで rails s して、「Hello Vue!」と表示されれば大丈夫です。

devサーバーを設定する

Vue.js(というよりWebpack)関連のファイルは変更したらコンパイルする必要があります。
コンパイルには、 bin/webpack というコマンドを使用する必要があります。

ただ、毎回コンパイルするのは面倒なので、変更を検出して自動コンパイルするようにします。
こちらの記事でも紹介されております。
Introducing Webpacker

まずは foreman のGemをインストールします。
foremanProcfile から複数のプロセスを管理することができます。
https://github.com/ddollar/foreman

これで bundle install します。
次に下記2点のファイルを作成します。

ファイル名 役割
bin/server Procfile.devのコマンドを実行する
Procfile.dev rails sbin/webpack-dev-server を実行する

また、 bin/server のパーミッションを変更しておきます。

これで bin/server を実行してみると、 http://localhost:5000 で起動します。
(ポート番号が変わります。)
ここで、 app.vue の下記の部分を変えて保存すると、コンパイル処理が走り、画面がリロードされるはずです。
適当に変えて遊んでみて、問題なさそうなら大丈夫です。

APIの準備

サーバーサイドのAPI部分を実装していきます。

テーブルとモデルの生成

テーブル名は tasks とし、下記のようにします。

カラム
name VARCHAR(255) NULL: false
is_done BOOLEAN NULL: false, DEFAULT: false
created_at DATETIME NULL: false
updated_at DATETIME NULL: false

マイグレーションファイルとモデルは rails generate で作成してしまいます。

マイグレーションファイルに null: falsedefault: false を追記するため、下記のように編集します。

マイグレーションを実行します。

ついでに、モデルの name プロパティにバリデーションをつけておきましょう。

コントローラーと返却するJSONファイルを生成

アクセスするURLは /api/tasks のように名前空間を切りたいと思います。
まずはルーティングからです。

次にコントローラーを作成します。
コントローラーは app/controllers の中に api というディレクトリを作成し、そこに作ります。

ビューのJSONファイルも同様に、 views/api/tasks 以下に作成します。

seeds.rbを作成し、curlコマンドで確認

seeds.rb を作成し初期データを作成できるようにします。
もしレコードが増えすぎてもこれでやり直しもできます。

DBに適用します。

なお、リセットする場合は、

curl コマンドを使用してAPIの確認をします。

次にPOSTで新規作成してみます。
ここで恐らくエラーになるかと思います。

log/development.log を確認すると、 ActionController::InvalidAuthenticityToken というエラーのようです。
CSRF対策のトークンがないため、Railsから怒られてしまいます。

application_controller.rb の下記をコメントアウトするとエラーが出なくなります。
本来であれば、API認証のようなものをつけた方が良いとは思いますが、今回は割愛します。
【Rails】RailsでAPIの簡単なトークン認証を実装する

これで作成されたTODOが返却されるかと思います。

Materializeの導入

CSSはMaterializeというフレームワークを使用しようと思います。
マテリアルデザインを意識したものになっており、個人的に使ってみたいと思っておりました。

Gemもすでにあるので、Rails側でインストールします。
https://github.com/mkhairi/materialize-sass

bundle install した後、下記の2ファイルに追記します。
cssscss に変更しておきます。

$primary-color$secondary-color を設定しておくことで、よしなに色を合わせてくれます。
カラースキームはこちらを参照してください。

コンポーネントを使ってヘッダーを作成

ここからVue.jsを中心に画面を作っていきます。
まずはヘッダーを作成します。

元となるビューファイルはRailsの index.html.erb になるので、こちらにVue.jsを載せられるようにします。

<navbar> というタグがありますが、Vue.js側でこのタグとコンポーネントを紐付け、表示します。
コンポーネント
また、新しく todo.js というファイルを app/javascript/packs に作成します。
hello_vue を修正したも良いのですが。。。)

ここで、 import Vue from 'vue/dist/vue.esm.js' としています。
これは、後ほどコンポーネントを使用する際に完全ビルドする必要があるからだそうです。
(すいません、まだよく分かっていません。。。)
詳しくはこちらの方が解説記事を書いてくださっています。
Rails5.1でVue.jsで単一ファイルコンポーネントのエラーがでる

これで index.html.erb 内の <div id="app"> にマウントされます。
このまま実行しても特に何もありません。

それではコンポーネントを作成します。
packs の下に components ディレクトリを作成して、そこに header.vue を作成します。
コンポーネントは .vue で作成します。

これを todo.js に登録します。

navbar という名前でコンポーネントとして登録します。
header だと <header> タグがすでにHTML5に存在しているため。)
これで <navbar> タグが使用できるようになりました。

サーバーを再起動してアクセスすると下図のようなヘッダーができているのではないでしょうか?

Vue-Routerを使用してSPAっぽく

Vue-Routerを使用することで、登録されたパスとコンポーネントで画面内を差し替えることができます。
Vue-Router

yarn を使って vue-router を追加します。

今回は「TODO一覧(メイン画面)」、「アバウト(おまけ)」、「コンタクト(おまけ)」を用意します。
正直、メイン画面でTODO管理はできるので、あと2つはおまけです。

まずはコンポーネントを作成します。

さて、このコンポーネントとパスを登録する router.js を作成します。
こちらも router ディレクトリを作成してそちらに作成します。

パスとコンポーネントを結びつけます。
また、 mode: 'history' とすることで、HTMLのhistory APIを使用して、一見同じビュー内ですがURLを書き換えることができます。
HTML5 Historyモード

また、VueRouterを使用すると、 <router-link><router-view> というタグが使用できます。
<router-link> は、 <a> タグとして変換されますが、画面遷移ではなくVueRouterに登録されたパスからコンポーネントを探します。
そして <router-view> の部分に表示します。

ヘッダーの各リンクを修正します。

それぞれのコンポーネントが表示される部分を index.html.erb に作ります。

最後に、 todo.js に追加します。

これで、ヘッダーの各リンクを押すと本文が切り替わるのではないでしょうか?
URLもHistoryモードのおかげで書き換わっています。

ただ、例えば http://localhost:5000/about でリロードすると、Rails側でエラーになってしまいます。
たしかに routes.rb で登録していません。
とりあえず、 /about でも /contact でも Home#index にとぶよう記述します。

これで http://localhost:5000/about にアクセスするとわかりますが、ちゃんとURLからAboutのコンポーネントを表示してくれます。

Axiosを使ってAPI通信

axiosは、Ajax通信ライブラリです。
まずはこれをインストールします。

Axiosを使用して index.vue にてAPI通信してタスク管理したいと思います。

まずは完成イメージを index.vuetemplate 内に記載します。
これを書き換えていきます。

こんな感じになります。

一覧表示

コンポーネントの中でHTML、JS、CSSをまとめて記載することを単一コンポーネントというようです。
.vue ファイルの中で、そのコンポーネントで使うJSも記述することができます。
一覧をAPIで取得するために必要なものを追記します。

インスタンスにプロパティとして tasksnewTask を与えます。
メソッドとして fetchTasks を登録し、APIで取得してきた値をループさせて tasks に格納します。
AxiosはJQueryと同じ感じで使えるので、使用しやすいかと思います。

mountedはVueインスタンスがマウントされたタイミングで実行されるライフサイクルフックです。
createdもあって今回の場合、あまり違いはありませんが、ライフサイクルダイアグラムについては、ちゃんと理解する必要がありそうです。

template 内の下記の部分を書き換えます。
(未完了と完了済みを両方変えます。)
v-forv-if を使って tasks プロパティの中で条件に合うものを表示しています。
条件付きレンダリング
また、v-bindで強引に id 名を作っています。

完了済みタスクを常に表示させておく必要はないと思います。
display_none というクラスをつけているので、ここに非表示のスタイルをあてたいと思います。
単一コンポーネントではCSSも管理できます。

ただ、デフォルトの設定だと、コンパイル時に別でスタイルシートを出力してしまうので、これを無しにします。
RailsとVue.js の設計覚書

Webpack 3から、下記のように loaders/vue.js の中で extractCSS = false と修正すれば良いようです。

index.vue にスタイルを追記します。
<style> タグ中に scoped という属性をつけておくと、そのファイルのみで有効なスタイルとして認識してくれます。
そのコンポーネントでしか使わないようなクラスはここで定義してしまえば良さそうです。

また、APIの返り値を使用してレンダリングする場合、API処理が終わるまでは、 {{ task.name }} がそのまま文字列としてレンダリングされてしまいます。
(インスタンスに値がセットされたら、その値が表示されます。)
このとき、v-cloakというディレクティブをCSSの display: none; と組み合わせて使うと、インスタンスが生成されたタイミングで表示してくれます。

これで完了済みの方は非表示になったかと思います。

ボタンを押すと、完了済みエリアが表示される

まずはVueの methods にボタンを押されたときのメソッドを登録します。

「ボタンを押されたとき」と記載するのはかなり簡潔にかけます。
テンプレートの方でv-onを使用してクリックされたタイミングで displayFinishedTasks を呼んでもらいます。

新規作成フォームをつくる

v-modelを利用することで、双方向バインディングさせることができます。
これで <input> タグで入力された値とインスタンスの newTask プロパティをバインドさせます。

次に新規作成のメソッドを追加します。
APIで新規作成できた場合は、 tasks プロパティの先頭に追加するようにします。

チェックをつけたら完了済みに移す

まずはチェックボックスにチェックがついたら更新メソッドを呼ぶようにしたいと思います。
(この辺りは毎回呼ばずに、一定時間で同期させても良いかもしれません。)

更新用のメソッドを追加します。
更新したタイミングで、未完了部分から消し、完了済みの方に追加します。
(このあたりの処理はもっと良い書き方がある気がします。。。)

ここまでで、下図のような動きができるかと思います。
(いくつかスタイルはたしました。)

テストコードを書いてみる

試しにテストコードを書いてみました。
RailsのSystemTestCaseで書いております。
理由は、Node.jsのテストフレームワークは(私にとって)学習コストが高いためです。
本当はフロントエンドと分けるのであれば、テストも分けた方が良いのではないかと思っています。
https://github.com/naoki85/todo_app_with_vue_and_rails/commit/4a5225395fa2fe6ba9c7a3f8a421648ab28c897c

テストの中で、 within を使用していますが、今回の場合は使用する必要はないと思います。
今後、さらに詳細なテストをする際に使用するかもと思い、残したままにしてあります。

まだ記事にできるほど試していないため、参考程度にご覧ください。

さいごに

今回は、状態管理に便利なVuexは今回できなかったため、機会があればまた書きたいと思います。
もしよろしければご意見いただけると嬉しいです。
参考にさせていただいたドキュメント、記事などは下記になります。

blog-page_footer_336




blog-page_footer_336




-FrontEnd
-,

執筆者:


  1. 匿名 より:

    extractCSS = falseの設定ですが、こちらの記述をloader/vue.jsにするのが正しいです。

    //コメントアウト
    // const extractCSS = !(inDevServer && (devServer && devServer.hmr)) || isProduction
    //これを追記
    const extractCSS = false

  2. 匿名2 より:

    とても参考になりました。ありがとうございます。

    補足ですが、index.vue の newTask ボタンに v-on:click=”createTask” の記載が漏れているようです。ご確認ください。

comment

メールアドレスが公開されることはありません。

CAPTCHA


関連記事

JQueryとmark.jsでマークダウンのリアルタイムプレビューをつくる

1 はじめに1.1 環境2 mark.js2.1 公式ドキュメント2.2 インストール2.3 実際に使用してみる2.4 オプションについて3 プレビュー機能3.1 vue.jsに関して3.2 JQue ...

Vue.js入門その1〜基本文法〜

はじめに 軽量JSフレームワークとして有名なVue.js。 最近、Laravelに触れる機会が増えたことと、以前からRails + Vueという構築を耳にするので、今更ではありますが勉強を始めようと思 ...

Vuexの機能と使い方

1 はじめに2 Vuexとは2.1 Single Source of Truth2.2 状態の取得、更新のカプセル化2.3 単方向データフロー3 Vuexのストア3.1 ステート3.2 ゲッター3.3 ...

react-icon

React Big Calendarの複数タイムゾーン対応

1 はじめに2 端末のタイムゾーンのみに対応する場合3 複数のタイムゾーンに対応する。3.1 日時を指定のタイムゾーンに変換する3.2 イベントの開始日時・終了日時を動的に設定する3.3 ラップクラス ...

tailwindcss

Tailwind CSSの基礎とVue.jsへの導入

1 はじめに2 Tailwind CSSとは2.1 utility class3 Bootstrapとの比較3.1 柔軟性3.2 コード量3.3 学習コスト3.4 ファイルサイズ3.5 その他4 なぜ ...

フォロー

blog-page_side_responsive

2018年4月
1234567
891011121314
15161718192021
22232425262728
2930  

アプリ情報

私たちは無料アプリもリリースしています、ぜひご覧ください。 下記のアイコンから無料でダウンロードできます。