Railsのルーティング

Railsのルーティングについては公式ドキュメントを参考にすることができ、その他にも「Rails ルーティング」のように検索すればたくさんの情報が見つかります。

APIモードを使う場合でも書き方は同じなので困ることはないのですが、あらためて format の扱いが気になったので調べてみました。

以降の内容は基本的にJSONを扱うAPIサーバーを実装する前提とし、動作確認は以下のバージョンで行いました。

  • ruby: 2.7.1
  • rails: 6.0.3.3

ルーティングでformatを設定する

ベースとして以下のようなルーティングを定義します。

Rails.application.routes.draw do
  namespace :v1 do
    resources :users
  end
end
# rails routesの出力結果(一部省略)
# v1_users GET    /v1/users(.:format)     v1/users#index
#          POST   /v1/users(.:format)     v1/users#create
# v1_user  GET    /v1/users/:id(.:format) v1/users#show
#          PATCH  /v1/users/:id(.:format) v1/users#update
#          PUT    /v1/users/:id(.:format) v1/users#update
#          DELETE /v1/users/:id(.:format) v1/users#destroy

また v1/users#show は以下のように定義されているとします。

    def show
      render json: User.find(params[:id])
    end

このとき3つのパターンでリクエストした結果を以下に示します。
(ヘッダ情報は一部省略しています)

# パターン1
$ curl -i localhost:3000/v1/users/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

# パターン2
$ curl -i localhost:3000/v1/users/1.json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

# パターン3
$ curl -i localhost:3000/v1/users/1.html
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

パターン1とパターン2は違和感がないように見えますが、パターン3では .html とリクエストしているにも関わらずJSONのレスポンスを返しています。
アクションで render json: としているので、formatによらずコントローラーはJSONを返すという挙動になっています。

formatを指定(defaultスコープ)

ルーティングを namespace :v1, default: { format: 'json' } do と変更。

Rails.application.routes.draw do
  namespace :v1, default: { format: 'json' } do
    resources :users
  end
end

同様に3つのパターンでリクエストした結果は指定しない場合と変わりませんでした。
ただし head メソッドを使ってレスポンスを返す場合はヘッダが変わりました。

# パターン1
$ curl -i localhost:3000/v1/users/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

# パターン2
$ curl -i localhost:3000/v1/users/1.json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

# パターン3
$ curl -i localhost:3000/v1/users/1.html
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

formatを指定

ルーティングを namespace :v1, format: 'json' do と変更。

Rails.application.routes.draw do
  namespace :v1, format: 'json' do
    resources :users
  end
end

同様に3つのパターンでリクエストした結果、パターン3ではRoutingErrorが発生し404となりました。

# パターン1
$ curl -i localhost:3000/v1/users/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

# パターン2
$ curl -i localhost:3000/v1/users/1.json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

# パターン3
$ curl -i localhost:3000/v1/users/1.html
HTTP/1.1 404 Not Found

# RoutingErrorが発生

結局どれにすればいいのか?

参考にさせていただいた以下の記事にも書かれていますが、JSONを扱うAPIであれば本記事で最後に紹介した format: 'json' とするのが良いように思いました。

例のように .html やその他のフォーマットを意図せず(あるいは悪意を持って)リクエストされたときに404を返すほうが挙動として望ましい気がします。

例外的に他のフォーマットを許容したい場合は、個別のルーティングにもブロックにも format を設定することで上書きが可能です。
または /json|yaml/ のような正規表現も可能なようです。