Railsで環境毎にサブドメインとパスを切り替える

はじめに

先日、とあるAPIを作成していたときに

  • 開発環境では、http://localhost:3000/api/users/1のように、パスの最初にapiをつける。
  • 本番環境では、http://api.example.com/users/1のように、apiというサブドメインを利用するためパス内のapiは不要。

というルーティングをやらなくてはいけなくなったので、色々調べてみました。

実装例

先に完成形を示します。これを見ればだいたいわかるかと。

# config/routes.rb 
constraints Subdomain::Api do
  namespace :api, path: Subdomain::Api.path do
    resources :users
  end
end
# lib/subdomain/api.rb
module Subdomain
  class Api

    def self.matches?(request)
      if Rails.env.production?
        request.subdomain == "api"  
      else
        request.subdomain.blank?
      end
    end

    def self.path
      "api" unless Rails.env.production?
    end

  end
end

解説

環境毎にサブドメインを切り替える

環境とか関係なく特定のサブドメインにアクセスされた場合のルーティングを行いたいのであれば、以下のように書けばOKです。

# config/routes.rb 
constraints subdomain: "api" do
  resources :users
end

# => サブドメインが"api"のときにGET /users/:id等が有効になる

もう少し複雑なことをやりたい場合、constraintsメソッドの引数にインスタンスを渡し、そのインスタンスメソッドにmatches?を用意します。このメソッドの中に、引数のrequest(コントローラーで使えるものと同じ)を使った処理を書きます。メソッドの戻り値がtrueなら、ブロック内のルーティングが有効になるという仕組みです。

上記の実装例では、Subdomain::Apiのクラスメソッドとしてmatches?を実装し、この中で環境毎に分岐した処理を行っています。そして、このクラスをconstraintsメソッドに渡すことで、環境毎に切り替えを行っています。

環境毎にパスを切り替える

パスの切り替えでは、namespaceメソッドのpathオプションを使います。
このオプションを使うことで、コントローラーとは別の名前空間が利用できます。
また、nilを指定するとパスがつかなくなる(=scopeメソッドでmoduleオプションを使ったときと同様の挙動になる)のがポイントです。

つまり、以下の2つは等価なコードです。

namespace "api", path: nil do
  resources :users
end

scope module: "api" do
  resources :users
end

上記の実装例では、Subdomain::Apiのクラスメソッドとしてpathを実装し、production以外では"api"という文字列を返すようにしています。そして、この値をpathオプションに渡すことで、環境毎に切り替えを行っています。

おわりに

書きながら、これと同じことやりたい人いるのかなって思ったけど備忘録も兼ねて頑張って書いた。

参考