ActionController::UrlWriter#url_forを色んなところで使うのは注意
最近、railsで作った自作ブログウェアでコメントが付けられなくなってました。何故かと言うとコメントを受け付けるurlがおかしかったからなんです。
なんでおかしかったか調べてみるとActionController::UrlWriter#url_for
とActionController::Base#url_for
の動作が異なってたから。
ってか、これってrailsのバグなんじゃね!?とか言ってみる。
ActionController::UrlWriter#url_for
「ユニットテストから link_to や url_for を使う方法 - Rails で行こう! - Ruby on Rails を学ぶ」や「 83's : url_forが使えないところで使えるようにする」の記事を見る限り、ActionController::UrlWriter#url_for
はControllerとView以外でやむなくurl_forが使いたくなった時に利用するモジュールらしい。
ソースコードを見てみると、こんな感じでurlを組み立ててる。
def url_for(options) options = self.class.default_url_options.merge(options) url = '' unless options.delete :only_path url << (options.delete(:protocol) || 'http') url << '://' raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host] url << options.delete(:host) url << ":#{options.delete(:port)}" if options.key?(:port) else # Delete the unused options to prevent their appearance in the query string [:protocol, :host, :port].each { |k| options.delete k } end anchor = "##{options.delete(:anchor)}" if options.key?(:anchor) url << Routing::Routes.generate(options, {}) return "#{url}#{anchor}" end
- options{:protocol] || 'http'
- '://'
- options[:host]
- options[:port]
- Routing::Routes.generate(options, {})
- "##{options.delete(:anchor)}"
おー、urlが組み立てられてるよー。
ってことで早速自分のクラスでinclude ActionController::UrlWriter
してurl_forしてみました。
ところが、開発環境ではうまくいってたのですが、いざ本番環境で試してみるとうまくいかない。
期待したurlはこんな感じでした。
/rana3/comment/post
けど、実際に組み立てられたurlはこんな感じ。
/comment/post
そう、本番環境ではrails用のドメインを作るのがめんどかったのでmongrelの--prefixオプションを使ってrelative_url_rootを利用してたのです。まあ要するに[Rails] Rails の動作環境と Location について - プログラミングは素晴らしいで説明してるようなことをやってた訳です。
ところが、実際に組み立てられたurlがおかしい…。
ActionController::Base#url_for
普通にrailsアプリ中のコントローラから呼び出したurl_forはちゃんと動いてるのにおかしいなーとちょっとそのソースコードを見てみると。
def url_for(options = {}, *parameters_for_method_reference) #:doc: case options when String ... (中略) ... when Hash @url.rewrite(rewrite_options(options)) end end
@url.rewrite
を呼び出してますね…。@urlがどんなクラスのインスタンスなのかを調べてみると、どうやらActionController::UrlRewriter
のインスタンスの模様。
じゃあ、@url.rewrite
は、何をやってるのかと調べてみると。
def rewrite(options = {}) rewrite_url(rewrite_path(options), options) end ... def rewrite_url(path, options) rewritten_url = "" unless options[:only_path] rewritten_url << (options[:protocol] || @request.protocol) rewritten_url << (options[:host] || @request.host_with_port) end rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root] rewritten_url << path rewritten_url << '/' if options[:trailing_slash] rewritten_url << "##{options[:anchor]}" if options[:anchor] rewritten_url end
あれ、ActionController::UrlWriter
の姿形が見えません!
組み立て方は、、
- options[:protocol]
- options[:host]
- @request.relative_url_root.to_s
- path
- '/'
- "##{options[:anchor]}"
ActionController::UrlWriter#url_for
と別物ですね…。ちゃんとrelative_url_rootも考慮してるし!
結論
というわけで、relative_url_rootを利用してる時にActionController::UrlWriter#url_for
を利用するのは要注意と言う事で!
その他
railsではたしてどんなところでActionController::UrlWriter
を利用してるのか気になったので調べてみるとActionMailer::Base
でincludeしてました。
ってことはrelative_url_rootを利用してるアプリケーションでrailsからメール使うとまずいんじゃないかと思うのですが…。
まあよく調べてはいないので他のところで修正されてるのかもしれませんが。