Railscasts - Handling Exceptions
rescue_action_in_publicが紹介されていた。
以前、存在しないpathを指定した際に404を出力させる方法 - うんたらかんたらRuby - Rubyist
で書いた方式とは別に、既存のrescue_action_in_publicをoverrideさせることで実現している。
やり方
# controller protected def local_request? false end def rescue_action_in_public(exception) case exception when ActiveRecord::RecordNotFound render :file => "#{RAILS_ROOT}/public/404.html", :status => 404 else super end end
よくわからんのでactionpackのソースを見てみた
local_request?って何よ(まぁメソッド名で想像つくけど)。
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/rescue.rb
rescue_action_in_public
# Overwrite to implement public exception handling (for requests # answering false to <tt>local_request?</tt>). By default will call # render_optional_error_file. Override this method to provide more # user friendly error messages. def rescue_action_in_public(exception) #:doc: render_optional_error_file response_code_for_rescue(exception) end
コメントにも、overrideする際は、local_request?をfalseにしろと。
render_optional_error_file
一応デフォで呼ばれるrender_optional_error_file
# Attempts to render a static error page based on the # <tt>status_code</tt> thrown, or just return headers if no such file # exists. At first, it will try to render a localized static page. # For example, if a 500 error is being handled Rails and locale is :da, # it will first attempt to render the file at <tt>public/500.da.html</tt> # then attempt to render <tt>public/500.html</tt>. If none of them exist, # the body of the response will be left empty. def render_optional_error_file(status_code) status = interpret_status(status_code) locale_path = "#{Rails.public_path}/#{status[0,3]}.#{I18n.locale}.html" if I18n.locale path = "#{Rails.public_path}/#{status[0,3]}.html" if locale_path && File.exist?(locale_path) render :file => locale_path, :status => status, :content_type => Mime::HTML elsif File.exist?(path) render :file => path, :status => status, :content_type => Mime::HTML else head status end end
噂のlocal_request?
# True if the request came from localhost, 127.0.0.1. Override this # method if you wish to redefine the meaning of a local request to # include remote IP addresses or other criteria. def local_request? #:doc: request.remote_addr == LOCALHOST && request.remote_ip == LOCALHOST end
rescue_action_without_handler
local_request?の呼出し元。
def rescue_action_without_handler(exception) log_error(exception) if logger erase_results if performed? # Let the exception alter the response if it wants. # For example, MethodNotAllowed sets the Allow header. if exception.respond_to?(:handle_response!) exception.handle_response!(response) end if consider_all_requests_local || local_request? rescue_action_locally(exception) else rescue_action_in_public(exception) end end
ここでlocal用とそれ以外でエラーのレンダリングを分けている。(動画にも出てくるが一応試してみた。後述。)
どうやら、consider_all_requests_localってのは、
config/environments/development.rb などの各環境で定義している↓らしい。
config.action_controller.consider_all_requests_local = true
つまり、これとlocalhost(172.0.0.1)両方見て判断している。
rescue_action_locally
# Render detailed diagnostics for unhandled exceptions rescued from # a controller action. def rescue_action_locally(exception) @template.instance_variable_set("@exception", exception) @template.instance_variable_set("@rescues_path", RESCUES_TEMPLATE_PATH) @template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception))) response.content_type = Mime::HTML render_for_file(rescues_path("layout"), response_code_for_rescue(exception)) end
rescue_action
一応更なる呼出し元。
rescue_action > rescue_action_without_handler > rescue_action_without_handler
# Exception handler called when the performance of an action raises # an exception. def rescue_action(exception) rescue_with_handler(exception) || rescue_action_without_handler(exception) end
個人的には
rescue_action_in_publicは、overrideされることが前提という印象を持った。
local_request?のoverrideも実害無いし、そもそもどのエラーを表示するかっていうのは
environments以下で定義できるので、この方式でいいような気がする。
例外処理なんて当然どこでもやってるんだろうから、ossのコードでもあとで見てみるか。
実践レベルのベストプラクティス欲しいなぁ。既にあるのかな。
あと、気軽にoverrideできるけど、やってokかどうかの判断っつーの難しいなぁ。