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が入ってしまう(collection do ... end に resources をネストさせるとなぜかおかしくなるので、単数扱いにしてresourceを使用します。/users/:user_id/commented/:commented_id/articles
となる)
/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」もリソースとして意味を持つ
これは、階層構造と考えると自然なことです。
もちろん、階層構造がなじまないデータもあるので、それを表現するときは別の手段を考えることになります。それはまたネタができれば書いてみたいかも?