Ruby on Railsでホスト名によるコントローラー振り分け - route.rb

さて、いよいよ僕のRuby on Rails覚え書きも、怪しいものになってまいりました。


この日記のタイトル
Ruby on Railsでホスト名によるコントローラー振り分け」
という意味ですが、通常Ruby on Railsでは

 http://ホスト名.ドメイン名/コントローラー名/アクション名/

のようなURLの解釈をされるということは、皆さんご存知のことだと思いますが、この解釈部分を変更して以下のようにするにはどうすれば良いのかということをノウハウとして掲載しているものです。

 http://コントローラー名.ドメイン名/アクション名/好きな名前/

もしくはRESTfulにしたいのであれば、以下のようなものも可能です。(共通の改造をした後に、route.rbで制御するだけですが・・・)

 http://コントローラー名.ドメイン名/名詞/アクション名


さて、やり方は至って簡単!!
まず、ActionControllerまわりを拡張します。

######################################################
# hostname の指定によってルーティングできるように設定します
######################################################
module ActionController
  module Routing
    class RouteSet
      def extract_request_environment(request)
        { :method => request.method, :hostname => request.env['HTTP_HOST'].split('.').first }
      end
    end
    class Route
      alias_method :old_recognition_conditions, :recognition_conditions
      def recognition_conditions
        result = old_recognition_conditions
        result << "conditions[:hostname] === env[:hostname]" if conditions[:hostname]
        result
      end
    end
  end
end

もういっちょ!

######################################################
# ActionController.url_forを使用した時に、hostnameの値を
# 利用してURLを作成するように設定します
######################################################
class ActionController::Base
  # :controller に指定された値を、hostnameに変換してURLを作成する
  def url_for(options = nil)

    if options[:controller]
      # オプション中に:controllerの指定がある場合、その値をホスト名に変換する
      options[:host] = get_full_hostname(options[:controller])
    else
      # :controllerが指定されていない場合は、現在リクエスト中のHTTP_HOSTのホスト名を使用する
      options[:host] = get_full_hostname(request.env['HTTP_HOST'].split('.').first)
    end
    
    # 必ずフルパスになるように設定
    options[:only_path] = false
    
    # もともと設定されていた:controllerは不要のため、削除する
    options[:controller] = '' 
    
    # アンエスケープは一番最後に実施します
    unescape_option = options[:unescape]
    options.delete(:unescape)
    
    result = original_url_for(options)

    # アンエスケープ実行
    if unescape_option == true
      CGI::unescape(result)
    else
      result
    end
  end
  
  private
  ##################################
  # オリジナルのurl_for
  ##################################  
  def original_url_for(options = nil)
    case options || {}
      when String
        options
      when Hash
        @url.rewrite(rewrite_options(options))
      else
        polymorphic_url(options)
    end
  end
  
  ##################################
  # ホスト名にドメイン名をつけたものを返す
  # 80番ポートかどうかは開発用
  # ※MYSITE_DOMAIN_NAMEはドメイン名の定数
  ##################################
  def get_full_hostname(hostname)
    if RAILS_ENV != 'production'
      "#{hostname}.#{MYSITE_DOMAIN_NAME}:#{request.env['SERVER_PORT']}"
    else
      "#{hostname}.#{MYSITE_DOMAIN_NAME}"
    end
  end
end

これの2つのコードを、どっかのファイルに書いておいてinitializerでrequireしておけばOKです。

ちなみに、ここではコードを削って掲載していますが、リバースプロキシを仕込んだ本番環境の場合、ブラウザからのリクエストがhttpsかどうかrailsで判別できないため、httpsの場合はリバースプロキシからの転送にHTTPヘッダ情報(「ブラウザからのリクエストはhttpsだよ!」)を追加して、only_pathをfalseに設定しなければならない制約を回避する必要があります。



そして最後にroute.rbを編集し、ホスト名でコントローラーのルーティングを設定してあげればOK。

map.connect ':action/:id', :controller => 'diary', :conditions => {:hostname => d'}
map.connect ':action/:id', :controller => 'photo', :conditions => {:hostname => f'}


もうちょっと難しく・・・

map.connect ':user_id/:date', :controller => 'diary', :action => 'story',
:conditions => {:hostname => 'd'},
:requirements => {:date => /[0-9]{8}/}

見たいな感じで設定すると
http://d.ドメイン名/yukizm/20080214/
てな感じで、呼び出しが出来て、この時に diaryコントローラーのstoryメソッドが実行されるようになります。
当然 params[:user_id]には 'yukizm'、params[:date]には '20080214'が入ってます。