|
|
|
|
Rubyで書かれたRails用の mod_rewrite のようなURL書き換え機能です。定義ファイルは config/routes.rb です。Railsは1ページ(コンテンツ)を基本的に「コントローラ」と「アクション」という組で管理(表現)します。コントローラとアクションは1時間Railsと遊ぶと大体わかりますが、簡単に説明するとアクションが通常のCGI('foo.cgi')の 'foo' に当たる識別子で、コントローラはそれらのある程度の集まりだと思って構いません。この集まりも適当なものでなく、正しくはMVCという概念に基づいて決まるのですが(DBのあるテーブルに対する一連の操作(アクション)単位でまとまる)、自分の好きな粒度でまとめても実害はありません。ここでは、「その2つの組でページが指定される」という理解だけでOKです。例に漏れず、この route (URL書き換え)でもその対象はページになりますので、その2つをどう指定するかという話になります。まずは、デフォルトページ(apache の DirectoryIndex)の設定例です。
ActionController::Routing::Routes.draw do |map| map.connect '', :controller => "berryz", :action=> "index" # DirectoryIndex の例 map.connect ':controller/:action/:id' # Rails のデフォルトの書き換え例 end
この map.connect の部分がルール定義です。上から順番に評価されます。第一引数に書き換え対象となる(ユーザによって入力された)URLのパスを表現し、第二引数以降に、マッチした時に表示するページを定義します。ここで表示するページの表現は、先程説明した「コントローラ」と「アクション」の組で、それぞれ :controller, :action をキーとして表現します。一行目では、書き換え対象が空文字列ですので URL のパスが "" の場合だけ、すなわちトップページにアクセスされた場合にだけ反応します。(第一引数は文字列での比較というより、/^(第一引数)$/ という正規表現を思い浮かべる方がよいです)。飛び先のページは"berryz"コントローラの"index"アクションです。まとめると、以下の流れでページが表示されます。
では、次に 'http://localhost:3000/test' へアクセスするとどうなるでしょう。
多分、ツッコミたい所が2点あるでしょう。まず、ひとーつ!(浜ちゃん)。流れから行くと今度は2つめの定義で当たる例を書きそうですが、敢えて狙いを外しました。これがお笑い用語で言う「スカシ」です。例えば、「おはスタ(2004/11/23)#おはおはカレンダー」でBerryz工房が芸暦10年を超える山ちゃん相手に見事に決めています。この魅力的なテーマに関して小一時間ほど解説したいのですが、話が逸れてきたので戻します。
ツッコミ2つ目。なにやら3の部分で怪しい正規表現が出てきたのが本質的な部分です。定義と見比べてみると、":controller/:action/:id" の部分ですね。route では先頭に ":" がついていると"/"を除く任意の文字列にマッチして、その名前でCGI(コントローラ)の @params[(名前)] に入れてくれるのです。(以降、便宜上、パラメータ文字列と呼びます)。何か便利っぽい匂いがするでしょう?では、2つ目にヒットする例を実際のコントローラのコードを添えて見てみましょう。
# app/controllers/berryz.rb
class BerryzController < ApplicationController
def member
render :text => @params.inspect # :text 表示だけなので view (member.rhtml) は不要
end
end
ちゃんと指定されたコントローラにパラメータで渡されてるのがわかります。あとは、member アクションの定義を @member = Berryz::find(@params["id"]) とかやるだけで、プロフィールページの大枠が完成です。(あとは好きなだけ view に凝るだけ)。ちなみにこのように簡潔でわかりやすいURLを Pretty URLs と呼ぶようで、Rails でも推奨されています。(route は本来それを目的としたツール、という記述もあり)。対義語は Ugly URLs で、同じ例だとこういう感じでしょうか。
http://localhost:3000/nuke/modules.cgi?action=print&id=maiha&target=berryz&mode=quote&p=14488
ちょっと無理に書きましたが、本来こうなりがちなURLを '/' の順序に意味を持たせることでシンプルに表現してくれるのが route の機能なのです。そもそも "&" でなく ";" が最近では推奨されてるようですし(">"とかとの衝突のため)、それを一歩進めて個人的にはお尻も拡張子なしにして「"."や"?" すらついたら負け組」「[a-z\d]以外は"/"のみ」原理主義者になりつつ私にとっては、route はもう必須なアイテムになっています。(悩ましい RewriteCond ともおさらばできて嬉しい限りです)
パラメータ文字列と通常のパス文字列を混ぜることもできます。
# config/routes.rb
ActionController::Routing::Routes.draw do |map|
map.connect '/top', :controller => "berryz", :action=> "index", :initial => true
# パラメータ文字列以外でも好きな値を @params に設定できる (ここでは :initial=>true)
map.connect '/new', :controller => "berryz", :action=> "create", :id => nil
map.connect '/edit/:id', :controller => "berryz", :action=> "edit"
map.connect '/diary/:year/:month/:day', :controller => "diary", :action=> "show"
# "/diary/2005/07/25" → @params = {"year"=>"2005", "month"=>"07, "day"=>"25", "controller"...}
end
今までの説明で route 定義のパス文字列やパラメータ文字列では正規表現を連想するとよいと書きましたが、パラメータ文字列の場合、本当に正規表現を条件として課することが可能です。requirements によって指定します。
# config/routes.rb
ActionController::Routing::Routes.draw do |map|
map.connect 'mpeg/:action/:file', :controller => "movie", :requiremetns => {:file => /(\d+)\.mpg/}
# "/mpeg/download/10.mpg" → @params = {"file"=>"10.mpg", "controller"=>"movie", "action"=>"download"}
# "/mpeg/download/15.avi" → スルー
# "/mpeg/download/a.mpg" → スルー
# "/mpeg/delete/10.mpg" → @params = {"file"=>"10.mpg", "controller"=>"movie", "action"=>"delete"}
# (あ、"." が入ってる。負け組だ...)
map.connect 'mpeg/:action/:file', :controller => "movie", :requiremetns => {:file => /(\d+)\.mpg/, :action=>/(delete|action)/}
# さらに action も指定してみる
map.connect 'mpeg/:action/:file', :controller => "movie", :file => /(\d+)\.mpg/, :action=>/(delete|action)/
# requirements と同じ挙動ですが、直接、パラメータ文字列に正規表現を与えてもよいようです。
# (Documentは見つけてないけど動いてるからいいや)
end
Ugly URLs をやめたいけど、パラメータが一杯あったらどうするの?route に全部書いていくのはメンド教の教義に反するし、そもそも個数が動的な場合は全部書けないのでは?同じ問題はメソッドの呼び出し時の引数でもありますが、Rubyは見事にクリアしています。そして、route では全く同じ解決策を準備してくれています。(こういう同じ発想(方法)で実現できることが多様性で、それが表現できるRubyはやはり素直で素晴らしい言語です)
# Ruby
def foo (arg1, arg2, *args)
end
# route
ActionController::Routing::Routes.draw do |map|
map.connect ':dir1/:dir2/*dirs', :controller=>"dir", :action=>"index"
# 'usr/share/rails/actionpack/README'
# → @params = {"controller"=>"dir", "action"=>"index", "dir1"=>"usr", "dir2"=>"share", "dirs"=>["rails", "actionpack", "README"]}
end
route の定義は、今まで見てきた「URL→コントローラ・アクションの決定」だけでなく「URL作成機能」にも影響します。Railsアプリケーション内で別のアクションやコントローラへのリンクのURLを作成するには、url_for を利用します。url_for はコントローラやアクションその他のパラメータ文字列を受け取り、route の定義にマッチするものを探してそのURLを作成するという、今までの逆変換のようなメソッドです。(link_to は url_for の前にリンク文字列を引数に取ります)
# (controller または view 内で)
# 1. controller, action, id を指定した標準的なURL作成
url_for(:controller=>'berryz', :action=>'show', :id=>'kumakuma')
→ "http://localhost:3000/berryz/show/kumakuma"
# 2. controller, action が省略された場合は、現在のページのものが引き継がれます
url_for(:id=>'risyako')
→ "http://localhost:3000/berryz/show/risyako"
# 3. route にないパラメータが利用されると通常のCGI引数になります
url_for(:controller=>'berryz', :action=>'show', :unknown=>'abc')
→ "http://localhost:3000/berryz/show?unknown=abc"
# 4. 任意の引数に関しても配列で渡すことができます
url_for(:dir1=>'usr', :dir2=>'share', :dirs=>['rails', 'activerecord', 'CHANGELOG'])
→ "http://localhost:3000/usr/share/rails/activerecord/CHANGELOG"
# 5. リンクまで作る場合は link_to にして、リンク文字列を渡す (これは view のみ?)
link_to("修正", :action=>'edit', :id=>'risyako')
→ "<a href='http://localhost:3000/berryz/edit/risyako'>修正</a>"
前述の通り、url_for などでURLを作成する場合、現在と同じ controller, action であればそれぞれを省略することができて非常に便利なのですが、それに慣れると別の controller へのリンクの時に少し悲しく(メンド)感じるときがあります。('controller'て長いよねー)。そんなときは、route の定義に名前をつけておくと大幅にタイプ量が減って便利です。定義も簡単で、'connect' の代わりに好きな名前を付けるだけで、"(名前)_url" で指定した controller, action 等を含むURLの作成ができるようになります。
# config/routes.rb ActionController::Routing::Routes.draw do |map| map.berryz '', :controller => "berryz", :action=> "show" end # controller berryz_url(:id => 'miyabi') → "http://localhost:3000/berryz/show/miyabi"
これでどんな controller からでも簡単に上記リンクを作成することができます。
| JRuby | Rails | Berryz | ℃-ute | エッグ | jQuery |
| 前 | 2005年 7月 |
次 | ||||
| 日 | 月 | 火 | 水 | 木 | 金 | 土 |
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 | ||||||