Railsの ActionController::RoutingError は ApplicationController での rescue_from で捕まえられない

# 背景
- Railsのエラーページを作っていて、ルーティングエラー時に404ページを表示したかった
- 検索したり記憶を掘り返すと、rescue_from というので捕まえて404ページをrenderすればいいと思った
- Rails Guides でも rescue_from が使われている。大丈夫だ
- しかし捕まらない ActionController::RoutingError

# コード
こんな感じでルーティングエラーを捕まえて404ページを表示できると思っていたのですが、捕まえることができませんでした。

```rb:application_controller.rb
  rescue_from ActionController::RoutingError, with: :render_404
  def render_404(exception = nil)
    render template: 'errors/error_404', status: 404, layout: 'application', content_type: 'text/html'
  end
```

# 原因
- ActionController::RoutingError は ApplicationController の外で発生する
- 検索して出て来る多くのページには以下のようなものが多かった
    - `rescue_from ActionController::RoutingError`を書き、`config/routes.rb`に`get '*path', to: 'errors#not_found'`のようなルーティングを書く。
    - するとしっかりエラーページが表示される（しかし`rescue_from`でActionController::RoutingErrorは捕まえられていない。）

つまり、ルーティング定義のワイルドカードでマッチさせてエラーページを出しているにもかかわらず、例外を捕まえた気になっていたのです。以前に書いたときはコピペで済ましたものの、今はrescueの意味くらいはわかるので違和感を持って調べたところ、そういうことだったというのがわかりました。

# 解決策
これは２通りの方法で解決できます。

- 1. Rails標準の`public/404.html`などを表示する（動的な表示はできない）
- 2. Railsにはconfig.exceptions_appという設定があるので、そこにエラーを表示するためのRackアプリケーションを設定する

今回は2の手段をとりました。ErrorsControllerというApplicationControllerを継承したコントローラを作って、そのメソッドを呼ぶlambdaを`config.exceptions_app`に設定しました。

```rb:config/initializers/exceptions_app.rb
config.exceptions_app = ErrorsController.action(:render_error)
```

実際にエラーページを表示するところまでの詳細は[Railsアプリの例外ハンドリングとエラーページの表示についてまとめてみた \- Qiita](http://qiita.com/upinetree/items/273ae574f1c021d24c37#exceptions_appによる例外捕捉) に良い例が載っていました。

## 補足
上の、`.action`とはどういうメソッドでしょうか。pryでおもむろにメソッドの定義を調べるとこんな感じです。

```rb:pry
[5] pry(ApplicationController):1> show-method ActionController::Metal.action

From: /Users/gaaamii/my_project/vendor/bundle/gems/actionpack-5.0.0.1/lib/action_controller/metal.rb @ line 240:
Owner: #<Class:ActionController::Metal>
Visibility: public
Number of lines: 15

def self.action(name)
  if middleware_stack.any?
    middleware_stack.build(name) do |env|
      req = ActionDispatch::Request.new(env)
      res = make_response! req
      new.dispatch(name, req, res)
    end
  else
    lambda { |env|
      req = ActionDispatch::Request.new(env)
      res = make_response! req
      new.dispatch(name, req, res)
    }
  end
end
```

Procオブジェクト(lambda)を返すようです。

```
[25] pry(ErrorsController):1> self.action(:show)
=> #<Proc:0x007ffa88ac6928@/Users/gaaamii/my_project/vendor/bundle/gems/actionpack-5.0.0.1/lib/action_contro
```

これを`config.exceptions_app` に設定するということは、例外を捕捉するコントローラを自分で定義したRackアプリケーションで差し替えるということになります。Procオブジェクトを設定しているのは、Rackがそういう（callに応答するオブジェクトをRackアプリケーションとする）仕様だからです。

# なぜワイルドカードのルーティングではいけないのか
例外を捕捉する際、ApplicationControllerで捕捉できないのはルーティングエラーくらいだと考えられます。だとすれば、ルーティングエラーが発生しないように、意図したルーティング意外のものは全てマッチさせてエラーのコントローラにエラーページを出させてあげればいいのではないでしょうか。

実際これはよく使われていますが、何がいけないのでしょうか。あるエラーページ表示のQiita記事に対する @yuki24 さんの[コメント](http://qiita.com/mr-myself/items/c2f4fb2e5dcee6a336f3#comment-23298b703d75b7d27487)を読み、理解することができました。

> get '*path', to: ... や match '*something', to: ... はよく見かけますが、あまり良いプラクティスではないので、他の方法で実現した方がいいです。

> - match は既に非推奨
> - get なので、他の HTTP Method のリクエストに対応不能
> - ActionController::RoutingError は ApplicationController に到達する前に投げられる例外なので、rescue_from ActionController::RoutingError, ... は期待した通りに動かない（get '*path', to: ... によって、動いているように見えているだけ）
> - ApplicationController で rescue_from Exception, ... すると Airbrake や Bugsnag なんかが動かなくなる。

なるほどと思うのと同時に、まあ `get '*path'`でも多くの場合大丈夫そうだなという感想を持ちました。

# まとめ
- Rails的には`public/404.html`のような静的ファイルでエラーを表示するのが真っ当なやりかた
- とはいえエラーを自分で捕捉したいときのために`config.exceptions_app`という設定が用意されてはいる
- `get '*path'` でもそれほど困ることは無さそうだが、Railsエンジニアの間ではイマイチなやり方として認識されているような空気を感じた

# 参考
- [ruby on rails \- rescue\_from ActionController::RoutingError doesn't work \- Stack Overflow](http://stackoverflow.com/questions/37173519/rescue-from-actioncontrollerroutingerror-doesnt-work)
- [RailsでAPIをつくるときのエラー処理 \- Qiita](http://qiita.com/r7kamura/items/2e88adbdd1782277b2e7)
- [Rails の rescue\_from で拾えない例外を exceptions\_app で処理する \- Qiita](http://qiita.com/ma2ge/items/938d9f8f4839eb336318)
- [Railsの404,500エラーページをカスタマイズ \- Qiita](http://qiita.com/mr-myself/items/c2f4fb2e5dcee6a336f3#comment-23298b703d75b7d27487)
- [Configuring Rails Applications — Ruby on Rails Guides](http://guides.rubyonrails.org/configuring.html#rails-general-configuration)
- [Railsアプリの例外ハンドリングとエラーページの表示についてまとめてみた \- Qiita](http://qiita.com/upinetree/items/273ae574f1c021d24c37)