Breaking Down Phx Gen Live

| Apr 17, 2024 min read

Finding the Mix Task

https://github.com/phoenixframework/phoenix/blob/v1.7.12/lib/mix/tasks/phx.gen.live.ex#L170

Finding the template

https://github.com/phoenixframework/phoenix/blob/v1.7.12/priv/templates/phx.gen.live/index.ex

EEX revisited

Given a string sometimes it’s nice to inject elixir code. That’s a great start but what if we want to interact with that string? This is where EEX comes in. The EEX documentation has a short but sweet example that helps get a gist of things.

EEx.eval_string("foo <%= bar %>", bar: "baz")
"foo baz"
  defp apply_action(socket, :edit, %{"id" => id}) do
    socket
    |> assign(:page_title, "Edit <%= schema.human_singular %>")
    |> assign(:<%= schema.singular %>, <%= inspect context.alias %>.get_<%= schema.singular %>!(id))
  end

Taking a look at some of the code we can see why this is useful. Intuitively it makes sense. We want Edit + variable_name and eex helps us with that. The inner details of this are not as clear for me. For example I see schema.human_singular but how does that match with the binding.

After some trial and error in iex it looks like EEX can take in a binding that has a map value.

EEx.eval_string("Edit <%= schema.human_singular %>", schema: %{human_singular: "Todo"})
"Edit Todo"

How Can Inspect Help (Use Inspect for more complex data)

The final question is determining how does inspect come into things. As far as you can see in the gen.live example, it looks like it’s used when you want to combine things. In the example above, we can see that since we want to combine some binding with .get_ we use inspect. However, right after that it looks like we don’t need to use inspect to connect to the end of .get_?

This led me to think it might matter what type of value the binding has? alias in this case refers to the alias name of a module. For example, if our module name is ProjectWeb.Controller.UserController, then we’d want the alias to be UserController. The use case with the mix.gen.live is that we want to determine the alias of a module and add that to the file. Essentially we’d want the generator task to properly insert aliases for us.

After more trial and error, it seems that when we pass in a map for a binding is when we need inspect.

EEx.eval_string("Edit <%= bar %>", bar: "Todo")
"Edit Todo"

But with the map, we need the inspect or else the value of "Todo" is treated as a literal string.

EEx.eval_string("Edit <%= schema.human_singular %>", schema: %{human_singular: "Todo"})
"Edit \"Todo\""

EEx.eval_string("Edit <%= inspect schema.human_singular %>", schema: %{human_singular: "Todo"})
"Edit Todo"

The binding is now a map, which is an Elixir data structure. In these cases we need to use inspect so that it is converted to an algebra document. What does that mean? Honestly not sure but the best I can guess is it’s more code friendly?

https://hexdocs.pm/elixir/1.15.6/Inspect.html “The Inspect protocol converts an Elixir data structure into an algebra document.”