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
andput_layout_formats/2
can be used to configure which formats support/require layout rendering (defaults to “html” only).
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.