Railsでリソースごとにサブドメインを分ける運用


目的は、サービス内のアカウントごとにサブドメインを利用できるようにすること。
Shopというリソースはdomain_nameを持ち、`http://example.com/:domain_name`が`shops#show`にルーティングされるようにしている。これを`http://domain_name.example.com/`で`shops#show`にアクセスできるようにする。


#### 参考

- [Ruby On Rails でサブドメインを使った Web サイトを作る](http://ssig33.com/text/Rails%20%E3%81%A7%E3%82%B5%E3%83%96%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3)
- [Railsで環境毎にサブドメインとパスを切り替える](http://web-salad.hateblo.jp/entry/2014/04/03/215341)
- [Railsでカスタムサブドメイン機能を実装する時のルーティング設定](http://moto.hatenadiary.com/entry/rails-custom-subdomain)
- [Rails3でサブドメインを利用したルーティングを設定する](http://rubyonrails.gachinko.org/3-2/%E3%82%B5%E3%83%96%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9F%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0/)

### はじめに

- 開発環境でサブドメインの検証をするには`lvh.me`を使う
  - `http://lvh.me:3000`でローカルホストにアクセスできる

### ルーティング設定

#### lib/subdomain.rbの作成

このクラスをルーティングに用いる。

```ruby
class Subdomain
  def self.matches?(request)
    # wwwの場合は追加するルーティングにマッチングしないようにしておく
    request.subdomain.present? && request.subdomain != 'www'
  end
end
```

#### config/routes.rbの設定

shopsリソースについて、Shopモデルはdomain_nameという項目を持ち、showアクションの際に、
domain_nameをキーにして取得している。

```ruby
  # config/routes.rb
  require Rails.root.join('lib', 'subdomain.rb')
  
  constraints Subdomain do # サブドメインが指定されていた場合に以下のルーティングが利用される
    get 'shop', to: 'shops#show', path: '/' # サブドメインごとのルートパスという感じ？
  end

  # ちなみにサブドメイン導入前にのルーティング（残している）
  get 'domain_shop', to: 'shops#show', path: ':domain_name', constraints: { format: /()/ }
```

#### コントローラ側

`constraints Subdomain do`内でルーティングされた際には、`request.subdomain`が取得できる。
なので、shops#showでshopインスタンスを取得する処理を以下のように変更する。

```ruby
def set_shop_by_domain_name
  if request.subdomain.blank?
    @shop = Shop.find_by!(domain_name: params[:domain_name])
  else
    @shop = Shop.find_by!(domain_name: request.subdomain)
  end
rescue
  redirect_to root_url(subdomain: false), notice: 'error message'
  # サブドメインが指定されたがShopにレコードがない場合にリダイレクトループになるので、
  # sudbomain: falseでサブドメインを除外しておく。
end
```

### Cookieをサブドメインで共有する

- [railsでサブドメイン間のcookieを共有する方法](http://hivecolor.com/id/117)

注意点は、参考リンクなどにもあるようにセッションの削除ができなくなるケースに陥らないこと。
セッションストアの設定は、環境ごとに分ける記事がいくつかあったけれど、以下の設定でいけた。

```ruby
# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store, key: key_name, domain: :all
```

### サブドメインとメインドメイン間のルーティング

このままでは、サブドメインでルーティングされた後にroot_urlが`sudbomain.example.jp`という風に固定されてしまう。

- [Railsでカスタムサブドメイン-本サイトとサブドメイン間のURL設定](http://moto.hatenadiary.com/entry/subdomain-url_for)
- [named_url and url_for: subdomain => false doesn't work](https://github.com/rails/rails/issues/2025)

`link_to`なりでリンクを作成する際に、以下のようにするとサブドメインをクリアできるみたい。

```ruby
link_to 'link_name', root_url(subdomain: false)
```

### CORS？

以下の様なエラーが発生。

```bash
ActionController::InvalidCrossOriginRequest - Security warning: an embedded <script> tag on another site requested protected JavaScript. If you know what youre doing, go ahead and disable forgery protection on this action to permit cross-origin JavaScript embedding.:
```

サブドメインから、メインドメインへ移るためにルーティングで以下のように設定していた。

```ruby
  constraints Subdomain do
    get 'shop', to: 'shops#show', path: '/'
    get '*a' => redirect(subdomain: false) #, constraints: { format: :html }
    # この部分でサブドメインからメインドメインにリダイレクトしていて、そのJSがエラーになっていた。
  end
```

メインドメインへ移るリンクは`root_url(subdomain: false)`を使うように決めた。


#### 関係ないけれど

最初は、CORSについて調べていたがAPIに対する記述ばかりみていた。意味がわからなかった。gemの`rack-cors`を使ったりしていたが、gemに問題があるような記事があったりして、良くわからなかった。せっかく調べたので設定箇所などをメモしておく。

- [How to avoid ActionController::InvalidCrossOriginRequest exception?](http://stackoverflow.com/questions/23209902/how-to-avoid-actioncontrollerinvalidcrossoriginrequest-exception)
- [ActionController::InvalidCrossOriginRequest exception due to bingbots](http://stackoverflow.com/questions/25361174/actioncontrollerinvalidcrossoriginrequest-exception-due-to-bingbots)
- [Wildcard CORS in RAILS](https://coderwall.com/p/cgw5_a)
- [CORS Headers With Devise](http://kellishaver.tumblr.com/post/40758797489/cors-headers-with-devise)
- [CORS(Cross-Origin Resource Sharing)について整理してみた](http://dev.classmethod.jp/etc/about-cors/)
- [sinatraでCORSに対応する](http://qiita.com/joe-re/items/c93907c44bdf3ac16f62)

```ruby
    # conifg/application.rb
    config.middleware.use Rack::Cors do
    # 以下のようにする必要があるケースがあるらしい
    #config.middleware.insert_before ActionDispatch::Static, Rack::Cors do
      allow do
        #origins '*'
        origins 'http://*.lvh.me:3000', '127.0.0.1:3000', 'localhost:3000', /http:\/\/.*\.lvh\.me:(\d*)/
        resource '*', headers: :any, :methods => [:get, :post, :delete, :put, :options]
      end
    end
```

けれど、responseのheaderに以下の様なヘッダーは入ってこなかった。コントローラをいじらなければならないのか？クライアントサイドの開発・APIの開発の際にもう一度調べることになりそう。

```
Access-Control-Allow-Methods →GET, POST, PUT, DELETE
Access-Control-Allow-Origin →*
```

ちなみに問題となっていたJSの処理は以下。

```javascript
$('#id').val(this.value);
$.ajax({
  type: 'GET',
  url: '/controller_a/action'
});
```

`Rendered controller_a/action.js.erb (89.7ms)`の際にリダイレクトされたのが問題だったということ？