概要
Rails の controller で別のパスにある template を呼び出したいことがたまにあり、その際は render template: "products/show"
のような指定をするのだが、render template: "/products/show"
のように先頭に /
があってもエラーにならずにレンダリングできてしまったので、なぜいけるのか調べてみる。
記事執筆時の環境
- Ruby on Rails: 7.0.4
結論
以下の path_prefix = path_prefix.from(1) if path_prefix.start_with?("/")
のところで、先頭の /
を消していることがわかった。
def normalize_name(name, prefixes) name = name.to_s idx = name.rindex("/") return name, prefixes.presence || [""] unless idx path_prefix = name[0, idx] path_prefix = path_prefix.from(1) if path_prefix.start_with?("/")
詳しく見てみる
controller で render template: "/products/show"
のような指定をするとどのように内部コードが呼び出されているのか見てみる。
まず、render
メソッドは以下である。
def render(context, options) @details = extract_details(options) template = determine_template(options) prepend_formats(template.format) render_template(context, template, options[:layout], options[:locals] || {}) end
引数の options には template: "/products/show"
が入っている。
determine_template(options)
で #<ActionView::Template app/views/products/show.html.slim locals=[]>
が返ってくることがわかった。
determine_template では、以下の部分が実行される。
elsif options.key?(:template) if options[:template].respond_to?(:render) options[:template] else @lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details) end
今回は template
の中身が文字列なので else の @lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details)
が実行される。
@lookup_context.find_template
は以下であるが、name, prefixes = normalize_name(name, prefixes)
の左辺の prefixes
が products
になっていたので、どうやら normalize_name
で先頭の /
が削除されているようだ。
(ちなみに、今回は render メソッドに prefix
を指定していないので、右辺の prefixes
にも何も入っていない)
def find(name, prefixes = [], partial = false, keys = [], options = {}) name, prefixes = normalize_name(name, prefixes) details, details_key = detail_args_for(options) @view_paths.find(name, prefixes, partial, details, details_key, keys) end alias :find_template :find
normalize_name
の中身を見てみる。
上述したが、今回は prefixes
は未指定であるため、name
(/products/show
) にだけ着目する。
def normalize_name(name, prefixes) name = name.to_s idx = name.rindex("/") return name, prefixes.presence || [""] unless idx path_prefix = name[0, idx] path_prefix = path_prefix.from(1) if path_prefix.start_with?("/")
まず、idx = name.rindex("/")
、path_prefix = name[0, idx]
により、path_prefix
には /products
が入る。
そして、path_prefix = path_prefix.from(1) if path_prefix.start_with?("/")
により先頭の /
が削除されることがわかった。
なので、render template: ~
では 先頭に /
があるパスを渡しても問題ない。