Rails で category.stg.example.com のような URL に対応する
きっかけ
ステージング環境上のランディングページ(LP)を操作中、知らない間に本番環境のページに移動し、誤ってコンバージョンしてしまった。ビューに本番環境の URL が直書きされていたことが原因だった。
課題
文字列で直書きされていた URL を、URL ヘルパーに置き換えればいいが、現在の実装ではサブドメインを考慮して URL ヘルパーを利用しなければならず、少し面倒である。
サブドメインの考慮について説明するため、LP で使われる URL のパターンを以下に列挙する。
- 本番環境
- example.com
- category.example.com
- ステージング環境
- stg.example.com
- category.stg.example.com
- 開発環境
ステージング環境は、category.stg.example.com
のように、カテゴリ(category
)と環境(stg
)のサブドメインから構成されているが、それ以外の環境は、環境を表すサブドメインはなく、カテゴリを表すサブドメインだけを持つ。
そうしたことから、ルーティングでは、環境を示すサブドメインは無視し、カテゴリのサブドメインだけをチェックする必要がある。これは以下のルーティングで実現できる。
# routes.rb # 環境を表すサブドメインが category の後ろに付いていても一致する(先頭一致) constraints ->(req) { req.subdomain.start_with?('category-a') } do # ... end constraints ->(req) { req.subdomain.start_with?('category-b') } do # ... end
URL ヘルパーでは、このようにサブドメインを指定する。
link_to form_index_url(subdomain: 'category.stg')
しかし、このように URL ヘルパーを使うたび、subdomain
オプションを指定するのは面倒である。
以下のようにサブドメインの文字列を返すヘルパーを定義し、URL の生成に使うこともできるが、面倒さはあまり緩和されない*1。
# app/helpers/application_helper.rb module ApplicationHelper def subdomain_for(category) "#{category}#{Rails.env.staging? ? '.stg' : ''}" end end
<%= link_to form_index_url(subdomain: subdomain_for('category')) %>
解決方法
そもそも Rails がパースしたサブドメインに環境名(stg
など)が入ってしまうことが問題なので、そうならないようにドメインのパース方法を変更すれば良い。
これには config.action_dispatch.tld_length
を利用できる。Ralis がドメインとサブドメインをパースするのに使用する設定である*2。
例えば、ホスト名が category.stg.example.com
で tld_length
が 1(デフォルト値)の場合
とパースされる。tld_length
が 2 の場合は
とパースされる。
この性質を利用し、ステージング環境の場合に
config.action_dispatch.tld_length = 2
とすることで、ルーティングを以下のようにシンプルに書けるようになる。これは Rails がパースしたリクエストのサブドメインと、constraints
の subdomain
オプションで渡した文字列と完全一致するためだ。
constraints subdomain: 'category' do # ブロック内のルーティング end
同様に開発環境では
config.action_dispatch.tld_length = 0
にすることで、
とパースされるので、category
をサブドメインと認識させることができる。
副作用
config.action_dispatch.tld_length
はアプリケーション中の URL のパースに関わる箇所すべてに適用されるため注意が必要である。
代替手段
副作用が気になる場合は、こういう代替手段を取ることができる。
# lib/routing_helper.rb module RoutingHelper def subdomain_for(category) "#{category}#{Rails.env.staging? ? '.stg' : ''}" end end
# config/routes.rb include RoutingHelper namespace :category, path: '' do constraints subdomain: subdomain_for(category) do # ... end end
<%= link_to category_form_index_url %>
*1:URL ヘルパーをオーバーライドする方法も考えられるが、Rails のバージョンアップで動かなくなるリスクを避けたいので、採用しなかった
*2:tld_length はトップレベルドメインの長さを表す。ドメイン部をドットで区切った配列の末尾から tld_length - 1 までがトップレベルドメイン、それ以降がサブドメインとしてパースされる