AngularJSとRailsの丁度良い関係を探る:コード解説編

20141124

以前投稿したAngularJSとRailsの丁度良い関係を探るという記事のコード解説編です。前回はざっくりとしたアーキテクチャの紹介のみにとどめていたので、このエントリでサンプルコードの詳細について解説します。

バージョン情報

  • ruby 2.1.3
  • rails 4.1.7
  • devise 3.2.4
  • angularjs 1.3.2

ディレクトリ構造

app以下のディレクトリ構造は以下のような形です。

ポイントは、

  • AngularJSのソースコードはアプリ毎にapp/assets/javascripts/app以下に格納している
  • AngularJSと通信するためのAPIはapp/controllers/api以下に格納している
  • その他は通常のRailsアプリケーションと同様の構成

というところです。それではまずはRails側からソースコードを見て行きましょう。

Rails(サーバサイド)のコード

Gemfile

AngularJSで必要となるJSコードはGemからではなく、Bowerを介してセットアップしています。そういうわけで、RailsからBowerを手軽に扱うためにbower-railsというGemを活用しています。

routes.rb

基本的にAPIはJSONで返すようにしていて、通常のユーザーのアクセスはnamespace :api以外のルーティングで返すようにします。今回はdeviseによる認証も入れているため、devise_for :usersでdeviseを介したユーザー認証が動くようにしています。

Controller

Rails側のコントローラは全然仕事をしていないので超シンプルです。ToDoページはログインしていることを条件に表示したいため、TasksControllerでbefore_action :authenticate_user!を入れています。ログインせずにtasks#indexにアクセスするとdeviseのログインページ(views/devise/sessions/new.html.erb)にリダイレクトします。

Model

ToDoの1行1行を意味するTaskはUserと紐づくようにモデルを設計しています。

View

AngularJSアプリを起動するviews/tasks/index.html.erbではng-app="todoApp"で起動するAngularJSアプリを指定しています。今回のアプリではui-routerを利用しているため、ui-view以下にAngularJSのテンプレートが描画されます。

AngularJSと接続するAPI

以上でいわゆるRailsぽい部分は解説し終わったので、ここからAngularJSと関連する部分を解説していきます。

少し特殊な部分はCSRFトークンをやりとりする部分ですね。

通常Railsアプリケーションは、viewにcsrf_meta_tagsを記載することでメタタグとして自動設定されるCSRFトークンをフォーム投稿時にjquery_ujsがサーバに送信し、意図しないサーバからリクエストが送られることを防止しています(CSRF対策)。ただこれはAngularJSの標準ではない動きなので、工夫してCSRFトークンをサーバに送信する必要があります。

このとき取れるアプローチとして、

  1. AngularJSからのPOST時にHTML内のメタタグからCSRFトークンを取得し、X-XSRF-TOKENヘッダに設定してサーバに送信する。
  2. クッキーのXSRF-TOKENキーにCSRFトークンが設定されていると、AngularJSからのPOST時に自動的にその値がX-XSRF-TOKENに設定される性質を利用する。
  3. protect_from_forgeryを無効にする(CSRF対策しない)。

といった手段がありますが、今回はサーバとクライアントを同一ドメインで運用する想定なので、2番目のクッキーに設定するアプローチをとっています。

API本体ではコード量を減らすためにinherited_resourceというGemを利用しています。resourcesに対応したCRUDを作りたいだけなら、このGemを利用するとコード量を大幅に減らすことができます。

以上がRails側のコードの解説でした。

AngularJS(クライアント)のコード

Bower

Bowerで取り込まれたコードは全てvendor/assets/bower_components以下に配置したかったため、vendorキー以下にJavaScriptのライブラリを記載しています。vendorキー以下に列挙すると、rake bower:installしたときにvendor/assets/bower_components以下にJavaScriptのライブラリが配置されます。

Herokuに手軽にデプロイするため、bower_components以下もバージョン管理に含めています。

Sprockets

Railsでは標準でSprocketsというライブラリを利用してアセットの統合&Minifyを行うため、その流儀に則ってapplication.jsでAngularJSのライブラリ読み込み、コード読み込みを行っています。

としているのはJSの読み込み順の関係です。

AngularJS

task.js.erbではアプリのモジュールの定義と、ルーティングの設定を行っています。この先ToDoの詳細画面をURLを変更して表示したいといったニーズに応えるために、あらかじめルーティングを定義するつくりにしています。

また、テンプレートとなるHTMLはSprocketsによってプリコンパイルされるため、プリコンパイル後も適切にHTMLのパスが指定されるようにするためERBでパスを指定しています。それが<%= asset_path 'app/tasks/tasks.html' %>というコードの意味するところです。人によっては.js.erbとなってしまう点に気持ち悪さを感じると思いますが、個人的には許容できるかなーっと思っています。

余談ですが、ng-includeディレクティブを利用してパーシャルのHTMLテンプレートを読み込む場合も、同様にasset_pathを利用してパスを指定します。

サーバサイドと通信する部分はサービスとして定義しています。$resourcesを活用すると簡潔に書けますね。Rails側でdoneというアクションを別途定義しているため、こちらもそのアクションに対応するファンクションを付け足ししています。

Controllerでは上記のTaskサービスをインジェクションして、ビューとサーバサイドとの通信の橋渡しをしています。AngularJSのコントローラもRailsのコントローラと同様に、詳細なロジックはサービスとして定義してるコンポーネントに任せて、橋渡しに徹するのが良いアプローチかなーと思っています。

最後はAngularJSで利用しているテンプレートです。

よくRailsを利用しているとAngularJSのテンプレートもhamlで書きたい!といった意見があったりしますが、個人的に思うのはAngularJSにおけるテンプレートはHTMLで書くことがあくまで前提とされている以上、HTMLで書いた方がハマるポイントが少ないのでは?と思っています。

「HTMLで書く」ということを含めてAngularJSのテンプレート記法が成り立っているというイメージ。HTMLの記述自体はEmmetを活用すれば、そこまで苦でもないですしね。

まとめ

以上が「AngularJSとRailsを丁度いい感じに組み合わせるとこんな感じになるんじゃない?」というサンプルコードの解説でした!

ただこのコードはあくまで骨組みだけであって、例えばページネーションを上手く連携するためには?とか、サーバサイドのバリデーションエラーを上手くクライアントに反映するためには?といった個別トピックは扱えていません。そういったトピックも少しずつ紹介していければなーと思っています。

あわせて読みたい

AngularJSリファレンス
AngularJSリファレンス
posted with amazlet at 14.11.21
池添 明宏 金井 健一 吉田 徹生
インプレス
売り上げランキング: 18,658