<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sophie DeBenedetto</title>
    <description>The latest articles on DEV Community by Sophie DeBenedetto (@sophiedebenedetto).</description>
    <link>https://dev.to/sophiedebenedetto</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F33067%2F18afb693-8227-42a4-ab35-82d1a5a1992a.png</url>
      <title>DEV Community: Sophie DeBenedetto</title>
      <link>https://dev.to/sophiedebenedetto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sophiedebenedetto"/>
    <language>en</language>
    <item>
      <title>What's New in Elixir 1.16</title>
      <dc:creator>Sophie DeBenedetto</dc:creator>
      <pubDate>Tue, 09 Jan 2024 12:00:00 +0000</pubDate>
      <link>https://dev.to/appsignal/whats-new-in-elixir-116-38pe</link>
      <guid>https://dev.to/appsignal/whats-new-in-elixir-116-38pe</guid>
      <description>&lt;p&gt;The &lt;a href="https://github.com/elixir-lang/elixir/releases/tag/v1.16.0-rc.1"&gt;Elixir 1.16 release candidate&lt;/a&gt; is out now, and it comes with some compelling improvements to diagnostics, documentation, and a few other enhancements that make Elixir an even better choice for developers.&lt;/p&gt;

&lt;p&gt;We'll dive into some of these changes and highlight Elixir's continued focus on developer happiness and community building.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Better Diagnostics for Compiler Errors in Elixir
&lt;/h2&gt;

&lt;p&gt;We'll begin with the improvements to compiler diagnostics provided in Elixir 1.16. Now, your &lt;code&gt;mix compile&lt;/code&gt; errors come with detailed code snippets that tell you exactly where your code goes wrong.&lt;/p&gt;

&lt;p&gt;You'll benefit from these improvements when you have syntax errors, mismatched delimiter errors, and generic compiler errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Syntax Errors
&lt;/h3&gt;

&lt;p&gt;First up, you can see below that syntax errors now come with a pointer to exactly where the error happened. Let's say we have the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Doctor&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;show_prescription_codes&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"DR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"DS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"amp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run &lt;code&gt;mix compile&lt;/code&gt;, you'll see a syntax error with an associated code snippet, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sophiedebenedetto[ 7:30PM] ~/personal-projects/doctor  💖 mix compile
Compiling 1 file &lt;span class="o"&gt;(&lt;/span&gt;.ex&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;==&lt;/span&gt; Compilation error &lt;span class="k"&gt;in &lt;/span&gt;file lib/doctor.ex &lt;span class="o"&gt;==&lt;/span&gt;
&lt;span class="k"&gt;**&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;SyntaxError&lt;span class="o"&gt;)&lt;/span&gt; invalid syntax found on lib/doctor.ex:48:26:
    error: syntax error before: &lt;span class="s1"&gt;']'&lt;/span&gt;
    │
 48 │     &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"DR"&lt;/span&gt;, &lt;span class="s2"&gt;"DS"&lt;/span&gt;, &lt;span class="s2"&gt;"amp"&lt;/span&gt;, &amp;amp;]
    │                          ^
    │
    └─ lib/doctor.ex:48:26
    &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.16.0-rc.1&lt;span class="o"&gt;)&lt;/span&gt; lib/kernel/parallel_compiler.ex:428: anonymous fn/5 &lt;span class="k"&gt;in &lt;/span&gt;Kernel.ParallelCompiler.spawn_workers/8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;^&lt;/code&gt; pointer to the exact location of the syntax error. With this, it's even easier to quickly spot and correct such errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mismatched Delimiter Errors
&lt;/h3&gt;

&lt;p&gt;Another diagnostic improvement comes with the code snippet representation for &lt;code&gt;MismatchedDelimiterError&lt;/code&gt; occurrences. Given the following code with a mismatched delimiter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Doctor&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;show_prescription_codes&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"DR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"DS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"amp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ad"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;mix compile&lt;/code&gt; will now show you the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sophiedebenedetto[ 7:29PM] ~/personal-projects/doctor  💖 mix compile
Compiling 1 file &lt;span class="o"&gt;(&lt;/span&gt;.ex&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;==&lt;/span&gt; Compilation error &lt;span class="k"&gt;in &lt;/span&gt;file lib/doctor.ex &lt;span class="o"&gt;==&lt;/span&gt;
&lt;span class="k"&gt;**&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;MismatchedDelimiterError&lt;span class="o"&gt;)&lt;/span&gt; mismatched delimiter found on lib/doctor.ex:48:29:
    error: unexpected token: &lt;span class="o"&gt;)&lt;/span&gt;
    │
 48 │     &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"DR"&lt;/span&gt;, &lt;span class="s2"&gt;"DS"&lt;/span&gt;, &lt;span class="s2"&gt;"amp"&lt;/span&gt;, &lt;span class="s2"&gt;"ad"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    │     │                       └ mismatched closing delimiter &lt;span class="o"&gt;(&lt;/span&gt;expected &lt;span class="s2"&gt;"]"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    │     └ unclosed delimiter
    │
    └─ lib/doctor.ex:48:29
    &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.16.0-rc.1&lt;span class="o"&gt;)&lt;/span&gt; lib/kernel/parallel_compiler.ex:428: anonymous fn/5 &lt;span class="k"&gt;in &lt;/span&gt;Kernel.ParallelCompiler.spawn_workers/8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error includes a display of the code snippet that is causing it, and even highlights the unclosed and mismatched delimiters associated to the &lt;code&gt;MismatchedDelimiterError&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Errors
&lt;/h3&gt;

&lt;p&gt;You'll now see similarly detailed code snippets for all kinds of compiler errors, including errors that describe undefined variables. Let's look at an example of that now.&lt;/p&gt;

&lt;p&gt;Given the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;list_patients&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Patient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;for_doctor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doctor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;mix compile&lt;/code&gt; will display the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sophiedebenedetto[ 7:36PM] ~/personal-projects/doctor  💖 mix compile
Compiling 1 file &lt;span class="o"&gt;(&lt;/span&gt;.ex&lt;span class="o"&gt;)&lt;/span&gt;
    error: undefined variable &lt;span class="s2"&gt;"doctor"&lt;/span&gt;
    │
 53 │     Patient.for_doctor&lt;span class="o"&gt;(&lt;/span&gt;doctor&lt;span class="o"&gt;)&lt;/span&gt;
    │                        ^^^^^^
    │
    └─ lib/doctor.ex:53:24: Doctor.list_patients/0


&lt;span class="o"&gt;==&lt;/span&gt; Compilation error &lt;span class="k"&gt;in &lt;/span&gt;file lib/doctor.ex &lt;span class="o"&gt;==&lt;/span&gt;
&lt;span class="k"&gt;**&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;CompileError&lt;span class="o"&gt;)&lt;/span&gt; lib/doctor.ex: cannot compile module Doctor &lt;span class="o"&gt;(&lt;/span&gt;errors have been logged&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These improved diagnostics make it even easier for Elixir developers to understand compilation errors, catch and remedy basic mistakes, and write clean and functioning code. These improvements show Elixir's continued focus on developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  ExDoc Content Matures with Elixir
&lt;/h2&gt;

&lt;p&gt;Some of the most exciting changes coming in Elixir 1.16 are the improvements to official Elixir language docs, offered through ExDoc. The "Getting Started" guide from &lt;a href="https://elixir-lang.org/docs.html"&gt;elixir-lang.org&lt;/a&gt; has been incorporated into the &lt;a href="https://hexdocs.pm/elixir/1.16/introduction.html"&gt;official Elixir documentation on Hex docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While this may not seem too exciting on the surface, it represents a concerted effort to refine and unify official guidance on the Elixir programming language. The language and the community have matured to such a stage that we can better align on official guides like this and provide the best agreed-upon direction for new developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anti-Patterns
&lt;/h3&gt;

&lt;p&gt;In this same vein, the official Elixir docs now include robust content on &lt;a href="https://hexdocs.pm/elixir/1.16.0-rc.1/what-anti-patterns.html"&gt;Elixir anti-patterns&lt;/a&gt;. This content is divided into four separate sections, covering code, design, processes, and metaprogramming.&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/blog/2023-12/anti-patterns.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/blog/2023-12/anti-patterns.png" alt="Anti-patterns documentation in Elixir"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once again, this content is made possible by the maturity of the Elixir language. As Elixir has grown up, and as adoption and the community have grown, we're at a point where Elixir developers can align on what good and bad Elixir code looks like.&lt;/p&gt;

&lt;p&gt;With this extensive guidance in the official docs, including framing context and code samples, Elixir developers have the resources they need to ship clean code that meets generally agreed-upon standards. It will be even easier for experienced and novice Elixir developers alike to write and maintain clean code in various scenarios.&lt;/p&gt;

&lt;p&gt;And that's not it for docs enhancements.&lt;/p&gt;

&lt;h3&gt;
  
  
  ExDoc Cheatsheets In the Official Elixir Docs
&lt;/h3&gt;

&lt;p&gt;The official docs now include an ExDoc cheatsheet for the first time. ExDoc &lt;a href="https://elixir-lang.org/blog/2022/12/22/cheatsheets-and-8-other-features-in-exdoc-that-improve-the-developer-experience/"&gt;cheatsheets&lt;/a&gt; allow developers to author quick, summary-type guides to modules and libraries.&lt;/p&gt;

&lt;p&gt;The official Elixir docs now include an &lt;a href="https://hexdocs.pm/elixir/main/enum-cheat.html"&gt;Enum module cheatsheet&lt;/a&gt;, and that's just the beginning.&lt;/p&gt;

&lt;p&gt;Elixir maintainers are looking for more community members to contribute cheatsheets to the official docs. This is a great way for first-timers to get an Elixir language contribution! Get started by opening an &lt;a href="https://github.com/elixir-lang/elixir/issues/new"&gt;issue&lt;/a&gt; that outlines what cheatsheet you'd like to add.&lt;/p&gt;

&lt;p&gt;Taken together, these docs improvements all point to the maturity of the Elixir language and community and the continued focus on education and support for Elixir newcomers. The official introduction docs, the anti-pattern docs, and the inclusion of cheatsheets provide clear and accessible guidance to Elixir developers at every level. This kind of alignment and clarity makes Elixir even more accessible to novices. At the same time, it empowers experienced Elixir devs to deliver clean, standardized Elixir code that performs well.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Enhancements
&lt;/h2&gt;

&lt;p&gt;You might be interested in taking advantage of some of the smaller enhancements from this release in your upgraded Elixir apps. Let's explore them briefly now.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;String.replace_invalid/2&lt;/code&gt; Function
&lt;/h3&gt;

&lt;p&gt;First up, there is a new &lt;code&gt;String.replace_invalid/2&lt;/code&gt; function that (you guessed it) lets you replace invalid characters in a string. Let's take a look at an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace_invalid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"asd"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="s2"&gt;"asd�"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default behavior is to replace the invalid character with a &lt;code&gt;�&lt;/code&gt;, but you can replace it with any character you like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace_invalid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"asd"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"😵"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="s2"&gt;"asd😵"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will come in handy if you find yourself processing strings of text from external sources. Such text may contain invalid characters, and identifying and replacing such characters just got a lot easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  New Functionality for &lt;code&gt;Task.yield_many/2&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Next up, let's take a look at the new option you can pass to &lt;code&gt;Task.yield_many&lt;/code&gt;. Previously, you could tell &lt;code&gt;Task.yield_many&lt;/code&gt; to timeout and stop waiting for tasks if a certain time limit was exceeded. Now, you can provoke that same behavior if a certain &lt;em&gt;number&lt;/em&gt; of tasks has been reached. You can do so by providing the &lt;code&gt;limit&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;Let's say you have the following code to spawn ten tasks. Each task will sleep for &lt;code&gt;i&lt;/code&gt; number of seconds and then return a message indicating what happened. Then, we yield each task with its result, and iterate over those &lt;code&gt;{task, result}&lt;/code&gt; tuples to operate on the result by &lt;code&gt;IO.inspect&lt;/code&gt;-ing them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="no"&gt;MyModule&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;do_tasks&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="s2"&gt;"I slept for &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds"&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;tasks_with_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yield_many&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;timeout:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks_with_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;# Shut down the tasks that did not reply nor exit&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:brutal_kill&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Here we are matching only on {:ok, value} and&lt;/span&gt;
    &lt;span class="c1"&gt;# ignoring {:exit, _} (crashed tasks) and `nil` (no replies)&lt;/span&gt;
    &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will continue to yield tasks and their results until all the tasks are done &lt;em&gt;or&lt;/em&gt; the timeout is exceeded. Now, you can use the &lt;code&gt;limit: task_num&lt;/code&gt; option instead of &lt;code&gt;timeout&lt;/code&gt;, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;tasks_with_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yield_many&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;limit:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we execute our function in IEx, you'll see that the code stops waiting for tasks after just one task is yielded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;MyModule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do_tasks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="s2"&gt;"I slept for 1 seconds"&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"I slept for 1 seconds"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the limit is reached before the default timeout (&lt;code&gt;5000&lt;/code&gt;), or before all the tasks are done, then &lt;code&gt;Task.yield_many&lt;/code&gt; returns immediately without triggering the &lt;code&gt;:on_timeout&lt;/code&gt; behaviour. You can dig further into this functionality in &lt;a href="https://hexdocs.pm/elixir/1.16.0-rc.1/Task.html#yield_many/2"&gt;the docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This new functionality gives you even more fine-grained control over how your program orchestrates async tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;Logger.levels/0&lt;/code&gt; Function
&lt;/h3&gt;

&lt;p&gt;The last enhancement I'll highlight here is the new &lt;code&gt;Logger.levels/0&lt;/code&gt; function. This function returns the list of all available log levels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:debug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:emergency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:alert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:critical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:warning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:notice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides a quick reminder of the levels you can use to configure your logger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;With the 1.16 release, Elixir continues to be a compelling choice for developers looking for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An excellent developer experience&lt;/li&gt;
&lt;li&gt;A welcoming and supportive community&lt;/li&gt;
&lt;li&gt;Strong and opinionated development guidelines&lt;/li&gt;
&lt;li&gt;Powerful language capabilities to meet the needs of various programming scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new developments in diagnostics, documentation, and language functionality continue to make Elixir a pleasure to work with. Better, more detailed diagnostic messages for compiler errors show Elixir's emphasis on developer experience, making it easier than ever before to identify and fix these errors.&lt;/p&gt;

&lt;p&gt;Improved docs and the addition of the anti-patterns guide empowers both experienced &lt;em&gt;and&lt;/em&gt; newbie Elixir devs to write clean and standardized code. And minor language improvements, like the functions we explored above, show Elixir's continued commitment to language excellence.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Observe Your Phoenix App with Structured Logging</title>
      <dc:creator>Sophie DeBenedetto</dc:creator>
      <pubDate>Tue, 25 Jul 2023 12:00:00 +0000</pubDate>
      <link>https://dev.to/appsignal/observe-your-phoenix-app-with-structured-logging-5cj7</link>
      <guid>https://dev.to/appsignal/observe-your-phoenix-app-with-structured-logging-5cj7</guid>
      <description>&lt;p&gt;In this post, we'll configure a Phoenix LiveView application to use a structured logger. We'll then use AppSignal to correlate log events with other telemetry signals, like exception reports and traces.&lt;/p&gt;

&lt;p&gt;Along the way, you'll learn about the benefits of structured logging, and you'll see how to configure a distinct framework and application logger in your Phoenix app.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Structured Logging?
&lt;/h2&gt;

&lt;p&gt;Structured logs conform to a specific structure in which an agreed-upon set of key/value pairs describe common log attributes. Without structured logging, you might get a log line that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;20:34:05.879 &lt;span class="nv"&gt;request_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;F1ssWNn62XasBTYAAAEj &lt;span class="o"&gt;[&lt;/span&gt;info] GET /
20:34:05.911 &lt;span class="nv"&gt;request_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;F1ssWNn62XasBTYAAAEj &lt;span class="o"&gt;[&lt;/span&gt;info] hello from the page controller
20:34:05.919 &lt;span class="nv"&gt;request_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;F1ssWNn62XasBTYAAAEj &lt;span class="o"&gt;[&lt;/span&gt;info] Sent 200 &lt;span class="k"&gt;in &lt;/span&gt;39ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few bits of information make up each log statement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a timestamp&lt;/li&gt;
&lt;li&gt;a level (in this case, &lt;code&gt;info&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;a log message&lt;/li&gt;
&lt;li&gt;a bit of metadata (the &lt;code&gt;request_id&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this unstructured format, all the data is included in one string, so it's difficult to search and analyze log statements based on each data point.&lt;/p&gt;

&lt;p&gt;Structured logging solves this problem for us. It breaks each piece of information into its own key/value pair. Here's an example of these same log statements structured using the &lt;a href="https://brandur.org/logfmt" rel="noopener noreferrer"&gt;logfmt&lt;/a&gt; format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"20:38:15.973 2023-05-01"&lt;/span&gt; &lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"GET /"&lt;/span&gt; &lt;span class="nv"&gt;request_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;F1sskxS1k-w0jUIAAAKB
&lt;span class="nv"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"20:38:16.013 2023-05-01"&lt;/span&gt; &lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"hello from the page controller"&lt;/span&gt; &lt;span class="nv"&gt;request_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;F1sskxS1k-w0jUIAAAKB
&lt;span class="nv"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"20:38:16.031 2023-05-01"&lt;/span&gt; &lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Sent 200 in 58ms"&lt;/span&gt; &lt;span class="nv"&gt;request_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;F1sskxS1k-w0jUIAAAKB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can search and analyze log statements based on the &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;level&lt;/code&gt;, &lt;code&gt;message&lt;/code&gt;, and &lt;code&gt;request_id&lt;/code&gt; fields. For example, you could search all log statements with a shared &lt;code&gt;request_id&lt;/code&gt;, filter log lines by their &lt;code&gt;level&lt;/code&gt; key, or search logs within a range of timestamps.&lt;/p&gt;

&lt;p&gt;Now that we can see the benefits of using structured logs, we can configure our Phoenix LiveView application to use the logfmt format in development and production. But first, we'll take a closer look at how Phoenix apps configure their loggers out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logger Configuration in Phoenix
&lt;/h2&gt;

&lt;p&gt;By default, your Phoenix application will configure its logger to output unstructured logs to the console. Open up your &lt;code&gt;config.exs&lt;/code&gt; file, and you'll see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:console&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;format:&lt;/span&gt; &lt;span class="s2"&gt;"$time $metadata[$level] $message&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;metadata:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:request_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the logger will use the &lt;a href="https://hexdocs.pm/logger/Logger.Backends.Console.html" rel="noopener noreferrer"&gt;console backend&lt;/a&gt; to print log messages to the console using the specified (unstructured) format. This configuration says that only the &lt;code&gt;request_id&lt;/code&gt; from the default metadata list should be included in each log statement. Elixir's &lt;code&gt;Logger&lt;/code&gt; adds a certain set of metadata to all log statements, and you can find the full list &lt;a href="https://hexdocs.pm/logger/1.12/Logger.html#module-metadata" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The &lt;code&gt;request_id&lt;/code&gt; metadata, however, is not added by default with the Elixir &lt;code&gt;Logger&lt;/code&gt;. Instead, it is added by the &lt;a href="https://hexdocs.pm/plug/Plug.RequestId.html" rel="noopener noreferrer"&gt;&lt;code&gt;Plug.RequestId&lt;/code&gt;&lt;/a&gt; plug that you'll see added to your application's &lt;code&gt;endpoint.ex&lt;/code&gt; file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# endpoint.ex&lt;/span&gt;
&lt;span class="n"&gt;plug&lt;/span&gt; &lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;RequestId&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a quick peek at the rest of the default metadata by setting &lt;code&gt;metadata: :all&lt;/code&gt; in &lt;code&gt;config.exs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:console&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;format:&lt;/span&gt; &lt;span class="s2"&gt;"$time $metadata[$level] $message&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;metadata:&lt;/span&gt; &lt;span class="ss"&gt;:all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when we fire up our app and visit the root path, we'll see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;21:09:24.947 &lt;span class="nv"&gt;erl_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;application&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;phoenix &lt;span class="nv"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;elixir &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;lib/phoenix/logger.ex &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;phoenix_endpoint_stop/4 &lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;231 &lt;span class="nv"&gt;mfa&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Phoenix.Logger.phoenix_endpoint_stop/4 &lt;span class="nv"&gt;module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Phoenix.Logger &lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;0.815.0&amp;gt; &lt;span class="nv"&gt;request_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;F1suRjwyEp4CjWAAAACi &lt;span class="o"&gt;[&lt;/span&gt;info] Sent 200 &lt;span class="k"&gt;in &lt;/span&gt;2ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a lot of information! Too much information for our development environment. These logs are so information-dense that they're difficult to parse with our human eyes. For that reason, you'll notice that the &lt;code&gt;dev.exs&lt;/code&gt; file overwrites this configuration to simplify it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:console&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;format:&lt;/span&gt; &lt;span class="s2"&gt;"$metadata[$level] $message&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the timestamp and metadata are excluded entirely, except for the metadata's &lt;code&gt;$level&lt;/code&gt; data point.&lt;/p&gt;

&lt;p&gt;Now that we've seen where and how your Phoenix app configures the logger out-of-the-box, let's configure the logger to emit structured logs that conform to logfmt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Logfmt in Phoenix Development
&lt;/h2&gt;

&lt;p&gt;First up, we'll configure our development environment to use the &lt;code&gt;console&lt;/code&gt; logger backend with a logfmt formatter. For this, we need the LogfmtEx dependency added to our application. Open up your &lt;code&gt;mix.exs&lt;/code&gt; file and add it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:logfmt_ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 0.4"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go ahead and run &lt;code&gt;mix deps.get&lt;/code&gt; to install the new dependency.&lt;/p&gt;

&lt;p&gt;Next up, we'll configure our application's console logger to use this formatter. In &lt;code&gt;dev.exs&lt;/code&gt;, add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:console&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;format:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;LogfmtEx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when you start up your application, you should see structured logs like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"21:18:36.828 2023-05-01"&lt;/span&gt; &lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Sent 200 in 45ms"&lt;/span&gt; &lt;span class="nv"&gt;request_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;F1suxrg8tRisBTYAAAmh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we haven't overwritten the &lt;code&gt;:metadata&lt;/code&gt; key from the console backend config in &lt;code&gt;config.exs&lt;/code&gt;, so we're still only logging the &lt;code&gt;request_id&lt;/code&gt; data point from the metadata.&lt;/p&gt;

&lt;p&gt;Now we've successfully emitted structured logs to the console, but this isn't good enough for production. To operate a Phoenix application in production, we need to emit our logs to a third-party service that allows us to search, aggregate, and analyze them at scale. This is where AppSignal comes in.&lt;/p&gt;

&lt;p&gt;In the next section, we'll incorporate the AppSignal logger into our Phoenix application and configure it to use logfmt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Logfmt with the AppSignal Logger in Production
&lt;/h2&gt;

&lt;p&gt;If you don't already have AppSignal installed and configured in your application, you can &lt;a href="https://docs.appsignal.com/elixir/installation/index.html" rel="noopener noreferrer"&gt;follow the instructions here&lt;/a&gt;. Assuming you have an AppSignal account and organization configured, we're ready to set up the AppSignal logger backend by &lt;a href="https://docs.appsignal.com/logging/platforms/integrations/elixir.html#configure-logging" rel="noopener noreferrer"&gt;following the steps in AppSignal's Logging from Elixir docs&lt;/a&gt;. We'll use the Elixir integration rather than set up a custom source.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;runtime.exs&lt;/code&gt; file, we'll specify the &lt;code&gt;Appsignal.Logger.Backend&lt;/code&gt;, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:backends&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Backend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;group:&lt;/span&gt; &lt;span class="s2"&gt;"phoenix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;format:&lt;/span&gt; &lt;span class="s2"&gt;"logfmt"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, in &lt;code&gt;runtime.exs&lt;/code&gt;, you'll want to set the application's log level to &lt;code&gt;:info&lt;/code&gt;, to ensure we send all logs at the info level and below to AppSignal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;level:&lt;/span&gt; &lt;span class="ss"&gt;:info&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will forward our application's logs to our AppSignal account and group them under the &lt;code&gt;"phoenix"&lt;/code&gt; group designation. It will treat the format of the log statements as logfmt and allow us to search and analyze our logs based on their attributes.&lt;/p&gt;

&lt;p&gt;If you log into your AppSignal account, select your organization, and select "Logging" -&amp;gt; "Application", you'll see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Fapp-signal-log-example.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Fapp-signal-log-example.png" alt="AppSignal log example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that the log line includes the timestamp, hostname, level, and group (in this case, &lt;code&gt;"phoenix"&lt;/code&gt;). Since we've specified &lt;code&gt;"logfmt"&lt;/code&gt; for the &lt;code&gt;Appsignal.Logger.Backend&lt;/code&gt;, each of these attributes is indexed and searchable. All we have to do is double-click any of these attributes on the log line itself, and the search filter on the top of the page will update and execute:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Flog-search.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Flog-search.png" alt="Log search"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's take a closer look at one of these log lines now. The &lt;code&gt;"hello from the page controller"&lt;/code&gt; log line is emitted explicitly in application code by the following line added to the &lt;code&gt;PagesController&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages_controller.ex&lt;/span&gt;
&lt;span class="kn"&gt;require&lt;/span&gt; &lt;span class="no"&gt;Logger&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello from the page controller"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:home&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;layout:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we click the &lt;code&gt;"+"&lt;/code&gt; icon next to the log line, we see all of the log line's metadata:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Flog-line-expanded.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Flog-line-expanded.png" alt="Expanded log line"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see that the &lt;code&gt;Appsignal.Logger.Backend&lt;/code&gt; emits &lt;em&gt;all&lt;/em&gt; of the default metadata, along with the &lt;code&gt;request_id&lt;/code&gt;. Since we're using logfmt, each data point is searchable. So, just like we did with the log attributes displayed in the log line itself, we can click on any metadata attributes to get them added to the search. If you click on the &lt;code&gt;request_id_string&lt;/code&gt; attribute, you'll see the search query update to look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Frequest-id-search.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Frequest-id-search.png" alt="Request ID Search"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In fact, we can add additional metadata when we emit log lines (using calls to &lt;code&gt;Logger&lt;/code&gt;), like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello from the page controller"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;user_id:&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, the &lt;code&gt;user_id&lt;/code&gt; attribute is displayed in the details of each log line and searchable just like the &lt;code&gt;request_id&lt;/code&gt; metadata.&lt;/p&gt;

&lt;p&gt;So far, we've:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configured our Phoenix application's logger to use the &lt;code&gt;Appsignal.Logger.Backend&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Taught that backend to use logfmt.&lt;/li&gt;
&lt;li&gt;Seen logs emitted and consumed into AppSignal, and got a feel for the basic logging search functionality there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we continue exploring the AppSignal logging UI and some of its more useful features, we need to address a problem that's cropped up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework Vs. Application Logs
&lt;/h2&gt;

&lt;p&gt;Taking another look at the log statements our Phoenix app has emitted to AppSignal so far, we see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;05-01-2023 10:21:04.383[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]Access StreamChatWeb.Endpoint at http://localhost:4000
+05-01-2023 10:21:04.363[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]Running StreamChatWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 &lt;span class="o"&gt;(&lt;/span&gt;http&lt;span class="o"&gt;)&lt;/span&gt;
+05-01-2023 10:19:02.709[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]GET /asdfads
+05-01-2023 10:18:51.429[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]Sent 200 &lt;span class="k"&gt;in &lt;/span&gt;4ms
+05-01-2023 10:18:51.426[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]GET /
+05-01-2023 10:18:51.388[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]Sent 302 &lt;span class="k"&gt;in &lt;/span&gt;252ms
+05-01-2023 10:18:51.137[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]POST /users/log_in
+05-01-2023 10:18:50.201[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]CONNECTED TO Phoenix.LiveView.Socket &lt;span class="k"&gt;in &lt;/span&gt;16µs Transport: :websocket Serializer: Phoenix.Socket.V2.JSONSerializer Parameters: %&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"_csrf_token"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"UQshLhEWIzUbAXgLQTE5bFNXRW8LZ00w3xVHuWYmHt1avWPZ4e6-97ut"&lt;/span&gt;, &lt;span class="s2"&gt;"_live_referer"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"undefined"&lt;/span&gt;, &lt;span class="s2"&gt;"_mounts"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;, &lt;span class="s2"&gt;"_track_static"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; %&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"http://localhost:4000/assets/app.css"&lt;/span&gt;, &lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"http://localhost:4000/assets/app.js"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="s2"&gt;"vsn"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"2.0.0"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
+05-01-2023 10:18:50.130[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]Sent 200 &lt;span class="k"&gt;in &lt;/span&gt;34ms
+05-01-2023 10:18:50.097[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]GET /users/log_in
+05-01-2023 10:18:48.255[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]Sent 200 &lt;span class="k"&gt;in &lt;/span&gt;689µs
+05-01-2023 10:18:48.254[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]GET /
05-01-2023 10:18:48.187[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]hello from the page contoller
+05-01-2023 10:18:48.215[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]Sent 200 &lt;span class="k"&gt;in &lt;/span&gt;10ms
+05-01-2023 10:18:48.206[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]POST /users/log_out
05-01-2023 10:19:25.413[application][ERROR][SOPHIEs-MacBook-Pro-2.local][phoenix]&lt;span class="k"&gt;**&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;RuntimeError&lt;span class="o"&gt;)&lt;/span&gt; boom! iex:5: &lt;span class="o"&gt;(&lt;/span&gt;file&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.2&lt;span class="o"&gt;)&lt;/span&gt; src/elixir.erl:309: anonymous fn/4 &lt;span class="k"&gt;in&lt;/span&gt; :elixir.eval_external_handler/1 &lt;span class="o"&gt;(&lt;/span&gt;stdlib 4.1.1&lt;span class="o"&gt;)&lt;/span&gt; erl_eval.erl:748: :erl_eval.do_apply/7 &lt;span class="o"&gt;(&lt;/span&gt;stdlib 4.1.1&lt;span class="o"&gt;)&lt;/span&gt; erl_eval.erl:987: :erl_eval.try_clauses/10 &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.2&lt;span class="o"&gt;)&lt;/span&gt; src/elixir.erl:294: :elixir.eval_forms/4 &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.2&lt;span class="o"&gt;)&lt;/span&gt; lib/module/parallel_checker.ex:107: Module.ParallelChecker.verify/1 &lt;span class="o"&gt;(&lt;/span&gt;iex 1.14.2&lt;span class="o"&gt;)&lt;/span&gt; lib/iex/evaluator.ex:329: IEx.Evaluator.eval_and_inspect/3 &lt;span class="o"&gt;(&lt;/span&gt;iex 1.14.2&lt;span class="o"&gt;)&lt;/span&gt; lib/iex/evaluator.ex:303: IEx.Evaluator.eval_and_inspect_parsed/3 &lt;span class="o"&gt;(&lt;/span&gt;iex 1.14.2&lt;span class="o"&gt;)&lt;/span&gt; lib/iex/evaluator.ex:292: IEx.Evaluator.parse_eval_inspect/3 &lt;span class="o"&gt;(&lt;/span&gt;iex 1.14.2&lt;span class="o"&gt;)&lt;/span&gt; lib/iex/evaluator.ex:187: IEx.Evaluator.loop/1 &lt;span class="o"&gt;(&lt;/span&gt;iex 1.14.2&lt;span class="o"&gt;)&lt;/span&gt; lib/iex/evaluator.ex:32: IEx.Evaluator.init/4
+05-01-2023 10:18:47.578[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]Sent 200 &lt;span class="k"&gt;in &lt;/span&gt;7ms
+05-01-2023 10:18:47.572[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]GET /
+05-01-2023 10:18:46.901[application][INFO][SOPHIEs-MacBook-Pro-2.local][phoenix]Sent 200 &lt;span class="k"&gt;in &lt;/span&gt;52ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a lot of noise in here mixed with some useful bits of information. You can see one error log that will very likely be helpful in debugging, as well as one instance of our &lt;code&gt;"hello from the page controller"&lt;/code&gt; log statement. Those two log lines are very hard to detect amidst the many log lines emitted that indicate the server is up, the WebSocket channel is connected, certain web requests are served, and so on.&lt;/p&gt;

