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)を使っていたとしても同様だろう。
この問題に対して、RubyもRailsの素人の私がその時の思いつきでやってみたのて以下の方法だった。
- 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
理由は不明だが、単に私の環境固有の問題か、やはりRubyとRailsのバージョンの組合せの問題かもしれない。
ならば基本に戻って通常のルートにはどのようなパスとアクションの組合せが登録されているのか調べてみることにした。
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 復旧