Rails routes ともっと仲良く
Rails routes を探索する初心者RailsプログラマのためのTipsです。
- Lv.0
config/routes.rb
とrake routes
- Lv.1 rails consoleで モデルからどのURLが生成されるか調べる
- Lv.2 rails consoleで URL 文字列からどのコントローラーが応答するのか調べる
- Lv.3 デフォルトのWelcome page
- Lv.4 全てにマッチするroutesを書く
- Lv.5 サブドメイン
Lv.0 config/routes.rb
と rake routes
Rails.application.routes.draw do
# comments...
end
rails new 直後の config/routes.rb
はろいろ書いてあるけど、全てコメントなので、ルートの定義は何も書いてない状態ですよね。
定義を追加して(resources :groups
みたいな)確認する方法はターミナルからおなじみの
> ./bin/rake routes
Lv.0の状態だと、多分 rake routes
が使えるだけなので、ちょっとroutesが混みいってきたり、狙ったroutesになってない!?となった時のデバッグがツライ・・
ここから徐々にレベルを上げていきます!
Lv.1 rails consoleで モデルからどのURLが生成されるか調べる
説明のため、Groupというモデルがあるとする。
Rails.application.routes.draw do
resources :groups
end
モデルからどのパスが生成されるか知りたい時
> ./bin/rails console
irb> group = Group.first
irb> app.polymorphic_path(group)
=> "/groups/1"
↓polymorphic_pathでいろいろ調べる方法は以前書いたのでスキップ
Rails でリンクパスを生成する方法色々・・とRails console で 生成される path を確認したい時 - Qiita
Lv.2 rails consoleで URL 文字列からどのコントローラーが応答するのか調べる
Lv.1の逆をやりたい時。
"/groups/1" -> GroupsController#show
が知りたい。
> ./bin/rails console
irb> > Rails.application.routes.recognize_path('/groups/1')
=> {:controller=>"groups", :action=>"show", :id=>"1"}
Recognize routes in rails console Session - Stack Overflow
Lv.3 デフォルトのWelcome page
Lv.2の方法で調べてみると、/
をどのこが担当してるのかわかる。
> ./bin/rails console
irb> > Rails.application.routes.recognize_path('/')
=> {:controller=>"rails/welcome", :action=>"index"}
明示的に root to:
を指定してない場合、 デフォルトのあの赤いページ (Welcome aboard) が出てくる
自分のコントローラーに対応させる場合
Rails.application.routes.draw do
resources :groups
root to: 'groups#index'
end
> ./bin/rails console
irb> > Rails.application.routes.recognize_path('/')
=> {:controller=>"groups", :action=>"index"}
rails/welcomeが応答してくれるのは、routes.rbに記載されたルールのうち、どれもマッチせず終了した場合
Lv.4 全てにマッチするroutesを書く
/以外のパスは、routes.rbどれにもマッチせずに応答するコントローラーないやん、となった場合、ルーティングのエラーが出る。
development環境なら、赤いRouting Errorのページ。
これを一旦キャッチして、なんかしたい場合、マッチしなかったルートを担当するメソッドに渡すルールを追加する。
Rails.application.routes.draw do
resources :groups
root to: 'groups#index'
get '*unmatched_route', to: 'application#unmatched'
end
ルーティングのルールは上から順にマッチするので、全部を受け取るようなルールは一番下に書くこと!
class ApplicationController < ActionController::Base
# ...
def unmatched
# なんかいろいろする
# リダイレクト先があればリダイレクト
redirect_to your_redirect_path if need_redirect?
# ルーティングエラーを出すときはこんな感じ
raise ActionController::RoutingError.new("No route matches #{params[:unmatched_route]}")
end
end
上の雑な例では need_redirect?
で何かのルールを判定してるものとする。
どのルーティングでここまで来ちゃったかは、受け取ったコントローラーのパラメータでわかる
params[:unmatched_route]
(config/routes.rbで書いたget '*unmatched_route'
と一致)
蛇足
特にコントローラーで判断するべきルールがなくて、一律にリダイレクトすれば良い場合は、routes.rbの記載だけで対応できる。
get '*unmatched_route' => redirect('/')
Lv.5 サブドメイン
全てのルーティングを www
サブドメインの下で使いたい、場合。
Rails.application.routes.draw do
constraints subdomain: 'www' do
resources :groups
root to: 'groups#index'
end
get '*unmatched_route', to: 'application#unmatched'
end
自分で作ってる全てのルーティングを www
下に置いてみました。
www
なしでアクセスされた時、www
付きにリダイレクトするようにしてみます。
class ApplicationController < ActionController::Base
# ...
def unmatched
raise_routing_error if request.subdomain.present?
url_to_redirect = root_url + params['unmatched_route'].to_s
route_hash = Rails.application.routes.recognize_path(url_to_redirect)
raise_routing_error if route_hash[:controller] == 'application' && route_hash[:action] == 'unmatched'
redirect_to url_to_redirect
end
private
def raise_routing_error
raise ActionController::RoutingError.new("No route matches #{params[:unmatched_route]}")
end
end
これで、http://your.domain/groups
-> http://www.your.domain/groups
にリダイレクトできるようになります。
残る問題点・・
やったーできた〜〜と思ったんですが・・実は問題点が残っています。
http://your.domain/
にアクセスすると、亡霊のようにデフォルトの Welcome aboard が残っているのです・・・
Lv.2で得たスキルで確認してみます。
> ./bin/rails console
irb> > Rails.application.routes.recognize_path('/')
=> {:controller=>"rails/welcome", :action=>"index"}
きゃーー;;
これが出てくるということは、どのルールにもマッチしていない、ということ。
routes.rbの一番最後に書いたはずの、
get '*unmatched_route', to: 'application#unmatched'
はどうしちゃったのか・・・
解決策
get '(*unmatched_route)', to: 'application#unmatched'
マッチするパスに()を付けて省略可にすると、全てマッチするようになる。
補足
recognize_path
の使い方を調べている途中で、
recognize_path
is private API and should not be used in applications.
との記載を発見・・ (参考)
あんまり積極的に利用すべきではなさそう・・・
修正してみた
def unmatched
raise_routing_error if request.subdomain.present?
redirect_to root_url + params['unmatched_route'].to_s
end
recognize_pathを使って、リダイレクト後に対応できるコントローラーがいるかどうかのチェックを外してみた。
サブドメインありでunmatched
まできた場合は、どこにも転送できないのでエラーに。
それ以外の場合はとりあえず www 付きのURLに転送するように。
(リダイレクト後にまたunmatched
に来て、404になるという無駄が発生するけど・・)
補足2
一律www付きにリダイレクト、とかだと、Nginxとかでやることのが多いのかも。。(なので後半のTipsは参考までに。。)