Hama Blog

Hama Blog

主にtech関連の記録

Railsでrenderの引数に渡したtemplateのパスの先頭に `/` があってもいける理由を調べてみた

概要

Rails の controller で別のパスにある template を呼び出したいことがたまにあり、その際は render template: "products/show" のような指定をするのだが、render template: "/products/show" のように先頭に / があってもエラーにならずにレンダリングできてしまったので、なぜいけるのか調べてみる。

記事執筆時の環境

結論

以下の path_prefix = path_prefix.from(1) if path_prefix.start_with?("/") のところで、先頭の / を消していることがわかった。

https://github.com/rails/rails/blob/8015c2c2cf5c8718449677570f372ceb01318a32/actionview/lib/action_view/lookup_context.rb#L193-L199

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 メソッドは以下である。

https://github.com/rails/rails/blob/8015c2c2cf5c8718449677570f372ceb01318a32/actionview/lib/action_view/renderer/template_renderer.rb#L5-L12

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 では、以下の部分が実行される。

https://github.com/rails/rails/blob/8015c2c2cf5c8718449677570f372ceb01318a32/actionview/lib/action_view/renderer/template_renderer.rb#L45-L50

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) の左辺の prefixesproducts になっていたので、どうやら normalize_name で先頭の / が削除されているようだ。

(ちなみに、今回は render メソッドに prefix を指定していないので、右辺の prefixes にも何も入っていない)

https://github.com/rails/rails/blob/8015c2c2cf5c8718449677570f372ceb01318a32/actionview/lib/action_view/lookup_context.rb#L124-L129

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) にだけ着目する。

https://github.com/rails/rails/blob/8015c2c2cf5c8718449677570f372ceb01318a32/actionview/lib/action_view/lookup_context.rb#L193-L199

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: ~ では 先頭に / があるパスを渡しても問題ない。