Prev Next

Here’s the initial index.eex template.

<h1>All The Bears!</h1>
 
<ul>
  <li>Name - Brown</li>
  <li>Name - Grizzly</li>
  <li>Name - Polar</li>
</ul>

And here’s the initial show.eex template.

<h1>Show Bear</h1>
<p>
Is Name hibernating? <strong>true/false</strong>
</p>

Comprehensions in Elixir

Silencing EEx Warnings

You may have noticed an EEx warning related to the application not depending on :eex. To fix this, edit mix.exs to add :eex to the extra_applications list inside the application function:

def application do
  [
    extra_applications: [:logger, :eex]
  ]
end
`
 
## A Subtle Difference with EEx
If you have used other templating languages, you might expect you could use `&lt;%&gt;` (without the equals sign) to run a list comprehension, like so:
 
```heex
<% for bear <- bears do %>
  <li><%= bear.name %> - <%= bear.type %></li>
<% end %>

However, in EEx, all expressions that output something to the template must use the equals sign (=). Here’s the correct syntax:

<%= for bear <- bears do %>
  <li><%= bear.name %> - <%= bear.type %></li>
<% end %>

Inspecting in EEx Templates

In the videos, we used the inspect function to get a representation of the bears list and output it to the template:

<%= inspect(bears) %>

Remember, inspect returns a string, while IO.inspect writes the result to a device such as the console.

Pattern Matching Comprehensions

Imagine we have a list of tuples where the first element is a person’s name and the second element indicates whether they prefer dogs or cats:

iex> prefs = [ {"Betty", :dog}, {"Bob", :dog}, {"Becky", :cat} ]

To get a list of all the dog lovers, you can pattern match using a comprehension:

iex> for {name, :dog} &lt;- prefs, do: name
["Betty", "Bob"]

You can also add a filter expression:

for {name, pet_choice} <- prefs, pet_choice == :dog, do: name
["Betty", "Bob"]

Notice we’ve used pattern matching to extract the name and pet_choice, and then added the expression pet_choice == :dog to the right of the generator, separating it from the generator with a comma. If the expression is truthy then the element is selected for inclusion in the resulting list. If the filter expression returns false or nil, then the element is rejected.

Taking it a step further, the filter can be any predicate expression that returns truthy or falsey values. And because functions are expressions, we can use a function:

iex> dog_lover? = fn(choice) -> choice == :dog end
 
iex> cat_lover? = fn(choice) -> choice == :cat end
 
iex> for {name, pet_choice} <- prefs, dog_lover?.(pet_choice), do: name
["Betty", "Bob"]
 
iex> for {name, pet_choice} <- prefs, cat_lover?.(pet_choice), do: name
["Becky"]

Tip: Atomize Keys

If you have a map with string keys and want to convert them to atoms, you can use Map.new:

iex> style = %{"width" => 10, "height" => 20, "border" => "2px"}

You can then use a comprehension to convert the keys to atoms:

iex> area = style["width"] * style["height"]
 
iex> style = Map.new(style, fn {key, val} -&gt; {String.to_atom(key), val} end)
%{border: "2px", height: 20, width: 10}

Alternatively, you can use a comprehension with the :into option:

for {key, val} <- style, into: %{}, do: {String.to_atom(key), val}
%{border: "2px", height: 20, width: 10}

Tip: Precompiling Templates

Instead of using EEx.eval_file each time, you can precompile templates with EEx.function_from_file. Here’s an example:

defmodule Servy.BearView do
  require EEx
 
  @templates_path Path.expand("../../templates", __DIR__)
 
  EEx.function_from_file :def, :index, Path.join(@templates_path, "index.eex"), [:bears]
  EEx.function_from_file :def, :show, Path.join(@templates_path, "show.eex"), [:bear]
end

This will generate the index and show functions, which can be used in your controllers:

def index(conv) do
  bears = Wildthings.list_bears() |> Enum.sort(&amp;Bear.order_asc_by_name/2)
  %{ conv | status: 200, resp_body: BearView.index(bears) }
end

Exercise: Deal a Hand of Cards

Use a comprehension to create a deck of cards:

ranks = [ "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A" ]
suits = [ "♣", "♦", "♥", "♠" ]
 
deck = for rank <- ranks, suit <- suits, do: {rank, suit}
IO.inspect(deck)

Then use Enum functions to deal 13 random cards or even deal 4 hands of 13 cards each.

Exercise: Reuse Render

Move the render/3 function from BearController to a reusable View module:

defmodule View do
  @templates_path Path.expand("../../templates", __DIR__)
 
  def render(template, assigns) do
    EEx.eval_file(Path.join(@templates_path, template), assigns)
  end
end

Then import render/3 into any controller as needed.

import Servy.View, only: [render: 3]

Code So Far

The code for this section is in the comprehensions directory found within the video-code directory of the code bundle

Prev Next