こんにちは。私は現在 bearfruits という GitHub と連携した就活支援Webサービスを作成、運営しています。
bearfruits はRuby on Railsで開発しています。 エラーページがRailsデフォルトのままだったのを改善したので、手順をまとめます。
環境
- Ruby on Rails 4.2.4
- Ruby 2.3.0
目標
- 静的なエラーページを作成する
public/404.html
など
- 静的だけど layout は適用する。
app/views/layouts/application.html.erb
- DRY にする
application.html.erb
とpublic/
下での layout の二重管理はやらない
目標に至るまで
(急いでいたら次の「生成方法」まで読み飛ばしても構いません)
前述した通り、bearfruitsでは今までエラーページがRailsデフォルトのままでした。
「これはまずい!」と言うことで、エラーページをきちんと作ることにしました。
第一段階 Rack の exceptions_app
を設定する
上記Qiitaの記事のコメントが参考になります。
- エラーハンドリング用のControllerを作成し
- その中でエラー処理を書いて
- そのControllerを
exceptions_app
に登録する
と言うことをしています。
まずこの方針で実装を進めたのですが、どう考えてもめんどくさいです。
めんどくさいのは嫌いなので解決策を調べていたところ、次のGemを見つけました。
第二段階 Rambulance Gem を使う
Rambulance は、エラーハンドリングをいい感じにやってくれるGemです(多分(結局使わなかったので断言は出来ない…))。
- 例外のクラスと、HTTPのステータスコードを指定しておけばいい感じにエラーページが出せる
- エラーページにlayoutもつけられる
これはよさそうだと思ったのですが、調べていくうちに「エラーページを動的に生成すること」には問題点があることが分かってきました。
特に大きいと感じたのは、「Railsサーバーが止まっているとエラーページすら返せない」ということです。
Railsサーバーが死んでいても手前のNginxなどが生きていれば適切にエラーページを返したいですね。
第三段階 静的にエラーページを作成する
上記の問題を解決するには、public/
下に存在する404.html
などを直接編集すればよいです。
public/
下をNginxなどでドキュメントルートに指定しておけば、Railsサーバーが落ちていてもエラーページを表示することが出来ます。
ですが、静的ファイルを使用するということは、layoutを適用できないと言うことです。
layout を適用するには二重管理が必要になってしまいます。
そのため、「静的なエラーページを生成する」という手段を取ることにしました。
生成方法
以下の2つのファイルを用意します。
lib/tasks/error_page.rake
namespace :error_page do desc 'generate error pages' task gen: :environment do re = /^error_(\d+)$/ ErrorsController.instance_methods.map(&:to_s).select{|m|m =~ re}.each do |action| code = action[re, 1] fpath = Rails.root.join('public', "#{code}.html") html = ErrorsController.render(action).gsub(/^\s*<meta name="csrf-token".+$/, "") File.write(fpath, html) end end end
app/controllers/errors_controller.rb
class ErrorsController < ApplicationController # Devise を使っている場合、current_userを上書きしないと怒られた def current_user return nil end def error_404;end def error_500;end def error_422;end end
そして、app/views/errors/
下に、以下の3つのファイルを用意します。
内容はそれぞれのエラーの際に表示したいページのHTMLです。
また、backport_new_renderer
gem をインストールします。
Rails 5からrender
メソッドをControllerの外で使えるようになりました。
このGemは、その機能のRails 4バックポート用です。
gem 'backport_new_renderer'
そして、以下のコマンドを実行することでpublic/*.html
が生成されます。
$ bundle exec rake error_page:gen
また、production環境用のエラーページを生成したい場合はRAILS_ENV=production
をつけて下さい。
しくみ
ErrorsController
を作成し、各ステータスコードに対応するアクションを作成します。
そして、そのアクションをRake task内から実行し、その結果をpublic/
下に書き出しています。
また、ErrorsController
に対するroutingは書いていないため、アプリケーション側からこのコントローラーが使われることはありません。
他やったこと
gitignore
public/*.html
は、RAILS_ENV
毎に生成されるものが変わる(はず)なので、gitの管理から外しました。
- .gitignore
/public/404.html /public/422.html /public/500.html
そして、上記のRake task実行をデプロイ手順に組み込むようにしました。
Controllerから404を返す
通常のコントローラーから404エラーを返したい時の為に、以下のようなメソッドを用意しました。
app/controllers/application_controller.rb
def render_404 render file: Rails.root.join('public', '404.html'), status: 404, layout: nil end
render_404
メソッドを404を返したいところから呼び出すことで、作成した404ページを返すことが出来ます。
また、他のステータスも同様に返すことが出来ます。
error layout の作成
エラーページはそれぞれ似たようなものになっていました。
そのため、実際にはapplication
layout をそのまま使うのではなく、それを継承したerrors
layout を作成し、それを使用するようにしました。
上記記事を参考に、入れ子レイアウトを作成しました。
課題
他の assets に依存している
現状では、layout にスタイルシートや画像のsrcを書いていた場合、それをそのまま使用しています。
つまり、スタイルシート等をNginxがサーブできない状態の場合、エラーページでもそれらを表示できません。
解決策としてはスタイルシートや画像をエラーページにインラインで埋め込んでしまう方法が考えられます。
この方法が取れるか調べて試してみようと思っています。
まとめ
静的にエラーページをサーブする場合の一つの手段として有用ではないでしょうか。
また、やろうと思えば Rambulance などの Gem との連携も可能だと考えています(多分…)。