やりたいこと
RailsのView側でVue.jsを連携させて、柔軟な処理をやりたい。元々これをやっていたプロダクトを事前に触っていたが、どういう実装だったのかそもそも知らなかったので後追いで学習してみた。
前勉強
そもそもvue.jsとかMVVMとかよくわからない。最低でも単体で何ができるのか、概要を掴むために、自分が追った道筋。
Vue.jsではじめるMVVM入門 | DeNA DESIGN BLOG ここでvue.jsはそもそもどんなものなのか、書き方と大まかな流れを理解
大規模アプリケーションの構築 - vue.js いわゆるチュートリアル。このリンクのページが一番勉強になったが初めから追って書き方をなんとなくインプットした。
vue-cliで始めるVue.jsチュートリアル (1) - Qiita 改めて実装系の記事を読んだ。結構流れが掴めるようになってきた。
Vue.js + Vuexでデータが循環する全体像を図解してみた - Qiita
Vuexというほぼ必須パーツのようなものの説明。これ含め、vue.jsで何ができるのかがだいたいわかったので目的達成。
RailsとVue.jsでデータを上手いこと受け渡す
ざっとみた感じ、方針は2つかと思います(多分)
vue.jsに別途Rails側で新たに用意したAPIを叩かせて、レコードデータを取得するRailsのコントローラからビューに渡されたモデルのインスタンスをページにベタがきして、id属性なんかを頼りにvue.jsに読ませる。
1は今までビューのコントローラがになっていた機能をわざわざ別に実装しなおすと言う、様々な面から良くない実装になりそうなので避けたい。となると、2を採用するのですが、取り決めをしっかりとしないと、ビューごとに受け渡しかたが違ってメンテしづらい読みづらいと地獄を見そうです。なので、上手いこと取り決めを作ってくれるものがあると、上手いこといきそう。
rails_vue_melt
vue_meltの導入により、少なくとも、ビューごとにvue.jsへのデータを渡し方がバラバラになってしまう(複数人プロジェクトなら尚更)と言う問題を解決できそうです。
rails_vue_meltの導入
参考サイト。初めはこの方達の記事を参考に進めていきます。
【動画付き】Rails 5.1で作るVue.jsアプリケーション ~Herokuデプロイからシステムテストまで~ - Qiita
今回の環境(Mac OS)
$ bin/rails -v
Rails 5.2.3
$ ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]
# Gemfile.lockより
rails_vue_melt (0.2.0)
Gemfileを編集し、webpackerとrails_vue_meltを導入します。
#Gemfile gem "rails_vue_melt" gem "webpacker"
webpackerをインストールします。Yarn`がインストールされている必要があるので、まだの方は別途調べて導入してください。
$ yarn -v
1.16.0
$ bin/rails webpacker:install
RAILS_ENV=development environment is not defined in config/webpacker.yml, falling back to production environment
create config/webpacker.yml
Copying webpack core config
create config/webpack
....
....
....
Webpacker successfully installed 🎉 🍰
vueをインストールします。このとき生成されるhello_vue.js,app.vueの2ファイルはデモファイルで、vue_meltの導入時に要らなくなるので削除して大丈夫だと思います。
$ rails webpacker:install:vue
Copying vue loader to config/webpack/loaders
create config/webpack/loaders/vue.js
...
...
次に下の通り進めることでapp/javascript/packs/vue_melt/にvue_meltに必要なファイルが生成されます。
$bundle install
$bin/rails g vue_melt
create app/javascript/packs/vue_melt
create app/javascript/packs/vue_melt/application.js
create app/javascript/packs/vue_melt/components/Hello.vue
create app/javascript/packs/vue_melt/options/users.js
create app/javascript/packs/vue_melt/store/actions.js
create app/javascript/packs/vue_melt/store/getters.js
create app/javascript/packs/vue_melt/store/index.js
create app/javascript/packs/vue_melt/store/mutation-types.js
create app/javascript/packs/vue_melt/store/mutations.js
insert app/views/layouts/application.html.erb
https://qiita.com/midnightSuyama/items/efc5441a577f3d3abe74#css
ここで説明されているように、お好みでcssファイルに関しての設定をしてあげます。config/webpack/environment.jsに以下を記述
# config/webpack/environment.js
environment.loaders.get('vue').options.extractCSS = false
config/webpack/loaders/vue.jsで設定を反映します。slimを利用している場合、若干デフォルトに追記する必要あり。
#config/webpack/loaders/vue.js
...
module.exports = {
test: /\.vue(\.erb|\.slim)?$/,
use: [
{
loader: 'vue-loader',
options: { extractCSS },
},
],
}
このまま進んでも良いですし、読み込みもとを変更することも可能です。これは、プロダクトが別途、管理画面等を作っているときのフォルダ分け程度の処置です。なんとなくapp/javascript/に入れたくない人とか。下の変更を行ったら、上で生成したpacksフォルダを移動先フォルダ下に移動させます。
#config/webpacker.yml source_path: app/[ここに移動先フォルダをかく]
Viewから読み込めるようにします。vue_meltはapplication.jsを起点に読み込みを始めているので、そこを指定するようです。またturbolinksのキャッシュを切ります。app/views/layouts/application.html.erbに以下を追記します。プロジェクトの設定によってはこの限りではないので(パーシャルに分けてたり)、レンダリングされる時に呼ばれる箇所に記述すれば大丈夫だと思います。
# app/views/layouts/application.html.erb <%= javascript_pack_tag 'vue_melt/application' %> <meta name="turbolinks-cache-control" content="no-cache">
私の環境ではturbolinksを切っていたのでイベントが発火しませんでした。なので、app/[移動先(デフォルトはjavascript)]/packs/application.jsロードタイミングをturbolinks:loadをDOMContentLoadedに変更しました。これはそれぞれの環境に合わせてください。
// document.addEventListener('turbolinks:load', () => { document.addEventListener('DOMContentLoaded', () => { const templates = document.querySelectorAll('[data-vue]') for (const el of templates) { const vm = new Vue(Object.assign(options[el.dataset.vue], { el, store })) vms.push(vm) } })
動作テスト
ここまでで一度動くかテストをしたいので、デフォルトで用意されているのもを使用してもいいですし、自分で動くものを作って試してもokです。vue_meltでは、data-vue属性で指定したjsファイルをpacks/options下から探して対応づけしてくれます。例えばview画面に次のコードを書いたとして
<div data-vue="users"> <p>{{ message }}</p> </div>
呼ばれるjsファイルはpacks/options/users.jsという名前にして
export default { data: () => ({ message: 'Hello Users' }) }
のようなオプションオブジェクトを用意する事で、上手い事してくれます。
ではwebpackを走らせてjsたちをコンパイルします。
$ bin/webpack
いろんなエラーが出ましたが、yarn add OOで追加していきます。例として
Module not found: Error: Can't resolve 'lodash.clonedeep' in #=> $ yarn add lodash.clonedeep
コンパイルが成功したら、bin/rails sでサーバを起動してしっかり動くか確認します。
無事私の手元では動きました。
Railsのレコードを扱う
レコードを扱うにはRailsのView側に渡したインスタンスをjsonにして渡す事で実現します。
data-vue-modelに渡す事で、いい具合に取り込んでくれます。例としてUserモデルがあり、emailを保持していることとします。Viewページはこうなります。
# slimから適当に書き換えているので間違いがあるかもしれません、、 <section "data-vue"="users"> <%= content_tag :ul, 'data-vue-model': "{ \"users\": #{@users.to_json} }" do %> <li v-for="user in users"> id:{{user.id}},{{ user.email }} </li> <% end %> </section>
packs/options/user.jsを作成して、テンプレートを記述しておきます。これがないとエラー起こします。
# ここにmethodを記述すれば、上のviewページからv-on:clickなどに設定できる export default { data: ()=>{}, }
こんな感じで無事表示できました。
v-on:clickなどに定義したmethodを紐づけることで、ここから柔軟な操作などもできるようになる気がします。