誰しも一度は考えたことがあると思います、POSTやPUTにリダイレクトしたい・・・できればパラメータも引き継いで、と。
しかし、HTTP/1.1 プロトコルの制約があるため、これを実現するのは不可能だと思われます。
ググってみると、sessionにパラメータを一時的に保存して、ページ遷移後に復元するという方法を見かけました。
RailsでPOSTリクエストではリダイレクトできないことへの対応
しかし、この方法はGET以外にリダイレクトさせたい場合は手間が増えますし、汎用化も難しい気がします。
そこで、別の方法を考えてみました。
概要
実現方法をざっくり説明すると、空ページをrenderし、そこに引き継ぎたいパラメータを埋め込んだフォームを設置して自動サブミットする、と言う感じです。特定の処理専用にこれを実現するのは簡単ですが、今回は汎用的に実現する実装を行いました。
実装:paramsをhidden_field_tagに展開
まずはapplication_helperあたりに下記のようなメソッドを定義します。今回はhelperに定義しましたが、viewから呼び出せればどこでも構いません。
def parse_from_params(params, anc = nil)
@tags ||= []
params.each do |row|
key = if anc
f, b = anc.split
f + "\[#{row.first} \]" + b.to_s
else
row.first.to_s
end
if row.last.is_a?(Hash)
parse_from_params(row.last, key)
else
@tags << hidden_field_tag(key.delete(' '), row.last)
end
end
@tags.reduce(:+)
end
かなり雑にコーディングしたので分かりにくい思いますが、簡単に説明すると、paramsを再帰的に分解して、hidden_field_tagを生成する、って感じです。
例えばparamsが以下のような状態の場合、
params = {a: 1, b: {c: 2, d: 3, e: {f: 4}}}
parse_from_params
は以下のhidden_field_tagを生成します。
hidden_field_tag "a", 1
hidden_field_tag "b[c]", 2
hidden_field_tag "b[d]", 3
hidden_field_tag "b[e[f]]", 4
実装:踏み台viewをrenderするメソッド
次に、application_controllerあたりに以下のメソッドを定義します。
private
def redirect_keeped_params(url, **options)
@url = url
params_option = options.first
@params = { params_option.first => params_option.last }
@method = options.last.last
render_text = <<-EOS
<%= form_tag @url, method: @method, id: "redirect_form" do %>
<%= parse_from_params(@params) %>
<%= javascript_tag 'document.getElementById("redirect_form").submit()' %>
<% end %>
EOS
render inline: render_text
end
先ほどhelperに定義したparse_from_params
によって生成されたhidden_field_tagを含むフォームが埋め込まれた空ページをrenderします。そして、描画されたら自動でsubmitし、引数で指定したurlに遷移します。
使用例
例えば、以下のようなcontrollerがあるとします。
class HogeController < ApplicationController
def new
〜 #何らかの処理
end
def create
〜 #何らかの処理
end
end
このHogeController
のcreateアクションに、外部のcontrollerからリダイレクトしたい場合を考えます。createアクションのurlは、hoge_url
とします。
controller側の処理としては、リダイレクトしたいタイミングでredirect_keeped_params
を呼び出すだけです。引数には遷移先のpathと、渡したいパラメータ、遷移先のリクエストメソッドを指定します。
class FugaController < ApplicationController
def new
〜 #何らかの処理
redirect_keeped_params(hoge_path, fuga: permited_params, method: :post)
end
private
def permited_params
params.require(:fuga).permit(以下略)
end
end
このように、普段redirect_to
を使ってるような感覚で、POSTやPUTに遷移することが可能です。
最後に
力技ではありますが、最小限の実装で目的を実現することができました。しかし、この方法にはいくつか問題もあります。例えば、ページ遷移時に一瞬白いページが表示されてしまうことです。ユーザビリティ的にはあまり影響はありませんが、やはりスマートではないので、何か対策を考えたいです。他にも、ブラウザバック時の挙動も考えたほうがよさそうです。現状だとブラウザバックすると、踏み台ページに遷移し自動サブミットになるので、結局バックする前のページに戻ってしまいます。
まあそもそも、このような手法を検討しなければならないようなアプリケーションは、基本的な設計から見直すべきかもしれませんが・・・