XML Layouts in Phoenix

While working on a Phoenix app with an API, I felt I was missing out on the great templating engine offered by Phoenix. Fortunately, the next part of the application involved writing TwiML, an XML-based DSL built for programming Twilio.

Taking the example from their docs, the following response would respond with “Hello, world!” over the phone!

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Hello, world!</Say>
</Response>

It would be pretty simple for the template to be that raw string, but I’m building an app with a few more planned responses. Initially, I split out a layout to reuse the Response portion and modified the controller accordingly.

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <%= render(@view_module, @view_template, assigns) %>
</Response>
def hello_world(conn, _params) do
  conn
  |> put_layout(LayoutView, "response.xml")
  |> put_view(HelloWorldView)
  |> render("say.xml")
end

Alas, all I got as a response was

<Say>Hello, world!</Say>

No Reponse! After, admittedly, far too long restarting things, scrapping put_layout and passing in a layout assign instead, pouring through docs, giving up and trying to use xml_builder, and pouring through more docs, I found an interesting line in the Controller module documentation.

layout_formats/1 and put_layout_formats/2 can be used to configure which formats support/require layout rendering (defaults to “html” only).

— Phoenix Documentation

Ah! Of course! HTML is not XML. The fix should be easy then:

def hello_world(conn, _params) do
  conn
  |> put_layout_formats(["xml"])
  |> put_layout(LayoutView, "response.xml")
  |> put_view(HelloWorldView)
  |> render("say.xml")
end

And so it was. Now, the receive response is as expected:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Hello, world!</Say>
</Response>

Because this entire controller is meant to handle these sorts of responses, I can lift the calls to put_ outside the specific controller using plug.

defmodule HelloWorld.HelloWorldController do
  use HelloWorld, :controller

  plug :put_layout_formats, ["xml"]
  plug :put_layout, {LayoutView, "response.xml"}

  def hello_world(conn, _params) do
    conn
    |> put_view(HelloWorldView)
    |> render("say.xml")
  end
      
end

Now all actions added to this controller will use my custom XML layout for handling TwiML.

I feedback. Let me know what you think of this article on Twitter .