Railsで簡単にできそうで、できなかったこと
Railsはデフォルトで、http://www.foo.com/my/action のURLでアクセスした時、MyControllerのactionメソッドを実行します。また、http://www.foo.com/my/ のURLでアクセスすると、MyControllerのindexメソッドを実行します。
やりたかったことは、MyControllerのメソッド(仮にメソッド名をdoだとします)をデフォルトアクションにしたい、でした。つまり、http://www.foo.com/my/ にアクセスした時にdoメソッドを実行するように変更したかったのです。
一番安易なのは、indexメソッドとdoメソッドをごっそり差し替える方法(メソッド名を交換)です。命名規約に従うと、ビュー側のindex.html.erbとdo.html.erbのふたつのファイルの中身も交換しなければいけません。この方法でやりたいことが実現できることは分かっていますが、次の理由でやりたくありませんでした。
- subversionで以下を同時に実行して履歴が狂わないのか自信が無かった
svn mv index.html.erb do.html.erb svn mv do.html.erb index.html.erb
要は、http://www.foo.com/my/ にアクセスした時にindex以外のメソッドを実行できればいいだけなので、もっと簡単にできそうです。実際、これは簡単でした。次のようにconfig/routes.rbに書き足すだけで実現できました。routes.rbをいじると色々とカオスになりそうなのであまり変更したくありませんが、これぐらいなら許容範囲だと判断しました。
ActionController::Routing::Routes.draw do |map| map.connect 'my', :controller => 'my', :action => 'do' # 追加 # 以下、デフォルトからあるコード map.connect ':controller/:action/:id' # もし、ここに :action=>'do' を書き足すと、すべてのコントローラのデフォルトメソッドが変わる map.connect ':controller/:action/:id.:format' end
これで一件落着のようですが、残念ながら話は終わりません。
RailsではURL生成のために url_forメソッドを使います。次のようにurl_forメソッドを呼び出すと
url_for :controller => 'my', :action => 'foo'
'http://www.foo.com/my/foo' の文字列を返します。
url_for :controller => 'my', :action => 'index'
は、'http://www.foo.com/my' の文字列を返します。routes.rbに上の設定をした場合、'http://www.foo.com/my/index' を返してくれないと困ります。
結論から言うと、Railsのコードを読んでも、url_forがどこで 'index' を特別扱いして上の結果になるのか分かりませんでした。
結局、次のようにlistメソッドをindexメソッドのalias指定をして回避しています(微妙な解決ですが)。url_for :controller => 'my', :action => 'index' は使わずに、url_for :controller => 'my', :action => 'list' を使うようにしました。
class MyController < FacebookAbstractController 省略 alias list index end
以下、url_forメソッドの中を追った記録です。
url_forメソッドはActionViewモジュールにもありますが、結局、以下のActionControllerモジュールのurl_forメソッドに来ます。
[action_controller/base.rb] def url_for(options = nil) case options || {} when String options when Hash @url.rewrite(rewrite_options(options)) else polymorphic_url(options) end end
case文のwhen節にクラス名を書く技法を初めて知りました。半端でもコードリーディングは役に立ちます。
脱線して少し実験(case文の一致のためには === が使われます)。
irb> s = 'x' irb> s === String #=> false irb> String === s #=> true # 同値演算子のように見える ===メソッドですが、交換則は成り立ちません # ここから分かることは、when節に書いた式の評価値に対して === メソッド呼び出しで、評価値が ===メソッドの引数ではないことです irb> String.method(:===) #=> "#<Method: Class(Module)#===>" # String::=== の実体は Module::=== であることが分かります。 # Module::=== のリファレンスを見ると、 # Returns true if anObject is an instance of mod or one of mod‘s descendents. # と書いてあります
閑話休題。
@url.rewrite(rewrite_options(options)) から呼ぶ rewrite_optionsメソッドは無視します。@urlは、次のコードがbase.rb内にあるのでUrlRewriterオブジェクトであることが分かります。変数に型があれば、定義部分を見ればいいのに、代わりに代入部分を探して当たりをつけるのが泥臭くて嫌です。
[action_controller/base.rb] def initialize_current_url @url = UrlRewriter.new(request, params.clone) end
UrlRewriterクラスのrewriteメソッドのコードを見ます。
[action_controller/url_rewriter.rb] def rewrite(options = {}) rewrite_url(options) end
rewrite_urlメソッドの実装を見ると、中でrewrite_pathといういかにもな名前のメソッドを呼んでいます(URLから/indexが省略される動作は、URLの中のパス部分に関係する処理のはずです)。rewrite_pathメソッドの実装を見ると、最後に次のような処理があります。
[action_controller/url_rewriter.rb] def rewrite_path(options) 省略 Routing::Routes.generate(options, @request.symbolized_path_parameters) end
と言うわけでRoutingモジュールのコードへ。routing.rbの中の断片を引用すると
[action_controller/routing.rb] module Routing 省略 class RouteSet 省略 def generate(options, recall = {}, method=:generate) 省略 end end 省略 Routes = RouteSet.new end
この技法がどれほど一般的か不明ですが、こうして外部からRouting::Routes.generateのように呼べます。
generateメソッドの途中は省略しますが、メソッド本体の最後が次のようになっています。
[action_controller/routing.rb] def generate(options, recall = {}, method=:generate) 省略 routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }] routes.each do |route| results = route.send!(method, options, merged, expire_on) return results if results && (!results.is_a?(Array) || results.first) end end
Routeオブジェクトに処理が委譲されているようです。class Routeの定義はmodule Routing内にあります。だいぶ本丸に近づいている気配です。
しかしここから先が分かりません(メソッドの本体をコード生成しています)。どこに'index'依存があるのか分かりません。class PathSegment は怪しいのですが、つかみきれません。コードリーディングには自信があったのですが、まだまだ修行が足りません。
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/rails-hard/tbping
Re:Railsで簡単にできそうで、できなかったこと