&lt;p&gt;These noisy logs are emitted not from our &lt;em&gt;application&lt;/em&gt; code, but from the Phoenix framework itself. They don't tell us anything interesting, and they are not an essential part of the overall observability picture of our application. Instead, they create noise and contribute to high log volume, making it harder to find and identify the log statements that we intend to emit from application code.&lt;/p&gt;

&lt;p&gt;For this reason, framework logs should be emitted at the &lt;code&gt;FATAL&lt;/code&gt; level. Application logs that are emitted when exceptions are raised or by explicit calls to &lt;code&gt;Logger&lt;/code&gt; should log at the &lt;code&gt;INFO&lt;/code&gt; level. Phoenix doesn't expose an easy way for us to do that, but we can build our own custom logger backend to solve this problem. Let's do that now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build A Custom Logger Backend
&lt;/h2&gt;

&lt;p&gt;A logger backend is really nothing more than a GenServer, and must conform to the following API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement an &lt;code&gt;init/1&lt;/code&gt; function&lt;/li&gt;
&lt;li&gt;Implement a &lt;code&gt;handle_event/3&lt;/code&gt; function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the skeleton for our backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/stream_chat/logger/backend.ex&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;group:&lt;/span&gt; &lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;format:&lt;/span&gt; &lt;span class="ss"&gt;:plaintext&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_gl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;init/1&lt;/code&gt; function will be called with whatever config we pass to the call to configure the logger backend in &lt;code&gt;runtime.exs&lt;/code&gt;. It returns the &lt;code&gt;{:ok, state}&lt;/code&gt; tuple.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;handle_event/2&lt;/code&gt; function will be called with all the data we need to emit a log statement with the &lt;code&gt;Appsignal.Logger&lt;/code&gt;, including the:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Level&lt;/li&gt;
&lt;li&gt;Log message&lt;/li&gt;
&lt;li&gt;Metadata&lt;/li&gt;
&lt;li&gt;Internal state of the logger backend process that we established in &lt;code&gt;init/1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whenever the Phoenix framework itself calls the Phoenix application's logger, the &lt;code&gt;metadata&lt;/code&gt; variable passed to &lt;code&gt;handle_event/2&lt;/code&gt; will include an &lt;code&gt;:application&lt;/code&gt; key pointing to a value of &lt;code&gt;:phoenix&lt;/code&gt;. Where logs are emitted by an explicit call to &lt;code&gt;Logger&lt;/code&gt; or by an exception raised in our application, the &lt;code&gt;metadata&lt;/code&gt; keyword list will &lt;em&gt;not&lt;/em&gt; include an &lt;code&gt;:application&lt;/code&gt; key. We want to be able to write some code that says: "Only log statements coming from Phoenix if they are at the configured log level or below". We can do exactly that by adding some logic that looks for &lt;code&gt;metadata&lt;/code&gt; with &lt;code&gt;application: :phoenix&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before we write that code, let's take a look at how we will configure this custom logger backend in our &lt;code&gt;runtime.exs&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:backends&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;StreamChat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Backend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;[&lt;/span&gt;
     &lt;span class="ss"&gt;format:&lt;/span&gt; &lt;span class="s2"&gt;"logfmt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="ss"&gt;application_config:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       &lt;span class="ss"&gt;phoenix:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
         &lt;span class="ss"&gt;level_lower_than:&lt;/span&gt; &lt;span class="ss"&gt;:fatal&lt;/span&gt;
       &lt;span class="p"&gt;],&lt;/span&gt;
       &lt;span class="ss"&gt;default:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="ss"&gt;level_lower_than:&lt;/span&gt; &lt;span class="ss"&gt;:info&lt;/span&gt;
       &lt;span class="p"&gt;]&lt;/span&gt;
     &lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we include a key &lt;code&gt;:application_config&lt;/code&gt;, which points to another keyword list. That keyword list contains configs for the &lt;code&gt;:phoenix&lt;/code&gt; app along with the &lt;code&gt;:default&lt;/code&gt; app. This data will be passed to the &lt;code&gt;init/1&lt;/code&gt; function and stored in our backend's state. So, when &lt;code&gt;handle_event/2&lt;/code&gt; is called later on, we can do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check if the &lt;code&gt;metadata&lt;/code&gt; contains &lt;code&gt;application: :phoenix&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If so, check the provided &lt;code&gt;level&lt;/code&gt; and only log if it is at or below the level stored in &lt;code&gt;application_config: [phoenix: [level_lower_than: level]]&lt;/code&gt; in state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what our function will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt; &lt;span class="nv"&gt;@levels&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
    &lt;span class="ss"&gt;fatal:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;error:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;warn:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;info:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;debug:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;trace:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_gl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
      &lt;span class="n"&gt;options&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:application&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;

  &lt;span class="n"&gt;log_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;@levels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nv"&gt;@levels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:application_config&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:level_lower_than&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;log_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level_lower_than&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;level_lower_than&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:group&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:group&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chardata_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;into&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{}),&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;log_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_level_lower_than&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;:noop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We pull the application config out of state and pass that along with the provided level to the &lt;code&gt;log_event&lt;/code&gt; helper function. That function includes a guard clause that will cause it to no-op if the provided level is &lt;em&gt;higher&lt;/em&gt; than the level configured in &lt;code&gt;runtime.exs&lt;/code&gt; for the given application. If we should in fact log, we do so by calling directly on the &lt;code&gt;AppSignal.Logger.log&lt;/code&gt; function. This allows us to emit logs to AppSignal at a specified level, for a given group, with a message and any metadata.&lt;/p&gt;

&lt;p&gt;With this, we will suppress Phoenix framework logs &lt;em&gt;above&lt;/em&gt; the &lt;code&gt;FATAL&lt;/code&gt; level, but ensure that logs emitted by application code are emitted at &lt;code&gt;INFO&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're hesitant to suppress framework logs in this way, remember that logs are just one piece of the observability puzzle. Rather than recording every instance of a &lt;code&gt;GET /&lt;/code&gt; request, we are more likely to care about trends in serving such requests over time. We want the ability to drill down into the details of &lt;em&gt;some such&lt;/em&gt; requests, especially where they are anomalous (for example, if a request experiences a high degree of latency).&lt;/p&gt;

&lt;p&gt;This is where traces come in, along with other telemetry data, like metrics and exceptions.&lt;/p&gt;

&lt;p&gt;In the next section, we'll look at how you can use AppSignal to correlate logs with traces and other types of telemetry, giving you a full picture of how your application is performing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correlating Log Events and Other Telemetry with AppSignal
&lt;/h2&gt;

&lt;p&gt;Application logs are just one part of your overall observability posture. They should be used to record specific events of interest in your application's lifecycle and provide additional details and context in the event of an error or exception.&lt;/p&gt;

&lt;p&gt;You should leverage application logs alongside other telemetry data like metrics, traces, and exception reports to fully observe your application in production.&lt;/p&gt;

&lt;p&gt;Here's a very high-level overview of how you can view each type of telemetry data that your application emits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Logs&lt;/strong&gt; record that an event occurred (for example, a failed user log-in attempt).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt; record trends in event occurrences over time. For example, you can record the distribution of request latency to a particular endpoint, or monitor the capacity utilization of your system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traces&lt;/strong&gt; record details of the lifecycle for a particular piece of code flow. For example, drill down into the trace of a specific request to understand possible causes of latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exception reports&lt;/strong&gt; show when an unexpected or "exceptional" error occurred. If your code encounters an exception, you can see the details of that exception, including the backtrace and full error message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These telemetry signals become especially powerful when they can be correlated. For example, when viewing a specific exception report, you may want to gain more insight into the performance of your overall system at the time the exception occurred. In this case, the ability to view logs and/or traces from that same timeframe can provide you with the extra information you need to remedy bugs and improve overall application performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Your Logs Using AppSignal
&lt;/h2&gt;

&lt;p&gt;Correlating telemetry data can be a particularly hard problem to solve in the observability domain, especially when using different third-party services for things like logging, tracing, and exception reporting. AppSignal provides functionality for managing all of these types of telemetry and empowers you to correlate different types of telemetry data to get the most out of various signals. Let's take a look at an example now.&lt;/p&gt;

&lt;p&gt;We'll use the "Errors" -&amp;gt; "Issue list" as the entry point for this exercise. I've selected an exception report from this list, displayed here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Fexception-report.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Fexception-report.png" alt="Exception report"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can correlate this exception report with some traces emitted by the live view that encountered this error. &lt;em&gt;Note that I've configured our application to emit LiveView traces to AppSignal by &lt;a href="https://docs.appsignal.com/elixir/integrations/phoenix.html" rel="noopener noreferrer"&gt;following the instructions here&lt;/a&gt;.&lt;/em&gt; By clicking on "samples" and selecting an exception sample to examine, we can see the following exception details:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Ftrace-sample.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Ftrace-sample.png" alt="Trace sample"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, in addition to the original exception report, we can see other useful information like the event that the live view was trying to handle when the exception occurred and the payload of that event, in addition to the full exception backtrace.&lt;/p&gt;

&lt;p&gt;Finally, we can use the Time Detective feature to explore the full range of telemetry data, including traces, that our application emitted around the time of this exception. If we click on the "Time Detective" action here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Ftime-detective-link.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Ftime-detective-link.png" alt="Time Detective"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Ftime-detective-performance.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Ftime-detective-performance.png" alt="Time Detective Performance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "Performance" view brings up some trace samples from the time period that we can drill down into. Here, we can see a list of traces that occurred around the time of the incident, including traces for the various LiveView lifecycle callbacks. From the details in the exception sample, we know this error occurred when an event handler in our live view tried to handle the &lt;code&gt;"update"&lt;/code&gt; message. So, let's select the &lt;code&gt;handle_event&lt;/code&gt; trace. We'll see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Fhandle-event-trace.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Fhandle-event-trace.png" alt="Event trace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also select the "Logs" tab to view logs from this time period.&lt;/p&gt;

&lt;p&gt;The Time Detective tool can be accessed via the logging UI as well. Just expand a given log line and select "Time Detective" from the bottom of the expanded view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Ftime-detective-from-log-view.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2023-06%2Ftime-detective-from-log-view.png" alt="Time Detective from Log"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you'll have access to all of the telemetry data that can be correlated to your log statement based on timeframe. This could be especially useful if you see a high volume of a particular request being logged, or note an interesting error log. For any interesting or informative log statements, a deep dive into correlated telemetry data is just a click away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logging in Phoenix: A Few Final Tips
&lt;/h2&gt;

&lt;p&gt;You're now ready to leverage logging as one part of the overall observability story in your Phoenix application. Just a few guiding rules to keep in mind when you do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer structured over unstructured logs so that you can &lt;a href="https://www.appsignal.com/tour/log-management" rel="noopener noreferrer"&gt;query and analyze your application's logs with tools like AppSignal&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Suppress framework logs to the &lt;code&gt;FATAL&lt;/code&gt; level and emit application logs at &lt;code&gt;INFO&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Treat logs as just one part of your observability posture. Logs are most useful when correlated with other telemetry data, like exceptions and traces.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;We've covered a lot of ground in this post, so let's wrap up before you go.&lt;/p&gt;

&lt;p&gt;We discussed the benefit of structured over unstructured logging, and configured our Phoenix application to use logfmt with the console logger backend. We then saw the benefits of structured logging in action in AppSignal.&lt;/p&gt;

&lt;p&gt;Finally, we took our log analysis skills to the next level when we suppressed framework logs and explored how our meaningful application logs correlated with other telemetry data in AppSignal.&lt;/p&gt;

&lt;p&gt;Now take on the world of Phoenix logging. Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>liveview</category>
      <category>logging</category>
      <category>apm</category>
    </item>
    <item>
      <title>Sanitize Strings in Elixir with Pattern Matching and Recursion</title>
      <dc:creator>Sophie DeBenedetto</dc:creator>
      <pubDate>Tue, 08 Nov 2022 14:48:08 +0000</pubDate>
      <link>https://dev.to/appsignal/sanitize-strings-in-elixir-with-pattern-matching-and-recursion-5229</link>
      <guid>https://dev.to/appsignal/sanitize-strings-in-elixir-with-pattern-matching-and-recursion-5229</guid>
      <description>&lt;p&gt;In this post, we'll use two of Elixir's most powerful features - pattern matching and recursion - to sanitize a string by removing invalid Unicode &lt;a href="https://en.wikipedia.org/wiki/Specials_(Unicode_block)"&gt;"Specials"&lt;/a&gt; characters.&lt;/p&gt;

&lt;p&gt;Let's dive straight in!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Strings with Unicode Specials
&lt;/h2&gt;

&lt;p&gt;Unicode Specials is a short Unicode block of characters allocated at characters U+FFF0-FFFF. If one of these characters is present in a string of text, that text is &lt;em&gt;not&lt;/em&gt; correctly encoded Unicode text. Here's the full set of those characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0xFFF0
0xFFF1
0xFFF2
0xFFF3
0xFFF4
0xFFF5
0xFFF6
0xFFF7
0xFFF8
0xFFF0
0xFFF10
0xFFF11
0xFFF12
0xFFF13
0xFFF14
0xFFFA
0xFFFB
0xFFFC
0xFFFD
0xFFFE
0xFFFF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might encounter incorrectly encoded strings that contain a member of the Unicode Specials block. Let's say you need a way to process those strings anyway. Maybe you have some code that reads file contents or receives string input from another external source, like a form file upload or external API. In this case, you will want to remove these invalid characters to continue processing the text.&lt;/p&gt;

&lt;p&gt;Elixir makes this easy with the help of binary pattern matching and recursion. We'll build out a function that removes Unicode specials from a string and replaces them with the &lt;code&gt;"�"&lt;/code&gt; replacement character, so your program can continue to process the string input. When we're done, you'll have a handy function you can use to sanitize any string and a deeper understanding of two Elixir language features that will prove useful in many other scenarios.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Sanitize Your String with Pattern Matching and Recursion in Elixir
&lt;/h2&gt;

&lt;p&gt;Our goal is to produce a function - &lt;code&gt;CleanString.unicode_only/2&lt;/code&gt; - that takes in any string and returns that string, with Unicode Specials replaced with the &lt;code&gt;"�"&lt;/code&gt; replacement character. Here are a few examples of our finished product in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;CleanString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"abc"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0xFFFF&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"abc�"&lt;/span&gt;
&lt;span class="no"&gt;CleanString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"a"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0xFFFF&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"bc"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0xFFFF&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"a�bc�"&lt;/span&gt;
&lt;span class="no"&gt;CleanString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"abc"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0xFFF2&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"def"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"abc�def"&lt;/span&gt;
&lt;span class="no"&gt;CleanString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0xFFF5&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"def"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"�def"&lt;/span&gt;
&lt;span class="no"&gt;CleanString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0xFFF5&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0xFFF4&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"def"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"��def"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Break Down the Problem
&lt;/h3&gt;

&lt;p&gt;Before we write any code, let's break down this problem one step at a time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For each character in a string&lt;/li&gt;
&lt;li&gt;Check if that character is present in the &lt;code&gt;@specials&lt;/code&gt; list&lt;/li&gt;
&lt;li&gt;If it is, remove it from the string&lt;/li&gt;
&lt;li&gt;And replace it with the replacement character&lt;/li&gt;
&lt;li&gt;If it is not present in the &lt;code&gt;@specials&lt;/code&gt; list&lt;/li&gt;
&lt;li&gt;Keep that character&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Changing elements in place in a string is a tricky and potentially expensive operation, but Elixir gives us another way.&lt;/p&gt;

&lt;p&gt;We can use binary pattern matching to iterate over each element in our string, recursively breaking off the "head" or first character of the string. Then, depending on whether or not that character is considered valid, we will add it to the head of a &lt;em&gt;new&lt;/em&gt; string.&lt;/p&gt;

&lt;p&gt;Pulling the head off a string and adding to the head of a new string are very performant tasks in Elixir--it doesn't matter how long the string is, Elixir will only ever need to remove from or add to the beginning of that string.&lt;/p&gt;

&lt;p&gt;Let's break down our problem one more time, this time with our new strategy in place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For each character in a string&lt;/li&gt;
&lt;li&gt;Check if that character is present in the &lt;code&gt;@specials&lt;/code&gt; list&lt;/li&gt;
&lt;li&gt;If it is, add a replacement character to the head of a &lt;em&gt;new string&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;If it is not, add that character to the head of a &lt;em&gt;new string&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will result in a brand new string that contains only replacement or valid Unicode characters.&lt;/p&gt;

&lt;p&gt;With our plan in place, we're ready to write some code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Define the Module in Elixir
&lt;/h3&gt;

&lt;p&gt;First up, we'll define a module, &lt;code&gt;CleanString&lt;/code&gt;, that sets a module attribute, &lt;code&gt;@specials&lt;/code&gt;, to a list of binary strings that represent the characters in the specials block listed above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;CleanString&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nv"&gt;@specials&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;241&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;242&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;243&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;244&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;245&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;246&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;247&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;248&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;251&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;252&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;253&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;254&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This list of binary strings is generated by taking each specials character and evaluating its hexadecimal bitstring version like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0xFFFF&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want our module to store this list of binary strings so that we can check each character of the given string against this list and remove it if it's found.&lt;/p&gt;

&lt;p&gt;Now that we have the basics of our module in place, let's start building the &lt;code&gt;unicode_only/2&lt;/code&gt; function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Define the &lt;code&gt;unicode_only/2&lt;/code&gt; Function in Elixir
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;unicode_only/2&lt;/code&gt; function will take in two arguments: the original string and the new "clean" string we're building.&lt;/p&gt;

&lt;p&gt;We plan to call our function recursively. The &lt;em&gt;first&lt;/em&gt; time, we call it with only one argument--the argument of the original string--like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;CleanString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"abc"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0xFFFF&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"abc�"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this scenario, we need to default the "clean string" second argument to an empty string. Let's define that default argument function head first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;CleanString&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="nv"&gt;@specials&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, now we're ready to define the remainder of our function heads. Here's where Elixir's powerful binary pattern matching feature comes in.&lt;/p&gt;

&lt;p&gt;We'll define one version of the function that pattern matches a scenario where the character at the head of the string is included in the &lt;code&gt;@specials&lt;/code&gt; list, and one in which it isn't. Let's start with that first scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="nv"&gt;@replacement_character&lt;/span&gt; &lt;span class="s2"&gt;"�"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;@specials&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;@replacement_character&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we've added a new module attribute, &lt;code&gt;@replacement_character&lt;/code&gt;, that stores our replacement character. Then, we've added a version of the &lt;code&gt;unicode_only/2&lt;/code&gt; function that uses binary pattern matching to extract the first two bytes of the string and set them equal to a variable, &lt;code&gt;head&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Why do we need to capture the first two bytes of the string to check if the first character is part of the Unicode Specials block? This is because each individual specials character is represented by a two-byte binary string.&lt;/p&gt;

&lt;p&gt;Let's take a closer look at how we capture those two bytes now. The magic happens in the first argument given to &lt;code&gt;unicode_only/2&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Elixir allows you to pattern match &lt;em&gt;in the function head&lt;/em&gt;. This means that when &lt;code&gt;unicode_only/2&lt;/code&gt; is called, the first argument given will be pattern matched against this definition. For example, if we call the function like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;original_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"abc"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0xFFFF&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;CleanString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the following pattern match will be executed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;original_string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned above, this results in setting the variable &lt;code&gt;head&lt;/code&gt; equal to the first two bytes of the string. Meanwhile, the &lt;code&gt;tail&lt;/code&gt; variable is bound to the remainder of the string. Then, we use a guard clause to check if that variable is included in the &lt;code&gt;@specials&lt;/code&gt; list.&lt;/p&gt;

&lt;p&gt;If it is, we don't want to add it to the new clean string we're constructing. Instead, we want to add the replacement character:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="nv"&gt;@replacement_character&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to perform this same operation for the &lt;em&gt;next&lt;/em&gt; character in the original string, and the next one, and so on. This is where recursion comes in. We will call &lt;code&gt;unicode_only/2&lt;/code&gt; &lt;em&gt;again&lt;/em&gt;. This time with only the &lt;code&gt;tail&lt;/code&gt; of the original string as a first argument, and the newly constructed clean string as a second argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;@replacement_character&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this way, we recursively pull the head off the string, until the string is empty. Now, we have a function head that runs when the first two bytes of the string &lt;em&gt;are&lt;/em&gt; included in &lt;code&gt;@specials&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's define another version of our function that will run when the first character &lt;em&gt;isn't&lt;/em&gt; included there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;new_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;
  &lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, we use binary pattern matching in the function head. This time, we pattern match the first argument given to &lt;code&gt;unicode_only/2&lt;/code&gt; against the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we pull off the &lt;em&gt;first byte&lt;/em&gt; of the string and set it equal to the &lt;code&gt;head&lt;/code&gt; variable, and we set the remainder of the string equal to &lt;code&gt;tail&lt;/code&gt;. We only want to grab the first byte of the string here because non-specials characters are represented by a single byte, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;"c"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving our attention back to the function body, let's talk about how this function behaves. In the scenario where the first character is not included in &lt;code&gt;@specials&lt;/code&gt; we &lt;em&gt;do&lt;/em&gt; want to retain that character in our new string. So we add it to the head of the new string, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we're ready to recursively call our function again, with the remaining &lt;code&gt;tail&lt;/code&gt; of the original string and our updated new string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're almost done. We just need a way to break out of our recursive loop when the time is right. When do we want to stop recursing? When we've processed every character in the original string. When this happens, the original string will be empty, and the first argument passed to &lt;code&gt;unicode_only/2&lt;/code&gt; in our recursive loop will be &lt;code&gt;""&lt;/code&gt;. Let's implement that function head now, once again using pattern matching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the original string is finally empty, we &lt;em&gt;don't&lt;/em&gt; want to call &lt;code&gt;unicode_only/2&lt;/code&gt; again. Instead, we'll return the reversed new string.&lt;/p&gt;

&lt;p&gt;We need to reverse the string because we added each processed character to the &lt;em&gt;head&lt;/em&gt; of the new string to keep our code performant. So, we added each retained or replacement character in the opposite order from their placement in the original string. Reversing the string when we're done puts everything back in the right order.&lt;/p&gt;

&lt;p&gt;Putting it all together, we end up with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;CleanString&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="nv"&gt;@specials&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;241&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;242&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;243&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;244&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;245&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;246&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;247&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;248&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;251&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;252&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;254&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nv"&gt;@replacement_character&lt;/span&gt; &lt;span class="s2"&gt;"�"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;@specials&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;@replacement_character&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;unicode_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;With a set of functions implemented in around ten lines of code, we've created an elegant solution for a common, tough problem. Thanks to Elixir's powerful pattern matching functionality, which we used here to pattern match binary strings &lt;em&gt;in&lt;/em&gt; function heads, we have a handy function for cleaning bad characters out of any string.&lt;/p&gt;

&lt;p&gt;String sanitization is something that you may need to do in the wild, and Elixir is a perfect fit for the job.&lt;br&gt;
Beyond that specific scenario though, this solution demonstrates how nicely Elixir pattern matching and recursion go hand in hand.&lt;/p&gt;

&lt;p&gt;Instead of implementing complex and difficult-to-read &lt;code&gt;if&lt;/code&gt; statements, our recursive code flow was built entirely with pattern matching. Each scenario was mapped to a different &lt;code&gt;unicode_only/2&lt;/code&gt; function head.&lt;/p&gt;

&lt;p&gt;Reach for function head pattern matching whenever you want to execute different function behavior based on different inputs, and you'll end up with clean, highly readable, and maintainable code. You can even pattern match on single characters, or groups of characters, in a binary string with the syntax you've seen here.&lt;/p&gt;

&lt;p&gt;Taken together, pattern matching and recursion are a match made in Elixir heaven. Add these tools to your Elixir toolbox and take them out whenever you have a complex input processing problem to solve.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Phoenix LiveView 0.18: New Special HTML Attributes</title>
      <dc:creator>Sophie DeBenedetto</dc:creator>
      <pubDate>Tue, 18 Oct 2022 11:39:24 +0000</pubDate>
      <link>https://dev.to/appsignal/phoenix-liveview-018-new-special-html-attributes-3pd3</link>
      <guid>https://dev.to/appsignal/phoenix-liveview-018-new-special-html-attributes-3pd3</guid>
      <description>&lt;p&gt;Phoenix LiveView 0.18 just shipped, with lots of new goodies to make developing LiveView an even better experience.&lt;/p&gt;

&lt;p&gt;In this post, I'll take you through a lesser-known new feature - LiveView's new special HTML attributes - and show you how to write cleaner HTML with &lt;code&gt;:if&lt;/code&gt;, &lt;code&gt;:for&lt;/code&gt;, and &lt;code&gt;:let&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When we're done, you'll have an eloquent, ergonomic, and dynamic function component you can use to render a list anywhere in your LiveView app.&lt;/p&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Special HTML Attributes in LiveView 0.18?
&lt;/h2&gt;

&lt;p&gt;LiveView special HTML attributes are inspired by a Surface feature called &lt;a href="https://surface-ui.org/template_syntax#directives"&gt;"directives"&lt;/a&gt;. Directives are built-in attributes that modify the translated code of a tag or component at compile time.&lt;/p&gt;

