Hi everyone. I’m trying to get more familiar with how rendering works in Phoenix. I have one question that is bugging me though.
How is it that when I invoke render/2 in a template, with a keyword list as a second argument, the invocation always seems to happen with a map as the second argument?
For example, start up a brand new Phoenix app, and follows the steps to generate a new resource:
mix phoenix.gen.html User users name:string age:integer
You end up with a new.html.eex file in the template/user directory:
<h2>New user</h2>
<%= render "form.html", changeset: @changeset,
action: user_path(@conn, :create) %>
<%= link "Back", to: user_path(@conn, :index) %>
That render line should be invoking HelloWorld.UserView.render/2 with a string and a keyword list. However, if you over-ride the appropriate function in the view module, it seems like my keyword list got turned into a map:
defmodule HelloWorld.UserView do
use HelloWorld.Web, :view
def render("form.html", assigns) do
"is assigns a list?: " <> "#{is_list(assigns)}"
end
end
If you refresh the new page, you’ll see that assigns is not a list. (You can make the appropriate changes to see that it is a map). There’s something going on that is messing up my mental model that templates just get turned into different clauses of render/2 functions on the view module.
@wmnnd - Hi. Thank you. This is indeed helpful but I am still left with a gap in my mental model.
My understanding is that I am invoking HelloWorld.UserView.render/2, not Phoenix.View.render/3.
I can’t point to the sources at the moment, but more than one tutorial from individuals that I trust have mentioned something along the lines of templates just being compiled to functions on the view module. So, my view module looks something like this:
defmodule HelloWorld.UserView do
use HelloWorld.Web, :view
def render("form.html", assigns) do
"this should really be the string returned by the form"
end
def render("new.html", assign) do
"<p>" <> render("form.html", [just: :data]) <> "</p>"
end
end
Maybe a more concrete question is how is render("form.html", [just: :data]) invoking Phoenix.View.render/3, if that is indeed what’s going on.
You are indeed invoking HelloWorld.UserView.render/2, a functions that gets injected into your view module by use Phoenix.Template. This function inturn calls Phoenix.View.render/2.
Hi @shankardevy! Thank you very much. That’s the part that was missing. Thanks @wmnnd as well. I simply wasn’t following what you were saying. (Sometimes I think in too linearly a fashion).
When you use HelloWorld.Web, :view, some functions are defined by the __using__ macro of Phoenix.Template:
There you can see that one of the clauses of render/2 makes sure the assigns are a map and otherwise uses Enum.into(assigns, %{}) on them.
Since you call the use statement before you define your own render/2 functions (and hence the render/2 functions from Phoenix.Template are defined before your own and have precedence), this clause always matches when there is a non-Map assigns.