railsのオーバーライドPOSTで独自のHTTPメソッドを扱う
みなさんは確認画面のURIってどうしてるんでしょうか?
例えばユーザの作成時に/users/confirm
ってするのはなんかしっくり来ないな、と思って調べて以下の記事にたどり着きました。
"confirm"などの追加のアクションはオーバーロードPOST*2であるから、URLは動詞でよいしGETできなくてかまわない、という考えがあって、たぶんRailsもその考えに沿っている感じがするのですが、これにはあまり同意できません。URLとして現れるものは、基本的にGETできる(リソースとして意味がある)べきだと思います。
多くのWebアプリケーションは、オーバーロードPOSTを通じてサポートする操作のために、新しいURIを作成する。たとえば
/weblog/myweblog/rebuild-index
のようなURIは、そのURIにリンクしても意味はない。このようにURIにメソッド情報を含める代わりに、既存のリソース(/weblog/myweblog
)でオーバーロードPOSTをサポートし、入力表現でメソッド情報(method=rebuild-index
)を要求すればよい。 「RESTful Webサービス」p.229
これを読んで、確かに、と思ったのでmethod=confirm
として送る方法を調べてみました。
環境
rails4で試しましたが、おそらく3でも同じはず。
view
rails4では編集画面などでPUT/PATCHとして送るべき場面でも、デフォルトではPOSTで投げ、代わりに_method
というパラメタにPUT/PATCHといった値を積んでます。
そのため、単に
<%= form_for @user, method: :confirm do |f| %> ... <% end %>
のようにすれば_method=confirm
として送ってくれます。スゴイ!
routing
とりあえず、なにも聞かずに以下のようなファイルを作ってください。ファイル名は何でも可。
# config/initializers/http_methods.rb %w(confirm).each do |method| Rack::MethodOverride::HTTP_METHODS << method.upcase ActionDispatch::Request::HTTP_METHODS << method.upcase ActionDispatch::Request::HTTP_METHOD_LOOKUP[method.upcase] = method.to_sym ActionDispatch::Routing::Mapper::HttpHelpers.class_eval do define_method(method.to_sym) do |*args, &block| map_method method.to_sym, args, &block end end end
これはCONFIRM
メソッドをサーバ側で処理できるようにするためにあれこれしている処理です。
まずRack::MethodOverride::HTTP_METHODS
のところ。
これはrack側でオーバライドPOSTを実現するための仕組みで、POSTメソッドを_method
パラメタやHTTP_X_HTTP_METHOD_OVERRIDE
ヘッダで指定された値で上書きます。
しかし、デフォルトではRFC2616で定義されてるメソッド(CONNECT
とTRACE
を除く)以外が渡された場合はなにもせず、ルーティング側にはそのままPOST
として渡されてしまうため、ホワイトリストに追加しています。
次にActionDispatch::Request::HTTP_METHODS*
のところ。
これはルーティング時に走る処理で、許可されたHTTPメソッドかチェックしています。 RFC2616、2518、3253、3648、3744、5323、5789で定義されているメソッド以外を渡そうとするとinternal server errorが発生してしまうため、ここでもホワイトリストに追加します。
最後にActionDispatch::Routing::Mapper::HttpHelpers
のところ。
これはroutes.rb
で
confirm :users, to: 'users#confirm’
のように書きたかったのでこのように定義してます。
match :users, to: 'users#confirm', via: :confirm
でも問題なく動くので、必要ない場合は削除しても問題ないです。
最後に
とまあrails初心者なりに色々調べてみました。が、こういうことをしている情報はあまり見つからなかったのでrails的には行儀が悪いのかも。もっとスマートな方法があればぜひ教えてください!
追記(2014/08/27)
rspecのrequest specなどでテストする際にconfirm '/hoge'
のように書きたい場合は、以下のコードを追加すればOKです。
# config/initializers/http_methods.rb %w(confirm).each do |method| ... ActionDispatch::Integration::RequestHelpers.class_eval do define_method(method.to_sym) do |path, parameters = nil, headers_or_env = nil| process method.to_sym, path, parameters, headers_or_env end end end