&lt;p&gt;Directives are just one of a few new features, like component &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#module-slots"&gt;slots&lt;/a&gt; and &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#module-attributes"&gt;declarative assigns&lt;/a&gt;, that LiveView has drawn from Surface. Surface is LiveView's independently developed, open-source component library, and it has often driven LiveView framework development forward by introducing (and battle-testing) new concepts quickly.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;:if&lt;/code&gt; and &lt;code&gt;:for&lt;/code&gt; special attributes provide syntactic sugar for the &lt;code&gt;&amp;lt;%= if ... do %&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;%= for ... do %&amp;gt;&lt;/code&gt; EEx calls you would normally expect to write in your templates.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;:let&lt;/code&gt; attribute works a little bit differently. It is used in component slots when you want to yield a variable from within the function component back up to the caller. You'll see this most often when you use the &lt;code&gt;.form/1&lt;/code&gt; function component, but you can also use it to make your own function components even more dynamic.&lt;/p&gt;

&lt;p&gt;Let's take a look at how you can use these attributes to write eloquent, ergonomic HTML in your LiveView templates. Along the way, you'll combine the usage of all three of these attributes to build a clean and dynamic LiveView function component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaner LiveView Templates with &lt;code&gt;:if&lt;/code&gt; and &lt;code&gt;:for&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You can use these two attributes in regular Phoenix templates, in components, and in slots to write cleaner HTML code. We'll take a look at an example using the &lt;code&gt;:if&lt;/code&gt; directive first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"alert alert-danger"&lt;/span&gt; &lt;span class="na"&gt;:if=&lt;/span&gt;&lt;span class="s"&gt;{@error_message}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@error_message&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The div with the &lt;code&gt;alert alert-danger&lt;/code&gt; class that contains the error message markup will only render &lt;em&gt;if&lt;/em&gt; the &lt;code&gt;@error_message&lt;/code&gt; is present and evaluates to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This approach replaces the more verbose one here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@error_message&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"alert alert-danger"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@error_message&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code that uses the &lt;code&gt;:if&lt;/code&gt; attribute is easier to read--more eloquent--and easier to write--more ergonomic.&lt;/p&gt;

&lt;p&gt;You can also use the &lt;code&gt;:if&lt;/code&gt; attribute to conditionally render a function component, all in one simple line of code. Let's say we have a function component, &lt;code&gt;.error/1&lt;/code&gt;, that wraps up the error message markup above. We can conditionally render it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt; &lt;span class="na"&gt;message=&lt;/span&gt;&lt;span class="s"&gt;{@error_message}&lt;/span&gt; &lt;span class="na"&gt;:if=&lt;/span&gt;&lt;span class="s"&gt;{@error_message}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's take a look at an example that uses the &lt;code&gt;:for&lt;/code&gt; attribute. The &lt;code&gt;:for&lt;/code&gt; attribute can be used to iteratively render some content for each member in a collection, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;tbody&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"books"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; &lt;span class="na"&gt;:for=&lt;/span&gt;&lt;span class="s"&gt;{book&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;books&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks to the &lt;code&gt;:for&lt;/code&gt; attribute, you don't have to establish your &lt;code&gt;for&lt;/code&gt; loop explicitly within EEx tags, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;tbody&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"books"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="vi"&gt;@books&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, we're left with code that is both more eloquent and more ergonomic. You can even combine the usage of &lt;code&gt;:if&lt;/code&gt; and &lt;code&gt;:for&lt;/code&gt; if you need to. Let's say we have a list of books to render, but we only want to display a given book if it is available in our online store. We can achieve that like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;:for=&lt;/span&gt;&lt;span class="s"&gt;{book&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;books&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="na"&gt;:if=&lt;/span&gt;&lt;span class="s"&gt;{book.available}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since both &lt;code&gt;:if&lt;/code&gt; and &lt;code&gt;:for&lt;/code&gt; can be used in either plain HTML or in LiveView function components, we can wrap our list item HTML markup in a function component. Given the following function component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;list_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;li&amp;gt;
    &amp;lt;%= @book.title %&amp;gt;
  &amp;lt;/li&amp;gt;
  """&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can call on it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;list_item&lt;/span&gt; &lt;span class="na"&gt;:for=&lt;/span&gt;&lt;span class="s"&gt;{book&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;books&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="na"&gt;:if=&lt;/span&gt;&lt;span class="s"&gt;{book.available}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a nice way to start compartmentalizing our markup into reusable function components made even cleaner with special HTML attributes.&lt;/p&gt;

&lt;p&gt;We can do better with our function component, though. Right now, we've only encapsulated the markup for list items, not the entire list. On top of that, our &lt;code&gt;.list_item/1&lt;/code&gt; function component isn't very reusable--it expects to be called with an assigns that contains an &lt;code&gt;@book&lt;/code&gt; attribute set to something that responds to &lt;code&gt;.title&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We'll build a more dynamic function component that uses component slots and the &lt;code&gt;:let&lt;/code&gt; attribute to encapsulate the &lt;em&gt;entire&lt;/em&gt; unordered list into one dynamic component. When we're done, you'll have a dynamic function component that uses &lt;code&gt;:for&lt;/code&gt; and &lt;code&gt;:let&lt;/code&gt; to render any collection, anywhere in your LiveView app.&lt;/p&gt;

&lt;p&gt;For this next example, we'll simplify our logic a bit by dropping the &lt;code&gt;:if&lt;/code&gt; attribute and the requirement that a book should only render if it's available. We'll just focus on using &lt;code&gt;:let&lt;/code&gt; and &lt;code&gt;:for&lt;/code&gt;. Let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic Function Components with &lt;code&gt;:let&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;First up, let's use a named component slot to create a function component that renders an unordered list and its list items. Start by defining a top-level &lt;code&gt;.unordered_list/1&lt;/code&gt; function component that renders a named slot, &lt;code&gt;:list_item&lt;/code&gt;, inside the appropriate markup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;unordered_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;%= render_slot(@list_item, ...) %&amp;gt;
    &amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
  """&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're on track to render our unordered list component like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;.&lt;/span&gt;&lt;span class="n"&gt;unordered_list&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;unordered_list&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we call on the component, we'll set an assigns - &lt;code&gt;items&lt;/code&gt; - equal to the list of books. Now, we can use &lt;code&gt;:for&lt;/code&gt; in the function component HEEx to render a list item slot for each item in the &lt;code&gt;@items&lt;/code&gt; assigns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;unordered_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;ul :for={item &amp;lt;- @items}&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;%= render_slot(@list_item, item) %&amp;gt;
    &amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
  """&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Putting it all together, we can call on our function component like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;.&lt;/span&gt;&lt;span class="n"&gt;unordered_list&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;@books&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="ss"&gt;:list_item&lt;/span&gt; &lt;span class="ss"&gt;:let=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="ss"&gt;:list_item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/.&lt;/span&gt;&lt;span class="n"&gt;unordered_list&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's where the &lt;code&gt;:let&lt;/code&gt; special attribute comes in. In our function component, we are using &lt;code&gt;:for&lt;/code&gt; to iterate over the list of books in the &lt;code&gt;@items&lt;/code&gt; assignment. For each book in the list, bound to the &lt;code&gt;item&lt;/code&gt; variable at each step in the iteration, we are calling &lt;code&gt;render_slot/2&lt;/code&gt; with a second argument of &lt;code&gt;item&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The call to &lt;code&gt;:let={book}&lt;/code&gt; when we call on our &lt;code&gt;:list_item&lt;/code&gt; slot sets a variable - &lt;code&gt;book&lt;/code&gt; - equal to the second argument passed into &lt;code&gt;render_slot/2&lt;/code&gt;. In this way, you yield variables from your function components back to the caller with the &lt;code&gt;:let&lt;/code&gt; attribute. So, where we call &lt;code&gt;:list_item&lt;/code&gt; when rendering the function component, we can operate on the &lt;code&gt;book&lt;/code&gt; variable to display its title.&lt;/p&gt;

&lt;p&gt;By combining &lt;code&gt;:for&lt;/code&gt; and &lt;code&gt;:let&lt;/code&gt;, we end up with an eloquent, ergonomic, and highly dynamic function component that we can use again and again in our application to render any collection into an unordered list.&lt;/p&gt;

&lt;p&gt;Before we wrap up, there's one limitation that I'd like to point out here. Bringing back our "only render a book title if the book is available" logic is a little tricky here. You might want to do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;.&lt;/span&gt;&lt;span class="n"&gt;unordered_list&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;@books&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="ss"&gt;:list_item&lt;/span&gt; &lt;span class="ss"&gt;:let=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="ss"&gt;:if=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="ss"&gt;:list_item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/.&lt;/span&gt;&lt;span class="n"&gt;unordered_list&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not possible, however. The component slot call cannot combine &lt;code&gt;:let&lt;/code&gt; with &lt;code&gt;:if&lt;/code&gt; in that manner. We can't take advantage of the &lt;code&gt;:if&lt;/code&gt; attribute here and instead have to write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;unordered_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;ul :for={item &amp;lt;- @items}&amp;gt;
    &amp;lt;%= if item.available %&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;%= render_slot(@list_item, item) %&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;% end %&amp;gt;
  &amp;lt;/ul&amp;gt;
  """&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The downside is that the component becomes aware of the need to call &lt;code&gt;.available&lt;/code&gt; on the item, making it less reusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;LiveView is being adopted so quickly in the Elixir community (and beyond) partly because of its gentle learning curve and emphasis on developer happiness.&lt;/p&gt;

&lt;p&gt;LiveView's new special HTML attributes make writing LiveView templates even easier and more fun. By borrowing ergonomic features from Surface, we end up with code that is painless to write (fewer onerous EEx tags!) and easy to understand and maintain.&lt;/p&gt;

&lt;p&gt;Reach for these special HTML attributes when writing plain HEEx templates, LiveView HEEx templates, or function components. You'll end up with beautiful code.&lt;/p&gt;

&lt;p&gt;Until next time, happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Fix Process Bottlenecks with Elixir 1.14's Partition Supervisor</title>
      <dc:creator>Sophie DeBenedetto</dc:creator>
      <pubDate>Tue, 27 Sep 2022 11:18:57 +0000</pubDate>
      <link>https://dev.to/appsignal/fix-process-bottlenecks-with-elixir-114s-partition-supervisor-52li</link>
      <guid>https://dev.to/appsignal/fix-process-bottlenecks-with-elixir-114s-partition-supervisor-52li</guid>
      <description>&lt;p&gt;&lt;a href="https://elixir-lang.org/blog/2022/09/01/elixir-v1-14-0-released/"&gt;Elixir v1.14&lt;/a&gt; shipped earlier this month with a bunch of new goodies.&lt;/p&gt;

&lt;p&gt;In this post, we'll explore Elixir's new &lt;code&gt;PartitionSupervisor&lt;/code&gt;. We'll take a look at some code that suffers from the exact bottleneck issue that partitions supervisors are designed to solve. Then, we'll fix that bottleneck. Along the way, you'll learn how partition supervisors work under the hood to prevent process bottlenecks.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Dynamic Supervisor Bottlenecks in Your Elixir App
&lt;/h2&gt;

&lt;p&gt;You may be familiar with using dynamic supervisors to start child processes on-demand in your application. Telling the dynamic supervisor to start its children can become a bottleneck, however.&lt;/p&gt;

&lt;p&gt;With just one dynamic supervisor, concurrent processes each have to wait their turn to tell your app to start up children. If the "start up child" process takes a long time, or your app handles a very high volume of "tell the dynamic supervisor to start children" requests, you've got a bottleneck. The single dynamic supervisor can only initialize one child process at a time.&lt;/p&gt;

&lt;p&gt;Let's take a look at a simple example that artificially replicates such a bottleneck.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Slow Worker and Its Dynamic Supervisor
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://github.com/sophiedebenedetto/slow_greeter"&gt;my sample app here&lt;/a&gt;, I have a worker - &lt;a href="https://github.com/SophieDeBenedetto/slow_greeter/blob/main/lib/slow_greeter/worker.ex"&gt;&lt;code&gt;SlowGreeter.Worker&lt;/code&gt;&lt;/a&gt;. The worker module is a genserver that initializes by sleeping for five seconds and then printing out a greeting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;SlowGreeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GenServer&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;from:&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;GenServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;from:&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="ss"&gt;:timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="ss"&gt;:greet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:greet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;from:&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello from &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; This is meant to replicate a genserver with some time-consuming work to do on initialization. It should be noted that a "slow-starting" genserver is something of a code smell. You should avoid doing time-consuming work in the &lt;code&gt;#init/1&lt;/code&gt; function and leverage &lt;a href="https://elixirschool.com/blog/til-genserver-handle-continue/"&gt;&lt;code&gt;handle_continue/2&lt;/code&gt;&lt;/a&gt; to prevent long-running tasks from blocking genserver start up.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But, sometimes doing such work when the genserver starts up is unavoidable. And regardless, this setup helps us replicate a bottleneck scenario that can happen:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;1. When your dynamic supervisor is either starting up a slow-to-initialize worker, or&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;2. If your app is handles many concurrent requests telling the same dynamic supervisor to start up its children.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Okay, with that disclaimer out of the way, let's proceed with illustrating our bottleneck. We have our slow-to-initialize worker, and we also have a &lt;a href="https://github.com/SophieDeBenedetto/slow_greeter/blob/main/lib/slow_greeter/dynamic_supervisor.ex"&gt;&lt;code&gt;SlowGreeter.DynamicSupervisor&lt;/code&gt; module&lt;/a&gt; that starts it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;SlowGreeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;SlowGreeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init_arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;init_arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_init_arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;strategy:&lt;/span&gt; &lt;span class="ss"&gt;:one_for_one&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;start_greeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="no"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;start:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:start_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[%{&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;from:&lt;/span&gt; &lt;span class="s2"&gt;"DynamicSupervisor"&lt;/span&gt;&lt;span class="p"&gt;}]}}&lt;/span&gt;
    &lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;SlowGreeter.DynamicSupervisor&lt;/code&gt; &lt;a href="https://github.com/SophieDeBenedetto/slow_greeter/blob/main/lib/slow_greeter/application.ex#L14"&gt;starts when the application starts up&lt;/a&gt;. Then, we can tell it to start the greeter worker like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;SlowGreeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_greeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Frankenstein"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# sleep for 5 seconds...&lt;/span&gt;
&lt;span class="s2"&gt;"Hello from DynamicSupervisor, Frankenstein"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're ready to demonstrate our bottleneck.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo: The Bottleneck
&lt;/h3&gt;

&lt;p&gt;Let's spawn five processes, each of which will make a call to &lt;code&gt;SlowGreeter.DynamicSupervisor.start_greeter/1&lt;/code&gt;. Since each of our spawned processes requests the &lt;em&gt;same&lt;/em&gt; dynamic supervisor, we'll see that each process must wait its turn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Faker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="c1"&gt;# The DynamicSupervisor starts a worker that sleeps for 5 seconds then puts a greeting&lt;/span&gt;
  &lt;span class="n"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;SlowGreeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_greeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the &lt;code&gt;SlowGreeter.DynamicSupervisor&lt;/code&gt; starts up its child &lt;code&gt;SlowGreeter.Worker&lt;/code&gt;, it must wait for the worker to initialize. The worker initialization process is slow--we told it to sleep for five seconds and then print out a greeting. We'll see that each spawned process's call to the dynamic supervisor is only processed when the preceding one finishes. As a result, each greeting prints out at a five-second interval, as you can see &lt;a href="https://www.youtube.com/watch?v=zcO9_U0yBe0"&gt;in this video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This approach is bottlenecked by the dynamic supervisor itself. It can only process one "start up child" request at a time, and must wait until the initialization of a child worker is complete before moving on to the next one.&lt;/p&gt;

&lt;p&gt;We can remove this bottleneck with the help of Elixir 1.14's new &lt;code&gt;PartitionSupervisor&lt;/code&gt;. Let's implement it now. Then, we'll take a deeper dive into how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: Supervise a Fleet of Dynamic Supervisors with a Partition Supervisor
&lt;/h2&gt;

&lt;p&gt;Instead of initializing just one dynamic supervisor when our application starts up, we'll use the new &lt;code&gt;PartitionSupervisor&lt;/code&gt; to spin up a set of dynamic supervisors.&lt;/p&gt;

&lt;p&gt;Then, instead of making direct calls to the dynamic supervisor to start its child worker, we will go &lt;em&gt;through&lt;/em&gt; the partition supervisor, which will route the request to one of the many dynamic supervisors it's overseeing. Let's take a look at the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;PartitionSupervisor&lt;/code&gt; in the Dynamic Supervisor Module
&lt;/h3&gt;

&lt;p&gt;We'll implement a new module, &lt;a href="https://github.com/SophieDeBenedetto/slow_greeter/blob/main/lib/slow_greeter/dynamic_supervisor_with_partition.ex"&gt;&lt;code&gt;SlowGreeter.DynamicSupervisorWithPartition&lt;/code&gt;&lt;/a&gt;, which uses the &lt;code&gt;DynamicSupervisor&lt;/code&gt; behaviour. This time, however, the &lt;code&gt;#start_greeter/1&lt;/code&gt; function will call on the dynamic supervisor to start its child &lt;em&gt;via&lt;/em&gt; the &lt;code&gt;PartitionSupervisor&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;SlowGreeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;DynamicSupervisorWithPartition&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;SlowGreeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init_arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;init_arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_init_arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;strategy:&lt;/span&gt; &lt;span class="ss"&gt;:one_for_one&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;start_greeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="no"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;start:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:start_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[%{&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;from:&lt;/span&gt; &lt;span class="s2"&gt;"PartitionSupervisor"&lt;/span&gt;&lt;span class="p"&gt;}]}}&lt;/span&gt;

    &lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:via&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PartitionSupervisor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;()}},&lt;/span&gt;
      &lt;span class="n"&gt;spec&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's where the magic happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:via&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PartitionSupervisor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;()}},&lt;/span&gt;
  &lt;span class="n"&gt;spec&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of calling on the &lt;code&gt;SlowGreeter.DynamicSupervisorWithPartition&lt;/code&gt; directly, we call it through the partition supervisor using the &lt;code&gt;{:via, PartitionSupervisor, {dynamic_supervisor_name, routing_key}}&lt;/code&gt; tuple.&lt;/p&gt;

&lt;p&gt;This will route the "start child request" to one of the running &lt;code&gt;SlowGreeter.DynamicSupervisorWithPartition&lt;/code&gt; processes supervised by the partition supervisor that starts up with our application. Let's configure our app to start such a supervisor now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application.ex&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;PartitionSupervisor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;child_spec:&lt;/span&gt; &lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="no"&gt;SlowGreeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;DynamicSupervisorWithPartition&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;strategy:&lt;/span&gt; &lt;span class="ss"&gt;:one_for_one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="no"&gt;SlowGreeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Supervisor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="no"&gt;Supervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, when the app starts, a partition supervisor will start up and begin supervising a set of &lt;code&gt;SlowGreeter.DynamicSupervisorWithPartition&lt;/code&gt; dynamic supervisors. The default behavior is to create a partition for each available scheduler (usually one per core of your machine). It will then place a dynamic supervisor on each partition.&lt;/p&gt;

&lt;p&gt;With our code in place, let's see a demo of our bottleneck fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo: No More Bottleneck
&lt;/h3&gt;

&lt;p&gt;Once again, we'll spawn five processes. Each process will call on the &lt;code&gt;SlowGreeter.DynamicSupervisorWithPartition#start_greeter/1&lt;/code&gt; function, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Faker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="c1"&gt;# The PartitionSupervisor tells one of its dynamic supervisors to start a worker that sleeps for 5 seconds then puts a greeting&lt;/span&gt;
  &lt;span class="n"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;SlowGreeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;DynamicSupervisorWithPartition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_greeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function goes &lt;em&gt;through&lt;/em&gt; the partition supervisor to route a "start child worker" request to dynamic supervisors across partitions. This means we don't have to wait for a single dynamic supervisor to finish initializing a child worker before moving on to process the next request from a spawned process.&lt;/p&gt;

&lt;p&gt;Instead, the partition supervisor will route these requests so that they're processed more or less concurrently by the dynamic supervisors spread across partitions. So, we'll see that all five greetings are printed out simultaneously after just &lt;em&gt;one&lt;/em&gt; five-second sleep. You can see this behavior &lt;a href="https://www.youtube.com/watch?v=wLqlKQqWMrw"&gt;in this video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With that, we've fixed our bottleneck! Remember that our worker's "sleep for five seconds on init" behavior is artificial, and you'll want to avoid lengthy worker startups in general. But, this example serves to illustrate the kind of bottleneck that can occur in your application if you deal with lots of concurrent requests to the same dynamic supervisor.&lt;/p&gt;

&lt;p&gt;Elixir 1.14's new partition supervisor neatly solves this problem by partitioning your dynamic supervisors and distributing requests to them, so that they can handle a greater load.&lt;/p&gt;

&lt;p&gt;Where you don't need to share state between child processes supervised by dynamic supervisors, this approach can help your dynamic supervisors handle scale and avoid bottlenecks.&lt;/p&gt;

&lt;p&gt;Now that we've seen partition supervisors in action, let's take a closer look at how they work.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;PartitionSupervisor&lt;/code&gt; in Elixir 1.14: Under the Hood
&lt;/h2&gt;

&lt;p&gt;Keep reading for a deeper dive into how partition supervisors establish partitions and route requests to dynamic supervisors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Partitioning Dynamic Supervisors
&lt;/h3&gt;

&lt;p&gt;As we saw earlier, you can tell your app to start a partition supervisor, overseeing some dynamic supervisors, in your &lt;code&gt;application.ex&lt;/code&gt;'s &lt;code&gt;#start/2&lt;/code&gt; function like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;PartitionSupervisor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;child_spec:&lt;/span&gt; &lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="no"&gt;SlowGreeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;DynamicSupervisorWithPartition&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;strategy:&lt;/span&gt; &lt;span class="ss"&gt;:one_for_one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="no"&gt;SlowGreeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Supervisor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="no"&gt;Supervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we start a partition supervisor that starts a &lt;code&gt;SlowGreeter.DynamicSupervisorWithPartition&lt;/code&gt; for each core in our machine. This is the default &lt;code&gt;start_link&lt;/code&gt; behavior.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PartitionSupervisor#start_link/1&lt;/code&gt; supports options that include a &lt;code&gt;:partitions&lt;/code&gt; key. The &lt;code&gt;:partitions&lt;/code&gt; key should be set to a positive integer representing the number of partitions and defaults to &lt;code&gt;System.schedulers_online()&lt;/code&gt;, which should return the number of cores on your machine.&lt;/p&gt;

&lt;p&gt;Under the hood, a partition supervisor is just a regular supervisor that generates a child spec for each partition and then stores the partition info in ETS or a Registry. Later, when you tell a dynamic supervisor to start its child via the partition supervisor, it selects a partition based on the routing algorithm and routes the call to a dynamic supervisor in that partition. So, it effectively spins up a set of dynamic supervisors when your app comes up, and then starts child processes across those dynamic supervisors.&lt;/p&gt;

&lt;p&gt;Let's take a closer look at the routing behavior now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Routing Requests to Dynamic Supervisors
&lt;/h3&gt;

&lt;p&gt;When you're ready to start a dynamic supervisor's child through a partition supervisor, you do so like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;DynamicSupervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:via&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PartitionSupervisor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;dynamic_supervisor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routing_key&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
  &lt;span class="n"&gt;spec&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;:via&lt;/code&gt; tuple specifies the name of the dynamic supervisor to start up and a routing key.&lt;/p&gt;

&lt;p&gt;In our example app, we used &lt;code&gt;self()&lt;/code&gt; as a routing key, which will return the PID of the current process, but you can also provide a positive integer here. If key is an integer, it is routed using &lt;code&gt;rem(abs(key), num_partitions)&lt;/code&gt;. Otherwise, the routing is performed by calling &lt;code&gt;:erlang.phash2(key, num_partitions)&lt;/code&gt;. So, when you use the same PID as a routing key, you can be assured that your call will be directed to the same partition every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;We've seen how you can use partition supervisors to avoid bottlenecks in your dynamic supervisor code. Provided the state of your child processes can be partitioned (i.e., you don't need to share state among them), you can reach for a partition supervisor to elegantly scale up your dynamic supervisors.&lt;/p&gt;

&lt;p&gt;In fact, partition supervisors can be used to partition and scale any process in your Elixir application. If a process can be started up, and have its state partitioned, then you can use a partition supervisor. Start the partition supervisor with its child spec (just like we did for our dynamic supervisor) and dispatch messages to the partition supervisor's children using the same &lt;code&gt;:via&lt;/code&gt; tuple we demonstrated.&lt;/p&gt;

&lt;p&gt;This is just one of the many exciting new features of Elixir v1.14. &lt;a href="https://blog.appsignal.com/2022/09/13/elixir-1-14-better-debugging-with-dbg-and-more"&gt;Read about another new feature, debugging with &lt;code&gt;dbg()&lt;/code&gt;&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Elixir 1.14: Better Debugging with dbg/2 and More</title>
      <dc:creator>Sophie DeBenedetto</dc:creator>
      <pubDate>Wed, 21 Sep 2022 11:52:49 +0000</pubDate>
      <link>https://dev.to/appsignal/elixir-114-better-debugging-with-dbg2-and-more-j58</link>
      <guid>https://dev.to/appsignal/elixir-114-better-debugging-with-dbg2-and-more-j58</guid>
      <description>&lt;p&gt;The latest Elixir release introduces new features to improve your developer and debugging experience.&lt;/p&gt;

&lt;p&gt;In this post, we'll take a look at the new &lt;code&gt;dbg()&lt;/code&gt; functionality, along with some improvements to &lt;code&gt;Inspect&lt;/code&gt; and binary evaluation error messaging. All these changes come together to make you an even more productive Elixirist.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging with &lt;code&gt;Kernel.dbg/2&lt;/code&gt; in Elixir
&lt;/h2&gt;

&lt;p&gt;The Elixir Kernel now exposes a &lt;code&gt;dbg/2&lt;/code&gt; macro that you can use with or without IEx for enhanced debugging. Let's walk through a few examples that illustrate just how useful this tool can be in your debugging toolkit.&lt;/p&gt;

&lt;p&gt;In this example, we have a Phoenix LiveView application called Live Library. Users can browse, checkout, and return books. Unfortunately for us, however, it looks like we have a bug--when a user tries to check out a book, &lt;a href="https://www.youtube.com/watch?v=67oEY46y5Wk"&gt;the live view crashes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can use the &lt;code&gt;dbg/2&lt;/code&gt; function to gain insight into what's going wrong. We'll start with a few well-placed calls to &lt;code&gt;dbg/2&lt;/code&gt;. Then, we'll move on to leveraging &lt;code&gt;dbg/2&lt;/code&gt; within an IEx session.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging the Current Binding
&lt;/h3&gt;

&lt;p&gt;First, we'll make a call to &lt;code&gt;dbg/2&lt;/code&gt; in the live view's event handler function that gets called when the user clicks "checkout". This is the entry point of our "checkout book" code flow, so we'll start our debugging process there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/live_library_web/live/book_live/checkout_component.ex&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;book:&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;dbg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# this is new!&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkout_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_err&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if we try once again to check out a book, we'll see the following output in our terminal where we're running the Phoenix server via &lt;code&gt;mix phx.server&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;live_library&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;live_library_web&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;book_live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;checkout_component&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;ex:&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;LiveLibraryWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CheckoutComponent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; [&lt;/span&gt;
  &lt;span class="ss"&gt;book:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;LiveLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;updated_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-09-11 17:42:19]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;inserted_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-02-28 16:33:45]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;book_loans:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;LiveLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;BookLoan&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;updated_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-09-11 17:39:18]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;inserted_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-09-11 17:39:18]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;book_id:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;user_id:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;__meta__:&lt;/span&gt; &lt;span class="c1"&gt;#Ecto.Schema.Metadata&amp;lt;:loaded, "book_loans"&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;author_id:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;title:&lt;/span&gt; &lt;span class="s2"&gt;"My Test Book!!!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;description:&lt;/span&gt; &lt;span class="s2"&gt;"A book I made up. Different from the book I wrote which I also technically made up."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;cover:&lt;/span&gt; &lt;span class="s2"&gt;"live_view_upload-1657544569-150520820351856-7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;available:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;__meta__:&lt;/span&gt; &lt;span class="c1"&gt;#Ecto.Schema.Metadata&amp;lt;:loaded, "books"&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="ss"&gt;socket:&lt;/span&gt; &lt;span class="c1"&gt;#Phoenix.LiveView.Socket&amp;lt;&lt;/span&gt;
    &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="s2"&gt;"phx-FxPfPGQDjqGwUAyB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;endpoint:&lt;/span&gt; &lt;span class="no"&gt;LiveLibraryWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;view:&lt;/span&gt; &lt;span class="no"&gt;LiveLibraryWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;BookLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;parent_pid:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;root_pid:&lt;/span&gt; &lt;span class="c1"&gt;#PID&amp;lt;0.780.0&amp;gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;router:&lt;/span&gt; &lt;span class="no"&gt;LiveLibraryWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
      &lt;span class="ss"&gt;__changed__:&lt;/span&gt; &lt;span class="p"&gt;%{},&lt;/span&gt;
      &lt;span class="ss"&gt;book:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;LiveLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;updated_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-09-11 17:42:19]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;inserted_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-02-28 16:33:45]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="ss"&gt;current_user:&lt;/span&gt; &lt;span class="c1"&gt;#LiveLibrary.Accounts.User&amp;lt;&lt;/span&gt;
        &lt;span class="ss"&gt;updated_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-02-18 14:49:02]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;inserted_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-02-18 14:49:02]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;admin:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;confirmed_at:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;email:&lt;/span&gt; &lt;span class="s2"&gt;"admin@email.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;__meta__:&lt;/span&gt; &lt;span class="c1"&gt;#Ecto.Schema.Metadata&amp;lt;:loaded, "users"&amp;gt;,&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;flash:&lt;/span&gt; &lt;span class="p"&gt;%{},&lt;/span&gt;
      &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="s2"&gt;"checkout-7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;myself:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LiveComponent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CID&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;cid:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;transport_pid:&lt;/span&gt; &lt;span class="c1"&gt;#PID&amp;lt;0.768.0&amp;gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When called without any arguments, &lt;code&gt;dbg/2&lt;/code&gt; defaults to debugging the binding for the current context. In this case, that's the &lt;code&gt;handle_event/3&lt;/code&gt; function (and the variables available to us by the time the &lt;code&gt;dbg/2&lt;/code&gt; call is hit on the first line of that function).&lt;/p&gt;

