redirect_toとsend_dataを同時に呼ぶとエラーが出る理由"
コントローラーの同一メソッド内でredirect_toとsend_dataを同時に呼ぶとエラーが出る
send_dataした後にリダイレクトする処理を書いたアクションを用意しルーティングを設定します。
class SampleController < ApplicationController
def hoge
send_data('hoge', filename: 'hoge.txt')
render html: "<h2>hello world</h2>".html_safe
end
end
その上でページに遷移するとAbstractController::DoubleRenderError
が出ます。
Render and/or redirect were called multiple times in this action.
Please note that you may only call render OR redirect, and at most once per action.
Also note that neither redirect nor render terminate execution of the action,
so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
これはsend_data、redirect_toメソッドが両方ともレスポンスを返す処理であり、二重にレスポンスを返す処理を書いていることになるためです。
詳しくエラーメッセージを見てみると
Please note that you may only call render OR redirect, and at most once per action.
とあります。しかしアクション内でredirect_to
メソッドは一度しか使用しておらずrender
メソッドに至っては呼ばれていません。一度調査してみましょう。
調査
予想
send_data
が内部的にrender
メソッドを呼び出している、とかであれば2回render
メソッドが呼ばれてエラーになることに辻褄が合いそうです。
binding.irbで調査
bindng.irb
を挟んで調査します。
class SampleController < ApplicationController
def hello
binding.irb
send_data('hoge', filename: 'hoge.txt')
render html: "<h2>hello world</h2>".html_safe
end
end
method(:send_data).source_location
でメソッドが定義されている場所を特定すると次のコードが見つかりました。
super
が呼ばれているのでモジュールまたは継承クラスに定義されている同名のインスタンスメソッドを探す必要があります。
そこでbinding.irb中で以下のような処理を実行します。
# bingig.irbの中
self.class.ancestors.each do |m|
i_methods = []
# モジュールまたはクラスの継承していないインスタンスメソッドを取り出す
i_methods << m.instance_methods(false)
i_methods << m.private_instance_methods(false)
# send_dataインスタンスメソッドを持っているモジュールまたはクラス
puts m if i_methods.map(&:to_s).grep(/send_data/).present?
end
実行すると以下のモジュールが該当することがわかりました。
ActionController::Instrumentation
は既に見ているのでActionController::DataStreaming
を調べます。
ActionController::DataStreaming.instance_method(:send_data).source_location
でメソッドの定義場所を特定します。
やはり予想通りrender
が呼ばれていました。めでたしめでたし。
Discussion