以前投稿したAngularJSとRailsの丁度良い関係を探るという記事のコード解説編です。前回はざっくりとしたアーキテクチャの紹介のみにとどめていたので、このエントリでサンプルコードの詳細について解説します。
バージョン情報
- ruby 2.1.3
- rails 4.1.7
- devise 3.2.4
- angularjs 1.3.2
ディレクトリ構造
app以下のディレクトリ構造は以下のような形です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
app ├── assets │ ├── images │ ├── javascripts │ │ ├── app │ │ │ └── tasks │ │ │ ├── tasks.controller.js.erb │ │ │ ├── tasks.html.erb │ │ │ ├── tasks.js.erb │ │ │ └── tasks.service.js │ │ └── application.js │ └── stylesheets │ └── application.css.sass ├── controllers │ ├── api │ │ ├── base_controller.rb │ │ └── tasks_controller.rb │ ├── application_controller.rb │ ├── concerns │ │ └── check_csrf_for_angular.rb │ └── tasks_controller.rb ├── helpers │ └── application_helper.rb ├── mailers ├── models │ ├── task.rb │ └── user.rb └── views ├── devise │ ├── sessions │ │ └── new.html.erb │ └── shared │ └── _links.erb ├── layouts │ └── application.html.erb └── tasks └── index.html.erb |
ポイントは、
- 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トークンをサーバに送信する必要があります。
このとき取れるアプローチとして、
- AngularJSからのPOST時にHTML内のメタタグからCSRFトークンを取得し、X-XSRF-TOKENヘッダに設定してサーバに送信する。
- クッキーのXSRF-TOKENキーにCSRFトークンが設定されていると、AngularJSからのPOST時に自動的にその値がX-XSRF-TOKENに設定される性質を利用する。
- 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のライブラリ読み込み、コード読み込みを行っています。
1 2 3 |
//= require app/tasks/tasks //= require app/tasks/tasks.service //= require app/tasks/tasks.controller |
としているのは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を丁度いい感じに組み合わせるとこんな感じになるんじゃない?」というサンプルコードの解説でした!
ただこのコードはあくまで骨組みだけであって、例えばページネーションを上手く連携するためには?とか、サーバサイドのバリデーションエラーを上手くクライアントに反映するためには?といった個別トピックは扱えていません。そういったトピックも少しずつ紹介していければなーと思っています。
あわせて読みたい
インプレス
売り上げランキング: 18,658