&lt;p&gt;As you can see in the output above, calling &lt;code&gt;dbg/2&lt;/code&gt; does a few things for us - printing out the:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Location of the debugged code:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;live_library&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;live_library_web&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;book_live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;checkout_component&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;ex:&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;LiveLibraryWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CheckoutComponent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Value of the current context's binding:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; [&lt;/span&gt;
  &lt;span class="ss"&gt;book:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;LiveLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;#...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call to &lt;code&gt;dbg/2&lt;/code&gt; also &lt;em&gt;returns the value of the debugged code itself&lt;/em&gt;. We'll see how helpful this is in a bit.&lt;/p&gt;

&lt;p&gt;For now, we're already experiencing some of the benefits of &lt;code&gt;dbg/2&lt;/code&gt; with just this basic usage. Let's examine the value of the arguments that &lt;code&gt;handle_event/3&lt;/code&gt; is called with. This way, we'll better understand how our code is executing and what might be going wrong.&lt;/p&gt;

&lt;p&gt;Based on the &lt;code&gt;dbg/2&lt;/code&gt; output, it looks like the input to &lt;code&gt;handle_event/3&lt;/code&gt; is exactly what we expect. We have a socket whose &lt;code&gt;assigns&lt;/code&gt; correctly contain a book and a current user. So, let's move our call to &lt;code&gt;dbg/2&lt;/code&gt; downstream a bit, into the &lt;code&gt;Library.checkout_book/2&lt;/code&gt; context function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging Provided Code
&lt;/h3&gt;

&lt;p&gt;At this point in our debugging journey, we're ready to turn our attention to the behavior of the &lt;code&gt;Library.checkout_book/2&lt;/code&gt; function. This is the next step in our broken "checkout book" code flow.&lt;/p&gt;

&lt;p&gt;We're interested in a few things. We'd like to understand what exact arguments the function is being called with, and what it returns. We can easily reveal this information with two calls to &lt;code&gt;debug/2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We'll call &lt;code&gt;dbg/2&lt;/code&gt; with the current default context at the beginning of the &lt;code&gt;checkout_book/2&lt;/code&gt; function to look at the arguments' values.&lt;/p&gt;

&lt;p&gt;Then, we'll pipe the code in &lt;code&gt;Library.checkout_book/2&lt;/code&gt; into a call to &lt;code&gt;dbg/2&lt;/code&gt; at the &lt;em&gt;end&lt;/em&gt; of the function. This will allow us to see the return value of the function. And, since &lt;code&gt;dbg/2&lt;/code&gt; returns the value of the executed code you pass to it as a first argument, the remainder of our "checkout book" code flow will be unaffected by this call to &lt;code&gt;dbg/2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's what our debugged function looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;checkout_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;dbg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_loan&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_book_loan&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;user_id:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;book_id:&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;available:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;book&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dbg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if we try to check out a book, we'll see the following output in our terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;live_library&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;live_library&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;ex:&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;LiveLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkout_book&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; [&lt;/span&gt;
  &lt;span class="ss"&gt;book:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;LiveLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;updated_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-09-11 18:27:59]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;inserted_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-02-28 16:33:45]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;author_id:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;title:&lt;/span&gt; &lt;span class="s2"&gt;"My Test Book!!!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;description:&lt;/span&gt; &lt;span class="s2"&gt;"A book I made up. Different from the book I wrote which I also technically made up."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;cover:&lt;/span&gt; &lt;span class="s2"&gt;"live_view_upload-1657544569-150520820351856-7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;available:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;__meta__:&lt;/span&gt; &lt;span class="c1"&gt;#Ecto.Schema.Metadata&amp;lt;:loaded, "books"&amp;gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="ss"&gt;user_id:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;live_library&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;live_library&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;ex:&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;LiveLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkout_book&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_loan&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_book_loan&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;user_id:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;book_id:&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;available:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;book&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;LiveLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;updated_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-09-11 18:28:00]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;inserted_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-02-28 16:33:45]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this, we can clearly see that the &lt;code&gt;checkout_book/2&lt;/code&gt; function returns a single book struct. We're close to solving our bug now.&lt;/p&gt;

&lt;p&gt;Let's return to the &lt;code&gt;handle_event/3&lt;/code&gt; function that calls &lt;code&gt;checkout_book/2&lt;/code&gt;. This time, we'll run our server in an IEx session with &lt;code&gt;iex -S mix phx.server&lt;/code&gt;. Then, we'll place another &lt;code&gt;dbg/2&lt;/code&gt; call in the &lt;code&gt;handle_event/3&lt;/code&gt; function to freeze code execution and interact with our code in real time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging with &lt;code&gt;dbg/2&lt;/code&gt; and IEx in Elixir
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;dbg/2&lt;/code&gt; function is configurable, and you can extend it with advanced behavior. IEx comes with one such extension baked in.&lt;/p&gt;

&lt;p&gt;In Elixir 1.14, IEx extends &lt;code&gt;dbg/2&lt;/code&gt; with an interactive shell, similar to &lt;code&gt;IEx.pry&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's add back our &lt;code&gt;dbg/2&lt;/code&gt; call to the event handler function and play around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/live_library_web/live/book_live/checkout_component.ex&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;book:&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;dbg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkout_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_err&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when we try to check out a book in the browser, we'll see that we have a request to pry in our terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;pry&lt;/span&gt; &lt;span class="c1"&gt;#PID&amp;lt;0.816.0&amp;gt; at LiveLibraryWeb.CheckoutComponent.handle_event/3 (lib/live_library_web/live/book_live/checkout_component.ex:33)&lt;/span&gt;

   &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;book:&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
   &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;dbg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkout_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
   &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
   &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="no"&gt;Endpoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@checkout_topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"checkout_event"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;book:&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="no"&gt;Allow&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Yn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="no"&gt;Y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now interact with and execute our code. Let's manually execute the &lt;code&gt;checkout_book/2&lt;/code&gt; function like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;pry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkout_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="no"&gt;QUERY&lt;/span&gt; &lt;span class="no"&gt;OK&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="n"&gt;idle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1240&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="no"&gt;INSERT&lt;/span&gt; &lt;span class="no"&gt;INTO&lt;/span&gt; &lt;span class="s2"&gt;"book_loans"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"book_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"inserted_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="no"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="no"&gt;RETURNING&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-09-11 18:46:06]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-09-11 18:46:06]&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="no"&gt;QUERY&lt;/span&gt; &lt;span class="no"&gt;OK&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;3&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="n"&gt;idle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1241&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="no"&gt;UPDATE&lt;/span&gt; &lt;span class="s2"&gt;"books"&lt;/span&gt; &lt;span class="no"&gt;SET&lt;/span&gt; &lt;span class="s2"&gt;"available"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"updated_at"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-09-11 18:46:06]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;LiveLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;updated_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-09-11 18:46:06]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;inserted_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2022-02-28 16:33:45]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we can see that we are returning the newly checked-out book. A call to &lt;code&gt;whereami&lt;/code&gt; will show us the next bit of code flow that will try to execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;pry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;whereami&lt;/span&gt;
&lt;span class="ss"&gt;Location:&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;live_library_web&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;book_live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;checkout_component&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;ex:&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;

   &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;book:&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
   &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;dbg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkout_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
   &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;

    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;live_library&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;live_library_web&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;book_live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;checkout_component&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;ex:&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;LiveLibraryWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CheckoutComponent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reminds us that the case statement expects to match on a return value &lt;em&gt;not&lt;/em&gt; of a single book struct, but rather of an &lt;code&gt;{:ok, book}&lt;/code&gt; tuple. With that, we've found our bug! By updating the &lt;code&gt;checkout_book/2&lt;/code&gt; function to return a tuple instead of a book struct, we'll fix our problem.&lt;/p&gt;

&lt;p&gt;Before we move on to some other new Elixir 1.14 features that will improve your development and debugging experience, let's take a last look at some additional &lt;code&gt;dbg/2&lt;/code&gt; functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  More &lt;code&gt;dbg/2&lt;/code&gt; Goodies
&lt;/h3&gt;

&lt;p&gt;Let's examine how &lt;code&gt;dbg/2&lt;/code&gt; makes it easier than ever before to debug pipelines. Then, we'll peek at some of the more advanced &lt;code&gt;dbg/2&lt;/code&gt; configuration capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging Pipelines with &lt;code&gt;dbg/2&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;dbg/2&lt;/code&gt; macro makes it easier to debug pipelines. Before &lt;code&gt;dbg/2&lt;/code&gt;, if you wanted to examine return values at each step of your pipeline, you'd have to add an explicit call to &lt;code&gt;IO.inspect&lt;/code&gt; at each step - like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/live_library_web/live/book_live/index.ex&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"user_token"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user_token&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;socket&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_books&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_search&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_search_changeset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_categories&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_category_ids&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't very eloquent and it can be tedious to implement. Now in Elixir 1.14, we can place just one call to &lt;code&gt;dgb/2&lt;/code&gt; at the end of our pipeline. This will print out the value at each individual step of the pipeline and ensure that the pipeline still returns the value of the executed code. Let's add in our &lt;code&gt;dbg/2&lt;/code&gt; call here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"user_token"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user_token&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;socket&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# %{assigns: %{user: #...}}&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_books&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# %{assigns: %{books: #...}}&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_search&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# %{assigns: %{search: #...}}&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_search_changeset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# # %{assigns: %{search_changeset: #...}}&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_categories&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# %{assigns: %{categories: #...}}&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_category_ids&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# %{assigns: %{category_ids: #...}}&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dbg&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when we visit the &lt;code&gt;/books&lt;/code&gt; index page that brings up this live view, we'll see that the return value of each step of the pipeline has been printed for us to examine.&lt;/p&gt;

&lt;p&gt;Additionally, if you run this code in an IEx session, you will be able to step, or pry, through each individual step in the pipeline. Code execution will freeze at each successive line in the pipeline.&lt;/p&gt;

&lt;p&gt;Pipelines represent one of Elixir's most powerful and eloquent language features, and debugging them just got easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring &lt;code&gt;dbg/2&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;You can extend the behavior of &lt;code&gt;dbg/2&lt;/code&gt; in your Elixir application by defining a custom function. Tell &lt;code&gt;dbg/2&lt;/code&gt; to use that custom function via your app's &lt;code&gt;:dbg_callback&lt;/code&gt; configuration key. Your app configuration should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/config.exs&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:elixir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:dbg_callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;MyMod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:debug_fun&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:stdio&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where you've defined a function &lt;code&gt;MyMod.debug_fun/4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then when &lt;code&gt;dbg/2&lt;/code&gt; is called, your custom function will be called with three arguments prepended to any arguments you specify in your app config:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An AST representing the code to be debugged that was passed to &lt;code&gt;dbg/2&lt;/code&gt; as a first argument.&lt;/li&gt;
&lt;li&gt;An AST representing any options given to &lt;code&gt;dbg/2&lt;/code&gt; as a second argument.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Macro.Env&lt;/code&gt; struct representing the context in which &lt;code&gt;dbg/2&lt;/code&gt; was invoked.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can find a more detailed example of this &lt;a href="https://hexdocs.pm/elixir/Kernel.html#dbg/2-configuring-the-debug-function"&gt;&lt;code&gt;dbg/2&lt;/code&gt; custom configuration approach in the docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now that we've taken a pretty comprehensive look at what &lt;code&gt;dbg/2&lt;/code&gt; has to offer, let's move on to some other new Elixir 1.14 features that improve the development experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect Improvements
&lt;/h2&gt;

&lt;p&gt;Elixir 1.14 has improved the &lt;code&gt;Inspect&lt;/code&gt; protocol implementation for a few core library modules: &lt;code&gt;MapSet&lt;/code&gt;, &lt;code&gt;Version.Requirement&lt;/code&gt;, and &lt;code&gt;Date.Range&lt;/code&gt;. Previously, calls to inspect a new &lt;code&gt;MapSet&lt;/code&gt; would return notation that is not valid Elixir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;MapSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:two&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"three"&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;
&lt;span class="c1"&gt;#MapSet&amp;lt;[1, :two, {"three"}]&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This output can't be copied and pasted in IEx, or operated on by subsequent function calls.&lt;/p&gt;

&lt;p&gt;This is no longer the case in Elixir 1.14. Now, you'll see this behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;MapSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:two&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"three"&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;
&lt;span class="no"&gt;MapSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:two&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"three"&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the output &lt;em&gt;is&lt;/em&gt; valid Elixir that can be operated on by subsequent Elixir code.&lt;/p&gt;

&lt;p&gt;Some additional improvements have been made to &lt;code&gt;Inspect&lt;/code&gt; protocol for structs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When you inspect a struct, fields will be displayed in the order in which they are defined in &lt;code&gt;defstruct&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you want to customize the &lt;code&gt;Inspect&lt;/code&gt; behavior for your struct by deriving it, you can use the new &lt;code&gt;:optional&lt;/code&gt; keyword to omit struct fields that have not changed from their default value when displaying the inspection results. This simplifies struct representation and adds some much-needed brevity to the representation of larger structs. You can learn more info about the &lt;a href="https://hexdocs.pm/elixir/Inspect.html#module-deriving"&gt;&lt;code&gt;Inspect&lt;/code&gt; customization option in the docs&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have just one more Elixir 1.14 improvement to look at before we wrap up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better Binary Evaluation Errors
&lt;/h2&gt;

&lt;p&gt;In keeping with the improved debugging and developer experience we see throughout the Elixir 1.14 release, the latest version of Elixir also improves error messaging on binary construction and evaluation. This brings Elixir in line with Erlang/OTP 25 improvements in the same area.&lt;/p&gt;

&lt;p&gt;Instead of getting generic &lt;code&gt;ArgumentError&lt;/code&gt; messages when constructing binaries in an invalid manner, we get more informative error messages like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"456"&lt;/span&gt;
&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;operator&lt;/span&gt; &lt;span class="n"&gt;but&lt;/span&gt; &lt;span class="ss"&gt;got:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Want to learn how you can also save time debugging an app in production with AppSignal? &lt;a href="https://www.appsignal.com/customers/upside"&gt;Check out how Upside does it&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;Elixir has a devoted following and an increasingly growing community and adoption rate, in part because the language prioritizes developer happiness.&lt;/p&gt;

&lt;p&gt;This latest Elixir release is no exception. With new debugging capabilities and improvements to &lt;code&gt;Inspect&lt;/code&gt; protocols as well as binary evaluation error messages, Elixir's developer experience toolkit has grown even more.&lt;/p&gt;

&lt;p&gt;Thanks to these new features, you'll be more productive in Elixir than ever before. Upgrade your Elixir apps today, and get going!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Phoenix LiveView Under The Hood: The Form Function Component</title>
      <dc:creator>Sophie DeBenedetto</dc:creator>
      <pubDate>Tue, 05 Apr 2022 11:59:40 +0000</pubDate>
      <link>https://dev.to/appsignal/phoenix-liveview-under-the-hood-the-form-function-component-2bg4</link>
      <guid>https://dev.to/appsignal/phoenix-liveview-under-the-hood-the-form-function-component-2bg4</guid>
      <description>&lt;p&gt;Thanks to HEEx and function components, LiveView provides developers with a sleek, ergonomic syntax for building and maintaining sophisticated interactive UIs. LiveView's &lt;code&gt;form/1&lt;/code&gt; function component is a great example of this, making it easier than ever before to render complex forms within LiveView.&lt;/p&gt;

&lt;p&gt;However, the &lt;code&gt;form/1&lt;/code&gt; function component can feel a little mysterious to anyone unfamiliar with LiveView's function components.&lt;/p&gt;

&lt;p&gt;In this post, we'll look under the hood of the &lt;code&gt;form/1&lt;/code&gt; function component. We'll dive into the Phoenix Component functionality that underpins this function and explore features like component slots. When we're done, you'll know exactly how the &lt;code&gt;form/1&lt;/code&gt; function renders your forms and you'll have a deeper understanding of LiveView components, setting you up to build your own components in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Function Components in Phoenix LiveView?
&lt;/h2&gt;

&lt;p&gt;Before we dive into the &lt;code&gt;form/1&lt;/code&gt; function component, let's discuss LiveView's function components at a high level. Function components are defined in modules that use the &lt;code&gt;Phoenix.Component&lt;/code&gt; behavior. Any function that takes in an argument of assigns and returns some markup wrapped in a HEEx template is a function component.&lt;/p&gt;

&lt;p&gt;Let's take a look at a simple example. We'll define a &lt;code&gt;UserDetails&lt;/code&gt; module that implements a function component, &lt;code&gt;contact_info&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyAppLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UserDetails&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Component&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;contact_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
    &amp;lt;div class="&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
      &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Phone:&amp;lt;/strong&amp;gt;&amp;lt;%= @user.phone_number %&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Email:&amp;lt;/strong&amp;gt;&amp;lt;%= @user.email %&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    """&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can call on this component with any HEEx template like this (assuming you have an available &lt;code&gt;@current_user&lt;/code&gt; assigns):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;UserDetails.contact_info&lt;/span&gt; &lt;span class="na"&gt;user=&lt;/span&gt;&lt;span class="s"&gt;"{@current_user}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you import the &lt;code&gt;UserDetails&lt;/code&gt; module or if you're calling on this function component somewhere where the function is defined locally (i.e., elsewhere in the &lt;code&gt;UserDetails&lt;/code&gt; module), you can leave off the module name from the function call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contact_info&lt;/span&gt; &lt;span class="na"&gt;user=&lt;/span&gt;&lt;span class="s"&gt;{@current_user}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this basic understanding of function components under your belt, we're ready to dive into the &lt;code&gt;.form/1&lt;/code&gt; function component that LiveView makes available to you. We'll take a look at some more advanced features of function components along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling the &lt;code&gt;form/1&lt;/code&gt; Function Component
&lt;/h2&gt;

&lt;p&gt;LiveView implements a function component, &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#form/1"&gt;&lt;code&gt;form/1&lt;/code&gt;&lt;/a&gt;. This function component is defined in &lt;code&gt;Phoenix.LiveView.Helper&lt;/code&gt; and imported into all of your live views. We'll start with the entry point of the &lt;code&gt;form/1&lt;/code&gt; code flow—the caller.&lt;/p&gt;

&lt;p&gt;In this example, we'll construct a form for a book record that has a title, description, and author, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;form&lt;/span&gt;
    &lt;span class="na"&gt;let=&lt;/span&gt;&lt;span class="s"&gt;{f}&lt;/span&gt;
    &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;{@changeset}&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"book-form"&lt;/span&gt;
    &lt;span class="na"&gt;phx-change=&lt;/span&gt;&lt;span class="s"&gt;"validate"&lt;/span&gt;
    &lt;span class="na"&gt;phx-submit=&lt;/span&gt;&lt;span class="s"&gt;"save"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt; &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:title&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;text_input&lt;/span&gt; &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:title&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;error_tag&lt;/span&gt; &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:title&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt; &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:description&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;textarea&lt;/span&gt; &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:description&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;error_tag&lt;/span&gt; &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:description&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt; &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:author&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt; &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:author_id&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Enum.map&lt;/span&gt;&lt;span class="err"&gt;(@&lt;/span&gt;&lt;span class="na"&gt;authors&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;{&amp;amp;1.&lt;/span&gt;&lt;span class="na"&gt;full_name&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;1.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="err"&gt;})&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;error_tag&lt;/span&gt; &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:author_id&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;submit&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Save&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt; &lt;span class="na"&gt;phx_disable_with:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Saving...&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nt"&gt;form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Already, we can see that the &lt;code&gt;form/1&lt;/code&gt; function component offers a clean and easy-to-read syntax for describing forms. Let's break down the function invocation here. Then, we'll trace what's happening under the hood.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;form/1&lt;/code&gt; function component, like all function components, is called with one argument of the assigns. Some of the attributes passed as part of the assigns probably look familiar from working with &lt;code&gt;Phoenix.HTML.form_for/4&lt;/code&gt;. We pass in the changeset, an ID, and some LiveView form bindings. In fact, we can optionally pass in any of the same options you would give to &lt;code&gt;Phoenix.HTML.form_for/4&lt;/code&gt;, and the &lt;code&gt;form/3&lt;/code&gt; function component will use those options in the same way.&lt;/p&gt;

&lt;p&gt;In addition to the form options we're giving to assigns, we also use the &lt;code&gt;let&lt;/code&gt; assigns to tell the function component to give a value back to the caller. More on this in a bit.&lt;/p&gt;

&lt;p&gt;Lastly, you'll notice that we're actually calling the &lt;code&gt;form/1&lt;/code&gt; function component with an opening and closing &lt;code&gt;&amp;lt;.form&amp;gt;&lt;/code&gt; tag, and we've enclosed the content of our form within those tags. This eloquent, declarative syntax is made possible by the Phoenix Component's &lt;code&gt;render_slot/2&lt;/code&gt; functionality. We'll see that in action later on in this post.&lt;/p&gt;

&lt;p&gt;Now that we've seen how to call on the &lt;code&gt;form/1&lt;/code&gt; function component, let's dive into the function's implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;form/1&lt;/code&gt; Component Under the Hood
&lt;/h2&gt;

&lt;p&gt;You already know that a function component takes in an argument of some assigns and returns some markup wrapped in a HEEx template. So, you won't be surprised to see that at its core, the &lt;code&gt;form/1&lt;/code&gt; function component sets up a &lt;code&gt;Phoenix.HTML.Form&lt;/code&gt; struct and renders it in a HEEx template that returns an HTML form.&lt;/p&gt;

&lt;p&gt;We'll break down this process one step at a time, beginning with the creation of the form struct.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the &lt;code&gt;Phoenix.Form&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The first thing that the &lt;code&gt;form/1&lt;/code&gt; function does under the hood is to construct a &lt;code&gt;Phoenix.HTML.Form&lt;/code&gt; struct. This is constructed using the data in the assigns that we passed into our function call. &lt;a href="https://github.com/phoenixframework/phoenix_live_view/blob/30ee942b3a18a9e2e1f222a76a707bfba7bd94f7/lib/phoenix_live_view/helpers.ex#L1034-L1044"&gt;Let's take a look at the code&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Extract options and then to the same call as form_for&lt;/span&gt;
&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:action&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"#"&lt;/span&gt;
&lt;span class="n"&gt;form_for&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:for&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"missing :for assign to form"&lt;/span&gt;
&lt;span class="n"&gt;form_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assigns_to_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:for&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Since FormData may add options, read the actual options from form&lt;/span&gt;
&lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;options:&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;FormData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_for&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form_options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, it casts the data from the assigns into the same format that &lt;code&gt;Phoenix.HTML.form_for/4&lt;/code&gt; uses to construct form structs. Then, it initializes a new struct.&lt;/p&gt;

&lt;p&gt;Next up, the function constructs the form method, CSRF token, and multi-part setting from the data in the form struct, &lt;a href="https://github.com/phoenixframework/phoenix_live_view/blob/30ee942b3a18a9e2e1f222a76a707bfba7bd94f7/lib/phoenix_live_view/helpers.ex#L1046-L1059"&gt;like this&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# And then process method, csrf_token, and multipart as in form_tag&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hidden_method&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;csrf_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop_lazy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:csrf_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CSRFProtection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_csrf_token_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:multipart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:enctype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"multipart/form-data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the form struct and options in place, we then move on to constructing the assigns that will be &lt;a href="https://github.com/phoenixframework/phoenix_live_view/blob/30ee942b3a18a9e2e1f222a76a707bfba7bd94f7/lib/phoenix_live_view/helpers.ex#L1062-L1068"&gt;rendered in the HEEx template here&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;assigns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="no"&gt;LiveView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;form:&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;csrf_token:&lt;/span&gt; &lt;span class="n"&gt;csrf_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;hidden_method:&lt;/span&gt; &lt;span class="n"&gt;hidden_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;attrs:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;method:&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For LiveView to track changes to assigns values rendered by a function component, it &lt;em&gt;must&lt;/em&gt; render a valid &lt;code&gt;assigns&lt;/code&gt; either passed in as the only argument given to the function or created via a call to &lt;code&gt;Phoenix.LiveView.assign/3&lt;/code&gt; or &lt;code&gt;Phoenix.LiveView.assign_new/3&lt;/code&gt;. So, the function component uses &lt;code&gt;LiveView.assign/3&lt;/code&gt; here to construct a new set of assigns to render in the HEEx template.&lt;/p&gt;

&lt;p&gt;With the assigns in place, the function will then render the template. Let's take a look at that now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rendering the HEEx Template
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;form/1&lt;/code&gt; function component, like all function components, returns some markup wrapped in a HEEx template. Let's take a look at &lt;a href="https://github.com/phoenixframework/phoenix_live_view/blob/30ee942b3a18a9e2e1f222a76a707bfba7bd94f7/lib/phoenix_live_view/helpers.ex#L1070-L1080"&gt;that return value&lt;/a&gt; now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt; &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
&amp;lt;form {@attrs}&amp;gt;
  &amp;lt;%= if @hidden_method &amp;amp;&amp;amp; @hidden_method not in ~w(get post) do %&amp;gt;
    &amp;lt;input name="&lt;/span&gt;&lt;span class="n"&gt;_method&lt;/span&gt;&lt;span class="s2"&gt;" type="&lt;/span&gt;&lt;span class="n"&gt;hidden&lt;/span&gt;&lt;span class="s2"&gt;" value={@hidden_method}&amp;gt;
  &amp;lt;% end %&amp;gt;
  &amp;lt;%= if @csrf_token do %&amp;gt;
    &amp;lt;input name="&lt;/span&gt;&lt;span class="n"&gt;_csrf_token&lt;/span&gt;&lt;span class="s2"&gt;" type="&lt;/span&gt;&lt;span class="n"&gt;hidden&lt;/span&gt;&lt;span class="s2"&gt;" value={@csrf_token}&amp;gt;
  &amp;lt;% end %&amp;gt;
  &amp;lt;%= render_slot(@inner_block, @form) %&amp;gt;
&amp;lt;/form&amp;gt;
"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unsurprisingly, the &lt;code&gt;form/1&lt;/code&gt; function component simply returns a HEEx template wrapping a call to an HTML &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;. Let's dig into how the template uses some of the assigns established in the previous step.&lt;/p&gt;

&lt;p&gt;First up, you can see that the &lt;code&gt;@attrs&lt;/code&gt; assigns is interpolated directly into the opening &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tag. This will render the tag with the appropriate &lt;code&gt;method=&lt;/code&gt; and &lt;code&gt;action=&lt;/code&gt; attributes. Next up, you can see that the &lt;code&gt;@hidden_method&lt;/code&gt; assigns determines if and how to populate the hidden input. Then the &lt;code&gt;@csrf&lt;/code&gt; token, if present, is added to the HTML form in another hidden input.&lt;/p&gt;

&lt;p&gt;Most of this template has been pretty straightforward so far. The HEEx template renders, and HTML is constructed from various assigns values. Next up, we'll look at how the form fields we specified between our opening and closing &lt;code&gt;&amp;lt;.form&amp;gt;&lt;/code&gt; tags are rendered into the template with the component slot functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Render the Inner Block with Phoenix Component Slots
&lt;/h3&gt;

&lt;p&gt;Phoenix Components implement a feature called &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#module-slots"&gt;"slots"&lt;/a&gt;. Slots enable us to give blocks to our function component calls, nesting them within opening and closing function component tags like regular HTML tags. Slots are the reason for the &lt;code&gt;form/1&lt;/code&gt; syntax we used above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;form&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="na"&gt;assigns&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- form fields --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nt"&gt;form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how it works. Suppose you call on a function component with opening and closing function component tags. That function component's template renders the content in those tags with the help of the &lt;code&gt;render_slot/2&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Let's take a look at an example. Recall the &lt;code&gt;UserDetails.contact_info/1&lt;/code&gt; function component we defined earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyAppLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UserDetails&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Component&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;contact_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Phone:&amp;lt;/strong&amp;gt;&amp;lt;%= @user.phone_number %&amp;gt;&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Email:&amp;lt;/strong&amp;gt;&amp;lt;%= @user.email %&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    """&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can refactor it to use slots like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyAppLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UserDetails&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Component&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;contact_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
    &amp;lt;div class="&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
      &amp;lt;%= render_slot(@inner_block) %&amp;gt;
    &amp;lt;/div&amp;gt;
    """&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we have a dynamic function component we can use to render any type of contact info:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;UserDetails.contact_info&lt;/span&gt; &lt;span class="na"&gt;user=&lt;/span&gt;&lt;span class="s"&gt;"{@current_user}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Phone:&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;current_user.phone_number&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/UserDetails.contact_info&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;UserDetails.contact_info&lt;/span&gt; &lt;span class="na"&gt;user=&lt;/span&gt;&lt;span class="s"&gt;{@current_user}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Address:&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;current_user.street_address&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;current_user.city&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;current_user.zip_code&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;current_user.country&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/UserDetails.contact_info&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTML markup we include between the opening and closing function component tags becomes available in the function component as the &lt;code&gt;@inner_block&lt;/code&gt; assigns. The &lt;code&gt;render_slot/2&lt;/code&gt; function renders that content for us.&lt;/p&gt;

&lt;p&gt;We can clean this up even further with the &lt;code&gt;let&lt;/code&gt; assigns to yield a variable from the function component's template back to the calling template. Let's take a look.&lt;/p&gt;

&lt;p&gt;First, we'll call the function component with an assigns of &lt;code&gt;let&lt;/code&gt; set equal to a variable, &lt;code&gt;user&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;UserDetails.contact_info&lt;/span&gt; &lt;span class="na"&gt;let=&lt;/span&gt;&lt;span class="s"&gt;"{user}"&lt;/span&gt; &lt;span class="na"&gt;user=&lt;/span&gt;&lt;span class="s"&gt;"{@current_user}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- coming soon --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/UserDetails.contact_info&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we'll update the function component's call to &lt;code&gt;render_slot/2&lt;/code&gt; by invoking it with a second argument of the &lt;code&gt;@user&lt;/code&gt; assignment, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;contact_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;div class="&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
    &amp;lt;%= render_slot(@inner_block, @user) %&amp;gt;
  &amp;lt;/div&amp;gt;
  """&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function component sets the variable we specified in the &lt;code&gt;let&lt;/code&gt; assignment equal to the value of whatever is passed in as the second argument to &lt;code&gt;render_slot/2&lt;/code&gt;. Now, the inner content between the function component's opening and closing tags in the calling template has access to the &lt;code&gt;user&lt;/code&gt; variable. We can update our inner block to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;UserDetails.contact_info&lt;/span&gt; &lt;span class="na"&gt;let=&lt;/span&gt;&lt;span class="s"&gt;"{user}"&lt;/span&gt; &lt;span class="na"&gt;user=&lt;/span&gt;&lt;span class="s"&gt;"{@current_user}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Phone:&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;user.phone_number&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/UserDetails.contact_info&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this basic understanding of slots in place, let's revisit the &lt;code&gt;form/1&lt;/code&gt; template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;~H"""
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="err"&gt;{@&lt;/span&gt;&lt;span class="na"&gt;attrs&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;hidden_method&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;hidden_method&lt;/span&gt; &lt;span class="na"&gt;not&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt; &lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt; &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_method"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"{@hidden_method}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;csrf_token&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_csrf_token"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"{@csrf_token}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;render_slot&lt;/span&gt;&lt;span class="err"&gt;(@&lt;/span&gt;&lt;span class="na"&gt;inner_block&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;form&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
"""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, its calling &lt;code&gt;render_slot/2&lt;/code&gt; with the &lt;code&gt;@inner_block&lt;/code&gt; assignment and the &lt;code&gt;@form&lt;/code&gt; assignment. Recall that we invoked &lt;code&gt;form/1&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;form&lt;/span&gt;
    &lt;span class="na"&gt;let=&lt;/span&gt;&lt;span class="s"&gt;{f}&lt;/span&gt;
    &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;{@changeset}&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"book-form"&lt;/span&gt;
    &lt;span class="na"&gt;phx-change=&lt;/span&gt;&lt;span class="s"&gt;"validate"&lt;/span&gt;
    &lt;span class="na"&gt;phx-submit=&lt;/span&gt;&lt;span class="s"&gt;"save"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt; &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:title&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;text_input&lt;/span&gt; &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:title&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;error_tag&lt;/span&gt; &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:title&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nt"&gt;form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, the form fields render as the &lt;code&gt;@inner_block&lt;/code&gt; assignment. The call to &lt;code&gt;&amp;lt;.form&amp;gt;&lt;/code&gt;&lt;br&gt;
