RailsでのURL設計を考えてみる(4) スラッシュと「持っている」関係

今回は、URLのスラッシュはどのようなときに使えばいいのか考えてみました。

resourcesのネスト

URLはスラッシュ「/」で階層構造を表します。Railsでは、routes.rbでresourcesをこのようにネストして使うと、

resources :users do
  resources :articles
end

このようなURLができます。

http://example.com/users
http://example.com/users/1
http://example.com/users/1/articles

/users で「ユーザー全体」、 /users/1 で「ユーザーの1番」を指し、さらに /users/1/articles で「ユーザーの1番」の書いた「記事(article)全体」を指すわけです。((さらに /users/1/articles/2 で「ユーザーの1番」の書いた「記事の2番」を指すURLもできますが、Railsではidは基本的にグローバルであることから、 /articles/2 で十分なのであまり必要とは思いません。resources :articles, :only => :indexなどとすることで除くことができます。))
このように、「持っている」has_one(1:1)やhas_many(1:n)の関係があるときに、階層とみなしてスラッシュでつなげたURLをつくるのが一般的になっています。

例えば、userとarticleがそれぞれhas_many commentsの関係のとき、

/users/1/comments
/articles/2/comments

というURLをつくることができます。意味が分かりやすいですね。

belongs_toのときは?

ところで、僕が今作っているRailsアプリでは、「ユーザーの1番」が「コメント」した「記事全体」のURLが必要になりました。comment belongs_to articles の関係です。
さて、これはどう表現すればいいでしょう?

(1)
/users/1/comments/articles

すぐ上からの類推です。うーん、belongs_toのときはなんか違和感があるのは僕だけでしょうか。コメントが記事を持っているわけではないのです。

(2)
/articles?commented_by_user_id=1

記事全体から、条件のパラメータで抽出したという感じ。素直で意味はわかりやすいのですが、もうちょっときれいな感じにできないでしょうか。

(3)
/users/1/commented/articles

(1)とほとんど変わらないじゃん!と思われるかもしれませんが、大きく違う点は「commented」です。これは言うなれば「commented_resources」の略で、記事を「持っている」関係になっていることがわかります。
コメントがつけられるのが記事だけならば、スラッシュは使わずに /users/1/commented_articles としてもいいのですが、今作っているアプリでは記事以外のものにもコメントがつけられるようになっていたということ、そしてリソース名を「articles」に統一することで、同じインターフェイスであることを示唆する効果があります。

routes.rbの書き方

(3)ではこう書けます。

resources :users do
  resource :commented, :only => :show do
    resources :articles, :only => :index
  end
#  CommentedsControllerが不要な場合は以下でもよい
#  resources :articles, :path => 'commented/articles', :as => 'commented_articles', :only => :index
end

ルーティングはこうなります。(users部分は省略)

user_commented_articles GET /users/:user_id/commented/articles(.:format) {:action=>"index", :controller=>"articles"}
         user_commented GET /users/:user_id/commented(.:format)          {:action=>"show", :controller=>"commenteds"}

本来「commented」は単数リソースではないのですが、resourcesのネストでは必ずidが入ってしまう(/users/:user_id/commented/:commented_id/articles となる)collection do ... end に resources をネストさせるとなぜかおかしくなるので、単数扱いにしてresourceを使用します。

/users/:user_id/commented というURLは使わない場合も多いので、:pathと:asを使った書き方もOKです。今は使わないとしてもcommentedがリソースとして「意味を持つ」ということが重要です。ちなみに、scopeを使って

  scope :path => 'commented', :as => 'commented' do
    resources :articles, :only => :index
  end

でもできるはずですが、なぜか手元ではうまくいきませんでした。

スラッシュの条件

スラッシュを使って「A/B/C」というURLをつくるとき、こういうことが言えると思います。

  • AはBを、BはCを持っている関係
  • 「A/B」も「A」もリソースとして意味を持つ

これは、階層構造と考えると自然なことです。

もちろん、階層構造がなじまないデータもあるので、それを表現するときは別の手段を考えることになります。それはまたネタができれば書いてみたいかも?