概要
みんなー、今日もがんばって仕事してるー!? まだまだ暑いのにえらい! (ゝω・)v
今日はちょっとrailsで引っかかったことについてのメモ記事だ。
要約してしまうとController内でふとHTTPリクエストのメソッドを参照したくなった時はrequest.methodよりもrequest.request_methodあるいはrequest.post?などを使うべきかも。何故ならDeleteメソッドなどを誤ってPostメソッドとしてしまうから。
コードと困った現象
例えばこんなアクションを書いてみよう。
def cage
result = {method: request.method,
request_method: request.request_method,
post?: request.post?
}
render json: result
end
内容は簡単で見ての通りHTTPリクエストについて調べようとActionDispatch::Requestオブジェクトであるrequestに#method、#request_method,#post?メソッドを使ったときの結果をjsonで返すだけの検証用コードだ。またこのアクションはget,post,deleteメソッドをどれも受ける。
まずは単純にGetメソッドを使ってみた場合の結果をみよう。
{
method: "GET",
request_method: "GET",
post?: false
}
うん、予想通りだね。これはいいんだ。
ではViewでlink_toを使ってdeleteメソッドを使ってみよう。
<%= link_to 'Delete Cage', cage_cats_path, method: :delete %>
{
method: "POST",
request_method: "DELETE",
post?: false
}
んん? なんで君はいきなりPOSTメソッドだと言い出したの?
request_method: リクエストメソッドを取得
method: HEAD以外は、「request.request_method」と同じ。headはgetを取得
post?: POSTかどうか
みんなだいすきRailsドキュメントでは、同じメソッドを得るメソッド(ややこしいな)についてその違いを「HEADの場合だけ挙動が違うけど他はおんなじだよ〜」としている。でも、実際のところ2つのメソッドはもう少し違いがある。
解説と対処
これは実際の処理を追って見よう。
def method
@method ||= check_method(env["rack.methodoverride.original_method"] || env['REQUEST_METHOD'])
end
def request_method
@request_method ||= check_method(env["REQUEST_METHOD"])
end
そう、2メソッドの違いはRackがメソッドを書き換えていた場合に生まれる。
methodメソッドは書き換えられていた場合に書き換えられる前のオリジナルのメソッドを返す。
request_methodメソッドは単に現在のメソッドを参照して返すだけだ。
Rackがメソッドを書き換えるだって? なんだそりゃ。
あ、Rackの説明は他の人の記事でも読んでね。 => Rackとは何か
簡単に言えばApacheやnginxといったWebサーバとRailsを繋いでくれる奴だよ。HTTPリクエストは最初からRailsアプリへ来るわけではなくてまずはWebサーバが受け取り、さらにrackを通してRailsアプリが処理する。
HTTPメソッドのオーバーライド
で、いったいどんな時にRackがメソッドの書き換えなんてしてくれるのか。そんなことをする必要ある?
def call(env)
if allowed_methods.include?(env[REQUEST_METHOD])
method = method_override(env)
if HTTP_METHODS.include?(method)
env["rack.methodoverride.original_method"] = env[REQUEST_METHOD]
env[REQUEST_METHOD] = method
end
end
@app.call(env)
end
def method_override(env)
req = Request.new(env)
method = method_override_param(req) ||
env[HTTP_METHOD_OVERRIDE_HEADER]
method.to_s.upcase
end
軽く説明するとhttpのパラメータに"_method=(メソッド名)"っていう形式が含まれるときは、指定されてるメソッドとして扱うってワケ。なんでそんなことを?と思うかもね。それはHTMLのFORMタグがGET/POSTしかサポートしてなかったりするからだよ。そうした場合、どうするかっていうと、そうパラメータに_method=DELETEみたいに持たせることでアプリ側にDELETEメソッドとして読み替えてもらうんだよ。
そもそも、一番最初にlink_toを使ってdeleteメソッドのリクエストをしていたよね。でもよく考えるとaタグなんか踏んでもGETメソッドにしかならないはずだよね? 実際にレンダリングされるHTMLはこうだ。
<a rel="nofollow" data-method="delete" href="/cats/cage">Delete Cage</a>
見ての通り、data属性でdata-method="delete"としてる。だけど今のところはただのGETメソッドでリクエストされる。しかし、これをRailsのrails.jsがPOSTメソッドに変換してくれるんだ。
(より詳しい解説 => link_to に :method => :delete を指定した時の動作 )
処理的にはaタグが押された時、普通にGETメソッドを飛ばす代わりにPOSTメソッドのformタグ(_method="DELETE"パラメータを持つ)を作ってsubmitする。こうしてaタグでDELETEメソッドを使うことが出来てるってワケ。
現場からは以上です。╭( ・ㅂ・)و