Railsで例外処理403/404を実装してmodule切り分け【実践Rails学習ノート#5】
はじめに
追記@2016/06/22
Railsでの例外処理についてこの後一度真剣に考えてみたので、先に貼っておきます。
本編
Rails学習ノート続きです。
今回は403 Forbiddenの例外処理を作っていきたいと思います。
おおまかに以下のような流れで書いていきます。
- 例外を補足する
- Viewをつくる
- 例外処理をつくる
403 Forbidden
403 Forbiddenのステータスコードは、要求されたリソースはサーバー上にあるのだけどアクセス権やIPアドレス制限などの問題でアクセス拒否されたことを表します。
例外を補足する
まずは例外を補足するための処理をcontrollerに書いていきます。
新しくActionController::ActionControllerErrorクラスを継承したForbiddenクラスと、IpAddressRejectedクラスを定義しておきます。
そのあとにForbiddenクラスとIpAddressRejectedクラスが発生した時に実行するメソッドを指定して、そのメソッドrescue403
を定義します。
# app/controllers/application_controller.rb ... class Forbidden < ActionController::ActionControllerError; end class IpAddressRejected < ActionController::ActionControllerError; end rescue_from Exception, with: :rescue500 rescue_from Forbidden, with: :rescue403 rescue_from IpAddressRejected, with: :rescue403 ... def rescue403(e) @exception = e render 'errors/forbidden', status: 403 end
Viewを作成
Viewはapp/views/errors/forbidden.slim
に以下のように作りました。
# app/views/errors/forbidden.slim #error h1 403 Forbidden - case @exception - when ApplicationController::IpAddressRejected = "あなたのIPアドレス(#{request.ip})からは利用できません。" - else = "指定されたページを閲覧する権限がありません。"
Rubyの埋め込みの書き方ちょっと迷ってしまった。あと明示的なend
を避けなければいけないことにも注意。
例外を起こす
そして、top#index
でraiseメソッドでIpAddressRejectedクラスの例外をわざと起こすようにしてみます。
class Admin::TopController < ApplicationController def index raise IpAddressRejected end end
errorページが表示されました!IPアドレスが表示され、クラスはApplicationController::IpAddressRejected
になっていることも確認できます。
404 Not Found
リソースのないURLにアクセスされた場合RailsはActionController::RoutingError
を発生しますが、これだとルーティング処理の段階で発生する例外を保続できません。そこで少し工夫を加えます。
まず、config/routes.rb
の最後に以下を加えます。
root 'errors#routing_error' get '*anything' => 'errors#routing_error'
これで、全てのルーティングに一致しなかった場合最後にerrors#routing_error
メソッドが呼ばれます。
$ bin/rails g controller errors
ActionController::RoutingErrorのときはrescue403のメソッドを呼ぶように設定します。status
でレスポンスのコードを渡しているのもRails流儀。
# application_controller.rb rescue_from ActionController::RoutingError, with: :rescue404 ... def rescue404(e) @exception = e render 'errors/not_found', status: 404 end
そしたら作成したErrorsControllerを以下のように書き換えてerrors#routing_error
メソッドを定義します。
class ErrorsController < ApplicationController def routing_error raise ActionController::RoutingError, "No route matched #{request.path.inspect}" end end
そしたらViewを作成します。
#error h1 404 not found p 指定されたページは見つかりません。 p.url = request.url
これで404も表示されるようになったかと思います。
controllerのmodule切り出し
app/controllers/application_controller.rb
に例外処理が増えてごちゃごちゃしてきたのでこいつをまとめていきます。
app/controllers/concern/error_handling.rb
というファイルを新たに作成して以下のようにします。application_controller.rb
で定義したメソッドなどをこちらに移します。
module ErrorHandlers extend ActiveSupport::Concern included do rescue_from Exception, with: :rescue500 rescue_from ApplicationController::Forbidden, with: :rescue403 rescue_from ApplicationController::FIpAddressRejected, with: :rescue403 rescue_from ActionController::RoutingError, with: :rescue404 rescue_from ActiveRecord::RecordNotFound, with: :rescue404 end private def rescue500(e) @exception = e render 'errors/internal_server_error', status: 500 end def rescue403(e) @exception = e render 'errors/forbidden', status: 403 end def rescue404(e) @exception = e render 'errors/not_found', status: 404 end end
app/controllers/concerns
はRails 4.0で導入されたディレクトリです。さっきみたいにcontrollerが冗長になりそうになったときには、こちらにmoduleとして定義してincludeすることによってfatなcontrollerになってしまうことが防げるってわけみたいです。
ActiveSupport::Concern
という仕組みで、2行目の
extend ActiveSupport::Concern
でmoduleがこのクラスを継承していることがわかります。これによりincluded
メソッドが使えるようになります。
pry> ls ActiveSupport::Concern constants: MultipleIncludedBlocks ActiveSupport::Concern.methods: extended ActiveSupport::Concern#methods: append_features class_methods included
includedメソッドに囲われたメソッドは、このクラスをincludeしたクラスのメソッドとして扱われるようにできます。
またForbidden
クラスなどがApplicationController::Forbidden
などに変わったことにも注意。前はApplicationControllerクラスの中での定義だったので名前空間の中だったけど今回は外に出てしまっているので指定が必要。
とういことでapplication_controller
からErrorHandlers
モジュールをincludeします。
# app/controllers/application_controller.rb include ErrorHandlers if Rails.env.production?
またproductionのときだけincludeすることで、普段の開発では普通のRailsの例外処理のページを表示することができます。これめっちゃ便利やんけ。
application_controllerはすぐfatになりがちなので、早い段階から切り取ってmodule化していくことを意識したほうがよさげです。
大事なこと
今回学んだ大事なことをまとめておきます。
- application_controllerは肥大化しやすい
- のでActiveSupport::Concernsを使ってcontrollerをmodule化する