IISサイト/マルチアプリケーションとRailsのURL(その2)

Railsで生成されるアプリケーションのURLは問答無用でサイトのルートからマップされるので、想定しているURLでアプリケーションにアクセスできない、ということを先週のエントリで書いた。

期待されるURL
http://localhost/addressbook/people/new
実際にRailsのlink_to等で生成されるパス
http://localhost/people/new

今回は偶々IISでサイト上に複数のRailsアプリケーションを配置するのがきっかけではあるが、同じホスト上にアプリケーションを分割して配置したい、という要望はIIS以外のWebサーバ(Mongrel, lighttpd , Apache, etc)を使っていたとしても同様だろう。

この問題に対して、RubyRailsの素人の私がその時の思いつきでやってみたのて以下の方法だった。

  • config/routes.rbを修正する

例) アプリケーション"addressbook"で使用されるroutes.rb

ActionController::Routing::Routes.draw do |map|
  map.resources :people
  map.connect 'addressbook/:controller/:action/:id'
  map.connect 'addressbook/:controller/:action/:id.:format'
end

この結果、アクションを直接呼び出すには問題無いが、ビューテンプレート上にlink_to等で生成されたURLが期待通りにはならなかった。

  • cuzic氏のRelativePathプラグインを組み込んでみる

...現在の環境(Ruby 1.8.7.p72 + Rails 2.2)では、インストールできなかった。

>ruby script/generate plugin relative_path
E:/www/addressbook/vendor/plugins/relative_path/lib/relative_path.rb:61:in `append_features': undefined method `send!' for Applica
tionController:Class (NoMethodError)
        from E:/www/addressbook/app/controllers/application.rb:5:in `include'
        from E:/www/addressbook/app/controllers/application.rb:5

理由は不明だが、単に私の環境固有の問題か、やはりRubyRailsのバージョンの組合せの問題かもしれない。

ならば基本に戻って通常のルートにはどのようなパスとアクションの組合せが登録されているのか調べてみることにした。

Rails2.0以降はrakeコマンドで現在のアプリケーションのルートを確認できるので、前回の様にscaffoldを生成後にconfig/routes.rbで定義されているマップに直接'addressbook'をプレフィクスとして追加して、その状態のルートを見てみよう。

>rake routes
(in E:\www\addressbook)
               people GET    /people                 {:action=>"index", :controller=>"people"}
     formatted_people GET    /people.:format         {:action=>"index", :controller=>"people"}
                      POST   /people                 {:action=>"create", :controller=>"people"}
                      POST   /people.:format         {:action=>"create", :controller=>"people"}
           new_person GET    /people/new             {:action=>"new", :controller=>"people"}
 formatted_new_person GET    /people/new.:format     {:action=>"new", :controller=>"people"}
          edit_person GET    /people/:id/edit        {:action=>"edit", :controller=>"people"}
formatted_edit_person GET    /people/:id/edit.:format {:action=>"edit", :controller=>"people"}
               person GET    /people/:id             {:action=>"show", :controller=>"people"}
     formatted_person GET    /people/:id.:format     {:action=>"show", :controller=>"people"}
                      PUT    /people/:id             {:action=>"update", :controller=>"people"}
                      PUT    /people/:id.:format     {:action=>"update", :controller=>"people"}
                      DELETE /people/:id             {:action=>"destroy", :controller=>"people"}
                      DELETE /people/:id.:format     {:action=>"destroy", :controller=>"people"}
                             /addressbook/:controller/:action/:id
                             /addressbook/:controller/:action/:id.:format

なるほど。Rails 2.2のscaffoldはデフォルトでRESTfulなURLを生成するんでroutes.rbに書かれたルールを直接修正しても、実際に使われているアクションにはルーティングされない訳だ。
原因が分かってしまえば情報を収集するのは一気に楽になる。routes.rbを単純に修正するだけでは駄目だということが解ったが、どうすれば良いのだろう。いろいろ調べていると、以下の方法のいずれかでこの問題に対応できることが解った。

1. routes.rb map.resourcesを修正する

routes.rbに書かれているデフォルトのルートを修正しても駄目ならば、map.resourcesメソッドの:path_prefixオプションを指定すれば良いようだ。

参考URL: module ActionControllerResources - api.rubyonrails.org

具体的には、routes.rbに追加されたscaffoldテーブルのシンボル部分を以下のように修正する

ActionController::Routing::Routes.draw do |map|
  map.resources :people, :path_prefix => '/addressbook'
  :

これで、アプリケーション内URLのコントローラ名の前にIISから見たアプリケーション名であり、ディレクトリ名でもある、'/addressbook'が追加される。では、もう一度Railsのルートを見てみよう。

>rake routes
(in E:/www/addressbook)
               people GET    /addressbook/people             {:action=>"index", :controller=>"people"}
     formatted_people GET    /addressbook/people.:format     {:action=>"index", :controller=>"people"}
                      POST   /addressbook/people             {:action=>"create", :controller=>"people"}
                      POST   /addressbook/people.:format     {:action=>"create", :controller=>"people"}
           new_person GET    /addressbook/people/new         {:action=>"new", :controller=>"people"}
 formatted_new_person GET    /addressbook/people/new.:format {:action=>"new", :controller=>"people"}
          edit_person GET    /addressbook/people/:id/edit    {:action=>"edit", :controller=>"people"}
formatted_edit_person GET    /addressbook/people/:id/edit.:format {:action=>"edit", :controller=>"people"}
               person GET    /addressbook/people/:id         {:action=>"show", :controller=>"people"}
     formatted_person GET    /addressbook/people/:id.:format {:action=>"show", :controller=>"people"}
                      PUT    /addressbook/people/:id         {:action=>"update", :controller=>"people"}
                      PUT    /addressbook/people/:id.:format {:action=>"update", :controller=>"people"}
                      DELETE /addressbook/people/:id         {:action=>"destroy", :controller=>"people"}
                      DELETE /addressbook/people/:id.:format {:action=>"destroy", :controller=>"people"}
                             /:controller/:action/:id
                             /:controller/:action/:id.:format

よしよし、これならば期待通りにアクセスできそうだ。

しかし、この方法には二つ気に入らない点がある。

相変わらずroutes.rbをアドホックに修正しなくてはならない
コントローラが複数ある場合、全てのコントローラのシンボルにpath_prefixを設定しなくてはならない?

もっと広範にプレフィクスを付加する方法が無いかと調べて見たが、環境変数を設定することで直接変更できることが解った。

参考URL:HowToInstallApplicationsInSubdirectories in Ruby on Rails - wiki.rubyonrails.org

リンク中ではPATH_PREFIX変数に環境変数'RAILS_RELATIVE_URL_ROOT'の値をセットしているが、この環境変数自体の値を設定してやれば良いようだ。

ENV['RAILS_RELATIVE_URL_ROOT'] = "/addressbook"

実際にこのコードを書くスクリプト(箇所)だが、モードに関わらず全て設定したい場合は起動時にboot.rbから呼ばれるconfig/environment.rbの先頭辺りに、モード毎に変更したい場合は同じくconfig/enenvironments/にある、各モード毎のスクリプト(development.rb, test.rb, prodeuction.rb)の先頭辺りにするのが通例のようだ。

同様にActionController::AbstractRequest.relative_url_rootを書き換えても同じ結果を得られる。

ActionController::AbstractRequest.relative_url_root = "/addressbook"

ここまでやって、やっとすっきりしてきたぞ。



20:44 復旧