Resource Routingつらくね?

Resource Routing

Rails Guidesでは「Resource Routing: the Rails Default」として namespace, resource, resources といったメソッドを利用する方法が紹介されている。これまでとりあえずこの機能を使って開発してきたが、これは果たして本当に積極的に利用した方が良い機能なのか。

あるとき

試しにTwitterのAPIのようなルーティングを定義しながら、Resource Routingを利用する場合としない場合とを比較してみることにする。ここでは (Resource Routingを利用するメリットを出すためにも)、API用のPrefixとして各パスの先頭に /api/v1 を付けることにする。このAPIはアクセストークンの発行や、tweetの取得、投稿、ユーザやフォロワー情報の取得などの機能を提供する。また、/api/v1 以下のどのパターンにも一致しなかった場合、Railsのデフォルトのエラーレスポンスを返すのではなく、この場合も適切なControllerで処理できるようにしている (※詳しくはRailsでルーティングが無いときの挙動を変えるを参照)。

Qiita::Application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :access_tokens, only: [:create, :destroy]
      resources :tweets, only: [:create, :index, :show] do
        resource :favorite, only: [:destroy, :update]
      end
      resources :tags, only: [:index, :show] do
        resources :tweets, controller: :tag_tweets, only: [:index]
      end
      resource :user, only: [:show]
      resources :users, only: [:index, :show] do
        resources :favorites, only: [:index]
        resources :followees, only: [:index]
        resources :followers, only: [:index]
        resources :tweets, controller: :user_tweets, only: [:index]
      end
      match '*any' => 'black_holes#show', via: :all
    end
  end
end

ないとき

Resource Routingを利用しない場合は以下のように get, post, delete などを用いて定義する。

Qiita::Application.routes.draw do
  post   '/api/v1/access_tokens'             => 'api/v1/access_tokens#create'
  delete '/api/v1/access_tokens/:id'         => 'api/v1/access_tokens#destroy'
  get    '/api/v1/tweets'                    => 'api/v1/tweets#index'
  post   '/api/v1/tweets'                    => 'api/v1/tweets#create'
  get    '/api/v1/tweets/:id'                => 'api/v1/tweets#show'
  put    '/api/v1/tweets/:tweet_id/favorite' => 'api/v1/favorites#update'
  delete '/api/v1/tweets/:tweet_id/favorite' => 'api/v1/favorites#destroy'
  get    '/api/v1/tags'                      => 'api/v1/tags#index'
  get    '/api/v1/tags/:id'                  => 'api/v1/tags#show'
  get    '/api/v1/tags/:tag_id/tweets'       => 'api/v1/tag_tweets#index'
  get    '/api/v1/user'                      => 'api/v1/users#show'
  get    '/api/v1/users'                     => 'api/v1/users#index'
  get    '/api/v1/users/:id'                 => 'api/v1/users#show'
  get    '/api/v1/users/:user_id/favorites'  => 'api/v1/favorites#index'
  get    '/api/v1/users/:user_id/followees'  => 'api/v1/followees#index'
  get    '/api/v1/users/:user_id/followers'  => 'api/v1/followers#index'
  get    '/api/v1/users/:user_id/tweets'     => 'api/v1/user_tweets#index'
  match  '/api/v1/*any'                      => 'api/v1/black_holes#show', via: :all
end

俺はこう思ったッス

Resource Routingを擁護しておくと、そういったDSLを半ば強制することで、RESTfulでないURLを定義しづらくなるという利点が挙げられる。この利点は、大勢の人間が開発に参加するような状況ではより有意性が増すだろう。しかしながら、例えもし既存のルーティングが全てResource Routingを利用して定義されていたとしても、Resource Routingを利用せずに新しいルーティングを定義する人間は現れる。そういう現象を何度も見てきた。

Resource Routingを利用するとルーティング定義が幾つかの論理的なブロックに分けられるので、Constraints (※拡張子やサブドメインなどの一致パターンを指定できる機能) などの制約がブロックごとに適用できる余地がある、という利点がある。例えばリクエストに対して任意のProcの内容を評価して一致したことにするかどうか検査する、なんてこともできる。しかし、ルーティング部分に多くのロジックが入る状況はあまり好ましくない。ちなみに最近見たコードでは、config/routes.rbの中で Kernel#escape が定義されていてオシャレ感があった。config/routes.rb にゴミみたいなコードたくさん入ってませんか、大丈夫ですか。