establishes a variable &lt;code&gt;f&lt;/code&gt;, which gets set to a value of the second argument passed to &lt;code&gt;render_slot/2&lt;/code&gt;. The second argument to &lt;code&gt;render_slot/2&lt;/code&gt; is the &lt;code&gt;@form&lt;/code&gt; assignment, pointing to our &lt;code&gt;Phoenix.HTML.Form&lt;/code&gt; struct. So, the inner content in our calling template can use the &lt;code&gt;f&lt;/code&gt; variable to reference the form struct and build out the Phoenix form to render.&lt;/p&gt;

&lt;p&gt;And that's it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up: Demystifying Phoenix LiveView's Form Function Component
&lt;/h2&gt;

&lt;p&gt;While the call to the &lt;code&gt;form/1&lt;/code&gt; function component can seem mysterious, tracing the code under the hood isn't too daunting.&lt;/p&gt;

&lt;p&gt;To recap: we can see that the function establishes a Phoenix form and assembles some assigns. Then, it returns a HEEx template that renders an HTML form with the assigns. The HEEx template uses Phoenix Component's slot functionality to render the inner content of our specified form fields, and yields the Phoenix form back to the calling template where we construct those fields.&lt;/p&gt;

&lt;p&gt;I hope that this dive under the hood of the &lt;code&gt;form/1&lt;/code&gt; function component has not only demystified that function, but also given you a deeper understanding of how you can use function components and slots in your own live views. Now, you're ready to build out your own extensible function component that dynamically renders different inner blocks of content.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Securing Your Phoenix LiveView Apps</title>
      <dc:creator>Sophie DeBenedetto</dc:creator>
      <pubDate>Tue, 01 Feb 2022 12:03:29 +0000</pubDate>
      <link>https://dev.to/appsignal/securing-your-phoenix-liveview-apps-gm0</link>
      <guid>https://dev.to/appsignal/securing-your-phoenix-liveview-apps-gm0</guid>
      <description>&lt;p&gt;LiveView is a compelling choice for building modern web apps. Built on top of Elixir's OTP tooling, and leveraging WebSockets, it offers super fast real-time, interactive features alongside impressive developer productivity.&lt;/p&gt;

&lt;p&gt;In this post, we'll show you how to secure your live view routes with function plugs and group live routes in a secure live session.&lt;/p&gt;

&lt;p&gt;Let's dive straight in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Live View to Build a Phoenix Web App
&lt;/h2&gt;

&lt;p&gt;We'll be building some authentication and authorization features into a Phoenix web app built with live view.&lt;/p&gt;

&lt;p&gt;The Arcade web app presents regular users with many online games to play. Our app has a survey feature that collects users' demographic data and game ratings. It also has an admin dashboard that should  &lt;em&gt;only&lt;/em&gt; be accessible to app admins to view survey results.&lt;/p&gt;

&lt;p&gt;For the purpose of this post, we'll assume that logged-in users can visit the &lt;code&gt;/games&lt;/code&gt; index and &lt;code&gt;/games/:id&lt;/code&gt; show routes to select and play a game, along with the &lt;code&gt;/survey&lt;/code&gt; route to fill out the user survey.&lt;/p&gt;

&lt;p&gt;Additionally, admin users should &lt;strong&gt;only&lt;/strong&gt; be able to visit the &lt;code&gt;/admin-dashboard&lt;/code&gt; page. We'll assume that these pages and the live views that back them have already been built. Our focus is on introducing the authentication and authorization code we need to secure these live views.&lt;/p&gt;

&lt;p&gt;Let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect Sensitive Routes in Your Phoenix LiveView App
&lt;/h2&gt;

&lt;p&gt;This post assumes you've already built your registration and login flows, along with some function plugs for authenticating the current user and storing their token in the Phoenix session. I recommend using the &lt;a href="https://hexdocs.pm/phx_gen_auth/overview.html"&gt;Phoenix Auth generator&lt;/a&gt; to generate this code for free.&lt;/p&gt;

&lt;p&gt;This generated code ensures that Phoenix will add a key of &lt;code&gt;:current_user&lt;/code&gt; to the &lt;code&gt;conn&lt;/code&gt; struct and a &lt;code&gt;"user_token"&lt;/code&gt; key to the session when a user logs in.&lt;/p&gt;

&lt;p&gt;Now, let's start in the router by putting some live routes behind authentication.&lt;/p&gt;

&lt;p&gt;If you run the Phoenix Auth generator, you generate a module, &lt;code&gt;ArcadeWeb.UserAuth&lt;/code&gt;, that implements a function plug &lt;code&gt;require_authenticated_user/2&lt;/code&gt;, shown here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;require_authenticated_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:current_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;put_flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"You must log in to access this page."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maybe_store_return_to&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;to:&lt;/span&gt; &lt;span class="no"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_session_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;halt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The details of this function aren't too important. Just understand that it takes in a first argument of the &lt;code&gt;conn&lt;/code&gt; struct and checks for the presence of a &lt;code&gt;:current_user&lt;/code&gt; key. If one is found, it returns the conn. If not, then it redirects to the login path.&lt;/p&gt;

&lt;p&gt;When the auth generator creates the &lt;code&gt;UserAuth&lt;/code&gt; module, it also imports into your router, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# router.ex&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UserAuth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can create a new router scope using this function plug. Let's require that a user is logged in for access to the scope routes, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pipe_through&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:require_authenticated_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the product index, show routes, and the &lt;code&gt;/survey&lt;/code&gt; route that any authenticated user can currently visit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pipe_through&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:require_authenticated_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ProductLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Index&lt;/span&gt;
  &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/products/:id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ProductLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Show&lt;/span&gt;
  &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/survey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SurveyLive&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when a user visits &lt;code&gt;/products&lt;/code&gt; or any other route in our new scope, Phoenix invokes the &lt;code&gt;require_authenticated_user&lt;/code&gt; function plug. Believe it or not, that's all we have to do to restrict our live routes to logged-in users.&lt;/p&gt;

&lt;p&gt;We can take a similar approach to authorizing admins to visit the &lt;code&gt;/admin-dashboard&lt;/code&gt;. We'll add a new function plug to the &lt;code&gt;UserAuth&lt;/code&gt; module, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;require_admin_user&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;current_user:&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;put_flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"You must log in to access this page."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maybe_store_return_to&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;to:&lt;/span&gt; &lt;span class="no"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;halt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;require_admin_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;conn&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;put_flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"You must log in to access this page."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maybe_store_return_to&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;to:&lt;/span&gt; &lt;span class="no"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;halt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the function plug is invoked with a &lt;code&gt;conn&lt;/code&gt; struct that does not contain the current user, we will redirect to the root path.&lt;/p&gt;

&lt;p&gt;If the function plug is called with a &lt;code&gt;conn&lt;/code&gt; that contains a current user, we will check if that user is an admin. If so, return the &lt;code&gt;conn&lt;/code&gt;, otherwise, redirect. The details of our check for the admin status, &lt;code&gt;current_user.admin&lt;/code&gt;, don't really matter here. Your app may implement admin logic differently. The main takeaway is that we now have a function plug that can &lt;em&gt;authorize&lt;/em&gt; certain routes by enforcing that the current user is present &lt;em&gt;and&lt;/em&gt; an admin.&lt;/p&gt;

&lt;p&gt;Let's now use our new function plug in our router. We'll create a second scope with a pipeline that uses the &lt;code&gt;require_admin_user/1&lt;/code&gt; function plug:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pipe_through&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:require_admin_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/admin-dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;DashboardLive&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! If a user points their browser at &lt;code&gt;/admin-dashboard&lt;/code&gt;, our function plug will be invoked.&lt;/p&gt;

&lt;p&gt;We've ensured that our live view routes are secure with nothing more than normal Phoenix auth plugs. We can implement authentication — requiring the presence of a current user — and authorization — requiring that the current user has specific permissions or roles — just like you would for regular Phoenix routes.&lt;/p&gt;

&lt;p&gt;Now, let's look at a new LiveView feature for grouping live routes together and a security challenge that it presents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Group Live Views in a Live Session
&lt;/h2&gt;

&lt;p&gt;You'll use live sessions to group similar live routes with shared layouts and auth logic. Grouping live routes together in a live session means that we can live redirect to those routes from any other route in the same live session group.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#live_redirect/2"&gt;live redirect&lt;/a&gt; is a special kind of redirect that leverages the existing WebSocket connection, minimizing network traffic and keeping your live view speedy.&lt;/p&gt;

&lt;p&gt;When you live redirect from one live view to another in the same live session, the current live view process terminates. The new live view is mounted over the current WebSocket connection without reloading the whole page.&lt;/p&gt;

&lt;p&gt;This works great for live views that share a layout. The shared layout that frames the live view content will stay in place, and only the portion of the page that renders the current live view within that layout will change.&lt;/p&gt;

&lt;p&gt;Let's create our first live session group now for the routes behind regular user authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pipe_through&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:require_authenticated_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;live_session&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;root_layout:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LayoutView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"authenticated.html"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ProductLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Index&lt;/span&gt;
    &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/products/:id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ProductLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Show&lt;/span&gt;
    &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/survey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SurveyLive&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we establish a live session block to contain all of our routes that need to be authenticated for regular users and tell them to share a common layout found in &lt;code&gt;lib/arcade_web/templates/layout/authenticated.html.heex&lt;/code&gt;. You might notice that the &lt;code&gt;live_session&lt;/code&gt; macro is called with a first argument, &lt;code&gt;:user&lt;/code&gt;. We'll see how that comes into play in just a bit.&lt;/p&gt;

&lt;p&gt;Whenever a user live redirects from the &lt;code&gt;/products&lt;/code&gt; route to &lt;code&gt;/products/:id&lt;/code&gt;, for example, the existing WebSocket connection will not terminate. Instead, we'll kill the current live view process, mount the new live view, and only re-render the necessary portions of the page within the shared layout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grouping Live Routes: The Security Problem
&lt;/h3&gt;

&lt;p&gt;This approach presents a security challenge. If we re-use the existing WebSocket connection, we &lt;em&gt;won't&lt;/em&gt; be sending a new HTTP request, and we &lt;em&gt;won't&lt;/em&gt; go through the plug pipeline defined in our router.&lt;/p&gt;

&lt;p&gt;So we must perform authentication and authorization in our router to prevent direct navigation to sensitive routes from the browser. We must also ensure that our live views can perform their own authentication and authorization &lt;em&gt;every time they mount&lt;/em&gt; (whether due to a user pointing their browser directly at a live route or a live redirect between live routes in a shared live session).&lt;/p&gt;

&lt;p&gt;Luckily for us, LiveView presents an API for performing authorization and authentication when the live view mounts, making it easy for us to apply this logic across all live routes in a shared session. Let's take a look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect Live Views When They Mount
&lt;/h2&gt;

&lt;p&gt;The LiveView framework allows us to hook into a callback function that will run whenever a live view mounts. The &lt;code&gt;on_mount/4&lt;/code&gt; lifecycle hook will fire before the live view mounts, making it the perfect place to isolate re-usable auth logic that can be shared among live views in a live session.&lt;/p&gt;

&lt;p&gt;Start by defining a module that implements an &lt;code&gt;on_mount/4&lt;/code&gt; function, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UserAuthLive&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LiveView&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Accounts&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;on_mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"user_token"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user_token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;socket&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:current_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_user_by_session_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:cont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:halt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to:&lt;/span&gt; &lt;span class="s2"&gt;"/login"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function will be called with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a first argument of the atom that we passed to our &lt;code&gt;live_session&lt;/code&gt; macro&lt;/li&gt;
&lt;li&gt;a second argument of any params that were part of the incoming web request&lt;/li&gt;
&lt;li&gt;a third argument of the session containing the &lt;code&gt;"user_token"&lt;/code&gt; used to identify the current user&lt;/li&gt;
&lt;li&gt;a fourth argument of the socket (remember, if you use the Phoenix Auth generator, Phoenix will add this &lt;code&gt;"user_token"&lt;/code&gt; to the session when a user logs in.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We perform some basic authentication here by looking up the current user and assigning the result to the socket. If the socket then contains a current user value rather than &lt;code&gt;nil&lt;/code&gt;, we continue. Otherwise, we halt and redirect. Any &lt;code&gt;on_mount/4&lt;/code&gt; function must conform to this API, returning the &lt;code&gt;:cont&lt;/code&gt; tuple or the &lt;code&gt;:halt&lt;/code&gt; tuple.&lt;/p&gt;

&lt;p&gt;Next up, let's tell our live session to apply this &lt;code&gt;on_mount/4&lt;/code&gt; callback to all of the live routes in its grouping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pipe_through&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:require_authenticated_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;live_session&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on_mount:&lt;/span&gt; &lt;span class="no"&gt;UserAuthLive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;root_layout:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LayoutView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"authenticated.html"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ProductLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Index&lt;/span&gt;
    &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/products/:id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ProductLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Show&lt;/span&gt;
    &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/survey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SurveyLive&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever there is a live redirect to &lt;code&gt;"/products"&lt;/code&gt; route (or any other route in that live session), the given live view will invoke &lt;code&gt;ArcadeWeb.UserAuthLive.on_mount/4&lt;/code&gt; with a first argument of &lt;code&gt;:user&lt;/code&gt; and our authentication logic will execute. Furthermore, any live view within the live session will mount with the &lt;code&gt;:current_user&lt;/code&gt; already set in its socket assigns, since we're adding it in the &lt;code&gt;on_mount&lt;/code&gt; callback.&lt;/p&gt;

&lt;p&gt;Let's set up a similar callback for the admin live session. Add this function to &lt;code&gt;UserAuthLive&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;on_mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"user_token"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user_token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:current_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_user_by_session_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:cont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:halt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to:&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, when &lt;code&gt;on_mount/4&lt;/code&gt; is called with a first argument of &lt;code&gt;:admin&lt;/code&gt;, we will authorize the current user &lt;em&gt;and&lt;/em&gt; authenticate them as an admin. Let's add this to a new live session for the admin-protected routes now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pipe_through&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:require_admin_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;live_session&lt;/span&gt; &lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on_mount:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;UserAuthLive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;root_layout:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LayoutView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"admin.html"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/admin-dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;DashboardLive&lt;/span&gt;
    &lt;span class="c1"&gt;# more admin routes&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we group admin-protected routes with a shared admin layout. And whenever any of the live views in this session block mount, the &lt;code&gt;UserAuthLive.on_mount/4&lt;/code&gt; function will be called with the &lt;code&gt;:admin&lt;/code&gt; atom as a first argument. This ensures that only admin users can access those pages, even when live redirected.&lt;/p&gt;

&lt;p&gt;Thanks to Elixir's pattern matching, we can group all of our auth-related &lt;code&gt;on_mount/4&lt;/code&gt; callbacks in a shared module and implement however many &lt;code&gt;live_session&lt;/code&gt; blocks we need to organize our live views.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up: Easily Group Live Views to Secure Your Phoenix LiveView App
&lt;/h2&gt;

&lt;p&gt;In this post, we explored how LiveView allows you to group live routes in a shared session. Grouping enables live views to easily share a layout and implement shared authentication and authorization logic.&lt;/p&gt;

&lt;p&gt;Remember, you must authenticate and authorize &lt;em&gt;both&lt;/em&gt; your protected routes in the router &lt;em&gt;and&lt;/em&gt; your live views when they mount. Reach for function plug pipelines to achieve the former, and live session and the &lt;code&gt;on_mount/4&lt;/code&gt; callback to accomplish the latter. With this combination of tools, you can bulletproof your live views, making them highly secure and capable of sophisticated authorization logic.&lt;/p&gt;

&lt;p&gt;I hope you've found this post useful. Until next time: happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>liveview</category>
    </item>
    <item>
      <title>Build Interactive Phoenix LiveView UIs with Components</title>
      <dc:creator>Sophie DeBenedetto</dc:creator>
      <pubDate>Tue, 18 Jan 2022 14:12:08 +0000</pubDate>
      <link>https://dev.to/appsignal/build-interactive-phoenix-liveview-uis-with-components-3m9o</link>
      <guid>https://dev.to/appsignal/build-interactive-phoenix-liveview-uis-with-components-3m9o</guid>
      <description>&lt;p&gt;LiveView empowers developers to build interactive, single-page web apps with ease by providing a framework that eliminates the need for guesswork.&lt;/p&gt;

&lt;p&gt;In this post, we'll take a look at how you can layer simple, single-purpose functional components to wrap up shared presentation logic. We'll also use more sophisticated live components to craft easy-to-maintain single-page flows that handle complex user interactions.&lt;/p&gt;

&lt;p&gt;Along the way, you'll gain a solid understanding of working with HEEx — Phoenix and LiveView's new templating engine — and you'll see some of LiveView's out-of-the-box function components in action.&lt;/p&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Feature: Compose a User Survey UI for a Phoenix LiveView App
&lt;/h2&gt;

&lt;p&gt;Before we dive into writing any actual code, let's talk about the feature we'll build. Imagine that you're responsible for a Phoenix web app, Arcade, that provides in-browser games to users. A user can log in, select a game to play, and even invite friends to play games with them.&lt;/p&gt;

&lt;p&gt;In this post, we'll build out a "user survey" feature that asks the user to fill out some demographic info about themselves and then provide a rating for each of several games. We'll focus on that second part of the survey — the game rating forms.&lt;/p&gt;

&lt;p&gt;Let's lay out what we'll build in a bit more detail. We'll begin with a parent live view that lives at the &lt;code&gt;/survey&lt;/code&gt; route, &lt;code&gt;ArcadeWeb.SurveyLive&lt;/code&gt;. This live view will render a child functional component, "ratings index". The ratings index component will iterate over the games and show a game rating if one by the current user exists, or a form for a new rating if not. If users haven't completed a game rating, they will see a list of forms to rate each game 1-5 stars. If they have completed some (or all) ratings, they will see those displayed and forms to submit ratings for the games they have not yet rated.&lt;/p&gt;

&lt;p&gt;Here's a look at how it will work:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lJXoIrnp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-01/rating-form-partially-complete.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lJXoIrnp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-01/rating-form-partially-complete.png" alt="ratings form partially complete" width="880" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With our plan in place, we're ready to start writing code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the Parent Live View
&lt;/h2&gt;

&lt;p&gt;We'll build a route first, then mount and render the initial live view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the Survey Route
&lt;/h3&gt;

&lt;p&gt;Our first job is to establish a route. The survey will live at &lt;code&gt;/survey&lt;/code&gt;, and it should only work for authenticated users so we can deliver a survey to single, identifiable users. We'll tie the route to the yet-to-be-written &lt;code&gt;SurveyLive&lt;/code&gt; live view, with the &lt;code&gt;:index&lt;/code&gt; live action, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# router.ex&lt;/span&gt;
&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pipe_through&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:require_authenticated_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/survey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SurveyLive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code assumes you've used the &lt;a href="https://github.com/aaronrenner/phx_gen_auth"&gt;Phoenix Auth generator&lt;/a&gt; to add authentication to your Phoenix app. The details of authentication aren't important for our purposes here. Just know that the &lt;code&gt;survey&lt;/code&gt; route is a protected route that requires an authenticated user. This means that when a logged-in user points their browser at &lt;code&gt;/survey&lt;/code&gt;, the &lt;code&gt;SurveyLive&lt;/code&gt; view will mount with a &lt;code&gt;session&lt;/code&gt; argument that contains a key of &lt;code&gt;"user_token"&lt;/code&gt; pointing to a token we can use to identify the current user. The generator also gives us a function, &lt;code&gt;Accounts.get_user_by_session_token(user_token)&lt;/code&gt;, that we will use to fetch the user for that token.&lt;/p&gt;

&lt;p&gt;With our route established, it's time to define the &lt;code&gt;SurveyLive&lt;/code&gt; live view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mount the Survey Live View
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;mount/3&lt;/code&gt; function builds the initial state for &lt;code&gt;SurveyLive&lt;/code&gt;. Let's think a bit about that initial state. We need to use the current user to build our survey's demographic and rating portions since a demographic belongs to a user and a rating belongs to a game and a user. So we want to store that user in the live view's state. This way, we can make it available to the rating form component later. Now, let's implement a &lt;code&gt;mount/3&lt;/code&gt; function that adds the current user to socket assigns, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/arcade_web/live/survey_live.ex&lt;/span&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SurveyLive&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_view&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"user_token"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user_token&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;socket&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="ss"&gt;:current_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_user_by_session_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_token&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, we're ready to render a simple version of our survey live view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Render the Survey Live View
&lt;/h3&gt;

&lt;p&gt;We won't provide a &lt;code&gt;render/1&lt;/code&gt; function, instead we'll use a template — &lt;code&gt;lib/arcade_web/live/survey_live.html.heex&lt;/code&gt;. Let's keep it simple for now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight eex"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Survey&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload your browser, and you'll see the bare-bones template shown here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HSHDbfqG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-01/survey-template-simple-content.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HSHDbfqG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-01/survey-template-simple-content.png" alt="simple survey template" width="880" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have the basic framework for our survey UI in place. Now, we're ready to build our first function component.&lt;/p&gt;

&lt;h2&gt;
  
  
  List Ratings in Phoenix LiveView
&lt;/h2&gt;

&lt;p&gt;Before we build our ratings index function component, let's talk about what function components are and how they work. A function component takes in an &lt;code&gt;assigns&lt;/code&gt; argument and returns a HEEx template. Function components are implemented in modules that use the &lt;code&gt;Phoenix.Component&lt;/code&gt; behaviour, which also gives us a convenient syntax for rendering function components.&lt;/p&gt;

&lt;p&gt;We're almost ready to define our function component. Let's take a step back and discuss HEEx.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding HEEx Templates
&lt;/h3&gt;

&lt;p&gt;A HEEx template is any file ending in the &lt;code&gt;.heex&lt;/code&gt; extension that is implicitly rendered by a live view, or any markup rendered by a live view or component that is encapsulated in the &lt;code&gt;~H"""&lt;/code&gt;, &lt;code&gt;"""&lt;/code&gt; tags. The HEEx templating engine is an extension of EEx. Just like EEx templates, HEEx will process template replacements within your HTML code. Everything between the &lt;code&gt;&amp;lt;%=&lt;/code&gt; and &lt;code&gt;%&amp;gt;&lt;/code&gt; expressions is a template replacement. HEEx will evaluate the Elixir code within those tags and replace them with the result.&lt;/p&gt;

&lt;p&gt;HEEx does more than just templating, though. It also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;provides compile-time HTML validations&lt;/li&gt;
&lt;li&gt;gives us a convenient component rendering syntax&lt;/li&gt;
&lt;li&gt;optimizes the amount of content sent over the wire, allowing LiveView to render &lt;em&gt;only those portions of the template that need updating when state changes&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HEEx is the default templating engine for Phoenix and LiveView. Any generated template files in your Phoenix app will be HEEx templates and end in the &lt;code&gt;.html.heex&lt;/code&gt; extension. When using inline &lt;code&gt;render/1&lt;/code&gt; functions in your live views, or function components, you'll return HEEx templates with the &lt;code&gt;~H&lt;/code&gt; sigil.&lt;/p&gt;

&lt;p&gt;With that basic understanding in place, we're ready to implement the ratings index function component.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the Function Component
&lt;/h3&gt;

&lt;p&gt;We'll build a rating index component responsible for orchestrating the state of &lt;em&gt;all&lt;/em&gt; the game ratings in our survey. This component will iterate over the games and render the rating details if a user rating exists, or the rating form if it doesn't. The responsibility for rendering rating details will be handled by a "rating show" function component. A live "rating form" component will handle rendering and managing a rating form. More on live components in a bit.&lt;/p&gt;

&lt;p&gt;Meanwhile, &lt;code&gt;SurveyLive&lt;/code&gt; will continue to be responsible for managing the overall state and appearance of the survey page. The rating index component will receive the list of game ratings to render from the parent live view. The parent live view is responsible for maintaining and updating that list.&lt;/p&gt;

&lt;p&gt;In this way, we keep our code organized and easy to maintain because it adheres to the single responsibility principle — each component has one job to do. By layering these components within the parent &lt;code&gt;SurveyLive&lt;/code&gt; view, we compose a series of small, manageable pieces into one interactive feature — the user survey page.&lt;/p&gt;

&lt;p&gt;We'll begin by implementing the &lt;code&gt;RatingLive.Index&lt;/code&gt; function component. Then, we'll move on to the rating show component, followed by the rating form component.&lt;/p&gt;

&lt;p&gt;Create a file, &lt;code&gt;lib/arcade_web/live/rating_live/index.ex&lt;/code&gt;, and key in the following component definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;RatingLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Component&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;RatingLive&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our function component uses the &lt;code&gt;Phoenix.Component&lt;/code&gt; behaviour which we need to render HEEx templates. Any module that implements &lt;em&gt;only&lt;/em&gt; function components will use this behaviour. You'll use a different behaviour when building live or stateful components, which we'll do later on in this post.&lt;/p&gt;

&lt;p&gt;We're also using the &lt;code&gt;Phoenix.HTML&lt;/code&gt; behaviour here to bring in the &lt;code&gt;Phoenix.HTML.raw/1&lt;/code&gt; function that we'll use to render unicode characters — more on that in a bit. Finally, we're aliasing the name of the component module itself so it's easy to ergonomically invoke other function components defined within the module from within our main function component here.&lt;/p&gt;

&lt;p&gt;The entry point of our module will be the  &lt;code&gt;games/1&lt;/code&gt; function. We'll call on this function component from the parent live view to render the list of games. The function will take in an &lt;code&gt;assigns&lt;/code&gt; argument containing the list of games passed in from the parent &lt;code&gt;SurveyLive&lt;/code&gt; view. It will return a HEEx template that iterates over that list and renders &lt;em&gt;another&lt;/em&gt; function component to show the game rating details if a rating exists and the rating form live component if not. Define that function now, as shown here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;games&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;div class="&lt;/span&gt;&lt;span class="n"&gt;survey&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;component&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
    &amp;lt;.heading games={@games} /&amp;gt;
    &amp;lt;.list games={@games} current_user={@current_user}/&amp;gt;
  &amp;lt;/div&amp;gt;
  """&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're composing our &lt;code&gt;games/1&lt;/code&gt; function out of two additional function components — &lt;code&gt;heading/1&lt;/code&gt; and &lt;code&gt;list/1&lt;/code&gt;. We call on those function components with the &lt;code&gt;.function_name assigns... /&amp;gt;&lt;/code&gt; syntax. This invokes the function component and passes in whatever assigns we provide as the &lt;code&gt;assigns&lt;/code&gt; argument to that function.&lt;/p&gt;

&lt;p&gt;It's also worth noting the &lt;code&gt;{}&lt;/code&gt; interpolation syntax here, instead of the &lt;code&gt;&amp;lt;%= %&amp;gt;&lt;/code&gt; EEx tags you might be used to. This is because HEEx, unlike EEx, isn't just responsible for evaluating and templating Elixir expressions into your HTML. It also parses and validates the HTML itself. So you can't use the traditional EEx tags &lt;em&gt;inside&lt;/em&gt; HTML tags in a HEEx template. Instead, use curly braces to interpolate values inside HTML tags and function component calls, and use EEx tags when interpolating values in the body, or inner content, of those tags.&lt;/p&gt;

&lt;p&gt;Let's build those additional function components now, starting with &lt;code&gt;heading/1&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
    &amp;lt;h2&amp;gt;
      Ratings
      &amp;lt;%= if ratings_complete?(@games), do: raw "&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="c1"&gt;#x2713;" %&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="s2"&gt;"""
end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;heading/1&lt;/code&gt; function is pretty small and single-purpose. It renders an &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; element that encapsulates some text along with a helper function that checks to see if &lt;em&gt;all&lt;/em&gt; of the games have a rating by the current user. If so, we render the unicode to a checkmark and the user can see that all of the ratings forms have been completed.&lt;/p&gt;

&lt;p&gt;Before we implement this helper function, let's see how we're going to render the index component with a list of games.&lt;/p&gt;

&lt;p&gt;When we render this index component from the &lt;code&gt;SurveyLive&lt;/code&gt; template, we'll use the &lt;code&gt;SurveyLive&lt;/code&gt; view to query for the list of games with ratings by the current preloaded user.&lt;/p&gt;

&lt;p&gt;Then, we'll pass that list of games down into the index component. So we can assume that each game in the &lt;code&gt;@games&lt;/code&gt; list has its &lt;code&gt;ratings&lt;/code&gt; list populated &lt;em&gt;only&lt;/em&gt; with a rating from the current user. With that in mind, we can implement the &lt;code&gt;ratings_complete?/1&lt;/code&gt; function to iterate over the list of games and return &lt;code&gt;true&lt;/code&gt; if there is a rating for every game. Add in your function now, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt; &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;ratings_complete?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;games&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;games&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if a user has completed all of the game ratings, they'll see the "Ratings" header with a nice checkmark next to it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fI1Rv1eF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-01/ratings-complete-header.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fI1Rv1eF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-01/ratings-complete-header.png" alt="ratings complete header" width="880" height="53"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;heading/1&lt;/code&gt; function component out of the way, let's turn our attention to &lt;code&gt;list/1&lt;/code&gt;. Add in this function now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;%= for {game, index} &amp;lt;- Enum.with_index(@games) do %&amp;gt;
    &amp;lt;%= if rating = List.first(game.ratings) do %&amp;gt;
      &amp;lt;h3&amp;gt;Show rating coming soon!&amp;lt;/h3&amp;gt;
    &amp;lt;% else %&amp;gt;
      &amp;lt;h3&amp;gt;Rating form coming soon!&amp;lt;/h3&amp;gt;
    &amp;lt;% end %&amp;gt;
  &amp;lt;% end %&amp;gt;
  """&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we use a &lt;code&gt;for&lt;/code&gt; comprehension that maps over all of the games in the system, where each game's &lt;code&gt;ratings&lt;/code&gt; list contains the single preloaded rating by the given user if one exists. Inside that comprehension, the template will render the rating details if a rating exists or a form for that rating if not. Nesting components in this manner lets the reader of the code deal with a tiny bit of complexity at a time.&lt;/p&gt;

&lt;p&gt;We'll dig into this logic a bit more when we're ready to implement these final two components. With the index component out of the way, we are ready to weave it into our &lt;code&gt;SurveyLive&lt;/code&gt; template.&lt;/p&gt;

&lt;h3&gt;
  
  
  Render the Component
&lt;/h3&gt;

&lt;p&gt;The next bit of code we'll write shows how the presentation of our view can change based on the contents of the socket. The &lt;code&gt;SurveyLive&lt;/code&gt; view will use the state of the overall survey to control what is shown to the user. This view holds the list of games, and their ratings by the current user, in state. It will pass this list into the &lt;code&gt;RatingLive.games/1&lt;/code&gt; function component as part of the component assigns. The contents of this list will allow the &lt;code&gt;games/1&lt;/code&gt; function component to determine if it should show rating details or a rating form.&lt;/p&gt;

&lt;p&gt;Let's update &lt;code&gt;SurveyLive&lt;/code&gt; now to query for the list of games and their ratings from the current user and add them to socket assigns, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SurveyLive&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_view&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Survey&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"user_token"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;socket&lt;/span&gt;
     &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:current_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_user_by_session_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
     &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:games&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Catalog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_games_with_user_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assume that the &lt;code&gt;Catalog.list_games_with_user_rating/1&lt;/code&gt; context function returns the list of all games, with &lt;em&gt;only the rating by the given user&lt;/em&gt; preloaded, if any.&lt;/p&gt;

&lt;p&gt;Now we're ready to render our rating index function component from the &lt;code&gt;SurveyLive&lt;/code&gt; template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight eex"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- lib/arcade_web/live/survey_live.html.heex --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;RatingLive.Index.games&lt;/span&gt; &lt;span class="na"&gt;games=&lt;/span&gt;&lt;span class="s"&gt;{@games}&lt;/span&gt;
      &lt;span class="na"&gt;current_user=&lt;/span&gt;&lt;span class="s"&gt;{@current_user}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we're once again using the &lt;code&gt;&amp;lt;.function_component assigns... /&amp;gt;&lt;/code&gt; syntax to render our function component and the &lt;code&gt;{}&lt;/code&gt; interpolation syntax for interpolating within tags in a HEEx template.&lt;/p&gt;

&lt;p&gt;Now that we're rendering our &lt;code&gt;RatingLive.Index.games/1&lt;/code&gt; function component with the game list, let's build the stateless function component to show the existing rating for a game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show a Rating
&lt;/h2&gt;

&lt;p&gt;We're getting closer to the goal of showing ratings, step by step. Remember, we'll show the existing ratings, and forms for ratings, otherwise.&lt;/p&gt;

&lt;p&gt;Let's cover the case for ratings that exist first. We'll define a stateless component to show a rating. Then, we'll render that component from within the HEEx template returned by &lt;code&gt;RatingLive.Index.games/1&lt;/code&gt;. Let's get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the Function Component
&lt;/h3&gt;

&lt;p&gt;Create a file, &lt;code&gt;lib/arcade_web/live/rating_live/show_component.ex&lt;/code&gt;, and key this in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;RatingLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Show&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Component&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're defining a module that uses the &lt;code&gt;Phoenix.Component&lt;/code&gt; behaviour and the &lt;code&gt;Phoenix.HTML&lt;/code&gt; behaviour, since we'll once again need support for the &lt;code&gt;Phoenix.HTML.raw/1&lt;/code&gt; function to render unicode characters.&lt;/p&gt;

&lt;p&gt;Okay, let's move on to the entry point of our function component, the &lt;code&gt;stars/1&lt;/code&gt; function. We'll call this function from within the HEEx template returned by &lt;code&gt;RatingLive.Index.games/1&lt;/code&gt; with an assigns that includes the given game's rating by the current user.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;stars/1&lt;/code&gt; function will operate on this rating and use some helper functions to construct a list of filled and unfilled unicode star characters. We'll construct that list using a simple pipeline, and then render it in a HEEx template, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;stars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;filled_stars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unfilled_stars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;div&amp;gt;
    &amp;lt;h4&amp;gt;
      &amp;lt;%= @game.name %&amp;gt;:&amp;lt;br/&amp;gt;
      &amp;lt;%= raw stars %&amp;gt;
    &amp;lt;/h4&amp;gt;
  &amp;lt;/div&amp;gt;
  """&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;filled_stars/1&lt;/code&gt; and &lt;code&gt;unfilled_stars/1&lt;/code&gt; helper functions are interesting. Take a look at them here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;filled_stars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;duplicate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;amp;#x2605;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;unfilled_stars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;duplicate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;amp;#x2606;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examining our pipeline in the &lt;code&gt;stars/1&lt;/code&gt; function, we can see that we call on &lt;code&gt;filled_stars/1&lt;/code&gt; to produce a list of filled-in, or "checked", star unicode characters corresponding to the number of stars the game rating has. Then, we pipe that into a call to &lt;code&gt;Enum.concat/2&lt;/code&gt; with a second argument of the output from &lt;code&gt;unfilled_stars/1&lt;/code&gt;. This second helper function produces a list of empty, or not checked, star characters for the remaining number of stars.&lt;/p&gt;

&lt;p&gt;For example, if the number of stars in the rating is 3, our pipeline of helper functions will create a list of three checked stars and two un-checked stars. Our pipeline concatenates the two lists together and joins them into a string of HTML that we can render in the template.&lt;/p&gt;

&lt;p&gt;We have everything we need to display a completed rating, so it's time to roll several components up together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Render the Component
&lt;/h3&gt;

&lt;p&gt;We're ready to implement the next phase of our plan. The &lt;code&gt;RatingLive.Index.games/1&lt;/code&gt; function component iterates over the list of games in the &lt;code&gt;@games&lt;/code&gt; assigns. If a rating is present, we show it. Add in the call to our new &lt;code&gt;Show.stars/1&lt;/code&gt; component now, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;%= for {game, index} &amp;lt;- Enum.with_index(@games) do %&amp;gt;
    &amp;lt;%= if rating = List.first(game.ratings) do %&amp;gt;
      &amp;lt;Show.stars rating={rating} game={game} /&amp;gt;
    &amp;lt;% else %&amp;gt;
      &amp;lt;h3&amp;gt;Rating form coming soon!&amp;lt;/h3&amp;gt;
    &amp;lt;% end %&amp;gt;
  &amp;lt;% end %&amp;gt;
  """&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a straight &lt;code&gt;for&lt;/code&gt; comprehension with an &lt;code&gt;if&lt;/code&gt; statement. If a rating exists, we render the function component by calling on it with the &lt;code&gt;game&lt;/code&gt; and &lt;code&gt;rating&lt;/code&gt; assigns. If not, we need to render the form. Let's build that form and render it now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Submit a Rating
&lt;/h2&gt;

&lt;p&gt;Our rating form will display the form and manage its state, validating and saving the rating. We'll need to pass a game and user for our database relationships, and the game's index in the parent LiveView's &lt;code&gt;socket.assigns.games&lt;/code&gt; list. We'll use this index later on to update &lt;code&gt;SurveyLive&lt;/code&gt; state efficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the Rating Form Component
&lt;/h3&gt;

&lt;p&gt;The component will be stateful since it needs to manage the state of the rating form and respond to user interactions to validate form changes and handle the form submission.&lt;/p&gt;

&lt;p&gt;A stateful, or live, component is any module that uses the &lt;code&gt;:live_component&lt;/code&gt; behaviour and renders a HEEx template. Such modules can implement the live component lifecycle functions, including &lt;code&gt;mount/3&lt;/code&gt;, &lt;code&gt;update/2&lt;/code&gt;, and &lt;code&gt;render/1&lt;/code&gt;, and respond to user events by implementing a &lt;code&gt;handle_event/3&lt;/code&gt; function. Let's define our live component module now. Create a file, &lt;code&gt;lib/arcade_web/live/rating_live/form.ex&lt;/code&gt;, and key this in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;RatingLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;FormComponent&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_component&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Survey&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Survey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Rating&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is simple enough, to begin with. We define our module, use the &lt;code&gt;:live_component&lt;/code&gt; behaviour, and add in some aliases that we'll need later.&lt;/p&gt;

&lt;p&gt;We'll use LiveView's &lt;code&gt;.form/1&lt;/code&gt; function (more on that in a bit) to construct the rating form. This function requires a changeset, so we'll need to store one in our component's state. Here's where the component lifecycle comes into play. When we render a live component, LiveView starts the component in the parent view's process and calls these callbacks, in order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;mount/1&lt;/code&gt;&lt;br&gt;
: The single argument is the socket, and we use this callback to set the initial state. This callback is invoked only once, when the component is first rendered from the parent live view.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;update/2&lt;/code&gt;&lt;br&gt;
: The two arguments are the assigns argument given to &lt;code&gt;live_component/3&lt;/code&gt; and the socket. By default, it merges the assigns argument into the &lt;code&gt;socket.assigns&lt;/code&gt; established in &lt;code&gt;mount/1&lt;/code&gt;. We'll use this callback to add additional content to the socket &lt;em&gt;each time &lt;code&gt;live_component/3&lt;/code&gt; is called&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;render/1&lt;/code&gt;&lt;br&gt;
: The one argument is &lt;code&gt;socket.assigns&lt;/code&gt;. It works like a render in any other live view.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stateful components will always follow this process when first mounted and rendered. Then, when the component updates in response to changes in the parent live view, only the &lt;code&gt;update/2&lt;/code&gt; and &lt;code&gt;render/1&lt;/code&gt; callbacks fire. Since these updates skip the &lt;code&gt;mount/1&lt;/code&gt; callback, the &lt;code&gt;update/2&lt;/code&gt; function is the safest place to establish the component's initial state. Let's create our component's &lt;code&gt;update/2&lt;/code&gt; function now, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_rating&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_changeset&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;assign_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;current_user:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;game:&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:rating&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Rating&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;user_id:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;game_id:&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;assign_changeset&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;rating:&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Survey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These reducer functions will add the necessary keys to our &lt;code&gt;socket.assigns&lt;/code&gt;. They'll drop in any &lt;code&gt;assigns&lt;/code&gt; our parent sends, add a new &lt;code&gt;Rating&lt;/code&gt; struct, and finally establish a changeset for the new rating.&lt;/p&gt;

&lt;p&gt;There are no surprises here. One reducer builds a new rating, and the other uses the &lt;code&gt;Survey&lt;/code&gt; context to build a changeset for that rating. Now, on to render.&lt;/p&gt;

&lt;p&gt;With our socket established, we're ready to render. We'll choose a template to keep our markup code neatly compartmentalized. Create a file, &lt;code&gt;lib/arcade_web/live/rating_live/form.html.heex&lt;/code&gt;. Add the game title markup followed by the game rating form shown here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight eex"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"survey-component-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h4&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="nv"&gt;@game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;form&lt;/span&gt;
      &lt;span class="na"&gt;let=&lt;/span&gt;&lt;span class="s"&gt;{f}&lt;/span&gt;
      &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;{@changeset}&lt;/span&gt;
      &lt;span class="na"&gt;phx-change=&lt;/span&gt;&lt;span class="s"&gt;"validate"&lt;/span&gt;
      &lt;span class="na"&gt;phx-submit=&lt;/span&gt;&lt;span class="s"&gt;"save"&lt;/span&gt;
      &lt;span class="na"&gt;phx_target=&lt;/span&gt;&lt;span class="s"&gt;{@myself}&lt;/span&gt;
      &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;{@id}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:stars&lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:stars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;error_tag&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:stars&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;hidden_input&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;hidden_input&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:game_id&lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;submit&lt;/span&gt; &lt;span class="s2"&gt;"Save"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;phx_disable_with:&lt;/span&gt; &lt;span class="s2"&gt;"Saving..."&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nt"&gt;form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The form template is really just a standard Phoenix form, although the syntax for rendering the form may be new to you. The main function in the template is the &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#form/1"&gt;&lt;code&gt;form/1&lt;/code&gt;&lt;/a&gt; function: a function component made available by LiveView under the hood. The form function component returns a rendered HEEx template containing an HTML form built with the help of &lt;code&gt;Phoenix.HTML.Form.form_for/4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're feeling adventurous, you can &lt;a href="https://github.com/phoenixframework/phoenix_live_view/blob/v0.17.5/lib/phoenix_live_view/helpers.ex#L1002"&gt;check out the source code for the form function component&lt;/a&gt;. For now, all you really need to know is that calling &lt;code&gt;form/1&lt;/code&gt; returns an HTML form for the specified changeset, with the specified LiveView bindings. Let's take a closer look at how our form is rendered.&lt;/p&gt;

&lt;p&gt;Since the &lt;code&gt;form/1&lt;/code&gt; function is built on top of the &lt;code&gt;form_for/4&lt;/code&gt; function, it presents a similar API. Here, we're generating a form for the &lt;code&gt;@changeset&lt;/code&gt; assignment that was put in assigns via the &lt;code&gt;update/2&lt;/code&gt; callback.&lt;/p&gt;

&lt;p&gt;Then, we bind two events to the form, a &lt;code&gt;phx-change&lt;/code&gt; to send a &lt;code&gt;validate&lt;/code&gt; event and a &lt;code&gt;phx-submit&lt;/code&gt; to send a &lt;code&gt;save&lt;/code&gt; event. We target our form component to receive events by setting &lt;code&gt;phx-target&lt;/code&gt; to &lt;code&gt;@myself&lt;/code&gt;, and we tack on an &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note that we've set a dynamic HTML id of the stateful component id, stored in socket assigns as &lt;code&gt;@id&lt;/code&gt;. This is because the game rating form will appear multiple times on the page, once for each game, and we need to ensure that each form gets a unique id. You'll see how we set the &lt;code&gt;id&lt;/code&gt; assigns for the component when we render it in a bit.&lt;/p&gt;

&lt;p&gt;Our form has a &lt;code&gt;stars&lt;/code&gt; field with a label and error tag and a hidden field for each &lt;code&gt;user&lt;/code&gt; and &lt;code&gt;game&lt;/code&gt; relationship. We tie things up with a submit button.&lt;/p&gt;

&lt;p&gt;We'll come back to the events a bit later. For now, let's fold our work into the &lt;code&gt;RatingLive.Index.list/1&lt;/code&gt; function component.&lt;/p&gt;

&lt;h3&gt;
  
  
  Render the Component
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;RatingLive.Index.games/1&lt;/code&gt; function component should render the rating form component if no rating for the given game and user exists. Let's do that now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;%= for {game, index} &amp;lt;- Enum.with_index(@games) do %&amp;gt;
    &amp;lt;%= if rating = List.first(game.ratings) do %&amp;gt;
      &amp;lt;Show.stars rating={rating} game={game} /&amp;gt;
    &amp;lt;% else %&amp;gt;
      &amp;lt;.live_component module={RatingLive.Form}
                        id={"&lt;/span&gt;&lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="c1"&gt;#{game.id}"}&lt;/span&gt;
                        &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="n"&gt;game_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;@current_user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="s2"&gt;"""
end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we call on the component with the &lt;code&gt;live_component/1&lt;/code&gt; function, passing the user and game into the component as assigns, along with the game's index in the &lt;code&gt;@games&lt;/code&gt; assignment. We add an &lt;code&gt;:id&lt;/code&gt;, a requirement of all stateful components. Since we'll only have one rating per component, our &lt;code&gt;id&lt;/code&gt; with an embedded &lt;code&gt;game.id&lt;/code&gt; should be unique.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;live_component/1&lt;/code&gt; function is a function component made available to us by the LiveView framework. It takes in an argument of some assigns and returns a HEEx template that renders the given component within the parent live view. When using &lt;code&gt;live_component/1&lt;/code&gt; to render a live component, you must specify an assigns of &lt;code&gt;module&lt;/code&gt;, pointing to the name of the live component module to mount and render, and an assigns of &lt;code&gt;id&lt;/code&gt;, which LiveView will use to keep track of the component. Also, note the &lt;code&gt;{}&lt;/code&gt; interpolation syntax we're using — this syntax is required when interpolating within HTML or HEEx tags.&lt;/p&gt;

&lt;p&gt;It's been a while since we've looked at things in the browser — but now, if you point your browser at &lt;code&gt;/survey&lt;/code&gt;, you should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JUiSMv5d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-01/ratings-forms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JUiSMv5d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-01/ratings-forms.png" alt="ratings forms" width="880" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Handle Component Events
&lt;/h3&gt;

&lt;p&gt;We've bound events to save and validate our form, so we should teach our component how to do both. We need one &lt;code&gt;handle_event/2&lt;/code&gt; function head for each of the &lt;code&gt;save&lt;/code&gt; and &lt;code&gt;validate&lt;/code&gt; events. Let's start with &lt;code&gt;validate&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"rating"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rating_params&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validate_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating_params&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to build the reducer next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;validate_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;changeset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rating&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Survey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rating_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:validate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our &lt;code&gt;validate_rating/2&lt;/code&gt; reducer function validates the changeset and returns a new socket with the validated changeset (containing any errors) in socket assigns. This will cause the component to re-render the template with the updated changeset, allowing the &lt;code&gt;error_tag&lt;/code&gt; helpers in our &lt;code&gt;form_for&lt;/code&gt; form to render any errors.&lt;/p&gt;

&lt;p&gt;Next up, we'll implement a &lt;code&gt;handle_event/2&lt;/code&gt; function that matches the &lt;code&gt;save&lt;/code&gt; event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"save"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"rating"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rating_params&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating_params&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the reducer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;save_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;product_index:&lt;/span&gt; &lt;span class="n"&gt;product_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;product:&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;rating_params&lt;/span&gt;
       &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Survey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rating_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="ss"&gt;ratings:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
      &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:created_rating&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_index&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="n"&gt;socket&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;changeset:&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we attempt to save the form. On failure, we assign a new changeset. On success, we send a message to the parent live view to do the heavy lifting for us. Then, as all handlers must do, we return the socket.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update the Rating Index
&lt;/h3&gt;

&lt;p&gt;What should happen when the game rating successfully saves? The &lt;code&gt;RatingLive.Index.games/1&lt;/code&gt; function should no longer render the form for that game. Instead, the survey should display the saved rating. This kind of state change is squarely the responsibility of &lt;code&gt;SurveyLive&lt;/code&gt;. Our message will serve to notify the parent live view to change.&lt;/p&gt;

&lt;p&gt;Here's the interesting bit. All the parent needs to do is update the socket. The &lt;code&gt;RatingLive.Index.games/1&lt;/code&gt; function already renders the right thing based on the content of the assigns that it receives from the parent, &lt;code&gt;SurveyLive&lt;/code&gt;. All we need to do is implement a handler to deal with the "created rating" message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/arcade_web/live/survey_live.ex&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_info&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:created_rating&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_index&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle_rating_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_index&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use &lt;code&gt;handle_info&lt;/code&gt; so that the parent live view process, &lt;code&gt;SurveyLive&lt;/code&gt;, can respond to the message sent by the child component. Now, our reducer can take the appropriate action. Notice that the message we match has a message name, an updated game, &lt;em&gt;and&lt;/em&gt; its index in the &lt;code&gt;:games&lt;/code&gt; list. We can use that information to update the game list without going back to the database. We'll implement the reducer below to do this work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_rating_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;products:&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;updated_product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;product_index&lt;/span&gt;
       &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="n"&gt;socket&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;put_flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Rating submitted successfully"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;:products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;handle_rating_created/3&lt;/code&gt; reducer adds a flash message and updates the game list with its rating. This causes the template to re-render, passing this updated game list to &lt;code&gt;RatingLive.Index.games/1&lt;/code&gt;. That function component, in turn, knows just what to do with a game containing a rating by the given user — it will render that rating's details instead of a rating form.&lt;/p&gt;

&lt;p&gt;Notice the lovely layering. In the parent live view layer, all we need to do is manage the list of games and ratings. All of the form handling and rating or demographic details go elsewhere.&lt;/p&gt;

&lt;p&gt;The end result of a submitted rating is an updated game list and a flash message. Submit a rating, and see what happens:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5OhsEEZ_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-01/rating-form-success.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5OhsEEZ_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-01/rating-form-success.png" alt="rating form success" width="880" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You just witnessed the power of components and LiveView.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up: You've Built a Complex LiveView UI with Components
&lt;/h2&gt;

&lt;p&gt;This post covered a lot of ground. You implemented a sophisticated UI composed from simple layers, all thanks to LiveView components. You can wrap up simple markup with function components, while live components allow you to maintain component state and respond to events. Meanwhile, HEEx templates and LiveView provide some nice semantics for rendering markup and both types of components.&lt;/p&gt;

&lt;p&gt;LiveView is growing fast — it's responding to the community's needs and providing even more ergonomic solutions for developing complex interactive single-page apps. Function components and HEEx are just some of the latest features that make LiveView even more enjoyable to use.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>What's New in Elixir 1.13</title>
      <dc:creator>Sophie DeBenedetto</dc:creator>
      <pubDate>Tue, 21 Dec 2021 12:53:01 +0000</pubDate>
      <link>https://dev.to/appsignal/whats-new-in-elixir-113-26hk</link>
      <guid>https://dev.to/appsignal/whats-new-in-elixir-113-26hk</guid>
      <description>&lt;p&gt;On December 3rd, the Elixir core team announced the latest Elixir release, 1.13. In this post, we'll explore some of the more exciting changes and new features, and discuss their impact on the Elixir community.&lt;/p&gt;

&lt;h2&gt;
  
  
  All About Tooling
&lt;/h2&gt;

&lt;p&gt;Elixir 1.13 comes packed with lots of goodies and improvements designed to make the day-to-day life of Elixir developers easier. With new additions like semantic code recompilation, better support for auto-complete in IEx, an API for custom code formatting, and more, quality of life for Elixir developers is going to greatly increase. Let's dive into some of the latest developments now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making IEx Better
&lt;/h3&gt;

&lt;p&gt;Elixir 1.13 comes with a number of changes that enhance the IEx experience. The introduction of the new &lt;a href="https://hexdocs.pm/elixir/master/Code.Fragment.html"&gt;&lt;code&gt;Code.Fragment&lt;/code&gt;&lt;/a&gt; module greatly improves the autocomplete story for Elixir, making your work in IEx even easier. &lt;code&gt;Code.Fragment&lt;/code&gt; provides conveniences for analyzing fragments of textual code and returning information about those fragments. Practically speaking, this means that IEx now supports autocomplete of sigils, structs, and paths. Let's take a look at some examples.&lt;/p&gt;

&lt;p&gt;Previously, attempting to autocomplete the &lt;code&gt;~&lt;/code&gt; character didn't lead to anything too useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iex(1)&amp;gt; ~
!/1                           !=/2
!==/2                         %/2
%{}/1                         &amp;amp;&amp;amp;/2
&amp;amp;/1                           */2
++/2                          +/1
+/2                           --/2
-/1                           -/2
..///3                        ../2
./2                           //2
::/2                          &amp;lt;/2
&amp;lt;&amp;lt;&amp;gt;&amp;gt;/1                        &amp;lt;=/2
&amp;lt;&amp;gt;/2                          =/2
==/2                          ===/2
=~/2                          &amp;gt;/2
&amp;gt;=/2                          @/1
^/1                           __CALLER__/0
__DIR__/0                     __ENV__/0
__MODULE__/0                  __STACKTRACE__/0
__aliases__/1                 __block__/1
abs/1                         alias!/1
alias/2                       and/2
apply/2                       apply/3
b/1                           binary_part/3
binding/0                     binding/1
bit_size/1                    break!/1
break!/2                      break!/3
break!/4                      breaks/0
byte_size/1                   c/1
c/2                           case/2
cd/1                          ceil/1
clear/0                       cond/1
continue/0                    def/1
def/2                         defdelegate/2
defexception/1                defguard/1
defguardp/1                   defimpl/2
defimpl/3                     defmacro/1
defmacro/2                    defmacrop/1
defmacrop/2                   defmodule/2
defo
# ... and the list goes on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you attempt the same autocomplete, you'll see this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;~&lt;/span&gt;
&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="no"&gt;C&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigil_C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="no"&gt;D&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigil_D&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="no"&gt;N&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigil_N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="no"&gt;R&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigil_R&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="no"&gt;S&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigil_S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigil_T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="no"&gt;U&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigil_U&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="no"&gt;W&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigil_W&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigil_c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigil_r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigil_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigil_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A helpful list of sigil functions.&lt;/p&gt;

&lt;p&gt;Similarly, in earlier Elixir versions, trying to autocomplete a struct would yield the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, assume you have a struct, &lt;code&gt;User&lt;/code&gt;, defined like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defstruct&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:age&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Attempt IEx autocomplete by hitting &lt;code&gt;%Use&amp;lt;TAB&amp;gt;&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iex&amp;gt; %Use
 %User.__struct__
__struct__/0    __struct__/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not exactly a helpful look at the struct. Let's see what the same operation yields in Elixir 1.13:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iex&amp;gt; %User{
name:    age:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we get a helpful look at all of the fields of our struct.&lt;/p&gt;

&lt;p&gt;These autocomplete improvements don't just make IEx better. Your autocomplete experience in your text editor of choice will also improve. With additional changes like new compilation tracers and new &lt;code&gt;Module&lt;/code&gt; functions for retrieving module metadata, we can expect to see new code intelligence features come out that will improve your text editor experiences with Elixir even further.&lt;/p&gt;

&lt;h3&gt;
  
  
  Faster Elixir Code Recompilation
&lt;/h3&gt;

&lt;p&gt;With Elixir's new semantic code recompilation, your code will be recompiled less frequently. This means a faster development life cycle, especially when it comes to working with large Elixir applications.&lt;/p&gt;

&lt;p&gt;Prior to Elixir 1.13, your &lt;em&gt;entire&lt;/em&gt; Elixir app would be recompiled whenever any of the &lt;code&gt;mix.exs&lt;/code&gt;, &lt;code&gt;config/config.exs&lt;/code&gt;, &lt;code&gt;src/*&lt;/code&gt;, or &lt;code&gt;mix.lock&lt;/code&gt; files changed. Now, thanks to semantic code compilation, your code will recompile &lt;em&gt;only&lt;/em&gt; when you change the compilation options in the &lt;code&gt;mix.exs&lt;/code&gt; file or the configuration in the &lt;code&gt;config/config.exs&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Code compilation has gotten smarter in other ways too. Not only will your application be recompiled less frequently, but certain changes will now trigger recompilation of smaller parts of your app, or no recompilation at all.&lt;/p&gt;

&lt;p&gt;For example, now if the size &lt;em&gt;and&lt;/em&gt; digest of a file stay the same, the file will not be recompiled. This means switching or rebasing git branches will trigger far fewer recompilations. Additionally, changing compile-time config files (like &lt;code&gt;config/config.exs&lt;/code&gt;) will &lt;em&gt;only&lt;/em&gt; recompile any files that depend on the reconfigured files. Practically speaking, this means that, for example, bumping your Phoenix version in &lt;code&gt;mix.exs&lt;/code&gt; will &lt;em&gt;only&lt;/em&gt; recompile &lt;code&gt;lib/my_app_web&lt;/code&gt; and &lt;em&gt;not&lt;/em&gt; &lt;code&gt;lib/my_app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Semantic compilation is accompanied by some improvements to the &lt;code&gt;mix xref&lt;/code&gt; task. This task gives you information about your files and how they depend on each other. Here's an abbreviated look at running &lt;code&gt;mix xref&lt;/code&gt; with the &lt;code&gt;graph&lt;/code&gt; option in a standard Phoenix app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="err"&gt;♥&lt;/span&gt; &lt;span class="n"&gt;mix&lt;/span&gt; &lt;span class="n"&gt;xref&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;
&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pento&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;
&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pento&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pento&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pento&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;user_notifier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pento&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;user_token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pento&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells us that all of the files listed &lt;em&gt;under&lt;/em&gt; &lt;code&gt;lib/pento/accounts/ex&lt;/code&gt; depend on &lt;code&gt;account.ex&lt;/code&gt; at compilation time. The task already gives us some great tooling to identify &lt;em&gt;that&lt;/em&gt; a compile-time dependency exists, but doesn't really provide any insight into &lt;em&gt;why&lt;/em&gt; such a dependency exists. So, if you needed to track down the source of an inappropriate compile-time dependency when that source is non-obvious (if it comes from a macro, for example), this tool used to fall short. Until now!&lt;/p&gt;

&lt;p&gt;Elixir 1.13 adds a new option to the &lt;code&gt;mix xref&lt;/code&gt; task - the &lt;code&gt;trace&lt;/code&gt; option. Now, you can run &lt;code&gt;mix xref trace &amp;lt;file name&amp;gt;&lt;/code&gt; and see a list of exactly where all of our compile time dependencies, exports, and runtime dependencies come from within that file, down to the exact line number. Let's check it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lib/pento/accounts.ex:26: call Pento.Repo.get_by/2 (runtime)
lib/pento/accounts.ex:43: call Pento.Repo.get_by/2 (runtime)
lib/pento/accounts.ex:44: call Pento.Accounts.User.valid_password?/2 (runtime)
lib/pento/accounts.ex:61: call Pento.Repo.get!/2 (runtime)
lib/pento/accounts.ex:78: struct Pento.Accounts.User (export)
lib/pento/accounts.ex:79: call Pento.Accounts.User.registration_changeset/2 (runtime)
lib/pento/accounts.ex:80: call Pento.Repo.insert/1 (runtime)
lib/pento/accounts.ex:92: struct Pento.Accounts.User (export)
lib/pento/accounts.ex:93: call Pento.Accounts.User.registration_changeset/3 (runtime)
lib/pento/accounts.ex:108: call Pento.Accounts.User.email_changeset/2 (runtime)
lib/pento/accounts.ex:126: call Pento.Accounts.User.email_changeset/2 (runtime)
lib/pento/accounts.ex:127: call Pento.Accounts.User.validate_current_password/2 (runtime)
lib/pento/accounts.ex:140: call Pento.Accounts.UserToken.verify_change_email_token_query/2 (runtime)
lib/pento/accounts.ex:141: struct Pento.Accounts.UserToken (export)
lib/pento/accounts.ex:141: call Pento.Repo.one/1 (runtime)
# ... I'm truncating this output for brevity, but there's more!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us a powerful tool for understanding the interdependencies in our code and eliminating them where necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Error Messages in Elixir
&lt;/h3&gt;

&lt;p&gt;Elixir 1.13 also brings us improved error messages for both the &lt;code&gt;SyntaxError&lt;/code&gt; and &lt;code&gt;TokenMissing&lt;/code&gt; errors. Such error messages now include code snippets that show you the exact source of your error. This is just one more feature to improve your development experience and make hunting down bugs even easier. Let's try it out.&lt;/p&gt;

&lt;p&gt;Assuming I've added the following bad code to my project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;add_nums&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;num1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;num2&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I should see the following error when I try to do something like run &lt;code&gt;iex -S mix&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;== Compilation error in file lib/pento/accounts.ex ==
** (SyntaxError) lib/pento/accounts.ex:26:9: syntax error before: '*'
    |
 26 |     num1 + *num2
    |         ^
    (elixir 1.13.0) lib/kernel/parallel_compiler.ex:346: anonymous fn/5 in Kernel.ParallelCompiler.spawn_workers/7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Extensible Formatting
&lt;/h3&gt;

&lt;p&gt;The latest Elixir release extends the capabilities of the Elixir formatter so you can implement custom formatting for any embedded code.&lt;/p&gt;

&lt;p&gt;Consider the following scenario. You have a LiveView app that renders lots of HEEx templates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/my_app_live/user_live.ex&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;h2&amp;gt; User Profile
    &amp;lt;/h2&amp;gt;
  """&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that the code in our HEEx template is formatted improperly, but since its &lt;em&gt;not&lt;/em&gt; actually invalid, HEEx won't complain about it. When we run &lt;code&gt;mix format&lt;/code&gt;, the code embed &lt;em&gt;within&lt;/em&gt; the &lt;code&gt;sigil_H&lt;/code&gt; won't be formatted.&lt;/p&gt;

&lt;p&gt;Now, though, Elixir provides an API through which we can implement and apply custom formatters for embedded code. Here's how it works.&lt;/p&gt;

&lt;p&gt;You'll define a custom formatter plugin module along these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MixHEExFormatter&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@behaviour&lt;/span&gt; &lt;span class="no"&gt;Mix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Format&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;sigils:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:H&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;extensions:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;".ex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;".heex"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# logic that formats HEEx&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you'll add your plugin to your app in &lt;code&gt;.formatter.exs&lt;/code&gt;, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="ss"&gt;plugins:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;MixHEExFormatter&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;# Remember to update the inputs list to include the new extensions&lt;/span&gt;
  &lt;span class="ss"&gt;inputs:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"{mix,.formatter}.exs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"{config,lib,test}/**/*.{ex,exs}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lib/my_app_live/live/*.{ex,.heex}"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this new functionality, we can expect to see some upcoming development of custom formatters for common types of embedded code. The Zigler project already has an issue open for a &lt;a href="https://github.com/ityonemo/zigler/issues/287"&gt;custom &lt;code&gt;~Z&lt;/code&gt; formatter&lt;/a&gt;, and I hope to see some development for a &lt;code&gt;~H&lt;/code&gt; soon.&lt;/p&gt;

&lt;h3&gt;
  
  
  AST-Based Tooling
&lt;/h3&gt;

&lt;p&gt;Another exciting new piece of functionality comes by way of two new functions added to the &lt;code&gt;Code&lt;/code&gt; module. &lt;a href="https://hexdocs.pm/elixir/Code.html#quoted_to_algebra/2"&gt;&lt;code&gt;Code.quoted_to_algebra/2&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://hexdocs.pm/elixir/Code.html#string_to_quoted_with_comments/2"&gt;`Code.string_to_quoted_with_comments/2&lt;/a&gt; will enable you to fetch the Elixir AST with original comments and convert it into formatted code.&lt;/p&gt;

&lt;p&gt;This capability will empower developers to create tools for custom formatting of Elixir source code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Testing in Elixir
&lt;/h3&gt;

&lt;p&gt;Elixir 1.13 ships with a few new ExUnit niceties for debugging tests. ExUnit will now show a hint when comparing two different but equivalent strings, making debugging minor discrepancies between strings in your ExUnit expectations much less of a headache.&lt;/p&gt;

&lt;p&gt;ExUnit also provides two new functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://hexdocs.pm/ex_unit/1.13.0/ExUnit.CaptureIO.html#with_io/3"&gt;&lt;code&gt;ExUnit.CaptureIO.with_io/3&lt;/code&gt;&lt;/a&gt; allows you to apply a function to captured IO output&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hexdocs.pm/ex_unit/1.13.0/ExUnit.CaptureLog.html#with_log/2"&gt;&lt;code&gt;ExUnit.CaptureLog.with_log/2&lt;/code&gt;&lt;/a&gt; invokes the given function and returns the result and a captured log.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the new example from the docs:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;elixir&lt;br&gt;
&lt;/code&gt;{result, log} =&lt;code&gt;&lt;br&gt;
&lt;/code&gt;with_log(fn -&amp;gt;&lt;code&gt;&lt;br&gt;
&lt;/code&gt;Logger.error("log msg")&lt;code&gt;&lt;br&gt;
&lt;/code&gt;2 + 2&lt;code&gt;&lt;br&gt;
&lt;/code&gt;end)`&lt;/p&gt;

&lt;p&gt;&lt;code&gt;assert result == 4&lt;/code&gt;&lt;br&gt;
&lt;code&gt;assert log =~ "log msg"&lt;/code&gt;&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With this, we can easily test the result of function invocations &lt;em&gt;and&lt;/em&gt; validate assertions against any log statements made during those function invocations.&lt;/p&gt;

&lt;p&gt;Lastly, mix supports a new option, &lt;code&gt;--profile-require=time&lt;/code&gt; - so that you can run &lt;code&gt;mix test --profile-require=time&lt;/code&gt; to debug test suite load times.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Elixir Language Features
&lt;/h2&gt;

&lt;p&gt;In addition to this impressive list of new tooling capabilities, Elixir 1.13 also ships with some exciting new pieces of Elixir functionality. We'll take a look at just of few of these changes here.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://hexdocs.pm/elixir/Enum.html#slide/3"&gt;&lt;code&gt;Enum.slide/3&lt;/code&gt;&lt;/a&gt; function takes in arguments of an enumerable, index or range of indices, and the insertion index. It "slides" the index or range of indices to the target insertion index, like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`elixir&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#Slide a single element&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Enum.slide([:a, :b, :c, :d, :e, :f, :g], 5, 1)&lt;/code&gt;&lt;br&gt;
&lt;code&gt;#=&amp;gt; [:a, :f, :b, :c, :d, :e, :g]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here, we "slid" the element living at index 5, &lt;code&gt;:f&lt;/code&gt;, and inserted it instead at index 1.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://hexdocs.pm/elixir/List.html#keyfind/4"&gt;&lt;code&gt;List.keyfind/4&lt;/code&gt;&lt;/a&gt; function receives a list of tuples and returns the first tuple where the element at the position in the tuple matches the given key. For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;elixir&lt;br&gt;
&lt;/code&gt;List.keyfind([a: 1, b: 2], :a, 0)&lt;code&gt;&lt;br&gt;
&lt;/code&gt;# =&amp;gt; {:a, 1}&lt;code&gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://hexdocs.pm/elixir/1.13.0/Keyword.html#validate!/2"&gt;&lt;code&gt;Keyword.validate/2&lt;/code&gt;&lt;/a&gt; function lets you validate a keyword list to ensure that it contains only the provided set of values.&lt;/p&gt;

&lt;p&gt;You can now also filter a keyword list with the &lt;a href="https://hexdocs.pm/elixir/1.13.0/Keyword.html#filter/2"&gt;&lt;code&gt;Keyword.filter/2&lt;/code&gt;&lt;/a&gt; function and map over a keyword list with the &lt;a href="https://hexdocs.pm/elixir/1.13.0/Keyword.html#map/2"&gt;&lt;code&gt;Keyword.map/2&lt;/code&gt;&lt;/a&gt; function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;This was a quick overview of the highlights from Elixir 1.13, but there's lots more to see, and you can &lt;a href="https://hexdocs.pm/elixir/1.13.0/changelog.html"&gt;check out the full changelog&lt;/a&gt;. This latest Elixir release means that quality of life for Elixir developers is getting even better.&lt;/p&gt;

&lt;p&gt;New autocomplete and associated features make writing Elixir code faster, semantic code recompilation means faster development cycles, and new testing conveniences and Elixir language capabilities give us even more tools to get the job done.&lt;/p&gt;

&lt;p&gt;We're excited to see what the community builds on top of all of this functionality.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>How to Do Live Uploads in Phoenix LiveView</title>
      <dc:creator>Sophie DeBenedetto</dc:creator>
      <pubDate>Tue, 19 Oct 2021 14:19:21 +0000</pubDate>
      <link>https://dev.to/appsignal/how-to-do-live-uploads-in-phoenix-liveview-1md9</link>
      <guid>https://dev.to/appsignal/how-to-do-live-uploads-in-phoenix-liveview-1md9</guid>
      <description>&lt;p&gt;The LiveView framework supports all of the most common features that Single-Page Apps must offer their users, including multipart uploads. In fact, LiveView can give us highly interactive file uploads, right out of the box.&lt;/p&gt;

&lt;p&gt;In this post, we'll add a file upload feature to an existing Phoenix LiveView application. Along the way, you'll learn how to use LiveView to display upload progress and feedback while editing and saving uploaded files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Image Upload Feature
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;The live upload examples that we'll be looking at in this post are drawn from the "Forms and Changesets" chapter in my book, &lt;a href="https://pragprog.com/titles/liveview/programming-phoenix-liveview/"&gt;Programming LiveView&lt;/a&gt;, co-authored with Bruce Tate. Check it out for an even deeper dive into LiveView forms and so much more.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this example, we have an online game store — Arcade — that allows users to browse and review products. Admins can access a product management interface to create, edit, and delete the products we offer our users. We'll give admins the ability to upload a product image that is then stored in the database along with the given product. Let's plan out our new image upload feature before we start writing any code.&lt;/p&gt;

&lt;p&gt;We'll begin in our application core by adding an &lt;code&gt;image_upload&lt;/code&gt; field to the table and schema for products. Then, we'll create a LiveView form component that supports file uploads. Finally, we'll teach our component to report on upload progress and other bits of upload feedback.&lt;/p&gt;

&lt;p&gt;This post will focus on adding the live upload functionality to an existing LiveView app that already implements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A live view for displaying products.&lt;/li&gt;
&lt;li&gt;A LiveView component that contains a form for creating/editing products.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll zero in on the code required to add the upload functionality to this form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.appsignal.com/2021/09/28/real-time-form-validations-with-phoenix-liveview.html"&gt;Check out my earlier post for a basic introduction to working with forms in LiveView&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we're ready to write some code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persist Product Images in Phoenix LiveView
&lt;/h2&gt;

&lt;p&gt;Assuming our Phoenix LiveView app already has a &lt;code&gt;products&lt;/code&gt; table and &lt;code&gt;Product&lt;/code&gt; schema, we'll now update both to store an &lt;code&gt;image_upload&lt;/code&gt; attribute. This attribute will point to the location of the uploaded file. Once we have our backend wired up, we'll be able to update the existing live view form to accommodate file uploads for a product.&lt;/p&gt;

&lt;p&gt;We'll start at the database layer by generating a migration to add a field, &lt;code&gt;:image_upload&lt;/code&gt;, to the &lt;code&gt;products&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;First, generate your migration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;arcade] ➔ mix ecto.gen.migration add_image_to_products
&lt;span class="k"&gt;*&lt;/span&gt; creating priv/repo/migrations/20201231152152_add_image_to_products.exs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a migration file for us, &lt;code&gt;priv/repo/migrations/20201231152152_add_image_to_products.exs&lt;/code&gt;. Open up that file and key in the contents to the &lt;code&gt;change&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;AddImageToProducts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;alter&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:image_upload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will add the new database field when we run the migration. Let's do that now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;arcade] ➔ mix ecto.migrate

&lt;span class="o"&gt;[&lt;/span&gt;info]  &lt;span class="o"&gt;==&lt;/span&gt; Running 20201231152152 &lt;span class="se"&gt;\&lt;/span&gt;

Arcade.Repo.Migrations.AddImageToProducts.change/0 forward
10:22:24.034 &lt;span class="o"&gt;[&lt;/span&gt;info]  alter table products

10:22:24.041 &lt;span class="o"&gt;[&lt;/span&gt;info]  &lt;span class="o"&gt;==&lt;/span&gt; Migrated 20201231152152 &lt;span class="k"&gt;in &lt;/span&gt;0.0s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This migration added a new column — &lt;code&gt;:image_upload&lt;/code&gt; — of type &lt;code&gt;:string&lt;/code&gt; to the &lt;code&gt;products&lt;/code&gt; table, but our schema still needs attention.&lt;/p&gt;

&lt;p&gt;Update the corresponding &lt;code&gt;Product&lt;/code&gt; schema by adding the new &lt;code&gt;:image_upload&lt;/code&gt; field to the &lt;code&gt;schema&lt;/code&gt; function, which should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Catalog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Product&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;

  &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:unit_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:float&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:image_upload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nv"&gt;@doc&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:unit_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:image_upload&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate_required&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:unit_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sku&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:unit_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;greater_than:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;unique_constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember, the changeset &lt;code&gt;cast/4&lt;/code&gt; function must explicitly whitelist new fields, so make sure you add the &lt;code&gt;:image_upload&lt;/code&gt; attribute there, as shown above.&lt;/p&gt;

&lt;p&gt;Now that the changeset has an &lt;code&gt;:image_upload&lt;/code&gt; attribute, we can save product records that know their image upload location. With that in place, we can make an image upload field available in the &lt;code&gt;ProductLive.FormComponent&lt;/code&gt;'s form. We're one step closer to giving users the ability to save products with images.&lt;/p&gt;

&lt;p&gt;Now, let's turn our attention to the component.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Allow Live Uploads
&lt;/h2&gt;

&lt;p&gt;We'll see our product changeset in action in a bit. First, we need to give the product form the ability to support file uploads. In our Phoenix application, both the "new product" and "edit product" pages use the &lt;code&gt;ProductLive.FormComponent&lt;/code&gt;. This provides one centralized place to maintain our product form. Changes to this component will enable users to upload an image for a new product and a product they are editing.&lt;/p&gt;

&lt;p&gt;To enable uploads for our component, or any live view, we need to call the &lt;code&gt;allow_upload/3&lt;/code&gt; function with a first argument of the socket. This will put the data into socket assigns that the LiveView framework will then use to perform file uploads. So, for a component, we'll call &lt;code&gt;allow_upload/3&lt;/code&gt; when the component first starts up and establishes its initial state in the &lt;code&gt;update/2&lt;/code&gt; function. For a live view, we'd call &lt;code&gt;allow_upload/3&lt;/code&gt; in the &lt;code&gt;mount/3&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;allow_upload/3&lt;/code&gt; function is a reducer that takes in an argument of the socket, the upload name, and the upload options and returns an annotated socket. Supported options include file types, file size, number of files per upload name, and more. Let's see it in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ProductLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;FormComponent&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_component&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Product&lt;/span&gt;

  &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;product:&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;changeset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{})&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;allow_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;accept:&lt;/span&gt; &lt;span class="sx"&gt;~w(.jpg .jpeg .png)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;max_entries:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;allow_upload/3&lt;/code&gt;, we pipe in a socket and specify a name for our upload: &lt;code&gt;:image&lt;/code&gt;. We also provide some options — the maximum number of permitted files and the accepted file formats.&lt;/p&gt;

&lt;p&gt;Let's take a look at what our socket assigns looks like after &lt;code&gt;allow_upload/3&lt;/code&gt; is invoked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;%{&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="ss"&gt;uploads:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
    &lt;span class="ss"&gt;__phoenix_refs_to_names__:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"phx-FlZ_j-hPIdCQuQGG"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:image&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;image:&lt;/span&gt; &lt;span class="c1"&gt;#Phoenix.LiveView.UploadConfig&amp;lt;&lt;/span&gt;
      &lt;span class="ss"&gt;accept:&lt;/span&gt; &lt;span class="s2"&gt;".jpg,.jpeg,.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;entries:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="ss"&gt;errors:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="ss"&gt;max_entries:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;max_file_size:&lt;/span&gt; &lt;span class="mi"&gt;8000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="ss"&gt;:image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;progress_event:&lt;/span&gt; &lt;span class="c1"&gt;#Function&amp;lt;1.71870957/3 ...&amp;gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;ref:&lt;/span&gt; &lt;span class="s2"&gt;"phx-FlZ_j-hPIdCQuQGG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The socket now contains an &lt;code&gt;:uploads&lt;/code&gt; map that specifies the configuration for each upload field your live view allows. We allowed uploads for an upload called &lt;code&gt;:image&lt;/code&gt;. So our map contains a key of &lt;code&gt;:image&lt;/code&gt; pointing to a value of the configuration constructed using the options we gave &lt;code&gt;allow_upload/3&lt;/code&gt;. This means that we can add a file upload field called &lt;code&gt;:image&lt;/code&gt; to our form, and LiveView will track the progress of files uploaded via the field within &lt;code&gt;socket.assigns.uploads.image&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can call &lt;code&gt;allow_upload/3&lt;/code&gt; multiple times with different upload names, thus allowing any number of file uploads in a given live view or component. For example, you could have a form that allows a user to upload a main image, a thumbnail image, a hero image, and more.&lt;/p&gt;

&lt;p&gt;Now that we've set up our uploads state, let's take a closer look at the &lt;code&gt;:image&lt;/code&gt; upload configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Upload Configurations in Phoenix LiveView
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;:image&lt;/code&gt; upload config looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;#Phoenix.LiveView.UploadConfig&amp;lt;&lt;/span&gt;
  &lt;span class="ss"&gt;accept:&lt;/span&gt; &lt;span class="s2"&gt;".jpg,.jpeg,.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;entries:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="ss"&gt;errors:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="ss"&gt;max_entries:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;max_file_size:&lt;/span&gt; &lt;span class="mi"&gt;8000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="ss"&gt;:image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;progress_event:&lt;/span&gt; &lt;span class="c1"&gt;#Function&amp;lt;1.71870957/3 ...&amp;gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;ref:&lt;/span&gt; &lt;span class="s2"&gt;"phx-FlZ_j-hPIdCQuQGG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that it contains the configuration options we passed to &lt;code&gt;allow_upload/3&lt;/code&gt;: the accepted file types list and file formats.&lt;/p&gt;

&lt;p&gt;It also has an attribute called &lt;code&gt;:entries&lt;/code&gt;, which points to an empty list. When a user uploads a file for the &lt;code&gt;:image&lt;/code&gt; form field, LiveView will automatically update this list with the file upload entry as it completes.&lt;/p&gt;

&lt;p&gt;Similarly, the &lt;code&gt;:errors&lt;/code&gt; list starts out empty and is automatically populated by LiveView with any errors from an invalid file upload entry.&lt;/p&gt;

&lt;p&gt;In this way, the LiveView framework does the work of performing the file upload and tracking its state for you. We'll see both of these attributes in action in a bit.&lt;/p&gt;

&lt;p&gt;Now that we've allowed uploads in our component, we're ready to update the template with the file upload form field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render the File Upload Field
&lt;/h2&gt;

&lt;p&gt;You'll use the &lt;code&gt;Phoenix.LiveView.Helpers.live_file_input/2&lt;/code&gt; function to generate the HTML for a file upload form field. Here's a look at our form component template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form_for&lt;/span&gt; &lt;span class="nv"&gt;@changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="s2"&gt;"product-form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;phx_target:&lt;/span&gt; &lt;span class="nv"&gt;@myself&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;phx_change:&lt;/span&gt; &lt;span class="s2"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;phx_submit:&lt;/span&gt; &lt;span class="s2"&gt;"save"&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;text_input&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;error_tag&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;text_input&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;error_tag&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:unit_price&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;number_input&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:unit_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;step:&lt;/span&gt; &lt;span class="s2"&gt;"any"&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;error_tag&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:unit_price&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sku&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;number_input&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sku&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;error_tag&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sku&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;%&lt;/span&gt; &lt;span class="c1"&gt;# File upload fields here: %&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:image&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;live_file_input&lt;/span&gt; &lt;span class="nv"&gt;@uploads&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;submit&lt;/span&gt; &lt;span class="s2"&gt;"Save"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;phx_disable_with:&lt;/span&gt; &lt;span class="s2"&gt;"Saving..."&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the use of &lt;code&gt;live_file_input/2&lt;/code&gt; with an argument of &lt;code&gt;@uploads.image&lt;/code&gt;. Remember, &lt;code&gt;socket.assigns&lt;/code&gt; has a map of uploads. Here, we provide &lt;code&gt;@uploads.image&lt;/code&gt; to &lt;code&gt;live_file_input/2&lt;/code&gt; to create a form field with the right configuration and tie that form field to the correct part of socket state. This means that LiveView will update &lt;code&gt;socket.assigns.uploads.image&lt;/code&gt; with any new entries or errors that occur when a user uploads a file via this form input.&lt;/p&gt;

&lt;p&gt;The live view can present upload progress by displaying data from &lt;code&gt;@uploads.image.entries&lt;/code&gt; and &lt;code&gt;@uploads.image.errors&lt;/code&gt;. LiveView will handle all of the details of uploading the file and updating socket assigns &lt;code&gt;@uploads.image&lt;/code&gt; entries and errors for us. All we have to do is render the data that is stored in the socket. We'll take that on soon.&lt;/p&gt;

&lt;p&gt;Now, we should be able to see the file upload field displayed in the browser like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t3CT9pQp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2021-10/file-upload-field.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t3CT9pQp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2021-10/file-upload-field.png" alt="file upload field"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you inspect the element, you'll see that the &lt;code&gt;live_file_input/2&lt;/code&gt; function generated the appropriate HTML:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--98hZ1LHx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2021-10/file-upload-button.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--98hZ1LHx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2021-10/file-upload-button.png" alt="file upload button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--99rQf-CL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2021-10/file-upload-html.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--99rQf-CL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2021-10/file-upload-html.png" alt="file upload html"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see that the generated HTML has the &lt;code&gt;accept=".jpg,.jpeg,.png"&lt;/code&gt; attribute set, thanks to the options we passed to &lt;code&gt;allow_upload/3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;LiveView also supports drag-and-drop file uploads. All we have to do is add an element to the page with the &lt;code&gt;phx-drop-target&lt;/code&gt; attribute, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form_for&lt;/span&gt; &lt;span class="nv"&gt;@changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="s2"&gt;"product-form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;phx_target:&lt;/span&gt; &lt;span class="nv"&gt;@myself&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;phx_change:&lt;/span&gt; &lt;span class="s2"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;phx_submit:&lt;/span&gt; &lt;span class="s2"&gt;"save"&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;phx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;%= @uploads.image.ref %&amp;gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;live_file_input&lt;/span&gt; &lt;span class="nv"&gt;@uploads&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We give the attribute a value of &lt;code&gt;@uploads.image.ref&lt;/code&gt;. This socket assignment is the ID that LiveView JavaScript uses to identify the file upload form field and tie it to the correct key in &lt;code&gt;socket.assigns.uploads&lt;/code&gt;. So now, if a user clicks the "choose file" button &lt;em&gt;or&lt;/em&gt; drags-and-drops a file into the area of this &lt;code&gt;div&lt;/code&gt;, LiveView will store the file info in the &lt;code&gt;socket.assigns.uploads&lt;/code&gt; assignment, under the name of the specified upload, in that upload's &lt;code&gt;:entries&lt;/code&gt; list.&lt;/p&gt;

&lt;p&gt;As with other form interactions, LiveView automatically handles the client/server communication. When the user submits the form, LiveView's JavaScript will first upload the file(s) and then invoke the &lt;code&gt;handle_event/3&lt;/code&gt; callback for the form's &lt;code&gt;phx-submit&lt;/code&gt; event. To process the file upload, this event handler will need to consume the file upload stored in &lt;code&gt;socket.assigns.uploads.image.entries&lt;/code&gt;. Let's do that now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consume Uploaded Entries
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;handle_event/3&lt;/code&gt; function for the &lt;code&gt;phx_submit: "save"&lt;/code&gt; form event will use LiveView's &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#consume_uploaded_entry/3"&gt;&lt;code&gt;consume_uploaded_entry/3&lt;/code&gt;&lt;/a&gt; function to process the uploaded file. For now, we'll have our function write the uploaded file to our app's static assets in &lt;code&gt;priv/static/images&lt;/code&gt;. This is so that we can display it on the product show template later on.&lt;/p&gt;

&lt;p&gt;Here's our code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ProductLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;FormComponent&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"save"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;consume_uploaded_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;path:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;_entry&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"priv/static/uploads"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cp!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="no"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;static_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/uploads/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;save_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:image_upload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;socket&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;push_redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;to:&lt;/span&gt; &lt;span class="no"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_show_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We save the image to static assets and return the file path from &lt;code&gt;consume_uploaded_entry/3&lt;/code&gt;. Then, we call a helper function — &lt;code&gt;save_product/1&lt;/code&gt; (not pictured here) — to update the product with the given form params, including the new &lt;code&gt;:image_upload&lt;/code&gt; attribute set to our new file path. Finally, we redirect to the Product Show page.&lt;/p&gt;

&lt;p&gt;To see our code in action, let's add some markup to the product show template to display image uploads. Then, we'll try out our feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Display Image Uploads
&lt;/h3&gt;

&lt;p&gt;Open up &lt;code&gt;lib/arcade_web/live/product_live/show.html.leex&lt;/code&gt; and add the following markup to display the uploaded image or a fallback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"column"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;
      &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"product image"&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;
      &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;%=Routes.static_path(
              @socket,
              @product.image_upload || "&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;thumbnail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="s2"&gt;")%&amp;gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;!&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect. Now, we can test drive it. Visit &lt;code&gt;/products/1/edit&lt;/code&gt; and upload a file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iOvXDup8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2021-10/edit-product-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iOvXDup8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2021-10/edit-product-image.png" alt="edit product image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you submit the form, you'll see the show page render the newly uploaded image, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GgrFgR7n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2021-10/product-show-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GgrFgR7n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2021-10/product-show-image.png" alt="product show image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We did it! The LiveView framework handled all of the client/server communication details that make the page interactive. LiveView performed the file upload for you and made responding to upload events easy and customizable. You only needed to tell the live view which uploads to track and what to do with uploaded files when the form is submitted. Then you added the file upload form field to the page with the view helper and LiveView handled the rest!&lt;/p&gt;

&lt;p&gt;There's one last thing to do. Earlier, I promised &lt;em&gt;reactive&lt;/em&gt; file uploads that share feedback with the user. Let's take a look at this now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Display Upload Feedback in Phoenix LiveView Forms
&lt;/h2&gt;

&lt;p&gt;We know that LiveView automatically updates the &lt;code&gt;:entries&lt;/code&gt; and &lt;code&gt;:errors&lt;/code&gt; lists in the uploads config portion of &lt;code&gt;socket.assigns&lt;/code&gt; once the upload begins. Let's display this information in the template to give the user real-time progress tracking. The code is amazingly simple. We'll iterate over &lt;code&gt;@uploads.image.entries&lt;/code&gt; to display the progress for each entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nv"&gt;@uploads&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"100"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;%&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we use the &lt;a href="https://www.w3schools.com/tags/tag_progress.asp"&gt;HTML progress tag&lt;/a&gt; to create a simple progress bar that is populated with the progress of our file upload in real-time. As LiveView's JavaScript is uploading the file for you, LiveView is updating the value of the entry's progress in socket assigns. This causes the relevant portion of the template to re-render, thereby showing the updated progress bar in real-time. LiveView handles the work of tracking the changes to the image entry's progress. All we have to do is display it.&lt;/p&gt;

&lt;p&gt;You can use a similar approach to iterate over and display any errors stored in &lt;code&gt;@uploads.image.errors&lt;/code&gt;. You won't have to do any work to validate files and populate errors. LiveView handles those details. All you need to do is display any errors based on the needs of your user interface. Here's a look at the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;upload_errors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@uploads&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"alert alert-danger"&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;friendly_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we use the &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#upload_errors/2"&gt;&lt;code&gt;Phoenix.LiveView.Helpers.upload_errors/2&lt;/code&gt;&lt;/a&gt; function to return the errors for the specified upload.&lt;/p&gt;

&lt;p&gt;The error messages aren't very user-friendly, though. So, we'll implement a helper function, &lt;code&gt;friendly_error/1&lt;/code&gt;, in our LiveView component that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ProductLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;FormComponent&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;error_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:too_large&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Image too large"&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;error_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:too_many_files&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Too many files"&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;error_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:not_accepted&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Unacceptable file type"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's more that LiveView file uploads can do. LiveView makes it easy to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cancel an upload&lt;/li&gt;
&lt;li&gt;upload multiple files for a given upload config&lt;/li&gt;
&lt;li&gt;upload files directly from the client to a cloud provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and more.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://hexdocs.pm/phoenix_live_view/uploads.html#content"&gt;LiveView file upload documentation&lt;/a&gt; for details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up: Build Complex Forms Easily with Phoenix LiveView
&lt;/h2&gt;

&lt;p&gt;LiveView enables reactive file uploads right out of the box. Without writing any JavaScript, or even any custom HTML, you can build interactive file upload forms directly into your live view.&lt;/p&gt;

&lt;p&gt;LiveView handles the details of client/server communication and upload state management, leaving you on the hook to write a very small amount of custom code specifying how your uploads should behave and how uploaded files should be saved.&lt;/p&gt;

&lt;p&gt;This is a pattern you'll see again and again in LiveView — the framework handles the communication and state management details of our SPA, and we can focus on writing application-specific code to support our features.&lt;/p&gt;

&lt;p&gt;Now that you've had a glimpse of what LiveView can do with form uploads, you're ready to build complex, interactive forms that support real-time uploads in the wild. Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/category/elixir-alchemy.html#elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sophie is a Senior Engineer at GitHub, co-author of Programming Phoenix LiveView, and co-host of the BeamRad.io podcast. She has a passion for coding education. Historically, she is a cat person but will admit to owning a dog. You can &lt;a href="https://twitter.com/sm_debenedetto"&gt;find her on Twitter&lt;/a&gt; or &lt;a href="https://www.thegreatcodeadventure.com/"&gt;check out her blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Real-Time Form Validation with Phoenix LiveView</title>
      <dc:creator>Sophie DeBenedetto</dc:creator>
      <pubDate>Tue, 05 Oct 2021 11:44:01 +0000</pubDate>
      <link>https://dev.to/appsignal/real-time-form-validation-with-phoenix-liveview-2laj</link>
      <guid>https://dev.to/appsignal/real-time-form-validation-with-phoenix-liveview-2laj</guid>
      <description>&lt;p&gt;LiveView is a compelling choice for building modern web apps. Built on top of Elixir's OTP tooling and leveraging WebSockets, it offers super-fast real-time, interactive features alongside impressive developer productivity.&lt;/p&gt;

&lt;p&gt;LiveView keeps the developer's mind firmly rooted on the server-side, even when testing and debugging. This can empower you to deliver interactive features in single-page apps faster than ever before. Some of the most common interactions on the web are form validation and submission. These days, users expect to see form feedback presented to them in real-time, and LiveView offers first-class support for exactly that.&lt;/p&gt;

&lt;p&gt;In this post, I'll show you how to build LiveView forms that validate changes and provide feedback to the user in real-time. Along the way, you'll learn how to model change in your Phoenix application with schemaless changesets and compose LiveView code to handle that change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Feature: Adding a Form to a Phoenix LiveView App
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;The form examples we'll be looking at in this post are inspired by the "Forms and Changesets" chapter in my book, &lt;a href="https://pragprog.com/titles/liveview/programming-phoenix-liveview/" rel="noopener noreferrer"&gt;Programming LiveView&lt;/a&gt;, co-authored with Bruce Tate. Check it out for an even deeper dive into LiveView testing and so much more.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before we dive into writing any actual code, let's talk about the feature we'll build. Imagine that you're responsible for a Phoenix web app, Arcade, that provides in-browser games to users. A user can log in, select a game to play, and even invite friends to play games with them.&lt;/p&gt;

&lt;p&gt;We'll focus on that last piece of functionality. A user can invite a friend to play a game with them by filling out a form with the friend's email address. This will email a link to the recipient's email address so they can join the game.&lt;/p&gt;

&lt;p&gt;Our form will need to implement some validation — namely, to ensure that a valid email address is provided. It should show any validation errors, as well as the results of a successful form submission in real-time. We won't worry too much about the exact details of sending emails for now. Instead, we'll keep our focus on the form validations in LiveView.&lt;/p&gt;

&lt;p&gt;We'll begin where you'll almost always want to start when building a new feature in a Phoenix application — in the application core. We'll model game invitations in their module. You'll build a boundary layer in a Phoenix context module that we'll call on in LiveView later to validate form input and send invitation emails.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Model Change in Phoenix with Ecto Changesets
&lt;/h2&gt;

&lt;p&gt;We're almost ready to start writing code. But first, let's think about the role that forms and changesets play in our Phoenix application.&lt;/p&gt;

&lt;p&gt;Consider &lt;a href="https://hexdocs.pm/ecto/Ecto.Changeset.html" rel="noopener noreferrer"&gt;Ecto changesets&lt;/a&gt;. Changesets are policies for changing data, and they:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Cast unstructured user data&lt;/em&gt; into a known, structured form — most commonly, an Ecto database schema, ensuring data &lt;em&gt;safety&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Capture differences&lt;/em&gt; between safe, consistent data and a proposed change, allowing for &lt;em&gt;efficiency&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Validate data&lt;/em&gt; using known consistent rules, ensuring data &lt;em&gt;consistency&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Provide a &lt;em&gt;contract&lt;/em&gt; for communicating error states and valid states, ensuring a &lt;em&gt;common interface for change&lt;/em&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;game&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:unit_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sku&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate_required&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:unit_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sku&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;unique_constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;changeset/2&lt;/code&gt; function captures differences between the structured &lt;code&gt;game&lt;/code&gt; and the unstructured &lt;code&gt;attrs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, with &lt;code&gt;cast/4&lt;/code&gt;, the changeset trims the attributes to a known field list and converts them to the correct types. This ensures safety by guaranteeing that you won't let any unknown or invalid attributes into your database.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;code&gt;validate/2&lt;/code&gt; and &lt;code&gt;unique_constraint/2&lt;/code&gt; functions validate the inbound data, ensuring consistency.&lt;/p&gt;

&lt;p&gt;The result is a data structure with known states and error message formats, ensuring interface compatibility.&lt;/p&gt;

&lt;p&gt;Consequently, any forms that use this changeset know exactly how to behave — validating form input and presenting errors in accordance with the changeset's rules.&lt;/p&gt;

&lt;p&gt;In this post, we're going to shift off of this well-known path of generated changesets. You'll see just how versatile changesets can be when it comes to modeling changes to data, with or without a database. You'll build a custom, schemaless changeset for data that &lt;em&gt;isn't&lt;/em&gt; backed by a database table, and you'll use that changeset in a form within a live view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schemaless Changesets in the Phoenix Application Core
&lt;/h2&gt;

&lt;p&gt;You've likely used changesets to model changes to data that is persisted in your database. Our game invitation feature doesn't require database persistence, however. A user will provide the email address of the friend they'd like to invite, and our application will simply send out the email with the link to the game. We don't need to store that invitee's data at this point.&lt;/p&gt;

&lt;p&gt;Luckily, we can use schemaless changesets to model data that &lt;em&gt;doesn't&lt;/em&gt; get saved to the database. A schemaless changeset is based on a simple Elixir map or struct, rather than an Ecto schema-backed struct. The only difference is that, when working with a plain Elixir struct, we need to provide the changeset with the type of information that Elixir would normally handle. We'll see this in action in a moment.&lt;/p&gt;

&lt;p&gt;First, let's define the core module that we'll use to model a game Recipient. Create a new file, &lt;code&gt;arcade/lib/game/Recipient.ex&lt;/code&gt;, and define the module like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Invite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Recipient&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defstruct&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our module is simple so far. It implements a struct with two keys, &lt;code&gt;:name&lt;/code&gt; and &lt;code&gt;:email&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next up, we need to give our module awareness of the types that will be considered valid by any changeset we create. Let's use a module attribute to store this map of types so that we can access it later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Invite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Recipient&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defstruct&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nv"&gt;@types&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;game_name:&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email:&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we'll alias the module and import &lt;code&gt;Ecto.Changeset&lt;/code&gt; so that we can use the changeset functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Invite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Recipient&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defstruct&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nv"&gt;@types&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;game_name:&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email:&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Invite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Recipient&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we're ready to define the &lt;code&gt;changeset/2&lt;/code&gt; function that will be responsible for casting recipient data into a changeset and validating it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Invite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Recipient&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Recipient&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;invitation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;invitation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;@types&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@types&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate_required&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:game_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;~r/@/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We validate the presence of the &lt;code&gt;:game_name&lt;/code&gt; and &lt;code&gt;:email&lt;/code&gt; attributes, and then validate the format of &lt;code&gt;:email&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, we can create recipient changesets like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Invite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Recipient&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Recipient&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Recipient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;email:&lt;/span&gt; &lt;span class="s2"&gt;"juniper@email.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;game_name:&lt;/span&gt; &lt;span class="s2"&gt;"Chess"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;#Ecto.Changeset&amp;lt;...valid?: true&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see what happens if we try to create a changeset with an attribute of an invalid type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Recipient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;email:&lt;/span&gt; &lt;span class="s2"&gt;"juniper@email.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;game_name:&lt;/span&gt; &lt;span class="mi"&gt;1234&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;#Ecto.Changeset&amp;lt;errors: [game_name: {"is invalid", ...]}],valid?: false&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Ecto.Changeset.cast/4&lt;/code&gt; relies on &lt;code&gt;@types&lt;/code&gt; to identify the invalid type and provide a descriptive error.&lt;/p&gt;

&lt;p&gt;Next, try a changeset that breaks one of the custom validation rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Recipient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;email:&lt;/span&gt; &lt;span class="s2"&gt;"juniper's email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;game_name:&lt;/span&gt; &lt;span class="s2"&gt;"Chess"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;#Ecto.Changeset&amp;lt;changes: %{email: "juniper's email", ...},&lt;/span&gt;
  &lt;span class="ss"&gt;errors:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;email:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"has invalid format"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="ss"&gt;valid?:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function successfully captures our change policy in code, and the returned changeset tells the user exactly what is wrong.&lt;/p&gt;

&lt;p&gt;Now that our changeset is up and running, let's quickly build out an &lt;code&gt;Invite&lt;/code&gt; context that will present the interface for interacting with the changeset.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Boundary Layer in Elixir
&lt;/h2&gt;

&lt;p&gt;Create a file, &lt;code&gt;lib/arcade/invite.ex&lt;/code&gt; and add in the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Invite&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Invite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Recipient&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;change_invitation&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Recipient&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="p"&gt;%{})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Recipient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;send_invite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# send email to promo recipient&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This context is a beautifully concise boundary for our service. The &lt;code&gt;change_invitation/2&lt;/code&gt; function returns a recipient changeset, and &lt;code&gt;send_invite/2&lt;/code&gt; is a placeholder for sending a game invite email.&lt;/p&gt;

&lt;p&gt;Other than the internal tweaks we made inside &lt;code&gt;Recipient.changeset/2&lt;/code&gt;, building a Phoenix context module with a schemaless changeset looks identical to building an Ecto-backed one. When all is said and done, in the view layer, schemaless changesets and schema-backed ones will look identical.&lt;/p&gt;

&lt;p&gt;Let's turn our attention to the view layer now and build out our live view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build and Define the Live View
&lt;/h2&gt;

&lt;p&gt;This live view will have the feel of a typical live view with a form. First, we'll create a simple route and wire it to a live view. Next, we'll use our &lt;code&gt;Invite&lt;/code&gt; context to produce a schemaless changeset, and add it to the socket within a &lt;code&gt;mount/3&lt;/code&gt; function. We'll render a form with this changeset and apply changes to the changeset by handling events from the form.&lt;/p&gt;

&lt;p&gt;Let's get going!&lt;/p&gt;

&lt;p&gt;Create a file, &lt;code&gt;lib/arcade_web/live/invite_live.ex&lt;/code&gt; and fill in the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;InviteLive&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_view&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Invite&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Arcade&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Invite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Recipient&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We pull in the LiveView behavior, alias our modules for later use and implement a simple &lt;code&gt;mount/3&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Let's use an implicit &lt;code&gt;render/1&lt;/code&gt;. Create a template file in &lt;code&gt;lib/arcade_web/live/invite_live.html.leex&lt;/code&gt;, starting with some simple markup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Invite a Friend to Play!&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h4&amp;gt;&lt;/span&gt;
  Enter the name of a game and your friend's email below and we'll send them an
  invite to join you in playing a game!
&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's define a live route and fire up the server. In the router, add the following route behind authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ArcadeWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pipe_through&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:require_authenticated_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/invite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;InviteLive&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, you should be able to fire up the Phoenix server, point your browser at &lt;code&gt;/invite&lt;/code&gt; and see your live view render the invitation template:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2021-09%2Fform-validations-simple-text.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2021-09%2Fform-validations-simple-text.png" alt="invite page intro text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the live view is up and running, we're ready to build out the form for an invitation recipient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Render the Phoenix LiveView Form
&lt;/h3&gt;

&lt;p&gt;We'll use &lt;code&gt;mount/3&lt;/code&gt; to store a recipient struct and a changeset in the socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_recipient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign_changeset&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;assign_recipient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;socket&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Recipient&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;assign_changeset&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;recipient:&lt;/span&gt; &lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;socket&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Invite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change_recipient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;mount/3&lt;/code&gt; function uses two helper functions, &lt;code&gt;assign_recipient/1&lt;/code&gt; and &lt;code&gt;assign_changeset/1&lt;/code&gt;, to add a recipient struct and a changeset for that recipient to socket assigns. These pure, single-purpose reducer functions are reusable building blocks for managing the live view's state.&lt;/p&gt;

&lt;p&gt;Remarkably, the schemaless changeset can be used in our form exactly like database-backed ones. We'll use &lt;code&gt;socket.assigns.changeset&lt;/code&gt; in the template's form, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form_for&lt;/span&gt; &lt;span class="nv"&gt;@changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="s2"&gt;"invite-form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;phx_change:&lt;/span&gt; &lt;span class="s2"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;phx_submit:&lt;/span&gt; &lt;span class="s2"&gt;"save"&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:game_name&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;text_input&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:game_name&lt;/span&gt;  &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;error_tag&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:game_name&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;text_input&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;error_tag&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;submit&lt;/span&gt; &lt;span class="s2"&gt;"Send Invite"&lt;/span&gt;&lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you point your browser at &lt;code&gt;/invite&lt;/code&gt;, you should see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2021-09%2Fform-validations-forms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2021-09%2Fform-validations-forms.png" alt="invite form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our form implements two LiveView bindings, &lt;code&gt;phx-change&lt;/code&gt;, and &lt;code&gt;phx-submit&lt;/code&gt;. Let's dig into these events now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handle Form Events in LiveView
&lt;/h3&gt;

&lt;p&gt;We'll start with a look at the &lt;code&gt;phx-change&lt;/code&gt; event. LiveView will send a &lt;code&gt;"validate"&lt;/code&gt; event each time the form changes and include the form params in the event metadata.&lt;/p&gt;

&lt;p&gt;So, we'll implement a &lt;code&gt;handle_event/3&lt;/code&gt; function for this event that builds a new changeset from the params and adds it to the socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/arcade_web/live/invite_live.ex&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"recipient"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;recipient_params&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;recipient:&lt;/span&gt; &lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;changeset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;recipient&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Invite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change_recipient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipient_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:validate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this down. The &lt;code&gt;Invite.change_recipient/2&lt;/code&gt; context function creates a new changeset using the recipient from socket state and the params from the form change event.&lt;/p&gt;

&lt;p&gt;Then, we use &lt;code&gt;Map.put(:action, :validate)&lt;/code&gt; to add the &lt;code&gt;validate&lt;/code&gt; action to the changeset, a signal that instructs Phoenix to display errors. Phoenix will &lt;em&gt;not&lt;/em&gt; display the changeset's errors otherwise.&lt;/p&gt;

&lt;p&gt;When you think about it, this approach makes sense. Not all invalid changesets should show errors on the page. For example, the empty form for the new changeset &lt;em&gt;shouldn't&lt;/em&gt; show any errors because the user hasn't provided any input yet. So, the Phoenix &lt;code&gt;form_for&lt;/code&gt; function needs to be told when to display a changeset's errors. If the changeset's action is empty, then no errors are set on the form object — even if the changeset is invalid and has a non-empty &lt;code&gt;:errors&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;Finally, &lt;code&gt;assigns/2&lt;/code&gt; adds the new changeset to the socket, triggering &lt;code&gt;render/1&lt;/code&gt; and displaying any errors.&lt;/p&gt;

&lt;p&gt;Let's take a look at the form tag that displays those errors on the page. Typically, each field has a label, an input control, and an error tag, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;text_input&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;error_tag&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;error_tag/2&lt;/code&gt; Phoenix view helper function displays the form's errors for a given field on a changeset, when the changeset's action is &lt;code&gt;:validate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, point your browser at &lt;code&gt;/invite&lt;/code&gt; and fill out the form with a game name and an invalid email. As you can see in this image, the UI updates to display the validation errors:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2021-09%2Fform-validations-errors.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2021-09%2Fform-validations-errors.png" alt="form with validation errors"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That was surprisingly easy! We built a simple and powerful live view with a reactive form that displays any errors in real-time.&lt;/p&gt;

&lt;p&gt;The live view calls on the context to create a changeset, renders it in a form, validates it on form change, and then re-renders the template after each form event. We get reactive form validation for free without writing any JavaScript or HTML. We let Ecto changesets handle the data validation rules, and we let the LiveView framework handle the client/server communication for triggering validation events and displaying the results.&lt;/p&gt;

&lt;p&gt;There's just one thing about our form validation that needs some improvement. You'll notice that as soon as you start typing into the email form, an error appears because our validation event fires whenever the form field changes. This doesn't present our users with the best experience — we're telling them there is an error with their input before they get a chance to finish typing their full email.&lt;/p&gt;

&lt;p&gt;Instead, we want the validation event to fire when a user clicks away or blurs from the email input. Luckily for us, LiveView makes it easy to implement this functionality with the help of the &lt;code&gt;phx-debounce&lt;/code&gt; binding. Update your email form field to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;text_input&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;phx_debounce:&lt;/span&gt; &lt;span class="s2"&gt;"blur"&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the &lt;code&gt;"validate"&lt;/code&gt; event will only fire when a user blurs away from the email input field, and we won't show any premature validation error messages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hexdocs.pm/phoenix_live_view/bindings.html#rate-limiting-events-with-debounce-and-throttle" rel="noopener noreferrer"&gt;Learn more about LiveView's support for debouncing and other rate-limiting options&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As you might imagine, the &lt;code&gt;phx-submit&lt;/code&gt; event works very similarly to &lt;code&gt;phx-change&lt;/code&gt;. The &lt;code&gt;"save"&lt;/code&gt; event fires when the user submits the form. To respond to this event, we can implement a &lt;code&gt;handle_event/3&lt;/code&gt; function that uses the (currently empty) context function, &lt;code&gt;Invite.send_invite/2&lt;/code&gt;. The context function should create and validate a changeset.&lt;/p&gt;

&lt;p&gt;If the changeset is, in fact, valid, we can pipe it to some helper function or service that handles the details of sending invitation emails.&lt;/p&gt;

&lt;p&gt;If the changeset is not valid, we can return an error tuple. Then we can update the UI with a success or failure message accordingly. I'll leave you to practice what you're learning by implementing this behavior on your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up: LiveView — A Great Choice to Build and Validate Forms in Elixir
&lt;/h2&gt;

&lt;p&gt;Now you've seen that Ecto changesets are not tightly coupled to the database. Schemaless changesets let you tie backend services to Phoenix forms any time you require validation and security, whether or not your application needs to access a full relational database.&lt;/p&gt;

&lt;p&gt;LiveView supports custom integration of forms to backend code with these schemaless changesets. To do so, you need only provide the &lt;code&gt;Changeset.cast/4&lt;/code&gt; function with the first argument of a two-tuple holding both data &lt;em&gt;and&lt;/em&gt; type information. This type of code is ideal for implementing form scenarios requiring validation but without the typical database backend.&lt;/p&gt;

&lt;p&gt;Whether you're working with schema-backed or schemaless changesets, LiveView provides real-time form validation and feedback, with very little hand-rolled code. We can use LiveView event bindings to handle form validation and submission in real-time with a few simple event handlers that call out to our nice, clean Phoenix context code.&lt;/p&gt;

&lt;p&gt;With that, you have everything you need to build basic forms in LiveView. To dig deeper into LiveView's rich forms offerings, &lt;a href="https://hexdocs.pm/phoenix_live_view/form-bindings.html#content" rel="noopener noreferrer"&gt;check out the docs&lt;/a&gt;. Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/category/elixir-alchemy.html#elixir-alchemy" rel="noopener noreferrer"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sophie is a Senior Engineer at GitHub, co-author of Programming Phoenix LiveView, and co-host of the BeamRad.io podcast. She has a passion for coding education. Historically, she is a cat person but will admit to owning a dog. You can &lt;a href="https://twitter.com/sm_debenedetto" rel="noopener noreferrer"&gt;find her on Twitter&lt;/a&gt; or &lt;a href="https://www.thegreatcodeadventure.com/" rel="noopener noreferrer"&gt;check out her blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
  </channel>
</rss>
