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 `<%>` (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} <- 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} -> {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]
endThis will generate the index and show functions, which can be used in your controllers:
def index(conv) do
bears = Wildthings.list_bears() |> Enum.sort(&Bear.order_asc_by_name/2)
%{ conv | status: 200, resp_body: BearView.index(bears) }
endExercise: 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
endThen 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
Why always me?