Upgrade to Pro — share decks privately, control downloads, hide ads and more …

rescue されない例外?! / A Exception not rescue #megurorb

35b58828e4e24c579c35529061711dfd?s=47 Kenta Suzuki
September 27, 2018

rescue されない例外?! / A Exception not rescue #megurorb

Meguro.rb #19 で話しました
https://megurorb.connpass.com/event/100401/

35b58828e4e24c579c35529061711dfd?s=128

Kenta Suzuki

September 27, 2018
Tweet

Transcript

  1. rescueされない例外?! #megurorb @suusan2go 2018/9/27 Meguro.rb #19 1

  2. • M3, Inc. Software Engineer • 半年くらいKotlin書いてましたが最近はPythonと Railsで機械学習系のアプリの機械学習以外を全部 やる仕事をしています •

    今日はRailsの話をします! 自己紹介: @suusan2go 2
  3. 突然ですが開発環境でこのコントローラにアクセスすると 何のエラーが表示されるでしょう 3 class HogeController < ApplicationController def hoge raise

    'hogehoge' rescue raise 'piyopiyo' end end
  4. 答え piyopiyo 4

  5. じゃあこれだったら? 5 def fuga raise ActiveRecord::RecordInvalid rescue raise 'piyopiyo' end

  6. 6

  7. 7

  8. 8

  9. れ、rescueされてないだと・・・ 9

  10. 10

  11. 11 というやりとりをTwitterでみかけて、 気になったので調べてみた話です

  12. rails consoleで試してみる 12 irb(main):001:0> def fuga irb(main):002:1> raise ActiveRecord::RecordInvalid irb(main):003:1>

    rescue irb(main):004:1> raise 'piyopiyo' irb(main):005:1> end => :fuga irb(main):006:0> fuga Traceback (most recent call last): 3: from (irb):6 2: from (irb):1:in `fuga' 1: from (irb):4:in `rescue in fuga' RuntimeError (piyopiyo) irb(main):007:0>
  13. binding.pryを仕込んでみる 13 def fuga raise ActiveRecord::RecordInvalid rescue binding.pry raise 'piyopiyo'

    end
  14. rescueされてる箇所にとまる!つまりrescueはされてるっぽい 14 8: def fuga 9: raise ActiveRecord::RecordInvalid 10: rescue

    11: binding.pry => 12: raise 'piyopiyo' 13: end
  15. 15 rescueはされてるっぽい つまり表示の問題? エラー表示箇所のコードを 見てみよう

  16. actionpack/lib/action_dispatch/middleware/debug_exceptions.rb 16 def call(env) request = ActionDispatch::Request.new env _, headers,

    body = response = @app.call(env) if headers["X-Cascade"] == "pass" body.close if body.respond_to?(:close) raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" end response rescue Exception => exception invoke_interceptors(request, exception) raise exception unless request.show_exceptions? render_exception(request, exception) end エラー画面を表示して いる箇所はここ! exceptionはそのまま 渡している
  17. actionpack/lib/action_dispatch/middleware/debug_exceptions.rb 17 def render_exception(request, exception) backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner") wrapper =

    ExceptionWrapper.new(backtrace_cleaner, exception) log_error(request, wrapper) if request.get_header("action_dispatch.show_detailed_exceptions") content_type = request.formats.first if api_request?(content_type) render_for_api_request(content_type, wrapper) else render_for_browser_request(request, wrapper) end else raise exception end end ExceptionWrapper 怪しい
  18. actionpack/lib/action_dispatch/middleware/exception_wrapper.rb 18 module ActionDispatch class ExceptionWrapper # 〜〜〜省略〜〜〜〜 def initialize(backtrace_cleaner,

    exception) @backtrace_cleaner = backtrace_cleaner @exception = original_exception(exception) expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError) end # 〜〜〜省略〜〜〜〜 end end original_exception!
  19. actionpack/lib/action_dispatch/middleware/exception_wrapper.rb 19 def original_exception(exception) if @@rescue_responses.has_key?(exception.cause.class.name) exception.cause else exception end

    end exception.causeとは
  20. https://docs.ruby-lang.org/ja/latest/method/Exception/i/cause.html 20

  21. @@rescue_responsesはなにかというと 21 module ActionDispatch class ExceptionWrapper # 〜〜〜省略〜〜〜〜 @@rescue_responses.merge!( "ActionController::RoutingError"

    => :not_found, "AbstractController::ActionNotFound" => :not_found, "ActionController::MethodNotAllowed" => :method_not_allowed, "ActionController::UnknownHttpMethod" => :method_not_allowed, "ActionController::NotImplemented" => :not_implemented, "ActionController::UnknownFormat" => :not_acceptable, "ActionController::InvalidAuthenticityToken" => :unprocessable_entity, "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity, "ActionDispatch::Http::Parameters::ParseError" => :bad_request, "ActionController::BadRequest" => :bad_request, "ActionController::ParameterMissing" => :bad_request, "Rack::QueryParser::ParameterTypeError" => :bad_request, "Rack::QueryParser::InvalidParameterError" => :bad_request )
  22. activerecord/lib/active_record/railtie.rb 22 力尽きてちゃんとコード読んでないんですけど、多分ここで追加されたやつが追加 されてるっぽい config.action_dispatch.rescue_responses.merge!( "ActiveRecord::RecordNotFound" => :not_found, "ActiveRecord::StaleObjectError" =>

    :conflict, "ActiveRecord::RecordInvalid" => :unprocessable_entity, "ActiveRecord::RecordNotSaved" => :unprocessable_entity )
  23. 結論 23 • 例外が発生した場合、その例外の根本の例外がcauseでチェックされる • 根本の例外がrescue_responsesに含まれる場合には画面には最終的 にraiseされた例外ではなく、根本の例外が表示される

  24. 再考 24 def fuga raise ActiveRecord::RecordInvalid rescue raise 'piyopiyo' end

  25. 再考 25 def fuga raise ActiveRecord::RecordInvalid rescue raise 'piyopiyo' end

    最終的にraiseされているのはこれ しかしエラー表示の際には、この例外の cause である ActiveRecord::RecordInvalidが表示さ れる
  26. 26 なんでこんな挙動になっているのだ ろう?

  27. 27 https://github.com/rails/rails/commit/dde54f00c6c08d1704d011d8108106076e8ea033

  28. なんと2010年のコミット #5784 はGitHubではなくlighthouseapp 28 https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/5784-show exceptions-renders-original-exceptions

  29. まとめ 29 • この挙動はちょっとぎょっとしますが仕様です • 試してみたら、templateでのエラーは ActionView::Template::Errorで Wrapされる • このcauseを見に行く仕様は、templateでおもむろに

    `User.find(:id)` と かやってActiveRecord::RecordNotFoundがraiseされても、ちゃんと 404を返したりするためのものっぽい・・・・・・? • もともとの用途がtemplateでのエラーのためのものなので、causeを見 る挙動はActionView::Template::Errorに絞ってもよいのではという気が してきた。 • なんか間違ってるところあれば教えてください :pray: