Phoenixでrender()を書く場所によって書き方が異なる？

[fukuoka.ex](https://fukuokaex.connpass.com/)代表のpiacereです
ご覧いただいて、ありがとうございます :bow:

Phoenixで、render()を使っていると、書く場所によって、書き方が違うのを、少し解説しておこうと思います

なお、[「Phoenix」](https://qiita.com/piacerex/items/b7787580fce5f148242f#phoenix%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)は、ElixirのWebフレームワークです

内容が、面白かったり、役に立ったら、「いいね」よろしくお願いします :wink:

# コントローラと.eexでrender()の書き方が異なる？

コントローラ（通常ページ、LiveView共に）では、下記のようにrender()を使います

```elixir:lib/sample_web/controllers/page_controller.ex
defmodule SampleWeb.PageController do
	use SampleWeb, :controller

	def index(conn, params ) do
		render( conn, page, params: params )
	end
end
```

一方、.eexファイル内（layout.html.eexも含む）で、別の.html.eexファイルを読み込む場合に使うrender()では、以下のように、第一引数がconnではありません

```erb:lib/sample_web/templates/page/index.html.eex
…
<article>
	<section>
		<%= render( @view_module, "header.html", params: @params ) %>
	</section>
</article>
…
```

## コントローラで使うrender()の第一引数を調べてみる

まず、コントローラで使用しているrender()の第一引数を見てみます

以下のようなIO.inspect()によるデバッグコードを追加して、ブラウザ表示を行い、コンソールを確認します

なお、ブラウザ表示時、``http://localhost:4000/?param_a=123&param_b=xyz``とパラメータを付けておきます

```elixir:lib/sample_web/controllers/page_controller.ex
defmodule SampleWeb.PageController do
	use SampleWeb, :controller

	def index(conn, params ) do

IO.puts "=============================="
IO.inspect conn
IO.puts "=============================="

		render( conn, page, params: params )
	end
end
```

このデバッグで、第一引数が「%Plug.Conn」であることが確認できます

```
iex> [info] GET /
iex> [debug] Processing with SampleWeb.PageController.index/2
  Parameters: %{"param_a" => "123", "param_b" => "xyz"}
  Pipelines: [:browser]
iex> ==============================
iex> %Plug.Conn{
  adapter: {Plug.Cowboy.Conn, :...},
  assigns: %{},
  before_send: [#Function<2.67953897/1 in Phoenix.Controller.fetch_flash/2>,
   #Function<0.58261320/1 in Plug.Session.before_send/2>,
   #Function<1.1812729/1 in Plug.Logger.call/2>,
   #Function<0.102641852/1 in Phoenix.LiveReloader.before_send_inject_reloader/2>],
  body_params: %{},
  cookies: %{},
  halted: false,
  host: "localhost",
  method: "GET",
  owner: #PID<0.32698.1>,
   params: %{"param_a" => "123", "param_b" => "xyz"},
  path_info: [],
  path_params: %{},
  port: 4000,
  private: %{
    SampleWeb.Router => {[], %{}},
    :phoenix_action => :index,
    :phoenix_controller => SampleWeb.PageController,
    :phoenix_endpoint => SampleWeb.Endpoint,
    :phoenix_flash => %{},
    :phoenix_format => "html",
    :phoenix_layout => {SampleWeb.LayoutView, :app},
    :phoenix_pipelines => [:browser],
    :phoenix_router => SampleWeb.Router,
    :phoenix_view => SampleWeb.PageView,
    :plug_session => %{},
    :plug_session_fetch => :done
  },
  query_params: %{"param_a" => "123", "param_b" => "xyz"},
  query_string: "param_a=123&param_b=xyz",
  remote_ip: {127, 0, 0, 1},
  req_cookies: %{},
  req_headers: [
    {"accept",
     "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
    {"accept-encoding", "gzip, deflate"},
    …
  ],
  request_path: "/",
  resp_body: nil,
  resp_cookies: %{},
  resp_headers: [
    {"cache-control", "max-age=0, private, must-revalidate"},
    {"x-request-id", "FZvM6ESlOHrlYv4AAv2j"},
    {"x-frame-options", "SAMEORIGIN"},
    …
  ],
  scheme: :http,
  script_name: [],
  secret_key_base: :...,
  state: :unset,
  status: nil
}
iex> ==============================
```

## .eexで使うrender()の第一引数は「ページ表示に使っているViewモジュール」

次に、.eexで指定している第一引数をIO.inspect()で見てみます

```erb:lib/sample_web/templates/page/index.html.eex
<%
IO.puts "------------------------------"
IO.inspect @view_module
IO.puts "------------------------------"
%>
…
<article>
	<section>
		<%= render( @view_module, "header.html", params: @params ) %>
	</section>
</article>
…
```

このデバッグで、第一引数が「SampleWeb.PageView」であることが確認できます

````
iex> [info] GET /
iex> [debug] Processing with SampleWeb.PageController.index/2
  Parameters: %{"param_a" => "123", "param_b" => "xyz"}
  Pipelines: [:browser]
iex> ------------------------------
iex> SampleWeb.PageView
iex> ------------------------------
```

# ネタばらし

実は、引数の数が同じ、異なる2つのrender()が、定義されているのです（紛らわしい…）

両方のrender()のリファレンスを確認してみましょう

## コントローラで使用されるrender()は「Phoenix.Controller」の関数

https://hexdocs.pm/phoenix/Phoenix.Controller.html#render/3
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/19277458-3971-76d8-9bfc-7be30fe95f30.png)

## .eexで使用されるrender()は「Phoenix.View」の関数

https://hexdocs.pm/phoenix/Phoenix.View.html#render/3
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/d8f69d4e-2561-cdf2-c657-183129bba3b7.png)

# 何故、こんな面倒な仕様になっているの？

render()は、本来、ビュー側での定義が、MVC的な責務です（PhoenixのソレがMVCなのかはさておき）

しかし、render()は、コントローラ内から頻繁に呼び出されるため、利便性を上げることを目的に、コントローラ側の関数としても実装されている … というのがPhoenixでの実態と思われます

ちなみに、[コントローラおよびビューのモジュール名のコラム](https://qiita.com/piacere_ex/items/688b3e2542bc67ef52c7#%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%A9%E3%81%A8%E3%83%93%E3%83%A5%E3%83%BC%E3%81%AE%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E5%90%8D%E3%81%8C%E4%B8%8D%E4%B8%80%E8%87%B4%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%81%A8)でも少し解説していますが、コントローラのモジュール名を基準に、自動的にビューのモジュール名を特定することが可能なため、このような実装が実現しているのでしょう

# p.s.「いいね」よろしくお願いします

ページ左上の ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/4d515047-cc48-382e-c2b1-3ad0cc50dbbf.png) や ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/a4e3da58-70a3-4197-95a2-6a6906650d01.png) のクリックを、どうぞよろしくお願いします:bow:
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私達と一緒に盛り上げてください！:tada: