devise+mobyletteでiPhone/iPad/Android向けサイトに対応する

mobylette gemを使ったUser Agent別のテンプレート切り替えを行う際、deviseとの連携部分で少し調整が必要だったので、そのメモです。

また、下記の内容を満たした前提で記述しています。

- Rails + deviseでのPCからの基本的な認証環境は構築/実装済み
- 認証に用いるmodelは`User`モデル
- mobile用のformatは`mobile` (mobyletteのデフォルトの設定)

# gem の追加

mobylette gemの追加と最低限必要な設定は[mobylette](https://github.com/tscolari/mobylette)のドキュメントを参考に追加する。

```rb:Gemfile
gem 'mobylette'
```

```shell-session
$ bundl install
```

# ApplicationController へmobyletteのmoduleを追加

`include Mobylette::RespondToMobileRequests`を行い、必要であれば、他の設定も追加する。

```rb:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include Mobylette::RespondToMobileRequests
  # ...
end
```

# ログイン画面へリダイレクトされるようにする
非ログイン時に、認証が必要な画面にアクセスした際に`before_action authenticate_user!`に引っかかるが、ログイン画面にはリダイレクトされない。
それを、ログイン画面にリダイレクトされるようにする。

- `Devise.navigational_formats`の設定を変更する。

```rb:config/initializers/devise.rb
Devise.setup do |config|
  # ...
  # config.navigational_formats = [:"*/*", "*/*", :html]
  config.navigational_formats = [:"*/*", "*/*", :html, :mobile] # :mobile を追加
end
```

# ログイン画面へリダイレクトされた際のURLを調整
デフォルトの場合、`http://example.com/sign_in.mobile` のようにformat(`.mobile`)が付いてしまうため、付与されないようにする。

URLにformatが付与されるかどうかは、`Devise::FailureApp#skip_format?`によって判定されているので、下記のどちらかの対応をすると良いと思う。

- Devise::FailureApp#skip_format? をオーバーライドする
- CustomFailureAppクラスを作り、そのクラスのskip_format?で適切な処理を行う

## Devise::FailureApp#skip_format? をオーバーライドする場合

`config/initializers/`あたりでskip_format?をオーバーライドしてやると良いと思う。
この例では、先述で設定した`Devise.navigational_formats`を使って判定するように変更している。

```rb:config/initializers/devise_failure_app.rb
module Devise
  class FailureApp < ActionController::Metal
    def skip_format?
      # 元のskip_format?っぽく処理するなら↓な感じ
      # %w(mobile html */*).include? request_format.to_s
      Devise.navigational_formats.map(&:to_s).include? request_format.to_s     
    end
  end
end
```

## CustomFailureAppクラスを作る場合

CustomFailureAppクラスを作る場合は、下記の2つの手順を行う。

- CustomFailureAppクラスを`autoload_paths`の通ったディレクトリに配置する
- `config/initializers/devise.rb`で`failure_app`にCustomFailureAppを指定する

custom failre_appの作り方は[ここら辺](https://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-when-the-user-can-not-be-authenticated)を参考にするとよいかも。

```rb:lib/custom_failure_app.rb
class CustomFailureApp < Devise::FailureApp
  def skip_format?
    %w(mobile html */*).include? request_format.to_s
  end
end
```

```rb:config/initializers/devise.rb
Devise.setup do |config|
  # ...
  config.warden do |manager|
    manager.failure_app = CustomFailureApp
  end
  #...
end
```

# ログイン認証成功後、特定のページにリダイレクトされるようにする

ログイン認証成功後`devise/sessions/create.mobile`などのテンプレートをレンダリングしようとするが、大抵は認証直前のページやユーザーホームなどにリダイレクトしたいと思うので、その設定について。

必要なことは2つ。

- ApplicationController#after_sign_in_path_for をdeviseの処理用に追加する
- ActionController::Responder#to_mobile を追加する

```rb:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include Mobylette::RespondToMobileRequests
  # ...
  private
  def after_sign_in_path_for(resource_or_scope)
    root_path # 例としてroot_pathへリダイレクト。redirect_toは不要でpathのみ返す
  end
end
```

大抵の場合`to_mobile`メソッドの追加は`to_html`へのaliasで問題がないので、`config/initialize/mobylette.rb`あたりにファイルを追加して行う。

```rb:config/initialize/mobylette.rb
ActionController::Responder.class_eval do
  alias :to_mobile :to_html
end
```