メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックリーディングカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

Vue.jsとRailsの最適な融合を考える

もう新年を迎えて2ヶ月が経ちますね。 多くの人は新年の目標を立てますが、皆さんは何かしら立てましたでしょうか? 英語を毎日勉強するという目標を立てましたが、既に挫折してしまったエンジニアの村上(pipopotamasu (pipopotamasu) · GitHub)です。

本日はその懺悔も込めてVue.jsとRailsの話をお送りします。

f:id:ec0156hx39:20180221120941p:plain

この記事を書く背景

以前ブログで書いた通り、現在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を送る部分のソースを見てみます。
f:id:ec0156hx39:20180212221613p:plain:w220

上記のようにCSRFトークンを設定しない状態だと...

f:id:ec0156hx39:20180212222555p:plain

このようなエラーが起こるので、ちゃんと通るようにしてみましょう。

設定する場合

今度はCSRFトークンを設定する場合です。
f:id:ec0156hx39:20180212221532p:plain:w700

上記の10行目でCSRFトークンの取得、11行目でリクエストヘッダーにトークンを設定しています。

f:id:ec0156hx39:20180212222617p:plain

今回は上手く処理されました。

この方法はViewのDOM内のCSRFトークンを直接取得し、axiosに設定する方法です。

実はこれと同じ方法を提供してくれるパッケージがあります。 次はそのパッケージ、「rails-ujs」を用いた方法を見てみましょう。

rails-ujsを使ってみる

まずはrails-ujsをインストールします。

yarn add rails-ujs

次にrails-ujsからcsrfTokenを取得するメソッドをimportし、CSRFトークンを設定してみましょう。

f:id:ec0156hx39:20180214161913p:plain:w500

これで先ほどの例のように破壊的なリクエストを送ることができるようになります。

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 f:id:ec0156hx39:20180214165844p:plain

次はエントリーファイルにて上記で作成したPluginをVueに組み込みます。

  • app/javascript/packs/chapter1/index.js f:id:ec0156hx39:20180214175736p:plain

最後にコンポーネントでVue経由でaxiosを呼び出してみましょう。

  • app/javascript/components/chapter1/App.vue f:id:ec0156hx39:20180214175851p:plain:w250

これで各々のコンポーネントで設定しなくていいようになりました。


以上のように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 f:id:ec0156hx39:20180215114443p:plain

まあ普通のHTMLですね笑

次はPugを使用する例を見ていきます。

Pugを使用する場合

まずはPugのパッケージと、WebpackでPugをコンパイルするためのpug-loaderをインストールします。

yarn add pug pug-loader

後は単一ファイルコンポーネントのtemplateタグにlang="pug"とpugの設定をするだけです。 Webpackerの場合はコンフィグファイルに設定を追記する必要はありません。

どうなるかというと...

  • app/javascript/components/chapter2/PugTodoApp.vue f:id:ec0156hx39:20180215120942p:plain

このようにかなりスッキリさせることができました。

みなさんも是非pugを使って見てください!

3.単一ファイルコンポーネントとフォームヘルパーの兼ね合い

Vue.jsの単一ファイルコンポーネントはとても便利です。 以前技術ブログでも書きましたが、単一ファイルコンポーネントは以下のようにとても便利です。 * シンタックスハイライト * 1ファイル内にテンプレートとテンプレートに適用するJavaScript、cssを書くことができる(コンポーネント化できる) * テンプレート内でES6以降のものが使用できる

しかし残念なことに、単一ファイルコンポーネントではRailsのフォームヘルパーを使うことができません。 かといって本来フォームヘルパーで生成されるはずのHTMLを単一ファイルコンポーネント内のテンプレートに直書きするのも効率が悪いです。

比較

そこで、単一ファイルコンポーネントでテンプレートを書くかRailsのViewでテンプレートを書くか比較してみることにしました。 今度はユーザーの新規作成画面を例にしてみます。

単一ファイルコンポーネント内にテンプレートを書く場合
  • app/javascript/components/chapter3/UserRegisterForm.vue f:id:ec0156hx39:20180220122803p:plain

全体としてこのような感じになりました。 このテンプレートを作ってみて面倒だったのはformタグとhidden要素です。

f:id:ec0156hx39:20180220123017p:plain

フォームヘルパーならform_forやform_withで一行で作成できる部分をわざわざ手打ちしてHTMLを書かなければなりません。 またhiddenのinputタグのauthenticity_tokenのvalueですが、フォームヘルパーなら自動的にCSRFトークンが設定されるところ、わざわざ自身で設定しなければなりません。

f:id:ec0156hx39:20180220123451p:plain

結果、テンプレートを作成するのにそこそこ手間がかかってしまいました。

RailsのViewにテンプレートを書く場合

今度はフォームヘルパーを活用する場合です。全体としてはこんな感じになります。

  • app/views/chapter3/new.html.erb f:id:ec0156hx39:20180220123722p:plain

先ほどのformタグとhiddenの件はフォームヘルパーのおかげでかなり楽になりました。 他にもtext_fieldやlabelなどのヘルパーのおかげで、全体的にコード量が減っているのが見て取れます。

しかし、もちろんデメリットもあります。

  • Vue.js部分がシンタックスハイライトが効かないため若干見にくい

  • polyfillが効かない

  • テンプレートとコードが離れる

デメリットこそありますが、メドピア内ではフォームヘルパーを使う時は基本的に単一ファイルコンポーネントを使わず、RailsのViewにテンプレートを書く方法にしています。

まとめ

今回の記事ではメドピア内でRailsとVue.jsを組み合わせる時に悩んだ3つのことと、その対応策を書いてみました。

使用したサンプルコードは以下のリポジトリに置いてあります。

GitHub - medpeer-inc/medpeer-dev-blog2

正直これがベストなソリューションであるという確信はありません。

もし、「もっといい方法があるよ」というアイデアがある方は是非メドピアに遊びに来てそれをご教授ください。 「俺がもっといけてるコードにしてやるぜ!」という方は一緒に働きましょう!

またメドピアでは毎週火曜日にVue.jsのもくもく会を実施しています。そちらも是非ご参加ください!

medpeer.connpass.com


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html