rails3 + backbone.jsでデータのやり取りをするために。
RailsでWEB APIサーバを作るときのアーキテクチャをどうするか考える
RESTful APIとWebサイトを1つのアプリケーションで作る
で、この時はAndroidアプリしかAPIを使うクライアントはいなかったんだけど、等々HTMLから使うときが来ました!
この時のために、いろいろ考えて自分なりのRESTfulなAPIを作ってきたつもりだった。
で、ここまでの積み重ねを活かせると思い、backbone.jsも採用したのでなんかもう自分的には伏線回収フェーズが来ました的な。
上記リンクのとおり、APIはサブドメインを切って受け付けることにしたけど、backbone.jsはデフォルトだと同一ドメインに対してリクエストを飛ばすので(あたりまえ)これを変更する。
backbone.jsでajaxのリクエスト先を別のHostにする方法
すると問題になるのがsame-originポリシー。これはCORSな対応で回避する。
具体的には、ApplicationControllerあたりにAllow-Originなどを設定してやる。
before_filter :set_format, :allow_cross_domain_access private def allow_cross_domain_access response.headers["Access-Control-Allow-Origin"] = Settings.allow_origin response.headers["Access-Control-Allow-Methods"] = Settings.allow_methods response.headers["Access-Control-Allow-Headers"] = Settings.allow_headers end
Setting.hogehogeはRailsで環境ごとの設定値を定数的に扱えるSettingslogicね。
で、これでとりあえず指定のOriginからリクエストを発行したブラウザはレスポンスを受け付けてくれるようになる。
が、backbone.jsは各リクエストの前にOPTIONSメソッドを発行してくるので、これの対応が必要。
OPTIONSメソッドについては、この辺。http://www.studyinghttp.net/method#OPTIONS
ざっくりいうと、APIサーバに対して、「おまえんとこは、どんなメソッドとかヘッダに対応してるの?」という問い合わせを本チャンのリクエストの前に送ってくる仕組みらしい。
で、これにRailsで対応するには、
routingで、こんな感じ。
match '/hoges', to: 'hoges#options', constraints: {method: 'OPTIONS'} match '/hoges/:id', to: 'hoges#options', constraints: {method: 'OPTIONS'}
この書き方だと、リソースごとどころか、URLパターンごとにいちいちroutingを定義しなきゃいけないので、ちょっと考えなきゃいけないな、と思ってる。どうしたらいいんだろ??
hoges_controller.rb
def options head :no_content end
メソッド名は別になんでもいい。返却するのも、さっき設定したヘッダが重要なのであって、ここはなんとなく204かなぁ、と思って決めた。っていうかどこを探してもOPTIONSメソッドに対してはこのレスポンスコードを返すべき、的な情報がなかった。
rspecでroutingのテストもできた。
it "routes to #options" do expect(options: "http://api.example.com/v1/hoges").to route_to(action: "options", controller: "v1/hoges") expect(options: "http://api.example.com/v1/hoges/1").to route_to(action: "options", controller: "v1/hoges", id: "1") end
optionsで通るのね・・・。
最後にけっこうハマったのがこれ。
backbone.jsでfetch()でもsave()でもとにかくエラーハンドラが発火してしまう。
あと、別個で認証をどうにかしなきゃいけないけど、それはなんとなくヒミツ。
とりあえず、こんな感じでbackbone.jsからのリクエストがさばけるはず。
Rails3にbackbone.jsを導入する
JavaScript(CoffeeScript)をどうやって、疎に、DRYに保つか、ということはいつも考えながらも、最終的にはムスビのコトワリに導かれゆく結末をたどってきたのですが、近頃ではJSにも各種フレームワークが浸透してきており、その中でもデファクトの地位を確立しつつある(?)backbone.jsの導入により、人修羅への道を切り開くことを試みることにした。
backbone.jsを知るにあたって参考にしたサイト等は、ここにまとめた。
backbone.jsについて調べたときのメモ
とにかく、イメージがなんとなく掴めるまでたくさんの情報を仕入れたほうがいいかも。
で、今回はとりあえず、細かい話は抜きにして、「おー!」とカンタンに思えるところまでを追ってみる。
backbone-on-rails の導入
Railsで扱うならGemだよねー、ということで探してみると、2,3メジャーどころがあるようだけど、どれも似たようなもんで、最終的にRailscastにチュートリアル(有料)があったbackbone-on-railsを採用することにした。
https://github.com/meleyal/backbone-on-rails
インストールとかは、上のギッハブのREADME.mdを読んでください。これができなかったらもうこの先絶対にムリなので導入を諦めるが吉。
適当にscaffoldしてみる
rails g backbone:scaffold hoge
そうすると、railsみたいにhogeリソースをベースにした各種スクリプトがassets/javascripts配下に生成される。
backbone.js側のModelをいじる
assets/javascripts/models/hoge.js.coffee class YourApp.Models.Hoge extends Backbone.Model urlRoot: '/hoges'
こんな感じに、リソースを指定する。こんだけ。
サーバサイドにAPIを用意する
今度はrails側で同じくhogeでscaffoldするなりする。とりあえずこの際Jsonで返さなくていいの、とかそういうのは無視する。
データも1件、適当に入れてください。
つまり、ブラウザで/hoge/1を表示できるようにしてください。
javascriptコンソールで感動する
safariでもchromeでも、なんでもいいので、Javasciprtコンソールを開いてbackbone.jsのModelがすごいことを確認する。
hoge = new YourApp.Models.Hoge({id:1}) hoge.fetch()
これでObjectが返ってきていて、その中身のresponseTextには、ブラウザで/hoge/1を表示したときを同じデータが入っているのが確認できる。
まとめ
まだ細かいことはよくわからないけど、なんだかすごそうだ。
追記)
もっと丁寧で参考になるサイトを後で見つけたので、上記はなかったことにして、こちらを参考にするがよかろう。
Rails3.2とBackbone.jsでToDoアプリを作ってみた〜backbone-on-rails
backbone.jsでajaxのリクエスト先を別のHostにする方法
Same-Origin PolicyはCORSなどで対処していただくとして。
最初に通るところ(your_app.js.coffeeのinitializeあたりがいいのかな)で、$.ajaxPrefilterを使う。
window.YourApp = Models: {} Collections: {} Views: {} Routers: {} initialize: -> $.ajaxPrefilter( (options, originalOptions, jqXHR) -> options.url = 'http://another.example.com' + options.url; ) $(document).ready -> YourApp.initialize()
Railsで環境ごとの設定値を定数的に扱えるSettingslogic
https://github.com/binarylogic/settingslogic
超絶便利。URLとかAPI_KEYとかそういうので重宝。
インストール方法や使い方はREADME見れば絶対にわかるので、そちらで。
以上、よろしくお願い致します。
backbone.jsでfetch()でもsave()でもとにかくエラーハンドラが発火してしまう。
ハマった。いやぁ、ハマった。
サーバサイドはきっちり200系でレスポンス返してるのに、クライアントサイドでエラーハンドラが発火するという現象に悩まされていた。
2時間以上、デバッグやトライアンドエラーをしてみたけど、まったくわからなかった。けど、風呂に入ってからググったらココがヒットした。
http://stackoverflow.com/questions/7040039/model-fetch-always-going-to-error-callback
dataTypeに'json' を指定するだけだった。
これがないだけで、イケてない扱いされて、しかも原因のヒントがどこにもなかったっていう。
具体的にはbackbone.jsでajaxのリクエスト先を別のHostにする方法と同じように
$.ajaxPrefilter( (options, originalOptions, jqXHR) -> options.url = $('meta[property="api-url"]').attr('content') + options.url; options.dataType = 'json' )
こんな感じで。