Elixir comes with a robust templating language called EEx. It allows you to embed Elixir code inside of a string. In today's episode we're going to explore it a bit. Let's get started.


We'll create a new project called eex_playground:

mix new eex_playground
cd eex_playground

Now open test/template.eex. If you're familiar with Ruby's erb templating or something like PHP templates, this should feel familiar:

This is a template.  It can evaluate expressions, for instance:

The sum of 1 and 2 is <%= 1 + 2 %>

Let's go ahead and write a test to verify that the template works as we expect, and to learn how to use EEx to render the template. Open up test/eex_playground_test.exs:

# OK, so we're going to write a test that verifies the output of our template.
# Let's read our template into the test and replace the templated expression
# with its result, then verify that's what we get when we eval the file

  @expected_template_output """
This is a template.  It can evaluate expressions, for instance:

The sum of 1 and 2 is 3

  test "rendering a template" do
    assert EEx.eval_file("test/template.eex") == @expected_template_output

Alright, go ahead and run the test, and it passes. OK, so you can see how easy it is to evaluate a file. There's another variant of this we'll look at before we move on to binding values into the template from outside.

Rather than just evaluating a file in place, you can also compile a template into a function inside a module using a macro provided by EEx. This is faster than reading from a file every time you want to render a template, if you expect to render it a lot - think of a web framework's templating engine for instance.

Anyway, let's add another test to explore this:

  test "compiling a template to a function" do
    defmodule Render do
      require EEx
      EEx.function_from_file(:def, :template, "test/template.eex")
# Now there, we could have also told it to define a private function, by passing
# defp as the first argument.  The second argument is the name of the function
# to be defined.

    assert Render.template == @expected_template_output

Alright, run the tests, and they pass. OK, so this is kind of neat - you can compile templates. So far they're essentially static though - there's no real benefit to compiling a template that takes no external input - might as well just generate a string and hardcode that function.

Let's look at binding values into the template at render time. We will, of course, be writing some more tests. This time we'll use a string rather than a file, just for variety and to show more of the EEx API:

  @template2 "Hey there, my name is <%= @person.name %> and I'm <%= @person.age %> years old."
  @josh %{name: "Josh", age: 31}
  @robby %{name: "Robby", age: 30}
  @expected_josh "Hey there, my name is Josh and I'm 31 years old."
  @expected_robby "Hey there, my name is Robby and I'm 30 years old."

  test "rendering a template with bound variables" do
    assert EEx.eval_string(@template2, [assigns: [person: @josh]]) == @expected_josh
    assert EEx.eval_string(@template2, [assigns: [person: @robby]]) == @expected_robby

Alright, so here we've constructed a template that takes external assignments and uses them to construct its output, and we've run it with a couple of different values and proven that it works like you'd expect. Next, we'll do the same thing, but as a precompiled function:

  test "compiling a template to a function that takes bound variables" do
    # Here we're going to bind the template2 module attribute to a local
    # variable because inside the inner module it won't be defined.
    template2 = @template2
    defmodule Render2 do
      require EEx
      EEx.function_from_string(:def, :template, template2, [:assigns])

    assert Render2.template(person: @josh) == @expected_josh
    assert Render2.template(person: @robby) == @expected_robby

Run the tests, and they'll pass.


In today's episode, we saw how to use EEx to trivially build templates and either render them in place or generate modules with functions to render a given template. See you soon!