RESTful APIとWebサイトを1つのアプリケーションで作る
RailsでWEB APIサーバを作るときのアーキテクチャをどうするか考えるの続き。
routingなど具体的にどうRailsに盛り込んでいくかの巻。
前提
とりあえずテスト
テストしたいのは・・・
- API側はapi.example.comからのアクセスしか受け付けないようになっていること。
- 何もしないとアプリは1つなので、example.comからも叩けちゃう。問題切り分けとかのときにややこしくならないように。
- Jsonフォーマットでレスポンスが返ってくること
user_api_spec.rb
describe "GET /user" do let(:user) { FactoryGirl.create(:user) } it "should respond with json" do get v1_user_path(user), nil, {'HTTP_HOST' => 'api.example.com'} parsed_body = JSON.parse(response.body) parsed_body["name"].should == user.name end it "should not respond access from domains other than the subdomain for api" do expect { get v1_user_path(user), nil, {'HTTP_HOST' => 'example.com' } }.to raise_error end end
実際は、このテストも一発で書けた訳じゃなくて、かなりトライアンドエラーを重ねてるんだけど、とにかくポイントは
get v1_user_path(user), nil, {'HTTP_HOST' => 'api.example.com'}
の部分。
getは3つ目のオプションでHOST指定できるのでここで「サブドメイン宛のみ許可」のテストができる。
エラーパターンとして以下のテストを書いた。
it "should not respond access from domains other than the subdomain for api" do expect { get v1_user_path(user), nil, {'HTTP_HOST' => 'example.com' } }.to raise_error end
RoutingErrorになるのでこの形になった。404にするのが正しいのかな、と一瞬思ったけどまぁこれでもいいか、ということにした。
このテストを通すコードが以降。
ルーティング
config/routes.rb
constraints subdomain: 'api' do namespace :v1 do resources :users end end
"constraints subdomain: 'api'"でapi.のサブドメイン宛のみ有効。
"namespace :v1"でapp/controllers/v1ディレクトリ配下のコントローラーを見に行く。
コントローラ
app/controllers/v1/application_controller.rb
class V1::ApplicationController < ActionController::Base respond_to :json before_filter :set_format def set_format request.format = :json end end
フォーマットはJsonしか扱わないのでここで決め打っておく。V2ができたときにいろいろ重複しそうだなぁ、と思ったけど、できたときに解決するのでも全然問題ないと思ったので今は気にしないことにした。
app/controllers/v1/users_controller.rb
class V1::UsersController < V1::ApplicationController def show @user = User.find(params[:id]) respond_with @user end end
respond_withを毎回書くのはめんどくさい、と思ったけど、省略するやり方がわからなかったのと、Jsonだとhas_manyなmodelの場合、どこまで追っていくか指定したりするからこれでしょうがないのか、と思うことにした。
こんな感じで行こうっと。