もう新年を迎えて2ヶ月が経ちますね。 多くの人は新年の目標を立てますが、皆さんは何かしら立てましたでしょうか? 英語を毎日勉強するという目標を立てましたが、既に挫折してしまったエンジニアの村上(pipopotamasu (pipopotamasu) · GitHub)です。
本日はその懺悔も込めてVue.jsとRailsの話をお送りします。
この記事を書く背景
以前ブログで書いた通り、現在Webpackerへの移行を機にフロントエンド周りの改善を進めています。
tech.medpeer.co.jp
その中でVue.jsとRailsをいい感じに組み合わせるにはどうしたら良いかについて悩むことがあったので、本記事にて共有させていただきます。
悩んだ内容としては以下になります。
1. Ajax通信時にCSRFトークンをどう設定すればいいか?
2. 単一ファイルコンポーネントで書くHTMLをもっと効率よく書けないか?
3. 単一ファイルコンポーネントとフォームヘルパーの兼ね合い
実行環境
今回の記事ではサンプルコードを多く載せています。主なもののバージョンは以下になります。
Rails 5.1.4
Webpacker 3.2.1
Vue.js 2.5.13
axios 0.17.1
pug 2.0.0-rc.4
1.Ajax通信時にCSRFトークンをどう設定すればいいか?
RailsではAjax通信時、破壊的なHTTPメソッド(POSTとかDELETEとか)を送る場合はCSRFトークンの検証が必要となります。 jquery-railsのGemで上記のようなAjax通信を行う場合は、Gemの方でCSRFトークンをリクエストヘッダーに自動的に含めてくれるのですが、jQuery以外だとそうはいきません。 自身でCSRFトークンをリクエストヘッダーに含める必要があります。
ではどういった風にVue.jsでCSRFトークンをリクエストヘッダーに含めたら良いのでしょうか? 私自身どうしたらいい感じに実装できるか悩んだため、ここで悩んだ末にたどり着いた実装をご紹介します。
なお、今回のケースではHTTPクライアントとしてaxiosを使用します。
リクエストヘッダーにCSRFトークンを仕込んでみる
通常、Railsが提供しているフォームヘルパーを利用せずにAjax通信を行う場合は明示的にCSRFトークンを設定する必要があります。
設定しない場合
まずはAjaxを送る部分のソースを見てみます。
上記のようにCSRFトークンを設定しない状態だと...
このようなエラーが起こるので、ちゃんと通るようにしてみましょう。
設定する場合
今度はCSRFトークンを設定する場合です。
上記の10行目でCSRFトークンの取得、11行目でリクエストヘッダーにトークンを設定しています。
今回は上手く処理されました。
この方法はViewのDOM内のCSRFトークンを直接取得し、axiosに設定する方法です。
実はこれと同じ方法を提供してくれるパッケージがあります。 次はそのパッケージ、「rails-ujs」を用いた方法を見てみましょう。
rails-ujsを使ってみる
まずはrails-ujsをインストールします。
yarn add rails-ujs
次にrails-ujsからcsrfTokenを取得するメソッドをimportし、CSRFトークンを設定してみましょう。
これで先ほどの例のように破壊的なリクエストを送ることができるようになります。
Vue.jsのPluginにしてみる
ここまででCSRFトークンを設定するところまでみてきました。 上記の例だと、1つのエントリーファイルに1つのコンポーネントしかありませんでしたが、実際には複数のコンポーネントを使用することが多いと思います。
その場合、上記の例だとaxiosで破壊的なHTTPメソッドを使用する全てのコンポーネントに...
axiosをimport
rails-ujsをimport
リクエストヘッダーにCSRFトークンを設定
しなければなりません。正直面倒です。
そこで、Vue.jsのプラグインを作成し上記の処理を一纏めにしてしまいましょう。
まずはVue.jsのPluginを実装します。Pluginを実装することにより、Vueオブジェクトからaxiosを呼び出せるようにしてみましょう。
- app/javascript/plugins/vue-axios.js
次はエントリーファイルにて上記で作成したPluginをVueに組み込みます。
- app/javascript/packs/chapter1/index.js
最後にコンポーネントでVue経由でaxiosを呼び出してみましょう。
- app/javascript/components/chapter1/App.vue
これで各々のコンポーネントで設定しなくていいようになりました。
以上のようにCSRFトークンの設定からコンポーネント内でのaxiosの呼び出しについて、メドピア内でどうしているかをみていきました。
皆さんはVue.jsとRailsを組み合わせる時、どのようにしているでしょうか?
2.単一ファイルコンポーネントで書くHTMLをもっと効率よく書けないか?
RailsのViewは独自のテンプレートエンジンを用いることで、効率的にHTMLコーディングをすることが可能になります。 ほとんどの人はHamlやSlimといったテンプレートエンジンを使って効率化しているのではないでしょうか?
Vue.jsの単一ファイルコンポーネントのデフォルトのテンプレートは通常のHTMLです。 せっかくRailsはHamlやSlimを使用しているのに、単一ファイルコンポーネントはHTMLって非効率です。
JSのテンプレートエンジンといえば、Vue.jsに組み込まれているMustachやEJSが有名ですが、今回の目的はHTMLの効率的なコーディングです。どうやらHTMLの省略記法に対応しているのはそれほど多くなく、調べてみた結果「Pug」の一強のようです(もし他に有名なやつがあればこっそり教えてください)。
そこで、メドピアではPugを採用することにしました。
Pugとは
Hamlに影響を受けたテンプレートエンジンです。ただHamlに影響を受けたという割にSlimに近いシンタックスだったりします笑
元々Jadeという名前でしたが、すでにJadeが商標登録?されていたためPugにしたらしいです。
上記でも書いたように、
・HTMLの省略記法に対応
・JSの実行
という特徴があります。 それでは次にPugを使って単一ファイルコンポーネントを書くとどのようになるのかを見ていきましょう。
Pugを使用しない場合
と言いつつも、Pugを使用した場合と使用しない場合のbefore/afterが見れた方が良いので、まずはpugを使用しない場合の単一ファイルコンポーネントを見てみます。
今回の例ではみんな大好きTodoリストです。
- app/javascript/components/chapter2/TodoApp.vue
まあ普通のHTMLですね笑
次はPugを使用する例を見ていきます。
Pugを使用する場合
まずはPugのパッケージと、WebpackでPugをコンパイルするためのpug-loaderをインストールします。
yarn add pug pug-loader
後は単一ファイルコンポーネントのtemplateタグにlang="pug"
とpugの設定をするだけです。
Webpackerの場合はコンフィグファイルに設定を追記する必要はありません。
どうなるかというと...
- app/javascript/components/chapter2/PugTodoApp.vue
このようにかなりスッキリさせることができました。
みなさんも是非pugを使って見てください!
3.単一ファイルコンポーネントとフォームヘルパーの兼ね合い
Vue.jsの単一ファイルコンポーネントはとても便利です。 以前技術ブログでも書きましたが、単一ファイルコンポーネントは以下のようにとても便利です。 * シンタックスハイライト * 1ファイル内にテンプレートとテンプレートに適用するJavaScript、cssを書くことができる(コンポーネント化できる) * テンプレート内でES6以降のものが使用できる
しかし残念なことに、単一ファイルコンポーネントではRailsのフォームヘルパーを使うことができません。 かといって本来フォームヘルパーで生成されるはずのHTMLを単一ファイルコンポーネント内のテンプレートに直書きするのも効率が悪いです。
比較
そこで、単一ファイルコンポーネントでテンプレートを書くかRailsのViewでテンプレートを書くか比較してみることにしました。 今度はユーザーの新規作成画面を例にしてみます。
単一ファイルコンポーネント内にテンプレートを書く場合
- app/javascript/components/chapter3/UserRegisterForm.vue
全体としてこのような感じになりました。 このテンプレートを作ってみて面倒だったのはformタグとhidden要素です。
フォームヘルパーならform_forやform_withで一行で作成できる部分をわざわざ手打ちしてHTMLを書かなければなりません。 またhiddenのinputタグのauthenticity_tokenのvalueですが、フォームヘルパーなら自動的にCSRFトークンが設定されるところ、わざわざ自身で設定しなければなりません。
結果、テンプレートを作成するのにそこそこ手間がかかってしまいました。
RailsのViewにテンプレートを書く場合
今度はフォームヘルパーを活用する場合です。全体としてはこんな感じになります。
- app/views/chapter3/new.html.erb
先ほどのformタグとhiddenの件はフォームヘルパーのおかげでかなり楽になりました。 他にもtext_fieldやlabelなどのヘルパーのおかげで、全体的にコード量が減っているのが見て取れます。
しかし、もちろんデメリットもあります。
Vue.js部分がシンタックスハイライトが効かないため若干見にくい
polyfillが効かない
テンプレートとコードが離れる
デメリットこそありますが、メドピア内ではフォームヘルパーを使う時は基本的に単一ファイルコンポーネントを使わず、RailsのViewにテンプレートを書く方法にしています。
まとめ
今回の記事ではメドピア内でRailsとVue.jsを組み合わせる時に悩んだ3つのことと、その対応策を書いてみました。
使用したサンプルコードは以下のリポジトリに置いてあります。
GitHub - medpeer-inc/medpeer-dev-blog2
正直これがベストなソリューションであるという確信はありません。
もし、「もっといい方法があるよ」というアイデアがある方は是非メドピアに遊びに来てそれをご教授ください。 「俺がもっといけてるコードにしてやるぜ!」という方は一緒に働きましょう!
またメドピアでは毎週火曜日にVue.jsのもくもく会を実施しています。そちらも是非ご参加ください!
(☝︎ ՞ਊ ՞)☝︎是非読者になってください
メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!
■募集ポジションはこちら
https://medpeer.co.jp/recruit/entry/
■開発環境はこちら