<?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: Lubien</title>
    <description>The latest articles on DEV Community by Lubien (@lubien).</description>
    <link>https://dev.to/lubien</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%2F186229%2Fa38baf39-8987-4672-a4b4-1f24ec4e7761.png</url>
      <title>DEV Community: Lubien</title>
      <link>https://dev.to/lubien</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lubien"/>
    <language>en</language>
    <item>
      <title>Free Beginner Friendly LiveView Course in English and Portuguese</title>
      <dc:creator>Lubien</dc:creator>
      <pubDate>Tue, 16 Apr 2024 09:40:40 +0000</pubDate>
      <link>https://dev.to/lubien/free-beginner-friendly-liveview-course-in-english-and-portuguese-3n8o</link>
      <guid>https://dev.to/lubien/free-beginner-friendly-liveview-course-in-english-and-portuguese-3n8o</guid>
      <description>&lt;p&gt;&lt;a href="https://adopt-liveview.lubien.dev"&gt;https://adopt-liveview.lubien.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've been working on writing beginner friendly lessons aimed at teaching LiveView, Phoenix and Elixir to those who might not have even prior Elixir experience. &lt;/p&gt;

&lt;p&gt;I've previously helped many folks joining in on this framework so I've wrote it in a hand-holding way for newcomers to feel safe when first learning things. It's a bottom-up course so most of time I'm teaching how things are built before diving in on the niceties of generators.&lt;/p&gt;

&lt;p&gt;This project is also open source and built with LiveView too:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/adopt-liveview/adopt-liveview"&gt;https://github.com/adopt-liveview/adopt-liveview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Huge thanks to &lt;a href="https://ultimatemercer.com/"&gt;https://ultimatemercer.com/&lt;/a&gt; (open to work) and &lt;a href="https://tanukesensei.github.io/"&gt;https://tanukesensei.github.io/&lt;/a&gt; (open to work) who helped me building this so I could focus on writing content!&lt;/p&gt;




&lt;p&gt;Sidenote: if you're into Portuguese LiveView content I've been also posting videos in 🇧🇷 at my youtube channel from time to time: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/@lubiendev"&gt;https://www.youtube.com/@lubiendev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
      <category>liveview</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Listing matches for users</title>
      <dc:creator>Lubien</dc:creator>
      <pubDate>Mon, 05 Feb 2024 19:37:11 +0000</pubDate>
      <link>https://dev.to/lubien/listing-matches-for-users-257c</link>
      <guid>https://dev.to/lubien/listing-matches-for-users-257c</guid>
      <description>&lt;p&gt;Last post we had to use IEx to see matches being inserted on the database. This time we are going to do a little bit of frontend and at the same time learn a bit more about Ecto and LiveView streams. Our goal is to make the user page list all their matches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewing some Ecto concepts
&lt;/h2&gt;

&lt;p&gt;First of all, we need a method that lists matches for a certain user. Since we established last time that the &lt;code&gt;Ranking&lt;/code&gt; module will handle those, let's go 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="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
Returns the list of matches a certain user participated.

## Examples

    iex&amp;gt; list_matches_for_user(1)
    [%Match{}, ...]

"""&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;list_matches_for_user&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="no"&gt;Match&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;or_where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_a_id:&lt;/span&gt; &lt;span class="o"&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="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;or_where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_b_id:&lt;/span&gt; &lt;span class="o"&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="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;desc:&lt;/span&gt; &lt;span class="ss"&gt;:id&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;Repo&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simple Ecto query that starts as broad as "list me all the matches" but uses &lt;a href="https://hexdocs.pm/ecto/Ecto.Query.html#or_where/3"&gt;or_where/3&lt;/a&gt; to list only when either  &lt;code&gt;user_a_id&lt;/code&gt; or &lt;code&gt;user_b_id&lt;/code&gt; is equal to &lt;code&gt;user_id&lt;/code&gt;. Not only that we use &lt;a href="https://hexdocs.pm/ecto/Ecto.Query.html#order_by/3"&gt;order_by/3&lt;/a&gt; to sort descendingly so the latest matches can shine first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to the tables
&lt;/h2&gt;

&lt;p&gt;Back when we needed to show a list of users we opted to use the &lt;code&gt;&amp;lt;.table&amp;gt;&lt;/code&gt; component. We will do so again. Go to &lt;code&gt;user_live/show.ex&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;defmodule ChampionsWeb.UserLive.Show do
&lt;/span&gt;  # some stuff here
&lt;span class="err"&gt;
&lt;/span&gt;  @impl true
  def handle_params(%{"id" =&amp;gt; id}, _session, socket) do
    user = Accounts.get_user!(id)
&lt;span class="gi"&gt;+   matches = Ranking.list_matches_for_user(user.id)
+
&lt;/span&gt;    {:noreply,
     socket
     |&amp;gt; assign(:page_title, "Showing user #{user.email}")
&lt;span class="gd"&gt;-    |&amp;gt; assign(:user, user)}
&lt;/span&gt;&lt;span class="gi"&gt;+    |&amp;gt; assign(:user, user)
+    |&amp;gt; stream(:matches, matches)
+   }
&lt;/span&gt;  end
&lt;span class="err"&gt;
&lt;/span&gt;  # some stuff here
  @impl true
  def render(assigns) do
    ~H"""
    &amp;lt;.header&amp;gt;
      User &amp;lt;%= @user.id %&amp;gt;
      &amp;lt;:subtitle&amp;gt;This is a player on this app.&amp;lt;/:subtitle&amp;gt;
    &amp;lt;/.header&amp;gt;
&lt;span class="err"&gt;
&lt;/span&gt;    &amp;lt;.list&amp;gt;
      &amp;lt;:item title="Email"&amp;gt;&amp;lt;%= @user.email %&amp;gt;&amp;lt;/:item&amp;gt;
      &amp;lt;:item title="Points"&amp;gt;&amp;lt;span data-points&amp;gt;&amp;lt;%= @user.points %&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/:item&amp;gt;
    &amp;lt;/.list&amp;gt;
&lt;span class="err"&gt;
&lt;/span&gt;    &amp;lt;div :if={@current_user &amp;amp;&amp;amp; @current_user.id != @user.id} class="my-4"&amp;gt;
      &amp;lt;.button type="button" phx-click="concede_loss"&amp;gt;I lost to this person&amp;lt;/.button&amp;gt;
      &amp;lt;.button type="button" phx-click="concede_draw"&amp;gt;Declare draw match&amp;lt;/.button&amp;gt;
    &amp;lt;/div&amp;gt;
&lt;span class="gi"&gt;+
+   &amp;lt;.table
+     id="matches"
+     rows={@streams.matches}
+   &amp;gt;
+     &amp;lt;:col :let={{_id, match}} label="User A"&amp;gt;&amp;lt;%= match.user_a_id %&amp;gt;&amp;lt;/:col&amp;gt;
+     &amp;lt;:col :let={{_id, match}} label="User B"&amp;gt;&amp;lt;%= match.user_b_id %&amp;gt;&amp;lt;/:col&amp;gt;
+     &amp;lt;:col :let={{_id, match}} label="Points"&amp;gt;&amp;lt;%= match.result %&amp;gt;&amp;lt;/:col&amp;gt;
+   &amp;lt;/.table&amp;gt;
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &amp;lt;.back navigate={~p"/users"}&amp;gt;Back to users&amp;lt;/.back&amp;gt;
    """
  end
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now there you go, another boring table! Wait, it's even more boring because it doesn't even show user names! Let's fix that. I'd like to take this opportunity to teach you how &lt;strong&gt;NOT&lt;/strong&gt; to do this and after show you a good way of doing this.&lt;/p&gt;

&lt;h2&gt;
  
  
  How NOT to load nested information on Phoenix LiveView
&lt;/h2&gt;

&lt;p&gt;We could just use &lt;code&gt;Accounts.get_user!&lt;/code&gt; inside our render function and just get users when rendering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&amp;lt;.table
  id="matches"
  rows={@streams.matches}
&lt;span class="gi"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="gd"&gt;- &amp;lt;:col :let={{_id, match}} label="User A"&amp;gt;&amp;lt;%= match.user_a_id %&amp;gt;&amp;lt;/:col&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+ &amp;lt;:col :let={{_id, match}} label="User A"&amp;gt;&amp;lt;%= Accounts.get_user!(match.user_a_id).email %&amp;gt;&amp;lt;/:col&amp;gt;
&lt;/span&gt;&lt;span class="gd"&gt;- &amp;lt;:col :let={{_id, match}} label="User B"&amp;gt;&amp;lt;%= match.user_b_id %&amp;gt;&amp;lt;/:col&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+ &amp;lt;:col :let={{_id, match}} label="User B"&amp;gt;&amp;lt;%= Accounts.get_user!(match.user_b_id).email %&amp;gt;&amp;lt;/:col&amp;gt;
&lt;/span&gt;  &amp;lt;:col :let={{_id, match}} label="Points"&amp;gt;&amp;lt;%= match.result %&amp;gt;&amp;lt;/:col&amp;gt;
&amp;lt;/.table&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above works but have you ever heard of the &lt;a href="https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping"&gt;N+1 problem&lt;/a&gt;? That's bad. We are basically querying once for the matches then for each match two more queries, one for each user. Here's how my terminal logs appear after rendering that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="err"&gt;↳&lt;/span&gt; &lt;span class="n"&gt;ChampionsWeb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserLive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_params&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="k"&gt;at&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;champions_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;user_live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;show&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&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="n"&gt;QUERY&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"matches"&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;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&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;374&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"user_a_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"user_b_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"inserted_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"updated_at"&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"matches"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"user_a_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;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"user_b_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;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="k"&gt;DESC&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="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="n"&gt;ChampionsWeb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserLive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_params&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="k"&gt;at&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;champions_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;user_live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;show&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&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="n"&gt;Replied&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="n"&gt;ms&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="n"&gt;QUERY&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"users"&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;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&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;372&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"hashed_password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"confirmed_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"points"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"inserted_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"updated_at"&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&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;1&lt;/span&gt;&lt;span class="p"&gt;)&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="err"&gt;↳&lt;/span&gt; &lt;span class="n"&gt;anonymous&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ChampionsWeb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserLive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="o"&gt;/&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;at&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;champions_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;user_live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;show&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;66&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="n"&gt;QUERY&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"users"&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;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="n"&gt;decode&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="mi"&gt;7&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;369&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"hashed_password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"confirmed_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"points"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"inserted_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"updated_at"&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&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;1&lt;/span&gt;&lt;span class="p"&gt;)&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="err"&gt;↳&lt;/span&gt; &lt;span class="n"&gt;anonymous&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ChampionsWeb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserLive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="o"&gt;/&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;at&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;champions_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;user_live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;show&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;67&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="n"&gt;QUERY&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"users"&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;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&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;365&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"hashed_password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"confirmed_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"points"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"inserted_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"updated_at"&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;u0&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&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;1&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add fuel to the problem, every single time our render function is called we would run those queries again. So if something triggers an update in the template, say the points changed, we'd run a ton of load on our database. Don't do queries inside your HEEx!&lt;/p&gt;

&lt;h2&gt;
  
  
  How to do load nested information on &lt;del&gt;Phoenix LiveView&lt;/del&gt; Ecto
&lt;/h2&gt;

&lt;p&gt;Ecto, just like any other good ORM, already knows that models can have relations to each other. We can teach it that a &lt;code&gt;Match&lt;/code&gt; model belongs to each &lt;code&gt;User&lt;/code&gt; model by simply changing the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;schema "matches" do
&lt;/span&gt;  field :result, Ecto.Enum, values: [:winner_a, :winner_b, :draw]
&lt;span class="gd"&gt;- field :user_a_id, :integer
&lt;/span&gt;&lt;span class="gi"&gt;+ belongs_to :user_a, Champions.Accounts.User
&lt;/span&gt;&lt;span class="gd"&gt;- field :user_b_id, :integer
&lt;/span&gt;&lt;span class="gi"&gt;+ belongs_to :user_b, Champions.Accounts.User
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;  timestamps()
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Implicetely when you use Ecto's &lt;a href="https://hexdocs.pm/ecto/Ecto.Schema.html#belongs_to/3"&gt;belongs_to/3&lt;/a&gt; you get two fields in your model. If you say &lt;code&gt;belongs_to: :user_a, Champions.Accounts.User&lt;/code&gt; a &lt;code&gt;user_a_id&lt;/code&gt; field will be created with &lt;code&gt;:integer&lt;/code&gt; type and also a &lt;code&gt;:user_a&lt;/code&gt; field will be created which will store &lt;code&gt;User&lt;/code&gt; when we preload it. Preloading relations in Ecto is quite easy. Go to your &lt;code&gt;Rankings&lt;/code&gt;  context and add this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;def list_matches_for_user(user_id) do
&lt;/span&gt;  Match
  |&amp;gt; or_where(user_a_id: ^user_id)
  |&amp;gt; or_where(user_b_id: ^user_id)
  |&amp;gt; order_by(desc: :id)
&lt;span class="gi"&gt;+ |&amp;gt; preload([:user_a, :user_b])
&lt;/span&gt;  |&amp;gt; Repo.all()
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now all matches will come with both fields pre-populated. We can use them like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&amp;lt;.table
  id="matches"
  rows={@streams.matches}
&lt;span class="gi"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="gd"&gt;- &amp;lt;:col :let={{_id, match}} label="User A"&amp;gt;&amp;lt;%= match.user_a_id %&amp;gt;&amp;lt;/:col&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+ &amp;lt;:col :let={{_id, match}} label="User A"&amp;gt;&amp;lt;%= match.user_a.email %&amp;gt;&amp;lt;/:col&amp;gt;
&lt;/span&gt;&lt;span class="gd"&gt;- &amp;lt;:col :let={{_id, match}} label="User B"&amp;gt;&amp;lt;%= match.user_b_id %&amp;gt;&amp;lt;/:col&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+ &amp;lt;:col :let={{_id, match}} label="User B"&amp;gt;&amp;lt;%= match.user_b.email %&amp;gt;&amp;lt;/:col&amp;gt;
&lt;/span&gt;  &amp;lt;:col :let={{_id, match}} label="Points"&amp;gt;&amp;lt;%= match.result %&amp;gt;&amp;lt;/:col&amp;gt;
&amp;lt;/.table&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's magic, ladies and gentleman. Our table is slightly less boring. One thing it doesn't do though is add matches as soon as someone clicks on the button. Let's do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning a little bit more about Phoenix LiveView Streams
&lt;/h2&gt;

&lt;p&gt;Whenever we run the functions that concede loss or declare a draw match we receive back the updated users so we can update our UI. We should do the same for the match that was just created so we can put that into our UI. Let's get back to the &lt;code&gt;Ranking&lt;/code&gt; context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  @doc """
  Adds 3 points to the winning user
&lt;span class="err"&gt;
&lt;/span&gt;  ## Examples
&lt;span class="err"&gt;
&lt;/span&gt;      iex&amp;gt; concede_loss_to(%User{points: 0})
&lt;span class="gd"&gt;-     {:ok, %User{points: 3}}
&lt;/span&gt;&lt;span class="gi"&gt;+     {:ok, %User{points: 3}, %Match{}}
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;  """
  def concede_loss_to(loser, winner) do
&lt;span class="gd"&gt;-   {:ok, _match} = create_match(%{
&lt;/span&gt;&lt;span class="gi"&gt;+   {:ok, match} = create_match(%{
&lt;/span&gt;      user_a_id: loser.id,
      user_b_id: winner.id,
      result: :winner_b
    })
&lt;span class="gd"&gt;-   increment_user_points(winner, 3)
&lt;/span&gt;&lt;span class="gi"&gt;+   {:ok, updated_user} = increment_user_points(winner, 3)
+   {:ok, updated_user, Repo.preload(match, [:user_a, :user_b])}
&lt;/span&gt;  end
&lt;span class="err"&gt;
&lt;/span&gt;  @doc """
  Adds 1 point to each user
&lt;span class="err"&gt;
&lt;/span&gt;  ## Examples
&lt;span class="err"&gt;
&lt;/span&gt;      iex&amp;gt; declare_draw_match(%User{points: 0}, %User{points: 0})
&lt;span class="gd"&gt;-     {:ok, %User{points: 1}, %User{points: 1}}
&lt;/span&gt;&lt;span class="gi"&gt;+     {:ok, %User{points: 1}, %User{points: 1}, %Match{}}
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;  """
  def declare_draw_match(user_a, user_b) do
&lt;span class="gd"&gt;-   {:ok, _match} = create_match(%{
&lt;/span&gt;&lt;span class="gi"&gt;+   {:ok, match} = create_match(%{
&lt;/span&gt;      user_a_id: user_a.id,
      user_b_id: user_b.id,
      result: :draw
    })
    {:ok, updated_user_a} = increment_user_points(user_a, 1)
    {:ok, updated_user_b} = increment_user_points(user_b, 1)
&lt;span class="gd"&gt;-   {:ok, updated_user_a, updated_user_b}
&lt;/span&gt;&lt;span class="gi"&gt;+   {:ok, updated_user_a, updated_user_b, Repo.preload(match, [:user_a, :user_b])}
&lt;/span&gt;  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see both functions now also send a %Match{} as the last element on the tuple. Now let's update our usages in the UI. It's very important to notice that we also preloaded the associations after creating the matches so our UI will have that info otherwise it's going to error later on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  def handle_event("concede_loss", _value, %{assigns: %{current_user: current_user, user: user}} = socket) do
&lt;span class="gd"&gt;-   {:ok, updated_user} = Ranking.concede_loss_to(current_user, user)
-   {:noreply, assign(socket, :user, updated_user)}
&lt;/span&gt;&lt;span class="gi"&gt;+   {:ok, updated_user, match} = Ranking.concede_loss_to(current_user, user)
+
+   {:noreply,
+     socket
+     |&amp;gt; assign(:user, updated_user)
+     |&amp;gt; stream_insert(:matches, match, at: 0)
+   }
&lt;/span&gt;  end
&lt;span class="err"&gt;
&lt;/span&gt;  def handle_event("concede_draw", _value, %{assigns: %{current_user: current_user, user: user}} = socket) do
&lt;span class="gd"&gt;-   {:ok, updated_my_user, updated_user} = Ranking.declare_draw_match(current_user, user)
&lt;/span&gt;&lt;span class="gi"&gt;+   {:ok, updated_my_user, updated_user, match} = Ranking.declare_draw_match(current_user, user)
&lt;/span&gt;    {:noreply,
      socket
      |&amp;gt; assign(:user, updated_user)
      |&amp;gt; assign(:current_user, updated_my_user)
&lt;span class="gi"&gt;+     |&amp;gt; stream_insert(:matches, match, at: 0)
&lt;/span&gt;    }
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have a new function here. Meet &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#stream_insert/4"&gt;stream_insert/4&lt;/a&gt;. Remember that whenever we defined tables we used &lt;code&gt;stream(socket, :stream_name, elements)&lt;/code&gt;? Streams are an efficient way of handling large collections of data in LiveView without storing them in memory.&lt;/p&gt;

&lt;p&gt;When the user opens the LiveView we render the matches stream once and leave it at that. Now that we have a new match we use &lt;code&gt;stream_insert&lt;/code&gt; using the same stream name and saying that the position should be 0 so this is effectively a prepend. If you're wondering why we prepend to the table, remember our match table is sorted in descending ID order so new ones should go at the top. Feel free to test it and when you're comfortable we will start fixing some tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing old tests
&lt;/h2&gt;

&lt;p&gt;Right now you should have at least a couple erroring tests. It's fine, we changed things on &lt;code&gt;Ranking&lt;/code&gt; module so let's head to &lt;code&gt;test/champions/ranking_test.exs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  describe "concede_loss_to/2" do
    test "adds 3 points to the winner" do
      loser = user_fixture()
      user = user_fixture()
      assert user.points == 0
&lt;span class="gd"&gt;-     assert {:ok, %User{points: 3}} = Ranking.concede_loss_to(loser, user)
-     match = get_last_match!()
&lt;/span&gt;&lt;span class="gi"&gt;+     assert {:ok, %User{points: 3}, match} = Ranking.concede_loss_to(loser, user)
&lt;/span&gt;      assert match.user_a_id == loser.id
      assert match.user_b_id == user.id
      assert match.result == :winner_b
    end
  end
&lt;span class="err"&gt;
&lt;/span&gt;  describe "declare_draw_match/2" do
    test "adds 1 point to each user" do
      user_a = user_fixture()
      user_b = user_fixture()
      assert user_a.points == 0
      assert user_b.points == 0
&lt;span class="gd"&gt;-     assert {:ok, %User{points: 1}, %User{points: 1}} = Ranking.declare_draw_match(user_a, user_b)
-     match = get_last_match!()
&lt;/span&gt;&lt;span class="gi"&gt;+     assert {:ok, %User{points: 1}, %User{points: 1}, match} = Ranking.declare_draw_match(user_a, user_b)
&lt;/span&gt;      assert match.user_a_id == user_a.id
      assert match.user_b_id == user_b.id
      assert match.result == :draw
    end
  end
&lt;span class="err"&gt;
&lt;/span&gt;# some stuff
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;- def get_last_match!() do
-   Match
-   |&amp;gt; order_by(desc: :id)
-   |&amp;gt; limit(1)
-   |&amp;gt; Repo.one!()
- end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of a sudden our &lt;code&gt;get_last_match!&lt;/code&gt; function is useless! But it was nice since it taught you some Ecto concepts last time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding tests for the UI changes
&lt;/h2&gt;

&lt;p&gt;You LiveView tests are probably not failing right now, but that doesn't mean they have good coverage. Let's add simple tests for the matches. Head out to &lt;code&gt;test/champions_web/live/user_live_test.exs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;describe "Authenticated Show" do
&lt;/span&gt;  setup [:register_and_log_in_user]
&lt;span class="err"&gt;
&lt;/span&gt;  # stuff
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;- test "concede 3 points when I lose to another player", %{conn: conn, user: _user} do
&lt;/span&gt;&lt;span class="gi"&gt;+ test "concede 3 points when I lose to another player", %{conn: conn, user: user} do
&lt;/span&gt;    other_user = user_fixture()
    {:ok, show_live, _html} = live(conn, ~p"/users/#{other_user}")
&lt;span class="err"&gt;
&lt;/span&gt;    assert other_user.points == 0
    assert show_live |&amp;gt; element("button", "I lost to this person") |&amp;gt; render_click()
    assert element(show_live, "span[data-points]") |&amp;gt; render() =~ "3"
&lt;span class="gi"&gt;+   assert element(show_live, "#matches") |&amp;gt; render() =~ user.email
+   assert element(show_live, "#matches") |&amp;gt; render() =~ other_user.email
+   assert element(show_live, "#matches") |&amp;gt; render() =~ "winner_b"
&lt;/span&gt;    assert Accounts.get_user!(other_user.id).points == 3
  end
&lt;span class="err"&gt;
&lt;/span&gt;  test "concede 1 point to each user when there's a draw match", %{conn: conn, user: user} do
    other_user = user_fixture()
    {:ok, show_live, _html} = live(conn, ~p"/users/#{other_user}")
&lt;span class="err"&gt;
&lt;/span&gt;    assert user.points == 0
    assert other_user.points == 0
    assert show_live |&amp;gt; element("button", "Declare draw match") |&amp;gt; render_click()
    assert element(show_live, "span[data-my-points]") |&amp;gt; render() =~ "1"
    assert element(show_live, "span[data-points]") |&amp;gt; render() =~ "1"
&lt;span class="gi"&gt;+   assert element(show_live, "#matches") |&amp;gt; render() =~ user.email
+   assert element(show_live, "#matches") |&amp;gt; render() =~ other_user.email
+   assert element(show_live, "#matches") |&amp;gt; render() =~ "draw"
&lt;/span&gt;    assert Accounts.get_user!(user.id).points == 1
    assert Accounts.get_user!(other_user.id).points == 1
  end
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simple tricky because &lt;code&gt;&amp;lt;.table&amp;gt;&lt;/code&gt;  will use an ID equal to the stream name so in this case &lt;code&gt;#matches&lt;/code&gt; then we just check the user emails and the &lt;code&gt;result&lt;/code&gt; to be &lt;code&gt;winner_b&lt;/code&gt; and &lt;code&gt;draw&lt;/code&gt;. In just a few lines we already added tests to check that our LiveView is reactive. Neat huh?&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Do not do queries on your LiveView, use assigns&lt;/li&gt;
&lt;li&gt;Preload that whenever need and possible&lt;/li&gt;
&lt;li&gt;We learned a bit more about LiveView streams, namely &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#stream_insert/4"&gt;stream_insert/4&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See you next time!&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>liveview</category>
      <category>phoenix</category>
      <category>backend</category>
    </item>
    <item>
      <title>Where are all these points coming from?</title>
      <dc:creator>Lubien</dc:creator>
      <pubDate>Thu, 06 Jul 2023 13:22:18 +0000</pubDate>
      <link>https://dev.to/lubien/where-are-all-these-points-coming-from-758</link>
      <guid>https://dev.to/lubien/where-are-all-these-points-coming-from-758</guid>
      <description>&lt;p&gt;Where's the fun of winning if people can't know who lost to you? With sportsmanship in our best interest, let's start recording players and outcomes for matches and in the meantime, we will learn some interesting stuff from Ecto and LiveView.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storing new stuff means we need a new model
&lt;/h2&gt;

&lt;p&gt;Currently, we have a User model and two other related models that Phoenix created for auth stuff. I just told you we need to store match outcomes so that means we need to create a new model. Remember I told you that to change a model we need a migration? The same goes to create a new one.&lt;/p&gt;

&lt;p&gt;We are going to create a table called &lt;code&gt;matches&lt;/code&gt; that will contain 3 pieces of information: who's player one, who's player two and what's the result of that match. We start that by generating the migration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mix ecto.gen.migration create_matches
* creating priv/repo/migrations/20230702143738_create_matches.exs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to edit that to add the information we want:&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;Champions&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;CreateMatches&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;create&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;:matches&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;:result&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;add&lt;/span&gt; &lt;span class="ss"&gt;:user_a_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on_delete:&lt;/span&gt; &lt;span class="ss"&gt;:delete_all&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:user_b_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on_delete:&lt;/span&gt; &lt;span class="ss"&gt;:delete_all&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:matches&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_a_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:matches&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_b_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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just a quick reminder: the &lt;code&gt;change&lt;/code&gt; method means Ecto knows how to run both the migration and rollback methods for this. Since we are using &lt;code&gt;create table(:matches)&lt;/code&gt;  ecto knows that migrating means &lt;code&gt;create table matches…&lt;/code&gt; and rolling back means &lt;code&gt;drop table matches&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As for the fields, the first one will be called &lt;code&gt;:result&lt;/code&gt; which we will talk more about later.  As for the other fields we have &lt;code&gt;:user_a_id&lt;/code&gt; and &lt;code&gt;:user_b_id&lt;/code&gt; both using the function &lt;a href="https://hexdocs.pm/ecto_sql/Ecto.Migration.html#references/2"&gt;references/2&lt;/a&gt; to link those IDs to the &lt;code&gt;users&lt;/code&gt; table. Let's talk about these.&lt;/p&gt;

&lt;p&gt;First of all the naming choice. I've been calling our users as 'players' sometimes and it definitely would make sense to call those &lt;code&gt;player_a_id&lt;/code&gt; and &lt;code&gt;player_b_id&lt;/code&gt;. I just think it's easier to reason about this code if the field is called &lt;code&gt;user&lt;/code&gt; since the referenced table is called &lt;code&gt;users&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The other thing you might have paid attention to is the keyword &lt;code&gt;on_delete: :delete_all&lt;/code&gt;. That means if that specific user were to be deleted this match will also be deleted. This might sound scary right now but I'm making a decision of making user records not delectable. That would cause issues with things like GDPR but since I won't be risking making my database invalid, when the time comes for the app to need users to be able to delete all their data I'll go for the path of obfuscating all &lt;a href="https://www.dol.gov/general/ppii"&gt;Personal Identifiable Information (PII)&lt;/a&gt; so John Doe would become 'Unknown User ABC'.&lt;/p&gt;

&lt;p&gt;We also add indexes to the foreign keys so it's faster to search for them and trust me we are going to be a lot of listing on those. Don't forget to run &lt;code&gt;mix ecto.migrate&lt;/code&gt;. If you do it's no big issue, Phoenix will show a big error message with a button to run this the next time you go to your app.&lt;/p&gt;

&lt;p&gt;Now there are 3 implicit columns being created there. First of all, when you use &lt;a href="https://hexdocs.pm/ecto_sql/Ecto.Migration.html#table/2"&gt;table/2&lt;/a&gt; on an Ecto migration, you're by default creating a field called &lt;code&gt;id&lt;/code&gt;. You can opt-out of that but in this case, we want this so we can easily sort, locate, delete, and update them. Next, there's &lt;a href="https://hexdocs.pm/ecto_sql/Ecto.Migration.html#timestamps/1"&gt;timestamps/1&lt;/a&gt; which adds &lt;code&gt;inserted_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt; fields. &lt;code&gt;inserted_at&lt;/code&gt; will be great to know when a match was declared and &lt;code&gt;updated_at&lt;/code&gt; could be useful in the future to track updates when this table gets more features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexts strike again
&lt;/h2&gt;

&lt;p&gt;In the Phoenix world, context modules dictate the business rules of our app. Up until today we only used the generated &lt;code&gt;Champions.Accounts&lt;/code&gt; module because we only edited things on our &lt;code&gt;users&lt;/code&gt; table. That made sense at that time but we are outgrowing the Accounts module and starting something bigger and more and more not so related to the main goal of Accounts which is managing users.&lt;/p&gt;

&lt;p&gt;We will start a new context called Ranking that will contain things related to matches and points. All you need to do to create a new module is create a new file under &lt;code&gt;lib/champions&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;# lib/champions/ranking.ex&lt;/span&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@moduledoc&lt;/span&gt; &lt;span class="sd"&gt;"""
  The Ranking context.
  """&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later on, we will reason about moving functions from Accounts to here but let's focus on the new code first.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Ecto migration needs an Ecto model
&lt;/h2&gt;

&lt;p&gt;Ecto migrations only change how data would be stored in your database, you can say it uses &lt;a href="https://www.postgresql.org/docs/current/ddl.html"&gt;Data Definition Language (DDL)&lt;/a&gt; if you're into details. But after that, you need to prepare our Elixir app to manage that data, which you can call &lt;a href="https://www.postgresql.org/docs/current/dml.html"&gt;Data Manipulation Language (DML)&lt;/a&gt; if you want to be fancy.&lt;/p&gt;

&lt;p&gt;The first step to creating a model is to define the module. Since we are working under the &lt;code&gt;Champions.Ranking&lt;/code&gt; module it makes sense for our model to live under &lt;code&gt;Champions.Ranking.Match&lt;/code&gt; and of course, the folder structure should match so the file will live under &lt;code&gt;lib/champions/ranking/&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;# lib/champions/ranking/match.ex&lt;/span&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Match&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;"matches"&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;:result&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;Enum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;values:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:winner_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:winner_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:draw&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:user_a_id&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;:user_b_id&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;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;match&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;match&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;:result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:user_a_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:user_b_id&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;:result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:user_a_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:user_b_id&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;foreign_key_constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user_a_id&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;foreign_key_constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user_b_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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a lot to unpack here, some of those things you've already seen before but lets take our time to analyze everything bit by bit here.&lt;/p&gt;

&lt;p&gt;First of all, this is an elixir module. There's no magic here, &lt;code&gt;defmodule Champions.Ranking.Match&lt;/code&gt; says it all. The fun begins in line 3, the &lt;code&gt;use Ecto.Schema&lt;/code&gt; means we are using a macro. I promise we will look into how &lt;a href="https://elixir-lang.org/getting-started/alias-require-and-import.html#use"&gt;use&lt;/a&gt; works later but for now, trust me that &lt;code&gt;use Ecto.Schema&lt;/code&gt; makes your module &lt;code&gt;Champions.Ranking.Match&lt;/code&gt; also be available as a struct like &lt;code&gt;%Champions.Ranking.Match{user_a_id: 1, user_b_id: 2, result: :winner_a}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the next line, we import &lt;code&gt;Ecto.Changeset&lt;/code&gt; so we have neat features for data validation. If we didn't import we could be still using all those functions but we'd need to type &lt;code&gt;Ecto.Changeset.cast&lt;/code&gt; and &lt;code&gt;Ecto.Changeset.validate_required&lt;/code&gt; and that takes soo long we might as well import all functions already. And you just learned how Elixir's &lt;a href="https://elixir-lang.org/getting-started/alias-require-and-import.html#import"&gt;import&lt;/a&gt; keyword works.&lt;/p&gt;

&lt;p&gt;Lines 6 to 12 are where the fun begins. The &lt;a href="https://hexdocs.pm/ecto/Ecto.Schema.html#schema/2"&gt;schema/2&lt;/a&gt; macro receives first the table name followed by a &lt;code&gt;do&lt;/code&gt; block. We need the table name to be the same as our migration so since we did &lt;code&gt;create table(:users)&lt;/code&gt; here we say &lt;code&gt;schema "users" do&lt;/code&gt;. Now let's talk about the &lt;code&gt;do&lt;/code&gt; block. Inside it, we do something very similar to our migration except we define fields that will be available inside our struct. The user IDs are very simple since Ecto will map Postgres IDs to Elixir integers we just say they're of the type &lt;code&gt;:integer&lt;/code&gt;. And at the end, you will notice there is &lt;a href="https://hexdocs.pm/ecto/Ecto.Schema.html#timestamps/1"&gt;timestamps/1&lt;/a&gt; again. The last time we saw that function it was from &lt;code&gt;Ecto.Migration&lt;/code&gt; now this one is from &lt;code&gt;Ecto.Schema&lt;/code&gt; but as you guessed it, it adds &lt;code&gt;inserted_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt; to your model.&lt;/p&gt;

&lt;p&gt;The new type here you should be curious about is &lt;a href="https://hexdocs.pm/ecto/Ecto.Enum.html"&gt;Ecto.Enum&lt;/a&gt;. So far you've only seen types in the form of atom such as &lt;code&gt;:integer&lt;/code&gt; and &lt;code&gt;:string&lt;/code&gt; but this time the type is a module. This is an &lt;a href="https://hexdocs.pm/ecto/Ecto.Type.html#module-custom-types-and-primary-keys"&gt;Ecto custom type&lt;/a&gt;. More specifically, this is a built-in custom type so you don't have to install anything. What it does is help us validate values users try to place on &lt;code&gt;:result&lt;/code&gt; and remember you, the programmer, is also an user so whatever validation we can get we take it.&lt;/p&gt;

&lt;p&gt;Remember that our migration only said &lt;code&gt;:result&lt;/code&gt; is a &lt;code&gt;:string&lt;/code&gt; so your database doesn't know anything about enums, this is all in the &lt;code&gt;Ecto.Model&lt;/code&gt;. If we ever need to stop using &lt;code&gt;Ecto.Enum&lt;/code&gt; all we need to do is change this type to &lt;code&gt;:string&lt;/code&gt; and maybe fix some tests.&lt;/p&gt;

&lt;p&gt;Last but not the least, when we define an &lt;code&gt;Ecto.Model&lt;/code&gt; we need to create at least one changeset to control how the outside world manipulates this data. Ours is pretty simple, we &lt;code&gt;cast&lt;/code&gt; and &lt;code&gt;validate_required&lt;/code&gt; all fields then do something you haven't seen so far: &lt;a href="https://hexdocs.pm/ecto/Ecto.Changeset.html#foreign_key_constraint/3"&gt;foreign_key_constraint/3&lt;/a&gt;. This validation is a delayed check on the foreign keys. If you try to &lt;code&gt;Repo.insert&lt;/code&gt; or &lt;code&gt;Repo.update&lt;/code&gt; this changeset, if the foreign keys don't match, Ecto will gracefully handle the error.&lt;/p&gt;

&lt;p&gt;Our database knows about our table, we have a model to manipulate such table. The next step is to create functions in our module to make this process easier for anyone who needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding functions to our Ranking context
&lt;/h2&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;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@moduledoc&lt;/span&gt; &lt;span class="sd"&gt;"""
  The Ranking context.
  """&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;Query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;warn:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;

  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Match&lt;/span&gt;

  &lt;span class="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
  Creates a match.

  ## Examples

      iex&amp;gt; create_match(%{field: value})
      {:ok, %Match{}}

      iex&amp;gt; create_match(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;create_match&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="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Match&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;Match&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;attrs&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;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&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;To start we can be simple, we need only a &lt;code&gt;create_match/1&lt;/code&gt; function. After that we need to teach the places that generate matches how to save them. Let's start with our &lt;code&gt;Champions.Accounts.declare_draw_match/2&lt;/code&gt; .&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;defmodule Champions.Accounts do
&lt;/span&gt;  alias Champions.Ranking
&lt;span class="err"&gt;#&lt;/span&gt; a lot of things
  def declare_draw_match(user_a, user_b) do
&lt;span class="gi"&gt;+   {:ok, _match} = Ranking.create_match(%{
+     user_a_id: user_a.id,
+     user_b_id: user_b.id,
+     result: :draw
+   })
&lt;/span&gt;    {:ok, updated_user_a} = increment_user_points(user_a, 1)
    {:ok, updated_user_b} = increment_user_points(user_b, 1)
    {:ok, updated_user_a, updated_user_b}
  end
&lt;span class="err"&gt;#&lt;/span&gt; a lot of things
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to &lt;code&gt;alias Champions.Ranking&lt;/code&gt; in the top otherwise you're going to get an error. There's no mystery here, we just used the &lt;code&gt;create_match/2&lt;/code&gt; function to start recording those matches. Now let's go see what it needs to be done for conceding losses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;defmodule Champions.Accounts do
&lt;/span&gt;  alias Champions.Ranking
&lt;span class="err"&gt;#&lt;/span&gt; a lot of things
&lt;span class="gd"&gt;- def concede_loss_to(winner) do
&lt;/span&gt;&lt;span class="gi"&gt;+ def concede_loss_to(loser, winner) do
+   {:ok, _match} = Ranking.create_match(%{
+     user_a_id: loser.id,
+     user_b_id: winner.id,
+     result: :winner_b
+   })
&lt;/span&gt;    increment_user_points(winner, 3)
  end
&lt;span class="err"&gt;#&lt;/span&gt; a lot of things
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time we need to do something to the function signature. &lt;code&gt;concede_loss_to/1&lt;/code&gt; became &lt;code&gt;concede_loss_to/2&lt;/code&gt; because we also need to know who is losing here for the record. Needless to say, you'll need to look for all places that use &lt;code&gt;Accounts.concede_loss_to/1&lt;/code&gt; and change. You could do that by global searching &lt;code&gt;"concede_loss_to("&lt;/code&gt; but if you run &lt;code&gt;mix test&lt;/code&gt; right now you will see a ton of errors, some from LiveViews and some from tests itself too. Let's quickly address those so we can move on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; lib/champions_web/live/user_live/show.ex
&lt;span class="gd"&gt;- def handle_event("concede_loss", _value, %{assigns: %{user: user}} = socket) do
&lt;/span&gt;&lt;span class="gi"&gt;+ def handle_event("concede_loss", _value, %{assigns: %{current_user: current_user, user: user}} = socket) do
&lt;/span&gt;&lt;span class="gd"&gt;-   {:ok, updated_user} = Accounts.concede_loss_to(current_user, user)
&lt;/span&gt;&lt;span class="gi"&gt;+   {:ok, updated_user} = Accounts.concede_loss_to(user)
&lt;/span&gt;    {:noreply, assign(socket, :user, updated_user)}
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fixing that LiveView will also fix its test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; test/champions/accounts_test.exs
&lt;span class="gd"&gt;- describe "concede_loss_to/1" do
&lt;/span&gt;&lt;span class="gi"&gt;+ describe "concede_loss_to/2" do
&lt;/span&gt;    test "adds 3 points to the winner" do
&lt;span class="gi"&gt;+     loser = user_fixture()
&lt;/span&gt;      user = user_fixture()
      assert user.points == 0
&lt;span class="gd"&gt;-     assert {:ok, %User{points: 3}} = Accounts.concede_loss_to(user)
&lt;/span&gt;&lt;span class="gi"&gt;+     assert {:ok, %User{points: 3}} = Accounts.concede_loss_to(loser, user)
&lt;/span&gt;    end
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For now, we just fixed this test error but we will come back to also test that the Match was created. Now your tests should be green.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I see data when there's no UI?
&lt;/h2&gt;

&lt;p&gt;If you have a Postgres client such as DBeaver or Postico you probably don't need this right now but in case you don't know about IEx I'll show you something very interesting in Elixir right now.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;iex&lt;/code&gt; (&lt;a href="https://hexdocs.pm/iex/1.14/IEx.html"&gt;Interactive Elixir&lt;/a&gt;) is a command that comes with Elixir by default that lets enter a &lt;a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop"&gt;REPL&lt;/a&gt; where you can quickly test Elixir code just as you would do with JavaScript's console in your browser. If you are inside a Mix project folder and run &lt;code&gt;iex -S mix&lt;/code&gt; you tell IEx that you also want to be able to do things using code from your project. Let's do this so we can list all our Matches.&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="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;iex&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="no"&gt;S&lt;/span&gt; &lt;span class="n"&gt;mix&lt;/span&gt;
&lt;span class="no"&gt;Erlang&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="no"&gt;OTP&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;erts&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;12.3&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;smp:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;ds:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;async&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;threads:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="no"&gt;Interactive&lt;/span&gt; &lt;span class="no"&gt;Elixir&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.14&lt;/span&gt;&lt;span class="o"&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;-&lt;/span&gt; &lt;span class="n"&gt;press&lt;/span&gt; &lt;span class="no"&gt;Ctrl&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="no"&gt;C&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="k"&gt;exit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="no"&gt;ENTER&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;help&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="no"&gt;Champions&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="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Match&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;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"matches"&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;23&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="n"&gt;decode&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;3&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;15&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;4&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;717&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;SELECT&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_a_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_b_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&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="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"updated_at"&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"matches"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="err"&gt;↳&lt;/span&gt; &lt;span class="ss"&gt;:erl_eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do_apply&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;at:&lt;/span&gt; &lt;span class="n"&gt;erl_eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;erl:&lt;/span&gt;&lt;span class="mi"&gt;685&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Match&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, "matches"&amp;gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;id:&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;result:&lt;/span&gt; &lt;span class="ss"&gt;:draw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;user_a_id:&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;user_b_id:&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;inserted_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2023-07-02 14:43:08]&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[2023-07-02 14:43:08]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Match&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick is on line 5: &lt;code&gt;Champions.Repo.all(Champions.Ranking.Match)&lt;/code&gt;. I've just used our &lt;code&gt;Repo.all&lt;/code&gt; on our &lt;code&gt;Ranking.Match&lt;/code&gt; model to see the results. But right now this is obnoxious to read, let's alias those:&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="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;alias&lt;/span&gt; &lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;
&lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&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;3&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;alias&lt;/span&gt; &lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Match&lt;/span&gt;
&lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Match&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;4&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;Repo&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="no"&gt;Match&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;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"matches"&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;23&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="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;1&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;1778&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;9&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_a_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_b_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&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="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"updated_at"&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"matches"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="err"&gt;↳&lt;/span&gt; &lt;span class="ss"&gt;:erl_eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do_apply&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;at:&lt;/span&gt; &lt;span class="n"&gt;erl_eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;erl:&lt;/span&gt;&lt;span class="mi"&gt;685&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's more nice right? But what if you wanted to see the lastest ones? I'll explain those functions in detail later but you can copy this so you can reuse it later when you need:&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;Query&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;Query&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;6&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;Match&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;desc:&lt;/span&gt; &lt;span class="ss"&gt;:id&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&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;Repo&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;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;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"matches"&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;22&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;18&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;1938&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_a_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_b_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&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="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"updated_at"&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"matches"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt; &lt;span class="no"&gt;ORDER&lt;/span&gt; &lt;span class="no"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;DESC&lt;/span&gt; &lt;span class="no"&gt;LIMIT&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="ss"&gt;:erl_eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do_apply&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;at:&lt;/span&gt; &lt;span class="n"&gt;erl_eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;erl:&lt;/span&gt;&lt;span class="mi"&gt;685&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Match&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, "matches"&amp;gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;result:&lt;/span&gt; &lt;span class="ss"&gt;:draw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;user_a_id:&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;user_b_id:&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;inserted_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2023-07-02 18:29:05]&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[2023-07-02 18:29:05]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Match&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, "matches"&amp;gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;result:&lt;/span&gt; &lt;span class="ss"&gt;:draw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;user_a_id:&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;user_b_id:&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;inserted_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2023-07-02 18:29:04]&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[2023-07-02 18:29:04]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Match&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, "matches"&amp;gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;result:&lt;/span&gt; &lt;span class="ss"&gt;:draw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;user_a_id:&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;user_b_id:&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;inserted_at:&lt;/span&gt; &lt;span class="sx"&gt;~N[2023-07-02 18:29:04]&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[2023-07-02 18:29:04]&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;We've imported &lt;code&gt;Ecto.query&lt;/code&gt; to get functions like &lt;code&gt;order_by&lt;/code&gt; and &lt;code&gt;limit&lt;/code&gt; and just piped our query into Repo.all. Feel free to use it whenever you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where should functions related to points live?
&lt;/h2&gt;

&lt;p&gt;As mentioned previously, these functions were added to the &lt;code&gt;Accounts&lt;/code&gt; context for the sake of simplicity and since points are stored in the &lt;code&gt;User&lt;/code&gt; model which is managed by &lt;code&gt;Accounts&lt;/code&gt;. There's no exact answer here but my gut say that for this project the &lt;code&gt;Ranking&lt;/code&gt; module should take care and know about everything related to points so I'm making a choice of moving those.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; lib/champions/ranking.ex
&lt;span class="p"&gt;defmodule Champions.Ranking do
&lt;/span&gt;  @moduledoc """
  The Ranking context.
  """

  import Ecto.Query, warn: false
  alias Champions.Repo

+ alias Champions.Accounts
  alias Champions.Ranking.Match
&lt;span class="gi"&gt;+ alias Champions.Accounts.User
&lt;/span&gt;
  @doc """
  Creates a match.

  ## Examples

      iex&amp;gt; create_match(%{field: value})
      {:ok, %Match{}}

      iex&amp;gt; create_match(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def create_match(attrs \\ %{}) do
    %Match{}
    |&amp;gt; Match.changeset(attrs)
    |&amp;gt; Repo.insert()
  end
&lt;span class="gi"&gt;+
+ @doc """
+ Returns an `%Ecto.Changeset{}` for tracking user changes.
+
+ ## Examples
+
+     iex&amp;gt; change_user_points(user)
+     %Ecto.Changeset{data: %User{}}
+
+ """
+ def change_user_points(%User{} = user, attrs \\ %{}) do
+   User.points_changeset(user, attrs)
+ end
+
+ @doc """
+ Updates the current number of points of a user
+
+ ## Examples
++     iex&amp;gt; update_user_points(user, 10)
+     {:ok, %User{points: 10}}
+
+ """
+ def update_user_points(%User{} = user, points) do
+   user
+   |&amp;gt; change_user_points(%{"points" =&amp;gt; points})
+   |&amp;gt; Repo.update()
+ end
+
+ @doc """
+ Adds 3 points to the winning user
+
+ ## Examples
++     iex&amp;gt; concede_loss_to(%User{points: 0})
+     {:ok, %User{points: 3}}
+
+ """
+ def concede_loss_to(loser, winner) do
+   {:ok, _match} = create_match(%{
+     user_a_id: loser.id,
+     user_b_id: winner.id,
+     result: :winner_b
+   })
+   increment_user_points(winner, 3)
+ end
+
+ @doc """
+ Adds 1 point to each user
+
+ ## Examples
+
+     iex&amp;gt; declare_draw_match(%User{points: 0}, %User{points: 0})
+     {:ok, %User{points: 1}, %User{points: 1}}
+
+ """
+ def declare_draw_match(user_a, user_b) do
+   {:ok, _match} = create_match(%{
+     user_a_id: user_a.id,
+     user_b_id: user_b.id,
+     result: :draw
+   })
+   {:ok, updated_user_a} = increment_user_points(user_a, 1)
+   {:ok, updated_user_b} = increment_user_points(user_b, 1)
+   {:ok, updated_user_a, updated_user_b}
+ end
+
+ @doc """
+ Increments `amount` points to the user and returns its updated model
+
+ ## Examples
+
+     iex&amp;gt; increment_user_points(%User{points: 0}, 1)
+     {:ok, %User{points: 1}}
+
+ """
+ def increment_user_points(user, amount) do
+   {1, nil} =
+     User
+     |&amp;gt; where(id: ^user.id)
+     |&amp;gt; Repo.update_all(inc: [points: amount])
+
+   {:ok, Accounts.get_user!(user.id)}
+ end
&lt;/span&gt;&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a lot of additions plus note one function from &lt;code&gt;Accounts&lt;/code&gt; is not there, &lt;code&gt;get_user!&lt;/code&gt; so we aliased that model and changed the code to be &lt;code&gt;Accounts.get_user!&lt;/code&gt;. Similarly we need to remove those from &lt;code&gt;Accounts&lt;/code&gt;. Just remove those functions from there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; lib/champions/accounts.ex
  @moduledoc """
  The Accounts context.
  """

  import Ecto.Query, warn: false
  alias Champions.Repo
&lt;span class="gd"&gt;- alias Champions.Ranking
&lt;/span&gt;
# a ton of code

- @doc """
&lt;span class="gd"&gt;- Returns an `%Ecto.Changeset{}` for tracking user changes.
-
- ## Examples
-
-     iex&amp;gt; change_user_points(user)
-     %Ecto.Changeset{data: %User{}}
-
- """
- def change_user_points(%User{} = user, attrs \\ %{}) do
-   User.points_changeset(user, attrs)
- end
-
- @doc """
- Updates the current number of points of a user
-
- ## Examples
-
-     iex&amp;gt; update_user_points(user, 10)
-     {:ok, %User{points: 10}}
-
- """
- def update_user_points(%User{} = user, points) do
-   user
-   |&amp;gt; change_user_points(%{"points" =&amp;gt; points})
-   |&amp;gt; Repo.update()
- end
-
- @doc """
- Adds 3 points to the winning user
-
- ## Examples
-
-     iex&amp;gt; concede_loss_to(%User{points: 0})
-     {:ok, %User{points: 3}}
-
- """
- def concede_loss_to(loser, winner) do
-   {:ok, _match} = Ranking.create_match(%{
-     user_a_id: loser.id,
-     user_b_id: winner.id,
-     result: :winner_b
-   })
-   increment_user_points(winner, 3)
- end
-
- @doc """
- Adds 1 point to each user
-
- ## Examples
-
-     iex&amp;gt; declare_draw_match(%User{points: 0}, %User{points: 0})
-     {:ok, %User{points: 1}, %User{points: 1}}
-
- """
- def declare_draw_match(user_a, user_b) do
-   {:ok, _match} = Ranking.create_match(%{
-     user_a_id: user_a.id,
-     user_b_id: user_b.id,
-     result: :draw
-   })
-   {:ok, updated_user_a} = increment_user_points(user_a, 1)
-   {:ok, updated_user_b} = increment_user_points(user_b, 1)
-   {:ok, updated_user_a, updated_user_b}
- end
-
- @doc """
- Increments `amount` points to the user and returns its updated model
-
- ## Examples
-
-     iex&amp;gt; increment_user_points(%User{points: 0}, 1)
-     {:ok, %User{points: 1}}
-
- """
- def increment_user_points(user, amount) do
-   {1, nil} =
-     User
-     |&amp;gt; where(id: ^user.id)
-     |&amp;gt; Repo.update_all(inc: [points: amount])
-
-   {:ok, get_user!(user.id)}
- end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to remove the &lt;code&gt;alias Champions.Ranking&lt;/code&gt; so you wont be seeing warnings in your terminal. Time to do a similar thing with tests. We will use this as an opportunity to get started on our &lt;code&gt;Ranking&lt;/code&gt; tests.&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;# test/champions/ranking_test.exs&lt;/span&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;RankingTest&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;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;DataCase&lt;/span&gt;

  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ranking&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;AccountsFixtures&lt;/span&gt;

  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"change_user_points/2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"accepts non-negative integers"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&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="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change_user_points&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"points"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="n"&gt;refute&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;valid?&lt;/span&gt;

      &lt;span class="n"&gt;assert&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="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change_user_points&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"points"&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;assert&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;valid?&lt;/span&gt;

      &lt;span class="n"&gt;assert&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="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change_user_points&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"points"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="n"&gt;assert&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;valid?&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"set_user_points/2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;setup&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;user:&lt;/span&gt; &lt;span class="n"&gt;user_fixture&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;test&lt;/span&gt; &lt;span class="s2"&gt;"updates the amounts of points of an existing user"&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:&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;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;updated_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_user_points&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;updated_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&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;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"concede_loss_to/2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"adds 3 points to the winner"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;loser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_fixture&lt;/span&gt;&lt;span class="p"&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;user_fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;assert&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;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="n"&gt;assert&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="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;points:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concede_loss_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loser&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;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"declare_draw_match/2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"adds 1 point to each user"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;user_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;user_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;user_a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;user_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="n"&gt;assert&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="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;points:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;points:&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;=&lt;/span&gt; &lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;declare_draw_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_b&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="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"increment_user_points/2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"performs an atomic increment on a single user points amount"&lt;/span&gt; &lt;span class="k"&gt;do&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;user_fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;assert&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;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="n"&gt;assert&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="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;points:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;increment_user_points&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;assert&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="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;points:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_user_points&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="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;assert&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="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;points:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;increment_user_points&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="mi"&gt;10&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;We pretty much copy-pasted   the functions from &lt;code&gt;Account&lt;/code&gt; tests and renamed the context name. Feel free to copy the code above under &lt;code&gt;test/champions/ranking_test.exs&lt;/code&gt; or challenge yourself to create the file and copy your current &lt;code&gt;test/champions/accounts_test.exs&lt;/code&gt; and fix the differences, that's going to be interesting. It's worth mentioning your LiveView will be erroring right now so to focus only on this file run &lt;code&gt;mix test test/champions/ranking_test.exs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;defmodule Champions.AccountsTest do
&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt; a lot of code
&lt;span class="gd"&gt;- describe "change_user_points/2" do
-   test "accepts non-negative integers" do
-     assert %Ecto.Changeset{} = changeset = Accounts.change_user_points(%User{}, %{"points" =&amp;gt; -1})
-     refute changeset.valid?
-
-     assert %Ecto.Changeset{} = changeset = Accounts.change_user_points(%User{}, %{"points" =&amp;gt; 0})
-     assert changeset.valid?
-
-     assert %Ecto.Changeset{} = changeset = Accounts.change_user_points(%User{}, %{"points" =&amp;gt; 10})
-     assert changeset.valid?
-   end
- end
-
- describe "set_user_points/2" do
-   setup do
-     %{user: user_fixture()}
-   end
-
-   test "updates the amounts of points of an existing user", %{user: user} do
-     {:ok, updated_user} = Accounts.update_user_points(user, 10)
-     assert updated_user.points == 10
-   end
- end
&lt;/span&gt;  describe "list_users/0" do
    test "show all users on our system" do
      user = user_fixture()
      assert [^user] = Accounts.list_users()
    end
  end
&lt;span class="gd"&gt;-
- describe "concede_loss_to/2" do
-   test "adds 3 points to the winner" do
-     loser = user_fixture()
-     user = user_fixture()
-     assert user.points == 0
-     assert {:ok, %User{points: 3}} = Accounts.concede_loss_to(loser, user)
-   end
- end
-
- describe "declare_draw_match/2" do
-   test "adds 1 point to each user" do
-     user_a = user_fixture()
-     user_b = user_fixture()
-     assert user_a.points == 0
-     assert user_b.points == 0
-     assert {:ok, %User{points: 1}, %User{points: 1}} = Accounts.declare_draw_match(user_a, user_b)
-   end
- end
-
- describe "increment_user_points/2" do
-   test "performs an atomic increment on a single user points amount" do
-     user = user_fixture()
-     assert user.points == 0
-     assert {:ok, %User{points: 10}} = Accounts.increment_user_points(user, 10)
-     assert {:ok, %User{points: 5}} = Accounts.update_user_points(user, 5)
-     assert {:ok, %User{points: 15}} = Accounts.increment_user_points(user, 10)
-   end
- end
&lt;/span&gt;&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I haven't realized before by &lt;code&gt;list_users/0&lt;/code&gt; test was in the middle of that mess but it doesn't matter now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;defmodule ChampionsWeb.UserLive.Show do
&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt; some code
  def handle_event("concede_loss", _value, %{assigns: %{current_user: current_user, user: user}} = socket) do
&lt;span class="gd"&gt;-   {:ok, updated_user} = Accounts.concede_loss_to(current_user, user)
&lt;/span&gt;&lt;span class="gi"&gt;+   {:ok, updated_user} = Ranking.concede_loss_to(current_user, user)
&lt;/span&gt;    {:noreply, assign(socket, :user, updated_user)}
  end

  def handle_event("concede_draw", _value, %{assigns: %{current_user: current_user, user: user}} = socket) do
&lt;span class="gd"&gt;-   {:ok, updated_my_user, updated_user} = Accounts.declare_draw_match(current_user, user)
&lt;/span&gt;&lt;span class="gi"&gt;+   {:ok, updated_my_user, updated_user} = Ranking.declare_draw_match(current_user, user)
&lt;/span&gt;    {:noreply,
      socket
      |&amp;gt; assign(:user, updated_user)
      |&amp;gt; assign(:current_user, updated_my_user)
    }
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no tricky here, this fixes our LiveView and consequently all our tests. Your &lt;code&gt;mix test&lt;/code&gt; should be all green now!&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the new things
&lt;/h2&gt;

&lt;p&gt;We so far only focused on refactoring code and got some tests on &lt;code&gt;ranking_tests.exs&lt;/code&gt; but those are all for old things, we need to ensure the new code works and will keep working. The easiest test we can do is just check if we can create a match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;defmodule Champions.RankingTest do
&lt;/span&gt;  use Champions.DataCase

  alias Champions.Ranking
&lt;span class="gi"&gt;+ alias Champions.Ranking.Match
&lt;/span&gt;  alias Champions.Accounts.User
  import Champions.AccountsFixtures

+ describe "create_match/1" do
&lt;span class="gi"&gt;+   test "create_match/1 with valid data creates a match" do
+     loser = user_fixture()
+     winner = user_fixture()
+     valid_attrs = %{result: :winner_a, user_a_id: loser.id, user_b_id: winner.id}
+
+     assert {:ok, %Match{} = match} = Ranking.create_match(valid_attrs)
+     assert match.result == :winner_a
+   end
+
+   test "create_match/1 with an invalid user ID fails" do
+     loser = user_fixture()
+     valid_attrs = %{result: :winner_a, user_a_id: loser.id, user_b_id: 10_000_000}
+
+     assert {:error, %Ecto.Changeset{} = changeset} = Ranking.create_match(valid_attrs)
+     assert {"does not exist", _rest} = Keyword.fetch!(changeset.errors, :user_b_id)
+   end
+ end
&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt; more code
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's talk about the functions that generate matches: &lt;code&gt;Ranking.concede_loss_to/2&lt;/code&gt; and &lt;code&gt;Ranking.declare_draw_match/2&lt;/code&gt;. Their signature is to always return the users that were updated so our LiveView can update them. Doesn't seems we have a reason to change those functions specifically now so we need a different way to test that matches where created. Since Mix tests use a &lt;a href="https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html"&gt;sandboxed Postgres adapter&lt;/a&gt; we can simply trust that the last Match will be the one just created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  describe "concede_loss_to/2" do
    test "adds 3 points to the winner" do
      loser = user_fixture()
      user = user_fixture()
      assert user.points == 0
      assert {:ok, %User{points: 3}} = Ranking.concede_loss_to(loser, user)
&lt;span class="gi"&gt;+     match = get_last_match!()
+     assert match.user_a_id == loser.id
+     assert match.user_b_id == user.id
+     assert match.result == :winner_b
&lt;/span&gt;    end
  end

  describe "declare_draw_match/2" do
    test "adds 1 point to each user" do
      user_a = user_fixture()
      user_b = user_fixture()
      assert user_a.points == 0
      assert user_b.points == 0
      assert {:ok, %User{points: 1}, %User{points: 1}} = Ranking.declare_draw_match(user_a, user_b)
&lt;span class="gi"&gt;+     match = get_last_match!()
+     assert match.user_a_id == user_a.id
+     assert match.user_b_id == user_b.id
+     assert match.result == :draw
&lt;/span&gt;    end
  end

# put this in the end:

+ def get_last_match!() do
&lt;span class="gi"&gt;+   Match
+   |&amp;gt; order_by(desc: :id)
+   |&amp;gt; limit(1)
+   |&amp;gt; Repo.one!()
+ end
&lt;/span&gt;&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We created a simple &lt;code&gt;get_last_match!&lt;/code&gt; helper function so we wont duplicate code here.  After getting the last match all we need is to check if the data is set according to our test and we are good. But now, assuming you never did an &lt;a href="https://hexdocs.pm/ecto/Ecto.Query.html"&gt;Ecto.Query&lt;/a&gt; before, you could be confused about that and that's already the second time in this post I use it so let's talk about that.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 3 minutes introduction to Ecto.Query
&lt;/h2&gt;

&lt;p&gt;Whenever we need to query data from our database, in the end, we use &lt;code&gt;Repo&lt;/code&gt; with functions such as &lt;a href="https://hexdocs.pm/ecto/Ecto.Repo.html#c:exists?/2"&gt;Repo.exists?/2&lt;/a&gt; to check if the query contains any results, &lt;a href="https://hexdocs.pm/ecto/Ecto.Repo.html#c:all/2"&gt;Repo.all/2&lt;/a&gt; to get all results that fit the query or &lt;a href="https://hexdocs.pm/ecto/Ecto.Repo.html#c:one/2"&gt;Repo.one/2&lt;/a&gt; that will get a single result and error if there's none or more than one possible result. What those functions don't do is tell "what am I looking for". &lt;code&gt;Ecto.Query&lt;/code&gt; and &lt;code&gt;Ecto.Repo&lt;/code&gt; combine themselves to be more powerful: &lt;code&gt;Ecto.Query&lt;/code&gt; scope things and &lt;code&gt;Ecto.Repo&lt;/code&gt; executes the database call.&lt;/p&gt;

&lt;p&gt;What's the most basic Ecto.Query possible? The answer is simple and you've been seeing me do that all the time: all &lt;code&gt;Ecto.Model&lt;/code&gt; are queries. If you go to &lt;code&gt;iex -S mix&lt;/code&gt; and do &lt;code&gt;Repo.all(Match)&lt;/code&gt; (don't forget the aliases) you will see a list of all queries. So &lt;code&gt;Ecto.Model&lt;/code&gt; is a query with no boundaries.&lt;/p&gt;

&lt;p&gt;What if I want to get more specific results? That's were &lt;a href="https://hexdocs.pm/ecto/Ecto.Query.html"&gt;Ecto.Query&lt;/a&gt; jumps in. It comes with very useful functions. They're so useful that, I don't know if you paid attention, all contexts contain &lt;code&gt;import Ecto.Query, warn: false&lt;/code&gt; at the top because we all know we will be using them at some point so we ignore the warn until them, Phoenix will generate that for you in every single context. If you don't import it's also fine but you'll quickly see yourself writing things like &lt;code&gt;Ecto.Query.where&lt;/code&gt; and &lt;code&gt;Ecto.Query.order_by&lt;/code&gt;a lot so why not import as soon as needed.&lt;/p&gt;

&lt;p&gt;Now how we scope things down with queries? We first start with a query that knows no boundaries: a model. After that we just keep adding constraints. Let's dive down into &lt;code&gt;get_last_match!/0&lt;/code&gt; so we have 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="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;get_last_match!&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;# The query starts with `Match` so it reads as 'give me all matches'&lt;/span&gt;
&lt;span class="c1"&gt;# or if you like SQL: `select * from matches`&lt;/span&gt;
    &lt;span class="no"&gt;Match&lt;/span&gt;
&lt;span class="c1"&gt;# As soon as we use order_by/3 we add a new constraint saying exactly what the&lt;/span&gt;
&lt;span class="c1"&gt;# function name days. SQL: `select * from matches order by id desc`&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;desc:&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Needless to say, limit/3 does the same.&lt;/span&gt;
&lt;span class="c1"&gt;# SQL: `select * from matches order by id desc limit 1`&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;limit&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="c1"&gt;# As for Repo.one!/2 it will run the query and error if the count is not&lt;/span&gt;
&lt;span class="c1"&gt;# exactly 1. In this case, it only can be 0 or 1 because of our limit.&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;one!&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;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;We learned how to create a new Ecto model from scratch, including migrations and why those two correlate.&lt;/li&gt;
&lt;li&gt;We created our first context by hand and reasoned about when functions should be moved from one to another&lt;/li&gt;
&lt;li&gt;We quickly glanced at how to see data on IEx also using &lt;code&gt;Ecto.Query&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;We also learned that Postgres tests live on a sandbox environment so things like getting the last row can be relied on for tests and we also created a helper function for our tests&lt;/li&gt;
&lt;li&gt;We quickly talked about how Ecto queries work scoping things down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To prevent this post from becoming much longer I will stop it here and next time we will learn interesting stuff related to how to show data on LiveView and some Ecto tricks to make getting related models easier. See you next time.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
      <category>liveview</category>
      <category>backend</category>
    </item>
    <item>
      <title>The Lazy Programmer's Intro to LiveView: Chapter 8</title>
      <dc:creator>Lubien</dc:creator>
      <pubDate>Sun, 18 Jun 2023 15:07:45 +0000</pubDate>
      <link>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-8-156k</link>
      <guid>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-8-156k</guid>
      <description>&lt;h2&gt;
  
  
  Distributed systems are hard
&lt;/h2&gt;

&lt;p&gt;I want to invite you to test our system in a particular way. I assume you have two users on your platform: Lubien and Enemy. Open one browser window logged in as Lubien and another browser window logged in as enemy (you should probably use incognito), on each account open the user page for the other user. As Lubien, I can see Enemy's points at 72 and mine at 104. From the other point of view, Enemy sees my points at 104 and their navbar tells they have 72.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l62CphiH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/Cl3ac8c0tW3PE8_GcXD7yZNr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l62CphiH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/Cl3ac8c0tW3PE8_GcXD7yZNr.png" alt="Lubien POV: Enemy has 72 points and his navbar says Lubien has 104." width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Lg-0068H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/ZfFbJ3Rz2IzTqlOPJnk43_ox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Lg-0068H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/ZfFbJ3Rz2IzTqlOPJnk43_ox.png" alt="Enemy POV: Lubien has 104 points and their navbar has 72." width="800" height="572"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I'm going to concede 10 losses to Enemy using my UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1jITfIDA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/6E_VLOTQLLV0jDHT333XxRl_.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1jITfIDA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/6E_VLOTQLLV0jDHT333XxRl_.png" alt="Lubien POV: Enemy has 102 points and his navbar still shows that he has 104 points." width="800" height="565"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without refreshing, I'm going to Enemy's browser and declare a draw match.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZOVOP8wm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/DQwW_AeGqY3L5Fzppp-JT6Qz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZOVOP8wm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/DQwW_AeGqY3L5Fzppp-JT6Qz.png" alt="Enemy POV: Lubien has 105 points and their navbar says they have 73 points." width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You probably already guessed but if you refresh my window it's going to downgrade Enemy's points to 73 too. All that mess happens because we trust the current LiveView state to apply updates to points and both users happened to be with their windows open. There are many solutions to this: sync LiveViews when points change, create a CRDT, use a Postgres table to record matches then always sum the results of points, etc. The last option seems really good too, but I'd like to use this bug as an excuse to show off some more Ecto so bear with me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Removing business logic from LiveView
&lt;/h2&gt;

&lt;p&gt;LiveView has no guilt in this bug but it definitely shouldn't be the one doing these calculations. We should put business logic inside our context modules not inside &lt;code&gt;handle_event/3&lt;/code&gt;. Let's start by fixing that. Edit &lt;code&gt;show.ex&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;def handle_event("concede_loss", _value, %{assigns: %{user: user}} = socket) do
&lt;/span&gt;&lt;span class="gd"&gt;- {:ok, updated_user} = Accounts.update_user_points(user, user.points + 3)
&lt;/span&gt;&lt;span class="gi"&gt;+ {:ok, updated_user} = Accounts.concede_loss_to(user)
&lt;/span&gt;  {:noreply, assign(socket, :user, updated_user)}
&lt;span class="p"&gt;end
&lt;/span&gt;
def handle_event("concede_draw", _value, %{assigns: %{current_user: current_user, user: user}} = socket) do
&lt;span class="gd"&gt;- {:ok, updated_user} = Accounts.update_user_points(user, user.points + 1)
- {:ok, updated_my_user} = Accounts.update_user_points(current_user, current_user.points + 1)
&lt;/span&gt;&lt;span class="gi"&gt;+ {:ok, updated_my_user, updated_user} = Accounts.declare_draw_match(current_user, user)
&lt;/span&gt;  {:noreply,
    socket
    |&amp;gt; assign(:user, updated_user)
    |&amp;gt; assign(:current_user, updated_my_user)
  }
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then head back to &lt;code&gt;accounts.ex&lt;/code&gt; to create those 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="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
Adds 3 points to the winning user

## Examples

    iex&amp;gt; concede_loss_to(%User{points: 0})
    {:ok, %User{points: 3}}

"""&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;concede_loss_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;winner&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;update_user_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&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="k"&gt;end&lt;/span&gt;

&lt;span class="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
Adds 1 point to each user

## Examples

    iex&amp;gt; declare_draw_match(%User{points: 0}, %User{points: 0})
    {:ok, %User{points: 1}, %User{points: 1}}

"""&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;declare_draw_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_b&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;updated_user_a&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update_user_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;updated_user_b&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update_user_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;updated_user_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_user_b&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;You should be able to easily run &lt;code&gt;mix test&lt;/code&gt; to verify everything still works just fine. Now what we need is to avoid assuming the current amount of points is the most updated one. Instead of &lt;code&gt;update_user_points&lt;/code&gt; we must create an atomic function that increments points based on the current database state:&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;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
Adds 3 points to the winning user

## Examples

    iex&amp;gt; concede_loss_to(%User{points: 0})
    {:ok, %User{points: 3}}

"""&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;concede_loss_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;winner&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;increment_user_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&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="sd"&gt;"""
Adds 1 point to each user

## Examples

    iex&amp;gt; declare_draw_match(%User{points: 0}, %User{points: 0})
    {:ok, %User{points: 1}, %User{points: 1}}

"""&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;declare_draw_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_b&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;updated_user_a&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;increment_user_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_a&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="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;updated_user_b&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;increment_user_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_b&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="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;updated_user_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_user_b&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="sd"&gt;"""
Increments `amount` points to the user and returns its updated model

## Examples

    iex&amp;gt; increment_user_points(%User{points: 0}, 1)
    {:ok, %User{points: 1}}

"""&lt;/span&gt;
&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;increment_user_points&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="n"&gt;amount&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="no"&gt;User&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id:&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;id&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;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;inc:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;points:&lt;/span&gt; &lt;span class="n"&gt;amount&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;get_user!&lt;/span&gt;&lt;span class="p"&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;id&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 created &lt;code&gt;increment_user_points/2&lt;/code&gt; that takes the user and the number of points. The real magic here comes from &lt;a href="https://hexdocs.pm/ecto/Ecto.Repo.html#c:update_all/3"&gt;Repo.update_all/3&lt;/a&gt;. We define a query and then pass it to &lt;code&gt;Repo.update_all/3&lt;/code&gt; to run. The query is pretty simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On line 30 we start by saying this query affects all users because we started with the User Ecto model.&lt;/li&gt;
&lt;li&gt;At line 31 we scope this query only for users with an id equal to &lt;code&gt;user.id&lt;/code&gt; using &lt;a href="https://hexdocs.pm/ecto/Ecto.Query.html#where/3"&gt;where/3&lt;/a&gt;. We need to use the pin operator (&lt;code&gt;^&lt;/code&gt;) to put a variable that comes from outside the query in there, it will escape inputs to prevent SQL injection.&lt;/li&gt;
&lt;li&gt;Last but not least, we run &lt;code&gt;Repo.update_all/3&lt;/code&gt; using the special &lt;code&gt;inc&lt;/code&gt; option to increment points by &lt;code&gt;amount&lt;/code&gt; as many times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since this query returns a tuple containing the count of updated entries and &lt;code&gt;nil&lt;/code&gt; unless we manually select fields, we just ignore the result and a &lt;code&gt;{:ok, get_user!(user.id)}&lt;/code&gt; to get the updated user. Your bug should be fixed now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's not forget our tests
&lt;/h2&gt;

&lt;p&gt;The good thing is that since these functions are already being used on our LiveView we know they work by simply running &lt;code&gt;mix test&lt;/code&gt;. But let's not forget to test those out on our &lt;code&gt;accounts_test.exs&lt;/code&gt; too. Who knows, maybe that LiveView page goes away and we lose that coverage.&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;describe&lt;/span&gt; &lt;span class="s2"&gt;"concede_loss_to/1"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"adds 3 points to the winner"&lt;/span&gt; &lt;span class="k"&gt;do&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;user_fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;assert&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;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;assert&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="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;points:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&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;concede_loss_to&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;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"declare_draw_match/2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"adds 1 point to each user"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;user_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;user_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;user_a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;user_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;assert&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="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;points:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;points:&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;=&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;declare_draw_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_b&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="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"increment_user_points/2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"performs an atomic increment on a single user points amount"&lt;/span&gt; &lt;span class="k"&gt;do&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;user_fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;assert&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;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;assert&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="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;points:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&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;increment_user_points&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&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="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;points:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&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;update_user_points&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="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&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="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;points:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&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;increment_user_points&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="mi"&gt;10&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 first two ones are pretty obvious but the &lt;code&gt;increment_user_points/2&lt;/code&gt; suite tries to reproduce the bug the out-of-sync bug we caught at the start of this post and ensures that this function solves it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Be careful doing updates on stated based on cached state, they can easily go out of sync.&lt;/li&gt;
&lt;li&gt;Business logic should live outside LiveView.&lt;/li&gt;
&lt;li&gt;Ecto allows you to run atomic update queries with Repo.update_all/3.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chapter 9: TODO 😉&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>liveview</category>
      <category>phoenix</category>
    </item>
    <item>
      <title>The Lazy Programmer's Intro to LiveView: Chapter 7</title>
      <dc:creator>Lubien</dc:creator>
      <pubDate>Sun, 18 Jun 2023 15:07:42 +0000</pubDate>
      <link>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-7-47e0</link>
      <guid>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-7-47e0</guid>
      <description>&lt;h2&gt;
  
  
  Conceding points to the winner
&lt;/h2&gt;

&lt;p&gt;Sometimes you will lose and that's okay. Just don't forget to give your opponent 3 points for their victory. In this chapter, we will explore how one user could give 3 points to another if they lose to them. In case you haven't already, create a second account on your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic interactions with LiveView
&lt;/h2&gt;

&lt;p&gt;So far we haven't created any sort of user interaction besides boring links from one page to another. Let's make our user LiveView more powerful. What we need to do is create an 'I lost to this person' button that when pressed gives them 3 points. Let's start with the render function. Update your &lt;code&gt;lib/champions_web/live/user_live/show.ex&lt;/code&gt; render function to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;@impl&lt;/span&gt; true
&lt;span class="p"&gt;def render(assigns) do
&lt;/span&gt;  ~H"""
  &amp;lt;.header&amp;gt;
    User &amp;lt;%= @user.id %&amp;gt;
    &amp;lt;:subtitle&amp;gt;This is a player on this app.&amp;lt;/:subtitle&amp;gt;
  &amp;lt;/.header&amp;gt;

  &amp;lt;.list&amp;gt;
    &amp;lt;:item title="Email"&amp;gt;&amp;lt;%= @user.email %&amp;gt;&amp;lt;/:item&amp;gt;
    &amp;lt;:item title="Points"&amp;gt;&amp;lt;%= @user.points %&amp;gt;&amp;lt;/:item&amp;gt;
  &amp;lt;/.list&amp;gt;

+ &amp;lt;div class="my-4"&amp;gt;
&lt;span class="gi"&gt;+   &amp;lt;.button type="button" phx-click="concede_loss"&amp;gt;I lost to this person&amp;lt;/.button&amp;gt;
+ &amp;lt;/div&amp;gt;
&lt;/span&gt;
  &amp;lt;.back navigate={~p"/users"}&amp;gt;Back to users&amp;lt;/.back&amp;gt;
  """
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We just added a Phoenix &lt;code&gt;&amp;lt;.button&amp;gt;&lt;/code&gt; component and it contains an attribute called &lt;code&gt;phx-click&lt;/code&gt;. Clicking this button will trigger an event on our LiveView but there's a catch: we are not ready for this event yet. Press that button right now and your terminal will show the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[error] GenServer #PID&amp;lt;0.3550.0&amp;gt; terminating
** (UndefinedFunctionError) function ChampionsWeb.UserLive.Show.handle_event/3 is undefined or private
    ChampionsWeb.UserLive.Show.handle_event("concede_loss", %{"value" =&amp;gt; ""}, #Phoenix.LiveView.Socket&amp;lt;id: "phx-F2mNT8Nhl-Qz2gxH", endpoint: ChampionsWeb.Endpoint, view: ChampionsWeb.UserLive.Show, parent_pid: nil, root_pid: #PID&amp;lt;0.3550.0&amp;gt;, router: ChampionsWeb.Router, assigns: %{__changed__: %{}, current_user: #Champions.Accounts.User&amp;lt;__meta__: #Ecto.Schema.Metadata&amp;lt;:loaded, "users"&amp;gt;, id: 1, email: "lubien@example.com", confirmed_at: ~N[2023-06-17 13:55:41], points: 0, inserted_at: ~N[2023-06-17 13:53:56], updated_at: ~N[2023-06-17 13:55:41], ...&amp;gt;, flash: %{}, live_action: :show, page_title: "Showing user enemy@example.com", user: #Champions.Accounts.User&amp;lt;__meta__: #Ecto.Schema.Metadata&amp;lt;:loaded, "users"&amp;gt;, id: 2, email: "enemy@example.com", confirmed_at: nil, points: 0, inserted_at: ~N[2023-06-18 10:28:49], updated_at: ~N[2023-06-18 10:28:49], ...&amp;gt;}, transport_pid: #PID&amp;lt;0.3544.0&amp;gt;, ...&amp;gt;)
    (phoenix_live_view 0.19.2) lib/phoenix_live_view/channel.ex:401: anonymous fn/3 in Phoenix.LiveView.Channel.view_handle_event/3
    (telemetry 1.2.1) /Users/lubien/workspace/champions/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
    (phoenix_live_view 0.19.2) lib/phoenix_live_view/channel.ex:221: Phoenix.LiveView.Channel.handle_info/2
    (stdlib 3.17.2) gen_server.erl:695: :gen_server.try_dispatch/4
    (stdlib 3.17.2) gen_server.erl:771: :gen_server.handle_msg/6
    (stdlib 3.17.2) proc_lib.erl:236: :proc_lib.wake_up/3
Last message: %Phoenix.Socket.Message{topic: "lv:phx-F2mNT8Nhl-Qz2gxH", event: "event", payload: %{"event" =&amp;gt; "concede_loss", "type" =&amp;gt; "click", "value" =&amp;gt; %{"value" =&amp;gt; ""}}, ref: "12", join_ref: "4"}
State: %{components: {%{}, %{}, 1}, join_ref: "4", serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket&amp;lt;id: "phx-F2mNT8Nhl-Qz2gxH", endpoint: ChampionsWeb.Endpoint, view: ChampionsWeb.UserLive.Show, parent_pid: nil, root_pid: #PID&amp;lt;0.3550.0&amp;gt;, router: ChampionsWeb.Router, assigns: %{__changed__: %{}, current_user: #Champions.Accounts.User&amp;lt;__meta__: #Ecto.Schema.Metadata&amp;lt;:loaded, "users"&amp;gt;, id: 1, email: "lubien@example.com", confirmed_at: ~N[2023-06-17 13:55:41], points: 0, inserted_at: ~N[2023-06-17 13:53:56], updated_at: ~N[2023-06-17 13:55:41], ...&amp;gt;, flash: %{}, live_action: :show, page_title: "Showing user enemy@example.com", user: #Champions.Accounts.User&amp;lt;__meta__: #Ecto.Schema.Metadata&amp;lt;:loaded, "users"&amp;gt;, id: 2, email: "enemy@example.com", confirmed_at: nil, points: 0, inserted_at: ~N[2023-06-18 10:28:49], updated_at: ~N[2023-06-18 10:28:49], ...&amp;gt;}, transport_pid: #PID&amp;lt;0.3544.0&amp;gt;, ...&amp;gt;, topic: "lv:phx-F2mNT8Nhl-Qz2gxH", upload_names: %{}, upload_pids: %{}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a lot to unpack but bear with me. I like Phoenix errors because they tell you a lot once you understand how to read them. I'd like to teach you that right now so let's break down it bit by bit:&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning how to interpret a LiveView event error
&lt;/h2&gt;

&lt;p&gt;First of all, the first line tells you something very important: &lt;code&gt;[error] GenServer #PID&amp;lt;0.3550.0&amp;gt; terminating&lt;/code&gt;. That means this error was bad, our LiveView page crashed and had to be restarted. That's not a great experience so you definitely want to fix this.&lt;/p&gt;

&lt;p&gt;On line 2 you can already see a good hint at the issue: &lt;code&gt;UndefinedFunctionError&lt;/code&gt;. That name says all: LiveView expected a certain function to be defined but it wasn't. Keep reading that line and we can see that &lt;code&gt;function ChampionsWeb.UserLive.Show.handle_event/3 is undefined or private&lt;/code&gt; tells us that we forgot to create a &lt;code&gt;handle_event&lt;/code&gt; function that takes 3 arguments or we mistakenly created it as a private function using &lt;code&gt;defp&lt;/code&gt;. Well you didn't forget it since you're learning yet, I just wanted you to experience this cool error message though.&lt;/p&gt;

&lt;p&gt;But say you've never heard of &lt;code&gt;handle_event/3&lt;/code&gt; , what do I need to do with that function? Line 3 gives you exactly what Phoenix tried passing to that function so you can pretty much get started with that: &lt;code&gt;ChampionsWeb.UserLive.Show.handle_event("concede_loss", %{"value" =&amp;gt; ""}, #Phoenix.LiveView.Socket&amp;lt;id: "phx-F2mNT8Nhl-Qz2gxH"...&amp;gt;)&lt;/code&gt;. Do note that &lt;code&gt;Phoenix.LiveView.Socket&lt;/code&gt; contains a lot of information so it can be threatening to read the whole piece but in time you will learn to ignore it and focus on the other arguments, I even cut it for you already in this paragraph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating our &lt;code&gt;handle_event/3&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveComponent.html#c:handle_event/3"&gt;handle_event/3&lt;/a&gt; is a new LiveView callback you should learn about. It is triggered when one of your LiveView events from the render function is called such as &lt;code&gt;phx-click&lt;/code&gt;. In this case, we created a &lt;code&gt;phx-click="concede_loss"&lt;/code&gt; so our event name will be &lt;code&gt;concede_loss&lt;/code&gt;. The second argument with contains values, we will ignore that for now, and last but not least there's the socket as the third argument. Did you notice that all callbacks have the socket as the last argument? That's because all callbacks are just functions that manage what is inside the socket so it makes sense for it to be available.&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;@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;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"concede_loss"&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="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;socket&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 code above is the minimal event handler needed to receive our custom event and do nothing. &lt;code&gt;handle_event/3&lt;/code&gt; must return either &lt;code&gt;{:noreply, socket}&lt;/code&gt; or &lt;code&gt;{:reply, map, socket}&lt;/code&gt;. For now, we will focus on the &lt;code&gt;:noreply&lt;/code&gt; case. Now that we have an event handler we need to modify it to get the user being shown on the page and add 3 points to them. Fortunately &lt;code&gt;socket&lt;/code&gt;  contains a property called &lt;code&gt;assigns&lt;/code&gt; that has all assigns. Since this page has a &lt;code&gt;user&lt;/code&gt; assigns, &lt;code&gt;socket.assigns.user&lt;/code&gt; is readily available to us.&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;@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;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"concede_loss"&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="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;user&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;user&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;updated_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;update_user_points&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="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&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="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;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;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_user&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;That's it! All we had to do was get the &lt;code&gt;user&lt;/code&gt; assign and run that function we created back in the early chapters of this project. Don't forget to reassign the user so the LiveView updates immediately without a page refresh needed to show the new points.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conceding a draw
&lt;/h2&gt;

&lt;p&gt;I've also told you when this series started that draws would give both players 1 point. You can probably guess 90% of what needs to be done by copying and pasting what you just learned so let's start with that. We will create a &lt;code&gt;concede_draw&lt;/code&gt; event that's a carbon copy of &lt;code&gt;concede_loss&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  @impl true
  def handle_event("concede_loss", _value, socket) do
    user = socket.assigns.user
    {:ok, updated_user} = Accounts.update_user_points(user, user.points + 3)
    {:noreply, assign(socket, :user, updated_user)}
  end
&lt;span class="gi"&gt;+
+ def handle_event("concede_draw", _value, socket) do
+   user = socket.assigns.user
+   {:ok, updated_user} = Accounts.update_user_points(user, user.points + 1)
+   {:noreply, assign(socket, :user, updated_user)}
+ end
&lt;/span&gt;
  @impl true
  def render(assigns) do
    ~H"""
    &amp;lt;.header&amp;gt;
      User &amp;lt;%= @user.id %&amp;gt;
      &amp;lt;:subtitle&amp;gt;This is a player on this app.&amp;lt;/:subtitle&amp;gt;
    &amp;lt;/.header&amp;gt;

    &amp;lt;.list&amp;gt;
      &amp;lt;:item title="Email"&amp;gt;&amp;lt;%= @user.email %&amp;gt;&amp;lt;/:item&amp;gt;
      &amp;lt;:item title="Points"&amp;gt;&amp;lt;%= @user.points %&amp;gt;&amp;lt;/:item&amp;gt;
    &amp;lt;/.list&amp;gt;

    &amp;lt;div class="my-4"&amp;gt;
      &amp;lt;.button type="button" phx-click="concede_loss"&amp;gt;I lost to this person&amp;lt;/.button&amp;gt;
&lt;span class="gi"&gt;+     &amp;lt;.button type="button" phx-click="concede_draw"&amp;gt;Declare draw match&amp;lt;/.button&amp;gt;
&lt;/span&gt;    &amp;lt;/div&amp;gt;

    &amp;lt;.back navigate={~p"/users"}&amp;gt;Back to users&amp;lt;/.back&amp;gt;
    """
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do note we didn't add &lt;code&gt;@impl true&lt;/code&gt; on the second &lt;code&gt;handle_event/3&lt;/code&gt; clause because Elixir will already infer that from the first one. How can I know which user am I on my LiveView so I can add 1 point to myself? Back when we generated our auth code Phoenix added something called LiveSession, which we will explore later, but the important bit is that it adds an assign called &lt;code&gt;current_user&lt;/code&gt; on your socket, so you can use it on callbacks and on your render functions, we even used it to show your points on the navbar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;def handle_event("concede_draw", _value, socket) do
&lt;/span&gt;&lt;span class="gi"&gt;+ my_user = socket.assigns.current_user
&lt;/span&gt;  user = socket.assigns.user
  {:ok, updated_user} = Accounts.update_user_points(user, user.points + 1)
&lt;span class="gi"&gt;+ {:ok, updated_my_user} = Accounts.update_user_points(my_user, my_user.points + 1)
&lt;/span&gt;&lt;span class="gd"&gt;- {:noreply, assign(socket, :user, updated_user)}
&lt;/span&gt;&lt;span class="gi"&gt;+ {:noreply,
+   socket
+   |&amp;gt; assign(:user, updated_user)
+   |&amp;gt; assign(:current_user, updated_my_user)
+ }
&lt;/span&gt;&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Amazing, we already have two main features of our app up and running. But if you pay attention to your navbar you will notice that your points aren't increasing but if you refresh the page your new points will show up. What is happening with our layout?&lt;/p&gt;

&lt;h2&gt;
  
  
  Difference between &lt;code&gt;root.html.heex&lt;/code&gt; and &lt;code&gt;app.html.heex&lt;/code&gt; layout
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;root.html.heex&lt;/code&gt; layout is &lt;a href="https://hexdocs.pm/phoenix_live_view/live-layouts.html"&gt;meant to be rendered only once&lt;/a&gt; and never update again with one single exception: updating page titles. That means that in order for us to see live updates to our navbar we must move our navbar logic to &lt;code&gt;app.html.heex&lt;/code&gt; which is LiveView-aware. That's quite simple actually. Remove this entire &lt;code&gt;ul&lt;/code&gt; from your &lt;code&gt;root.html.heex&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en" class="[scrollbar-gutter:stable]"&amp;gt;
&lt;/span&gt;  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="utf-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1" /&amp;gt;
    &amp;lt;meta name="csrf-token" content={get_csrf_token()} /&amp;gt;
    &amp;lt;.live_title suffix=" · Phoenix Framework"&amp;gt;
      &amp;lt;%= assigns[:page_title] || "Champions" %&amp;gt;
    &amp;lt;/.live_title&amp;gt;
    &amp;lt;link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} /&amp;gt;
    &amp;lt;script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}&amp;gt;
    &amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body class="bg-white antialiased"&amp;gt;
&lt;span class="gd"&gt;-   &amp;lt;ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end"&amp;gt;
-     &amp;lt;%= if @current_user do %&amp;gt;
-       &amp;lt;li class="text-[0.8125rem] leading-6 text-zinc-900"&amp;gt;
-         &amp;lt;%= @current_user.email %&amp;gt;
-         (&amp;lt;%= @current_user.points %&amp;gt; points)
-       &amp;lt;/li&amp;gt;
-       &amp;lt;li&amp;gt;
-         &amp;lt;.link
-           href={~p"/users/settings"}
-           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
-         &amp;gt;
-           Settings
-         &amp;lt;/.link&amp;gt;
-       &amp;lt;/li&amp;gt;
-       &amp;lt;li&amp;gt;
-         &amp;lt;.link
-           href={~p"/users/log_out"}
-           method="delete"
-           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
-         &amp;gt;
-           Log out
-         &amp;lt;/.link&amp;gt;
-       &amp;lt;/li&amp;gt;
-     &amp;lt;% else %&amp;gt;
-       &amp;lt;li&amp;gt;
-         &amp;lt;.link
-           href={~p"/users/register"}
-           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
-         &amp;gt;
-           Register
-         &amp;lt;/.link&amp;gt;
-       &amp;lt;/li&amp;gt;
-       &amp;lt;li&amp;gt;
-         &amp;lt;.link
-           href={~p"/users/log_in"}
-           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
-         &amp;gt;
-           Log in
-         &amp;lt;/.link&amp;gt;
-       &amp;lt;/li&amp;gt;
-     &amp;lt;% end %&amp;gt;
-   &amp;lt;/ul&amp;gt;
&lt;/span&gt;    &amp;lt;%= @inner_content %&amp;gt;
  &amp;lt;/body&amp;gt;
&lt;span class="gd"&gt;&amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add it to the top of your &lt;code&gt;app.html.heex&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+&amp;lt;ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end"&amp;gt;
+&amp;lt;%= if @current_user do %&amp;gt;
+  &amp;lt;li class="text-[0.8125rem] leading-6 text-zinc-900"&amp;gt;
+    &amp;lt;%= @current_user.email %&amp;gt;
+    (&amp;lt;%= @current_user.points %&amp;gt; points)
+  &amp;lt;/li&amp;gt;
+  &amp;lt;li&amp;gt;
+    &amp;lt;.link
+      href={~p"/users/settings"}
+      class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+    &amp;gt;
+      Settings
+    &amp;lt;/.link&amp;gt;
+  &amp;lt;/li&amp;gt;
+  &amp;lt;li&amp;gt;
+    &amp;lt;.link
+      href={~p"/users/log_out"}
+      method="delete"
+      class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+    &amp;gt;
+      Log out
+    &amp;lt;/.link&amp;gt;
+  &amp;lt;/li&amp;gt;
+&amp;lt;% else %&amp;gt;
+  &amp;lt;li&amp;gt;
+    &amp;lt;.link
+      href={~p"/users/register"}
+      class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+    &amp;gt;
+      Register
+    &amp;lt;/.link&amp;gt;
+  &amp;lt;/li&amp;gt;
+  &amp;lt;li&amp;gt;
+    &amp;lt;.link
+      href={~p"/users/log_in"}
+      class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+    &amp;gt;
+      Log in
+    &amp;lt;/.link&amp;gt;
+  &amp;lt;/li&amp;gt;
+&amp;lt;% end %&amp;gt;
+&amp;lt;/ul&amp;gt;
&lt;/span&gt;&lt;span class="gd"&gt;&amp;lt;header class="px-4 sm:px-6 lg:px-8"&amp;gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt; rest of the things here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your points should be updated each time you click on the draw button! But here's a catch: we just broke our tests! If you run &lt;code&gt;mix test&lt;/code&gt; you will spot two errors. They're both related to the fact that the login and register tests send you to the root path &lt;code&gt;/&lt;/code&gt; and expect your navbar to be there but the homepage explicitly says it doesn't want to use &lt;code&gt;app.html.heex&lt;/code&gt; on &lt;code&gt;lib/champions_web/controllers/page_controller.ex&lt;/code&gt; by &lt;code&gt;render(conn, :home, layout: false)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's okay and since we won't be caring for that page in the future the solution is quite simple: let's change those tests to send users to the &lt;code&gt;/users&lt;/code&gt; page. Locate and change this on &lt;code&gt;test/champions_web/controllers/user_session_controller_test.exs&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; Now do a logged in request and assert on the menu
&lt;span class="gd"&gt;-conn = get(conn, ~p"/")
&lt;/span&gt;&lt;span class="gi"&gt;+conn = get(conn, ~p"/users")
&lt;/span&gt;&lt;span class="p"&gt;response = html_response(conn, 200)
assert response =~ user.email
assert response =~ ~p"/users/settings"
assert response =~ ~p"/users/log_out"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do the same thing on &lt;code&gt;test/champions_web/live/user_registration_live_test.exs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; Now do a logged in request and assert on the menu
&lt;span class="gd"&gt;-conn = get(conn, "/")
&lt;/span&gt;&lt;span class="gi"&gt;+conn = get(conn, "/users")
&lt;/span&gt;&lt;span class="p"&gt;response = html_response(conn, 200)
assert response =~ email
assert response =~ "Settings"
assert response =~ "Log out"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;mix test&lt;/code&gt; should be running just fine now!&lt;/p&gt;

&lt;h2&gt;
  
  
  Preventing unauthorized users from using these buttons
&lt;/h2&gt;

&lt;p&gt;Right now if you open an anonymous tab and hit those buttons you will have pleasant surprises. Hitting the concede loss button will work just fine even though I'm not a player. Hitting the concede draw button will crash our LiveView because there's no &lt;code&gt;current_user&lt;/code&gt; assign. Let's fix that.&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;@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;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"concede_loss"&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="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="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="n"&gt;user&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;user&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;updated_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;update_user_points&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="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&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;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;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;put_flash&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="s2"&gt;"You must be a player to do that!"&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="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;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;"concede_draw"&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="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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;my_user&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;current_user&lt;/span&gt; &lt;span class="k"&gt;do&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;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;user&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;updated_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;update_user_points&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="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;updated_my_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;update_user_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_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="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="n"&gt;updated_my_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;put_flash&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="s2"&gt;"You must be a player to do that!"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time a simple &lt;code&gt;if&lt;/code&gt; expression could solve our problems. Even if our user shouldn't be able to do some events we still need to return &lt;code&gt;{:noreply, socket}&lt;/code&gt; but in case they're unauthorized we will use &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#put_flash/3"&gt;put_flash/3&lt;/a&gt; to show a pretty error message. Now let's hide these buttons from those who shouldn't see them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;@impl&lt;/span&gt; true
&lt;span class="p"&gt;def render(assigns) do
&lt;/span&gt;  ~H"""
  &amp;lt;.header&amp;gt;
    User &amp;lt;%= @user.id %&amp;gt;
    &amp;lt;:subtitle&amp;gt;This is a player on this app.&amp;lt;/:subtitle&amp;gt;
  &amp;lt;/.header&amp;gt;

  &amp;lt;.list&amp;gt;
    &amp;lt;:item title="Email"&amp;gt;&amp;lt;%= @user.email %&amp;gt;&amp;lt;/:item&amp;gt;
    &amp;lt;:item title="Points"&amp;gt;&amp;lt;%= @user.points %&amp;gt;&amp;lt;/:item&amp;gt;
  &amp;lt;/.list&amp;gt;

- &amp;lt;div class="my-4"&amp;gt;
&lt;span class="gi"&gt;+ &amp;lt;div :if={@current_user} class="my-4"&amp;gt;
&lt;/span&gt;    &amp;lt;.button type="button" phx-click="concede_loss"&amp;gt;I lost to this person&amp;lt;/.button&amp;gt;
    &amp;lt;.button type="button" phx-click="concede_draw"&amp;gt;Declare draw match&amp;lt;/.button&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;.back navigate={~p"/users"}&amp;gt;Back to users&amp;lt;/.back&amp;gt;
  """
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HEEx nodes can use &lt;code&gt;:if={condition}&lt;/code&gt; to show elements, pretty handy. At this point, you could be asking yourself 'Why bother validating in the events if we are not going to show these buttons?' And that's a really good question. The thing about LiveView events is that there's no real magic on the HTML side, a &lt;code&gt;&amp;lt;.button phx-click="concede_loss"&amp;gt;&lt;/code&gt; is just a regular HTML button like &lt;code&gt;&amp;lt;button phx-click="concede_loss&amp;gt;&lt;/code&gt;. If your users are clever to figure that out they could 👻 haxx your system! Don't believe me? Paste the HTML below on an unauthenticated tab on &lt;code&gt;/users/:id&lt;/code&gt; using your browser dev tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;button type="button" phx-click="concede_loss"&amp;gt;
  Hacking to the Gate 🔥
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to prevent a user from giving points to himself infinitely
&lt;/h2&gt;

&lt;p&gt;Right now you can also go to your own page and concede losses/draws to yourself and you get all the points. That's not right so let's fix 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="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;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"concede_loss"&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="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="k"&gt;case&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="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;user&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;nil&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="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="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="s2"&gt;"You must be a player to do that!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="p"&gt;{%{&lt;/span&gt;&lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;my_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;id:&lt;/span&gt; &lt;span class="n"&gt;other_id&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;my_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other_id&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="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="s2"&gt;"You can't give points to yourself"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_my_user&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="o"&gt;-&amp;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;updated_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;update_user_points&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="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&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;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;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_user&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="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;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;"concede_draw"&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="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="k"&gt;case&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="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;user&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;nil&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="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="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="s2"&gt;"You must be a player to do that!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="p"&gt;{%{&lt;/span&gt;&lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;my_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;id:&lt;/span&gt; &lt;span class="n"&gt;other_id&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;my_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other_id&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="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="s2"&gt;"You can't give points to yourself"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_user&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="o"&gt;-&amp;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;updated_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;update_user_points&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="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;updated_my_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;update_user_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_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="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="n"&gt;updated_my_user&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time we changed our &lt;code&gt;if&lt;/code&gt; to a pattern match to compare &lt;code&gt;{my_user, user}&lt;/code&gt; so we can prevent both &lt;code&gt;my_user&lt;/code&gt; being &lt;code&gt;nil&lt;/code&gt; and &lt;code&gt;my_user&lt;/code&gt; and &lt;code&gt;user&lt;/code&gt; being the same person. As for the HEEx change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-&amp;lt;div :if={@current_user} class="my-4"&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+&amp;lt;div :if={@current_user &amp;amp;&amp;amp; @current_user.id != @user.id} class="my-4"&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That should cover all cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't forget the tests
&lt;/h2&gt;

&lt;p&gt;How do we test a LiveView with a button click? How do we test whether being authenticated or not and whether I'm the same user on that page or not? In all tests that required a user so far we did 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;describe&lt;/span&gt; &lt;span class="s2"&gt;"Show"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:create_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"displays user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;conn:&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;user:&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;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;_show_live&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;live&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="sx"&gt;~p"/users/#{user}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="s2"&gt;"This is a player on this app"&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;html&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;email&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;setup [:create_user]&lt;/code&gt; will create a user for us and pass it under our context at line 4 so we can immediately go to its page. &lt;code&gt;:create_user&lt;/code&gt; does not authenticates us. If we change that to &lt;code&gt;setup [:register_and_log_in_user]&lt;/code&gt; it will authenticate us and our context user will be ourselves so we can use this to go to our own page. Let's add a test for that. Go to &lt;code&gt;test/champions_web/live/user_live_test.exs&lt;/code&gt; and add this suite:&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;describe&lt;/span&gt; &lt;span class="s2"&gt;"Authenticated Show"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:register_and_log_in_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"displays my own user but no action buttons"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;conn:&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;user:&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;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;_show_live&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;live&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="sx"&gt;~p"/users/#{user}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="s2"&gt;"This is a player on this app"&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;html&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;email&lt;/span&gt;
    &lt;span class="n"&gt;refute&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="s2"&gt;"I lost to this person"&lt;/span&gt;
    &lt;span class="n"&gt;refute&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="s2"&gt;"Declare draw match"&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 should also be able to go to another user's page and see those buttons. Inside that &lt;code&gt;describe&lt;/code&gt; block add this test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test "displays another user with action buttons", %{conn: conn, user: _user} do
  other_user = user_fixture()
  {:ok, _show_live, html} = live(conn, ~p"/users/#{other_user}")

  assert html =~ "This is a player on this app"
  assert html =~ other_user.email
  assert html =~ "I lost to this person"
  assert html =~ "Declare draw match"
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we should also verify that unauthenticated users can't see those buttons. Luckly we already have a &lt;code&gt;describe&lt;/code&gt; for that! Here's how the full suite for this page ended up as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;describe "Authenticated Show" do
&lt;/span&gt;  setup [:register_and_log_in_user]

  test "displays my own user but no action buttons", %{conn: conn, user: user} do
    {:ok, _show_live, html} = live(conn, ~p"/users/#{user}")

    assert html =~ "This is a player on this app"
    assert html =~ user.email
    refute html =~ "I lost to this person"
    refute html =~ "Declare draw match"
  end

  test "displays another user with action buttons", %{conn: conn, user: _user} do
    other_user = user_fixture()
    {:ok, _show_live, html} = live(conn, ~p"/users/#{other_user}")

    assert html =~ "This is a player on this app"
    assert html =~ other_user.email
    assert html =~ "I lost to this person"
    assert html =~ "Declare draw match"
  end
&lt;span class="p"&gt;end
&lt;/span&gt;
describe "Show" do
  setup [:create_user]

  test "displays user", %{conn: conn, user: user} do
    {:ok, _show_live, html} = live(conn, ~p"/users/#{user}")

    assert html =~ "This is a player on this app"
    assert html =~ user.email
&lt;span class="gi"&gt;+   refute html =~ "I lost to this person"
+   refute html =~ "Declare draw match"
&lt;/span&gt;  end
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But we forgot to test our actions! Before we do that we are going to do two small HTML tweaks that will make our testing live easier. We are going to add tags to help us find the number of points users have inside our HTML. Inside &lt;code&gt;show.ex&lt;/code&gt; change this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-&amp;lt;:item title="Points"&amp;gt;&amp;lt;%= @user.points %&amp;gt;&amp;lt;/:item&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+&amp;lt;:item title="Points"&amp;gt;&amp;lt;span data-points&amp;gt;&amp;lt;%= @user.points %&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/:item&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And inside &lt;code&gt;app.html.heex&lt;/code&gt; change this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;li class="text-[0.8125rem] leading-6 text-zinc-900"&amp;gt;
  &amp;lt;%= @current_user.email %&amp;gt;
- (&amp;lt;%= @current_user.points %&amp;gt; points)
+ &amp;lt;span data-my-points&amp;gt;(&amp;lt;%= @current_user.points %&amp;gt; points)&amp;lt;/span&amp;gt;
&amp;lt;/li&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, add an alias to &lt;code&gt;Champions.Accounts&lt;/code&gt; on the top of our &lt;code&gt;user_live_test.exs&lt;/code&gt; file. We are going to be using it to check the state of things in the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;defmodule ChampionsWeb.UserLiveTest do
&lt;/span&gt;  use ChampionsWeb.ConnCase

+ alias Champions.Accounts
  import Phoenix.LiveViewTest
  import Champions.AccountsFixtures
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From now on we will add tests to "Authenticated Show". Here's how you can trigger a click action from LiveView:&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;test&lt;/span&gt; &lt;span class="s2"&gt;"concede 3 points when I lose to another player"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;conn:&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;user:&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;do&lt;/span&gt;
  &lt;span class="n"&gt;other_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_fixture&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;show_live&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_html&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;live&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="sx"&gt;~p"/users/#{other_user}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;other_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;show_live&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"I lost to this person"&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;render_click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;show_live&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"span[data-points]"&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;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt;
  &lt;span class="n"&gt;assert&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!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other_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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&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 time we use new helpers from &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html"&gt;LiveViewTest&lt;/a&gt;. First, we need to locate the button so we use &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#element/3"&gt;element/3&lt;/a&gt; to locate a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; with a certain text. After that, we use &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#render_click/2"&gt;render_click/2&lt;/a&gt; to trigger our event. After that, we use &lt;code&gt;element/3&lt;/code&gt; again on our LiveView to find the &lt;code&gt;span[data-points]&lt;/code&gt; we just added to see if the value 3 appears there, this ensures our LiveView is updating the UI. The last test line gets the latest state of the other user and asserts it should be 3. Time to add the draw match test:&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;test&lt;/span&gt; &lt;span class="s2"&gt;"concede 1 point to each user when there's a draw match"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;conn:&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;user:&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;do&lt;/span&gt;
  &lt;span class="n"&gt;other_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_fixture&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;show_live&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_html&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;live&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="sx"&gt;~p"/users/#{other_user}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;assert&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;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;other_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;show_live&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Declare draw match"&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;render_click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;show_live&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"span[data-my-points]"&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;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;
  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;show_live&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"span[data-points]"&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;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;
  &lt;span class="n"&gt;assert&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!&lt;/span&gt;&lt;span class="p"&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="n"&gt;assert&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!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other_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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Very similar to the previous test but we add checks for the logged-in user too. At this point, we should be very confident our tests cover the important bits of our user interactions. Now let's spend a bit of our time on optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactoring because we can
&lt;/h2&gt;

&lt;p&gt;Now that we have unit tests that ensure our system work we have room for some refactoring without having the trouble of manual testing every step of the way. Whenever we change something just run &lt;code&gt;mix test&lt;/code&gt; and you should be good to go. I've saved some cool things just for now.&lt;/p&gt;

&lt;p&gt;The first thing we can notice is that both &lt;code&gt;handle_event/3&lt;/code&gt; have the same check for users being logged in and users being different. We can use pattern matching to make that simpler! Let's start with one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;@impl&lt;/span&gt; true
&lt;span class="gi"&gt;+def handle_event(_event_name, _value, %{assigns: %{current_user: nil}} = socket) do
+ {:noreply, put_flash(socket, :error, "You must be a player to do that!")}
+end
+
&lt;/span&gt;&lt;span class="p"&gt;def handle_event("concede_loss", _value, socket) do
&lt;/span&gt;  socket =
    case {socket.assigns.current_user, socket.assigns.user} do
&lt;span class="gd"&gt;-     {nil, _user} -&amp;gt;
-       put_flash(socket, :error, "You must be a player to do that!")
-
&lt;/span&gt;      {%{id: my_id}, %{id: other_id}} when my_id == other_id -&amp;gt;
        put_flash(socket, :error, "You can't give points to yourself")

      {_my_user, user} -&amp;gt;
        {:ok, updated_user} = Accounts.update_user_points(user, user.points + 3)
        assign(socket, :user, updated_user)
    end

  {:noreply, socket}
&lt;span class="p"&gt;end
&lt;/span&gt;
def handle_event("concede_draw", _value, socket) do
  socket =
    case {socket.assigns.current_user, socket.assigns.user} do
&lt;span class="gd"&gt;-     {nil, _user} -&amp;gt;
-       put_flash(socket, :error, "You must be a player to do that!")
-
&lt;/span&gt;      {%{id: my_id}, %{id: other_id}} when my_id == other_id -&amp;gt;
        put_flash(socket, :error, "You can't give points to yourself")

      {my_user, user} -&amp;gt;
        {:ok, updated_user} = Accounts.update_user_points(user, user.points + 1)
        {:ok, updated_my_user} = Accounts.update_user_points(my_user, my_user.points + 1)
        socket
        |&amp;gt; assign(:user, updated_user)
        |&amp;gt; assign(:current_user, updated_my_user)
    end

  {:noreply, socket}
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We added a clause for any &lt;code&gt;handle_event/3&lt;/code&gt; on the top that ignores the &lt;code&gt;_event_name&lt;/code&gt; and matches if &lt;code&gt;current_user&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt;. If it is, stop everything and &lt;code&gt;put_flash&lt;/code&gt; that error message. With that simple addition, we already traded 4 duplicated lines with 3 lines that should be easier to read. It's not really about the line count, it's all about being able to easily see that this first clause has an important meaning for all the events below. Let's do the same for the other error condition. Here's the final product:&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;@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;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_event_name&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="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="no"&gt;nil&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="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;put_flash&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="s2"&gt;"You must be a player to do that!"&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="n"&gt;_event_name&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="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;current_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;user:&lt;/span&gt; &lt;span class="n"&gt;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;socket&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;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="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;id&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;put_flash&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="s2"&gt;"You can't give points to yourself"&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="s2"&gt;"concede_loss"&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="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;user:&lt;/span&gt; &lt;span class="n"&gt;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;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;updated_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;update_user_points&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="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&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="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;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;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_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;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;"concede_draw"&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="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;current_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;user:&lt;/span&gt; &lt;span class="n"&gt;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;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;updated_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;update_user_points&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="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;updated_my_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;update_user_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_user&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="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_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="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="n"&gt;updated_my_user&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;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;phx-click&lt;/code&gt; works for sending events from HTML to your LiveView.&lt;/li&gt;
&lt;li&gt;LiveView events are caught by &lt;code&gt;handle_event/3&lt;/code&gt; which takes 3 arguments: event name, value, and the socket.&lt;/li&gt;
&lt;li&gt;Being able to read LiveView errors will help you creating your applications.&lt;/li&gt;
&lt;li&gt;You can access assigns from &lt;code&gt;socket.assigns&lt;/code&gt; including &lt;code&gt;current_user&lt;/code&gt; to get data about yourself if you're logged in.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;root.html.heex&lt;/code&gt; never updates based on assigns except for &lt;code&gt;page_title&lt;/code&gt;. &lt;code&gt;app.html.heex&lt;/code&gt; will be updated if they use assigns.&lt;/li&gt;
&lt;li&gt;Dead Views and LiveViews can opt out of using &lt;code&gt;app.html.heex&lt;/code&gt; if they need.&lt;/li&gt;
&lt;li&gt;To verify that your user is logged in, check for &lt;code&gt;socket.assigns.current_user&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You can add &lt;code&gt;:if={condition}&lt;/code&gt; to HTML tags to make HEEx render or not them.&lt;/li&gt;
&lt;li&gt;You should not trust hiding buttons from unauthorized users directly on HEEx, you should validate your events callbacks.&lt;/li&gt;
&lt;li&gt;You can use &lt;code&gt;put_flash/3&lt;/code&gt; to send messages to your user.&lt;/li&gt;
&lt;li&gt;LiveView comes with useful helper functions for tests ranging from getting elements on the screen to clicking buttons.&lt;/li&gt;
&lt;li&gt;You can create &lt;code&gt;handle_event/3&lt;/code&gt; clauses to handle errors for all events below them b simply ignoring the event name.&lt;/li&gt;
&lt;li&gt;You can keep your mind at easy refactoring your code if you have a good battery of tests. &lt;code&gt;mix test&lt;/code&gt; can be your best friend.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
      <category>liveview</category>
    </item>
    <item>
      <title>The Lazy Programmer's Intro to LiveView: Chapter 6</title>
      <dc:creator>Lubien</dc:creator>
      <pubDate>Sun, 18 Jun 2023 15:07:39 +0000</pubDate>
      <link>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-6-17ef</link>
      <guid>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-6-17ef</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-1-1487"&gt;Go to Chapter 1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the user page
&lt;/h2&gt;

&lt;p&gt;Now that we have a way to list users in our application we should add a page that shows just one of them so later we can add features such as 'request match'. This chapter be very similar to the previous one with very few additions, you will be able to exercise more of the basics of LiveView that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the route
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;live_session :current_user,
&lt;/span&gt;  on_mount: [{ChampionsWeb.UserAuth, :mount_current_user}] do
  live "/users/confirm/:token", UserConfirmationLive, :edit
  live "/users/confirm", UserConfirmationInstructionsLive, :new
  live "/users", UserLive.Index, :index
&lt;span class="gi"&gt;+ live "/users/:id", UserLive.Show, :show
&lt;/span&gt;&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back to the &lt;code&gt;router.ex&lt;/code&gt; add a new route just beside your &lt;code&gt;UserLive.Index&lt;/code&gt;. This time, since we are talking about a single user, we are going to call the module &lt;code&gt;UserLive.Show&lt;/code&gt; using the &lt;code&gt;:show&lt;/code&gt; action. If you pay attention to the URL you're going to see a variable there: &lt;code&gt;:id&lt;/code&gt;. Phoenix calls those variables in the URL &lt;code&gt;params&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using params for the first time
&lt;/h2&gt;

&lt;p&gt;Inside &lt;code&gt;lib/champions_web/live/user_live/show.ex&lt;/code&gt; paste 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;ChampionsWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UserLive&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;ChampionsWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_view&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;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="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;handle_params&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;: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;:page_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Show user"&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;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="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;I'm a user&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;This time we are looking at &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:handle_params/3"&gt;handle_params/3&lt;/a&gt; a new LiveView callback. This callback is specifically meant to handle your LiveView params both when your view starts but also when those params change as we will be seeing in later chapters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sg5NiZtX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/rB5iREN12_QRqlzkGLHkl8o_.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sg5NiZtX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/rB5iREN12_QRqlzkGLHkl8o_.png" alt="" width="800" height="40"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In short and omitting some cool details for now, that's the cycle of callbacks before your view is first rendered. Whenever you need to use params, you should prefer to use &lt;code&gt;handle_params&lt;/code&gt;. One thing worth noting here: our &lt;code&gt;mount/3&lt;/code&gt; does absolutely nothing, it just returns &lt;code&gt;{:ok, socket}&lt;/code&gt; so it's fine to delete it. How can we get the &lt;code&gt;id&lt;/code&gt; from params then?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;defmodule ChampionsWeb.UserLive.Show do
&lt;/span&gt;  use ChampionsWeb, :live_view

- @impl true
&lt;span class="gd"&gt;- def mount(_params, _session, socket) do
-   {:ok, socket}
- end
&lt;/span&gt;
  @impl true
&lt;span class="gd"&gt;- def handle_params(_params, _session, socket) do
&lt;/span&gt;&lt;span class="gi"&gt;+ def handle_params(%{"id" =&amp;gt; id}, _session, socket) do
&lt;/span&gt;    {:noreply,
     socket
&lt;span class="gd"&gt;-    |&amp;gt; assign(:page_title, "Show user")}
&lt;/span&gt;&lt;span class="gi"&gt;+    |&amp;gt; assign(:page_title, "Showing user #{id}")
+    |&amp;gt; assign(:id, id)}
&lt;/span&gt;  end

  @impl true
  def render(assigns) do
    ~H"""
&lt;span class="gd"&gt;-   &amp;lt;div&amp;gt;I'm a user&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+   &amp;lt;div&amp;gt;I'm the user &amp;lt;%= @id %&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span&gt;    """
  end
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of ignoring our &lt;code&gt;params&lt;/code&gt; using the &lt;code&gt;_&lt;/code&gt; operator at the start of its name we changed the first argument of &lt;code&gt;handle_params/3&lt;/code&gt; to receive a Map that contains the key &lt;code&gt;"id"&lt;/code&gt; and pattern matched it out. Now we have a variable &lt;code&gt;id&lt;/code&gt; so we used that to update the &lt;code&gt;page_title&lt;/code&gt; assign to be more specific and even created a new assign called &lt;code&gt;id&lt;/code&gt; to be used on our render function as &lt;code&gt;@id&lt;/code&gt;. So far we haven't actually fetched the user though.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;defmodule ChampionsWeb.UserLive.Show do
&lt;/span&gt;  use ChampionsWeb, :live_view

+ alias Champions.Accounts

  @impl true
  def handle_params(%{"id" =&amp;gt; id}, _session, socket) do
&lt;span class="gi"&gt;+   user = Accounts.get_user!(id)
&lt;/span&gt;    {:noreply,
     socket
&lt;span class="gd"&gt;-    |&amp;gt; assign(:page_title, "Showing user #{id}")
&lt;/span&gt;&lt;span class="gi"&gt;+    |&amp;gt; assign(:page_title, "Showing user #{user.email}")
&lt;/span&gt;&lt;span class="gd"&gt;-    |&amp;gt; assign(:id, id)}
&lt;/span&gt;&lt;span class="gi"&gt;+    |&amp;gt; assign(:user, user)}
&lt;/span&gt;  end

  @impl true
  def render(assigns) do
    ~H"""
&lt;span class="gd"&gt;-   &amp;lt;div&amp;gt;I'm the user &amp;lt;%= @id %&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+   &amp;lt;div&amp;gt;I'm the user &amp;lt;%= @user.email %&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span&gt;    """
  end
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing we needed to do was an alias to &lt;code&gt;Champions.Accounts&lt;/code&gt; so we won't need to write &lt;code&gt;Champions.&lt;/code&gt; when we use the &lt;code&gt;Accounts&lt;/code&gt; context. After that we used &lt;code&gt;Accounts.get_user!/1&lt;/code&gt;, a function created by Phoenix auth generator. We then updated our &lt;code&gt;page_title&lt;/code&gt; to be more descriptive and changed from using an &lt;code&gt;id&lt;/code&gt; assignment to an &lt;code&gt;user&lt;/code&gt; assignment. Let's make this page slightly prettier. Change your render function to 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="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;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="k"&gt;do&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;.header&amp;gt;
    User &amp;lt;%= @user.id %&amp;gt;
    &amp;lt;:subtitle&amp;gt;This is a player on this app.&amp;lt;/:subtitle&amp;gt;
  &amp;lt;/.header&amp;gt;

  &amp;lt;.list&amp;gt;
    &amp;lt;:item title="&lt;/span&gt;&lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;%= @user.email %&amp;gt;&amp;lt;/:item&amp;gt;
    &amp;lt;:item title="&lt;/span&gt;&lt;span class="no"&gt;Points&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;%= @user.points %&amp;gt;&amp;lt;/:item&amp;gt;
  &amp;lt;/.list&amp;gt;

  &amp;lt;.back navigate={~p"&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="s2"&gt;"}&amp;gt;Back to users&amp;lt;/.back&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 already know about the &lt;code&gt;&amp;lt;.header&amp;gt;&lt;/code&gt; component albeit you're seeing how to use &lt;code&gt;&amp;lt;:subtitle&amp;gt;&lt;/code&gt; inside it now but there are also two new components here for you and both come from Phoenix by default too. The first one is &lt;code&gt;&amp;lt;.list&amp;gt;&lt;/code&gt; which is kinda like a horizontal table but instead of each row being an element on a list we use manually add each row using &lt;code&gt;&amp;lt;:item&amp;gt;&lt;/code&gt;. Also, another fun component to use is &lt;code&gt;&amp;lt;.back&amp;gt;&lt;/code&gt; which will simulate a back button press to the appointed path passed to it's &lt;code&gt;navigate&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;Oh no, we just realized we have a way from going from &lt;code&gt;/users/:id&lt;/code&gt; to &lt;code&gt;/users&lt;/code&gt; but we don't have the other way around. Our users are in trouble. Let's go back to &lt;code&gt;lib/champions_web/live/user_live/index.ex&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;@impl&lt;/span&gt; true
&lt;span class="p"&gt;def render(assigns) do
&lt;/span&gt;  ~H"""
  &amp;lt;.header&amp;gt;
    Listing Users
  &amp;lt;/.header&amp;gt;

  &amp;lt;.table
    id="users"
    rows={@streams.users}
&lt;span class="gi"&gt;+   row_click={fn {_id, user} -&amp;gt; JS.navigate(~p"/users/#{user}") end}
&lt;/span&gt;  &amp;gt;
    &amp;lt;:col :let={{_id, user}} label="Email"&amp;gt;&amp;lt;%= user.email %&amp;gt;&amp;lt;/:col&amp;gt;
    &amp;lt;:col :let={{_id, user}} label="Points"&amp;gt;&amp;lt;%= user.points %&amp;gt;&amp;lt;/:col&amp;gt;
  &amp;lt;/.table&amp;gt;
  """
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phoenix got you covered. The &lt;code&gt;&amp;lt;.table&amp;gt;&lt;/code&gt; component can receive an &lt;code&gt;row_click&lt;/code&gt; attribute that receives a function that takes a single argument &lt;code&gt;{id, element}&lt;/code&gt; and you can use &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.JS.html#navigate/1"&gt;JS.navigate/1&lt;/a&gt; to change into the user page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding tests!
&lt;/h2&gt;

&lt;p&gt;Let's get back to &lt;code&gt;test/champions_web/live/user_live_test.exs&lt;/code&gt; and add a new test suite:&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;describe&lt;/span&gt; &lt;span class="s2"&gt;"Show"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:create_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"displays user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;conn:&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;user:&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;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;_show_live&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;live&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="sx"&gt;~p"/users/#{user}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="s2"&gt;"This is a player on this app"&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;html&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;email&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;Nothing really magical here, we just go straight to the page and verify that the information that should be there is there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Phoenix routes can receive variables using the &lt;code&gt;:param_name&lt;/code&gt; syntax inside it's URL.&lt;/li&gt;
&lt;li&gt;Params are passed to LiveViews inside both &lt;code&gt;mount/3&lt;/code&gt; and &lt;code&gt;handle_params/3&lt;/code&gt;. They are run in that order before our render function.&lt;/li&gt;
&lt;li&gt;Devs should prefer using params on &lt;code&gt;handle_params/3&lt;/code&gt; because it's aware of when params change in some specific cases we will dive in later.&lt;/li&gt;
&lt;li&gt;Phoenix comes with a &lt;code&gt;&amp;lt;.list&amp;gt;&lt;/code&gt; component for horizontal lists and &lt;code&gt;&amp;lt;.back&amp;gt;&lt;/code&gt; for navigating back through pages.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;.table&amp;gt;&lt;/code&gt; component can receive an &lt;code&gt;row_click&lt;/code&gt; attribute to handle clicks on rows. We used that to navigate from the users list to the user page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-7-47e0"&gt;Read chapter 7: Conceding points to the winner&lt;/a&gt;&lt;/p&gt;

</description>
      <category>phoenix</category>
      <category>liveview</category>
      <category>elixir</category>
    </item>
    <item>
      <title>The Lazy Programmer's Intro to LiveView: Chapter 5</title>
      <dc:creator>Lubien</dc:creator>
      <pubDate>Sun, 18 Jun 2023 15:07:35 +0000</pubDate>
      <link>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-5-5cn3</link>
      <guid>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-5-5cn3</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-1-1487"&gt;Go to Chapter 1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Listing our users
&lt;/h2&gt;

&lt;p&gt;Before it's possible for users to create matches between themselves they must at least be able to know who else is on the platform. For this, we are going to build a user list page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating &lt;code&gt;Accounts.list_users&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Now you should be aware how to add a new context function so let's speedrun through it. Head out to &lt;code&gt;accounts.ex&lt;/code&gt; and add this 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="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
List users.

## Examples

    iex&amp;gt; list_users()
    [%User{}]

"""&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;list_users&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;Repo&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="no"&gt;User&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;Unlike the previous function we created this time we are using &lt;a href="https://hexdocs.pm/ecto/Ecto.Repo.html#c:all/2"&gt;Repo.all/2&lt;/a&gt; to list all entries on the table corresponding to the &lt;code&gt;User&lt;/code&gt; model. That should be enough for now. Let's add a new unit test to &lt;code&gt;account_test.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;describe&lt;/span&gt; &lt;span class="s2"&gt;"list_users/0"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"show all users on our system"&lt;/span&gt; &lt;span class="k"&gt;do&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;user_fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;list_users&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 cool thing about the code above is that the assertion above just says 'create an user then listing users should return that exact same user' using the amazing &lt;a href="https://elixir-lang.org/getting-started/pattern-matching.html#the-pin-operator"&gt;pin operator&lt;/a&gt; (&lt;code&gt;^&lt;/code&gt;). In case you never seen it in action, definitely take a look later. This should be enough for our context for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating your first new route
&lt;/h2&gt;

&lt;p&gt;So far you got new routes for free using generators. Time to do it by hand. The first thing you want to check is your &lt;code&gt;router.ex&lt;/code&gt; file under &lt;code&gt;champions_web&lt;/code&gt;. There's a ton of new things here but to start simple, look for &lt;code&gt;live_session :current_user&lt;/code&gt;, and we are going to put our new route there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;live_session :current_user,
&lt;/span&gt;  on_mount: [{ChampionsWeb.UserAuth, :mount_current_user}] do
  live "/users/confirm/:token", UserConfirmationLive, :edit
  live "/users/confirm", UserConfirmationInstructionsLive, :new
&lt;span class="gi"&gt;+ live "/users", UserLive.Index, :index
&lt;/span&gt;&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we will be creating a LiveView we used the &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Router.html#live/4"&gt;live/4&lt;/a&gt; macro. The arguments we pass are the route path name, the LiveView file name and the live action. Since the LiveView we passed is called &lt;code&gt;UserLive.Index&lt;/code&gt; that means we must define a module called &lt;code&gt;ChampionsWeb.UserLive.Index&lt;/code&gt;. As for the live action it could be any atom but it's recommended to use &lt;code&gt;:index&lt;/code&gt;, &lt;code&gt;:show&lt;/code&gt;, &lt;code&gt;:edit&lt;/code&gt;, &lt;code&gt;:new&lt;/code&gt;, &lt;code&gt;:create&lt;/code&gt; and &lt;code&gt;:update&lt;/code&gt; whenever possible. We will take a look on them over the project's progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the your first LivView
&lt;/h2&gt;

&lt;p&gt;Create the &lt;code&gt;lib/champions_web/live/user_live&lt;/code&gt; folder then make a file called &lt;code&gt;index.ex&lt;/code&gt;. Let's start by creating your first LiveView ever.&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;ChampionsWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UserLive&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;ChampionsWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_view&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;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="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;Hello LiveView&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;This is the minimal LiveView module you can create to show a Hello World. First of all, the module name is important: it should match what you defined inside &lt;code&gt;router.ex&lt;/code&gt; to the letter prepended by &lt;code&gt;ChampionsWeb&lt;/code&gt;. The second line contains the &lt;code&gt;use&lt;/code&gt; macro to import useful things for LiveView modules defined under &lt;code&gt;ChampionsWeb&lt;/code&gt;. If you're curious open &lt;code&gt;champions_web.ex&lt;/code&gt; and look for &lt;code&gt;def live_view do&lt;/code&gt;, we will talk about this at another time.&lt;/p&gt;

&lt;p&gt;The real start of the module above really is the &lt;code&gt;render/1&lt;/code&gt; function. All LiveViews must have a render function that takes exactly one argument called &lt;code&gt;assigns&lt;/code&gt;. Those functions often return a HEEx markup delimited by the &lt;code&gt;H sigil&lt;/code&gt; (often wrote as &lt;code&gt;~H"""&lt;/code&gt;). This time it only returns a single div. Open &lt;a href="http://localhost:4000/users"&gt;http://localhost:4000/users&lt;/a&gt; to see this working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving the page a title
&lt;/h2&gt;

&lt;p&gt;LiveViews separate state management from view rendering. The rule of thumb is that your render function should only know how to render whatever it is in your assigns and the other LiveView functions, known as callbacks, are responsible of managing the state you need.&lt;/p&gt;

&lt;p&gt;To get started let's meet your first LiveView callback: &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:mount/3"&gt;mount/3&lt;/a&gt;. This callback is responsible often for starting the initial state of your view and also is likely to fetch data from the server. Let's start with the simplest 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;@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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;mount/3&lt;/code&gt; callback receives parameters from the URL, the session state and the socket but ignore everything but the socket. All &lt;code&gt;mount/3&lt;/code&gt; must return a tuple with &lt;code&gt;:ok&lt;/code&gt; as the first element and a socket as the second argument. Sockets maintain the state of your LiveView. We will talk more about that over time. The current state of that callback is the same as it didn't exist, it does nothing.&lt;/p&gt;

&lt;p&gt;If you are on &lt;code&gt;/users&lt;/code&gt; and look at your tab name you're going to see something generic including the name of your app. Let's improve that. Update your &lt;code&gt;mount/3&lt;/code&gt; callback to 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="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;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&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:page_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Listing Users"&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;Now instead of returning the socket as-is we used &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#assign/3"&gt;assign/3&lt;/a&gt; to assign &lt;code&gt;page_title&lt;/code&gt; to your socket state. Why &lt;code&gt;page_title&lt;/code&gt;? This assign is used by &lt;code&gt;root.html.heex&lt;/code&gt; to generate your HTML &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag. Looking at your tab name should show 'Listing Users · Phoenix Framework' now. You'll notice we will be adding &lt;code&gt;page_title&lt;/code&gt; assign to most if not all pages from now on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using streams to list users
&lt;/h2&gt;

&lt;p&gt;Recently LiveView added a new memory efficient way of handling collections of data: streams. For the sake of this chapter will overly simplify things and just say streams are used for lists of things. They behave a lot like assigns: you defined them on callbacks and your render function renders them. Let's rewrite out module:&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;ChampionsWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UserLive&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;ChampionsWeb&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;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Accounts&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;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&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:page_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Listing Users"&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;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:users&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;list_users&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="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;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="k"&gt;do&lt;/span&gt;
    &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
    &amp;lt;.header&amp;gt;
      Listing Users
    &amp;lt;/.header&amp;gt;

    &amp;lt;.table
      id="&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="s2"&gt;"
      rows={@streams.users}
    &amp;gt;
      &amp;lt;:col :let={{_id, user}} label="&lt;/span&gt;&lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;%= user.email %&amp;gt;&amp;lt;/:col&amp;gt;
      &amp;lt;:col :let={{_id, user}} label="&lt;/span&gt;&lt;span class="no"&gt;Points&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;%= user.points %&amp;gt;&amp;lt;/:col&amp;gt;
    &amp;lt;/.table&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;The first step to create a stream is, just like assigns, add it to the socket. With line 11 we create &lt;code&gt;@streams.users&lt;/code&gt; containing the list of all users in our platform. Phoenix comes packed with a component called &lt;code&gt;&amp;lt;.table&amp;gt;&lt;/code&gt; that is aware of streams and can render those just fine. All you need to do is pass two parameters to it: &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;rows&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;&amp;lt;.table&amp;gt;&lt;/code&gt; we can spot two &lt;code&gt;&amp;lt;:col&amp;gt;&lt;/code&gt; slots, each representing a single column on our table. In short those part of the &lt;code&gt;&amp;lt;.table&amp;gt;&lt;/code&gt; component as we used them to render each item from our stream. There's also the &lt;code&gt;&amp;lt;.header&amp;gt;&lt;/code&gt; component but that's very self explanatory. Your &lt;code&gt;/users&lt;/code&gt; page should look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Lj8PdJUu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/xx2_DEE-QiC3Gg2TDfnyux5C.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Lj8PdJUu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/xx2_DEE-QiC3Gg2TDfnyux5C.png" alt="A simple table listing one user with email lubien@example.com with 0 points." width="800" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing your LiveView
&lt;/h2&gt;

&lt;p&gt;It brings me so much joy to say that testing LiveViews are not only easy but support for it comes by default. Since our LiveView file is &lt;code&gt;lib/champions_web/live/user_live/index.ex&lt;/code&gt; our test file will live in &lt;code&gt;test/champions_web/live/user_live_test.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="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ChampionsWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UserLiveTest&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;ChampionsWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ConnCase&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;LiveViewTest&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Champions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;AccountsFixtures&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;create_user&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="k"&gt;do&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;user_fixture&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:&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="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"Index"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:create_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"lists all users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;conn:&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;user:&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;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;_index_live&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;live&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="sx"&gt;~p"/users"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="s2"&gt;"Listing Users"&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;html&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;email&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;The whole magic lies in &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#live/2"&gt;live/2&lt;/a&gt; from the &lt;code&gt;Phoenix.LiveViewTest&lt;/code&gt; module. All you need to do is pass a &lt;code&gt;conn&lt;/code&gt;, which already comes ready for tests that &lt;code&gt;use ChampionsWeb.ConnCase&lt;/code&gt; and a route such as &lt;code&gt;~p"/users"&lt;/code&gt;. The return value is a 3-tuple of &lt;code&gt;:ok&lt;/code&gt;, a reference to the LiveView so we could do things like navigation or clicking buttons, and last but not least the rendered HTML so far. For this test all we need to do is check if the title and HTML contain what we expect such as the user email and the title on our render function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Using &lt;a href="https://hexdocs.pm/ecto/Ecto.Repo.html#c:all/2"&gt;Repo.all/2&lt;/a&gt; its possible to query all items from an Ecto model.&lt;/li&gt;
&lt;li&gt;To add new pages to Phoenix always start with the &lt;code&gt;router.ex&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Phoenix routes map to  modules such as &lt;code&gt;live "/users", UserLive.Index, :index&lt;/code&gt; mapping to &lt;code&gt;ChampionsWeb.UserLive.Index&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It's a common practice to call route actions as &lt;code&gt;:index&lt;/code&gt;, &lt;code&gt;:new&lt;/code&gt;, &lt;code&gt;:create&lt;/code&gt;, &lt;code&gt;:edit&lt;/code&gt;, &lt;code&gt;:update&lt;/code&gt; and &lt;code&gt;:delete&lt;/code&gt; but any other atom works such as &lt;code&gt;:confirm_email&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To create a LiveView you start with creating the render function.&lt;/li&gt;
&lt;li&gt;Render functions always take one argument: &lt;code&gt;assigns&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;LiveView manages the state via callback functions.&lt;/li&gt;
&lt;li&gt;We can use the &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:mount/3"&gt;mount/3&lt;/a&gt; callback to create the initial state of our LiveView.&lt;/li&gt;
&lt;li&gt;Assigning something to &lt;code&gt;page_title&lt;/code&gt; change the value of the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag because that's used on &lt;code&gt;root.html.heex&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Assigns are just variables accessible on the LiveView render function using &lt;code&gt;@assign_name&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Streams are how LiveView handles collections of data in a memory-efficient manner. Usually used for lists.&lt;/li&gt;
&lt;li&gt;Streams are very like assigns: you defined and manage them on callbacks then use them on the render function as &lt;code&gt;@streams.stream_name&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Phoenix comes with helper components such as &lt;code&gt;&amp;lt;.header&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;.table&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;.table&amp;gt;&lt;/code&gt; component does all the stream's magic so we can learn that later.&lt;/li&gt;
&lt;li&gt;Testing a LiveView can be quite easy with the &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#live/2"&gt;live/2&lt;/a&gt; helper.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-6-17ef"&gt;Read chapter 6: Creating the user page&lt;/a&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
      <category>liveview</category>
    </item>
    <item>
      <title>The Lazy Programmer's Intro to LiveView: Chapter 4</title>
      <dc:creator>Lubien</dc:creator>
      <pubDate>Sun, 18 Jun 2023 15:06:39 +0000</pubDate>
      <link>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-4-1nim</link>
      <guid>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-4-1nim</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-1-1487"&gt;Go to Chapter 1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding points to users
&lt;/h2&gt;

&lt;p&gt;In the previous chapter, we just added the points column to &lt;code&gt;users&lt;/code&gt; table. We need a way to add points to users. This time we are going to learn how to use Ecto to modify rows in our database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecto Changesets 101
&lt;/h2&gt;

&lt;p&gt;Ecto comes with a powerful abstraction to validate and map data from user input to the database, it's called &lt;a href="https://hexdocs.pm/ecto/Ecto.Changeset.html"&gt;Ecto.Changeset&lt;/a&gt;. Think of it as an intermediate state between user input and actually storing the data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WPmqCAAE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/3UMQmIMv1yKOyZzmWePbFS2H.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WPmqCAAE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://slabstatic.com/prod/uploads/or5adfz5/posts/images/3UMQmIMv1yKOyZzmWePbFS2H.png" alt='Words "Model+User input" with a arrow pointing to "Changet" with another arrow pointing to "Stored in Database"' width="800" height="33"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;User inputs usually come in the form of an Elixir map (a key-value structure) such as &lt;code&gt;%{"points" =&amp;gt; 10}&lt;/code&gt;. Since we want to make changes, we must change something. If you wanted to update an existing user, you first need to get that user then you'd have a variable with a value such as &lt;code&gt;%User{email: "…", …}&lt;/code&gt;. Combine both with a &lt;code&gt;change_*&lt;/code&gt; function and we have a changeset. Let's do it from scratch!&lt;/p&gt;

&lt;p&gt;Since we are going to make a function that modifies our users it makes sense to use the &lt;code&gt;accounts.ex&lt;/code&gt; context and since this is related to our Ecto model we will also be modifying our &lt;code&gt;user.ex&lt;/code&gt; file too. With the same reasoning that means we will be testing this at &lt;code&gt;accounts_test.exs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Head out to &lt;code&gt;lib/champions/accounts/user.ex&lt;/code&gt;. You'll notice there are multiple &lt;code&gt;*_changeset&lt;/code&gt; named functions with specific intents. Scroll to the bottom and create a new function called &lt;code&gt;points_changeset/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="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
A user changeset to update points
"""&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;points_changeset&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="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;user&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;:points&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;:points&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;:points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;greater_than_or_equal_to:&lt;/span&gt; &lt;span class="mi"&gt;0&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;There's a lot to unpack so let's go bit by bit. We use &lt;code&gt;@doc&lt;/code&gt; to give this function a nice documentation for the developers of the future who want to use this. Following that we define a function that takes two arguments: the user to be modified and the new data on the &lt;code&gt;attrs&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;The fun comes with the pipes. It's all about transformation. On line 5 we start with the user as-is. On line 6 we use &lt;a href="https://hexdocs.pm/ecto/Ecto.Changeset.html#cast/4"&gt;cast/4&lt;/a&gt; to convert the user into a changeset but we limit the &lt;code&gt;attrs&lt;/code&gt; to only read &lt;code&gt;points&lt;/code&gt; changes and ignore everything else. Next, we do two &lt;a href="https://hexdocs.pm/ecto/Ecto.Changeset.html#summary"&gt;validations&lt;/a&gt;: we want to make &lt;code&gt;points&lt;/code&gt; mandatory and it should be a non-negative integer. By the end of this pipe chain, we have a validated changeset without any trouble at all.&lt;/p&gt;

&lt;p&gt;Then, we need to expose this function to our Accounts context. Head out to &lt;code&gt;lib/champions/accounts.ex&lt;/code&gt; and create a new function in the end called &lt;code&gt;change_user_points/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="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
Returns an `%Ecto.Changeset{}` for tracking user changes.

## Examples

    iex&amp;gt; change_user_points(user)
    %Ecto.Changeset{data: %User{}}

"""&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;change_user_points&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;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;user&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;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points_changeset&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="n"&gt;attrs&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;As you can see this is just an alias to the changeset contained in the Ecto model. Making your context module the place to talk to the external world (your_app_web) is a good practice. Last but not least, we need to test that this actually works. go to &lt;code&gt;test/champions/accounts_test.exs&lt;/code&gt; and add a new test case in the end:&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;describe&lt;/span&gt; &lt;span class="s2"&gt;"change_user_points/2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"accepts non-negative integers"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;assert&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="o"&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;change_user_points&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"points"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;refute&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;valid?&lt;/span&gt;

    &lt;span class="n"&gt;assert&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="o"&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;change_user_points&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"points"&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;assert&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;valid?&lt;/span&gt;

    &lt;span class="n"&gt;assert&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="o"&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;change_user_points&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"points"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;assert&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;valid?&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;With this test, we pass an empty user and a negative, zero, and positive amount of points. Note that at all times it returns an Ecto.Changeset, what changes is that if it's invalid the changeset &lt;code&gt;valid?&lt;/code&gt; property will be false.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying changesets using Repo
&lt;/h2&gt;

&lt;p&gt;Whenever an Ecto user wants to apply changes to the database they must go through a Repo. Think of those as abstractions between Elixir and the database itself. You don't need to handle creating connections, recovering from disconnections, writing SQL, and many other things because Repo will take care of that for you alongside your changeset. Since Phoenix by default comes with Postgres support you should have a &lt;code&gt;Champions.Repo&lt;/code&gt; module ready to be used at any time, otherwise not even the migrations would have worked.&lt;/p&gt;

&lt;p&gt;Let's create a high-level function on &lt;code&gt;Accounts&lt;/code&gt; that takes a user and a number of points and updated the user points. Inside &lt;code&gt;accounts.ex&lt;/code&gt; create a new function &lt;code&gt;update_user_points/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="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
Updates the current number of points of a user

## Examples

    iex&amp;gt; update_user_points(user, 10)
    {:ok, %User{points: 10}}

"""&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;update_user_points&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;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;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;points&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;user&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;change_user_points&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="s2"&gt;"points"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;points&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;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&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 only new thing here is that there's a pipe after the changeset that calls &lt;a href="https://hexdocs.pm/ecto/Ecto.Repo.html#c:update/2"&gt;Repo.update/2&lt;/a&gt; to trigger the changes to the database. In summary, the changeset works as a mapping of what needs to be done in the database and Repo actually applies those changes. To test this out add a new unit test to &lt;code&gt;accounts_test.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;describe&lt;/span&gt; &lt;span class="s2"&gt;"set_user_points/2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;setup&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;user:&lt;/span&gt; &lt;span class="n"&gt;user_fixture&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;test&lt;/span&gt; &lt;span class="s2"&gt;"updates the amounts of points of an existing user"&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:&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;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;updated_user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;update_user_points&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;updated_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&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;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;A cool thing worth mentioning here is that out is that the Phoenix auth generator also created the helper &lt;code&gt;user_fixture/1&lt;/code&gt; function that creates a user with zero trouble so your unit tests can be DRY.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;To store data in the database Ecto uses changesets.&lt;/li&gt;
&lt;li&gt;Changesets validate and prepare which data can go to the database, you can even have more than one per Ecto model. Ecto.Changeset comes with useful validation functions.&lt;/li&gt;
&lt;li&gt;Repo is how Ecto talks to the database.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hexdocs.pm/ecto/Ecto.Repo.html#c:update/2"&gt;Repo.update/2&lt;/a&gt; reads a changeset and applies changes to the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-5-5cn3"&gt;Read chapter 5: Listing our users&lt;/a&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>liveview</category>
      <category>phoenix</category>
    </item>
    <item>
      <title>The Lazy Programmer's Intro to LiveView: Chapter 3</title>
      <dc:creator>Lubien</dc:creator>
      <pubDate>Sun, 18 Jun 2023 15:04:23 +0000</pubDate>
      <link>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-3-4b59</link>
      <guid>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-3-4b59</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-1-1487"&gt;Go to Chapter 1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a points system
&lt;/h2&gt;

&lt;p&gt;From the very beginning, I mentioned this project is all about competition, which means we need a way to say a user has a certain amount of points. In this chapter, we are going to create the simplest and dumbest point system so we can use this as an excuse to learn more about what Phoenix auth generator created for us.&lt;/p&gt;

&lt;p&gt;The modeling is pretty simple: we are going to add a &lt;code&gt;points&lt;/code&gt; column to the &lt;code&gt;users&lt;/code&gt; table and in the meantime, we are going to learn some Ecto.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are migrations?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;If you know what migrations are, skip this section, it's meant for beginners.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Development teams need a way of synchronizing changes to their databases. Migrations is a simple technique where all changes to the database are expressed as a migration file that contains the change. If we want to add a column, we need to create a migration file that says 'Please add a column points to my table users with type integer'. This file will be committed to the project and all developers will be able to run it and make their databases be on the latest state. We call applying one or more migrations 'migrating the database'. Remember that after running &lt;code&gt;mix phx.gen.auth&lt;/code&gt; we needed to do &lt;code&gt;mix ecto.migrate&lt;/code&gt;? That's it.&lt;/p&gt;

&lt;p&gt;Migrations also need to be reversible in case something goes wrong. Using the example above the reverse command would be 'Please remove the column points from the table users'. We call reversing one or more migrations doing a 'rollback of the database'. Say you messed up your &lt;code&gt;mix phx.gen.auth&lt;/code&gt; and created a table called &lt;code&gt;userss&lt;/code&gt;, you could easily undo the error with &lt;code&gt;mix ecto.rollback&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Usually, web frameworks come with migrations support such as Rails' Active Record, Phoenix uses Ecto and AdonisJS Lucid.  Some frameworks don't come with anything related to databases such as ExpressJS so you'd need to install something like Sequelize which has migrations support too.&lt;/p&gt;

&lt;p&gt;Here's what a migration file looks like in AdonisJS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// database/migrations/1587988332388_users.js
import BaseSchema from '@ioc:Adonis/Lucid/Schema'

export default class extends BaseSchema {
  protected tableName = 'users'

  public async up() {
    this.schema.createTable(this.tableName, (table) =&amp;gt; {
      table.increments('id')
      table.timestamp('created_at', { useTz: true })
      table.timestamp('updated_at', { useTz: true })
    })
  }

  public async down() {
    this.schema.dropTable(this.tableName)
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see there's an &lt;code&gt;up&lt;/code&gt; and &lt;code&gt;down&lt;/code&gt; method to teach how to migrate and how to rollback. Also notice the filename uses a timestamp: the reason for that is so migrations must be run in the order they were created to ensure consistency.&lt;/p&gt;

&lt;p&gt;Now that you know how roughly a migration looks like and what they're used for, let's get to what matters, Ecto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrations with Ecto
&lt;/h2&gt;

&lt;p&gt;Ecto migrations are slightly different. They (most of times) don't need to define &lt;code&gt;up&lt;/code&gt; and &lt;code&gt;down&lt;/code&gt; methods because the syntax is very aware of how to migrate and rollback it. Let's take the users migrations for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 20230617121436_create_users_auth_tables.exs
defmodule Champions.Repo.Migrations.CreateUsersAuthTables do
  use Ecto.Migration

  def change do
    execute "CREATE EXTENSION IF NOT EXISTS citext", ""

    create table(:users) do
      add :email, :citext, null: false
      add :hashed_password, :string, null: false
      add :confirmed_at, :naive_datetime
      timestamps()
    end

    create unique_index(:users, [:email])

    create table(:users_tokens) do
      add :user_id, references(:users, on_delete: :delete_all), null: false
      add :token, :binary, null: false
      add :context, :string, null: false
      add :sent_to, :string
      timestamps(updated_at: false)
    end

    create index(:users_tokens, [:user_id])
    create unique_index(:users_tokens, [:context, :token])
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no &lt;code&gt;up&lt;/code&gt; and &lt;code&gt;down&lt;/code&gt;, just a &lt;code&gt;change&lt;/code&gt; method. This means we are confident Ecto can do and undo things with whatever we do there. To make it easier to understand let's break down its blocks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;execute "CREATE EXTENSION IF NOT EXISTS citext", ""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;execute/2&lt;/code&gt; function works as &lt;code&gt;up&lt;/code&gt; and &lt;code&gt;down&lt;/code&gt;. The first SQL statement is the &lt;code&gt;up&lt;/code&gt; case and the second one is the &lt;code&gt;down&lt;/code&gt;. This specific statement says 'When you migrate this, please create the extension &lt;a href="https://www.postgresql.org/docs/current/citext.html"&gt;citext&lt;/a&gt; if it does not exist' and the empty string as the second argument means 'don't do anything during rollbacks'.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create table(:users) do
  add :email, :citext, null: false
  add :hashed_password, :string, null: false
  add :confirmed_at, :naive_datetime
  timestamps()
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This block tells ecto to create this table during migration and delete it during rollbacks. It's all under the hood because of the &lt;a href="https://hexdocs.pm/ecto_sql/Ecto.Migration.html#create/2"&gt;create/2&lt;/a&gt; function combined with &lt;a href="https://hexdocs.pm/ecto_sql/Ecto.Migration.html#table/2"&gt;table/2&lt;/a&gt; function. Inside the &lt;code&gt;do&lt;/code&gt; block we can see we ask for 5 fields to be added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;code&gt;email&lt;/code&gt; of citext (case insensitive text) type non-nullable field.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;hashed_password&lt;/code&gt; string type non-nullable field.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;confirmed_at&lt;/code&gt;  &lt;a href="https://hexdocs.pm/elixir/1.12.3/NaiveDateTime.html"&gt;naive datetime&lt;/a&gt;  &lt;a href="https://hexdocs.pm/elixir/1.12.3/NaiveDateTime.html"&gt;(not aware of timezones)&lt;/a&gt; nullable field.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;timestamps/1&lt;/code&gt; creates &lt;code&gt;inserted_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt;, both naive datetime non-nullable fields with default values to what their names point at.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create unique_index(:users, [:email])
# other things here
create index(:users_tokens, [:user_id])
create unique_index(:users_tokens, [:context, :token])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the name implies, the first &lt;a href="https://hexdocs.pm/ecto_sql/Ecto.Migration.html#unique_index/3"&gt;unique_index/3&lt;/a&gt; will create on migration and delete on rollback a unique index for the &lt;code&gt;users&lt;/code&gt; table under &lt;code&gt;email&lt;/code&gt; only. &lt;a href="https://hexdocs.pm/ecto_sql/Ecto.Migration.html#index/3"&gt;index/3&lt;/a&gt; will generate a regular index and the second unique_index/3 is a composite index under &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;token&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;create table(:users_tokens) do
  add :user_id, references(:users, on_delete: :delete_all), null: false
  add :token, :binary, null: false
  add :context, :string, null: false
  add :sent_to, :string
  timestamps(updated_at: false)
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should already be able to understand most of what's happening here but the new things are &lt;a href="https://hexdocs.pm/ecto_sql/Ecto.Migration.html#references/2"&gt;references/2&lt;/a&gt; used to create a foreign key from &lt;code&gt;users_tokens&lt;/code&gt; to &lt;code&gt;users&lt;/code&gt; which will make rows from the tokens table be deleted if the foreign key in users is deleted and the fact that this table opts-out of &lt;code&gt;updated_at&lt;/code&gt; since tokens are never updated.&lt;/p&gt;

&lt;p&gt;For more details on Ecto migrations, I recommend reading their &lt;a href="https://hexdocs.pm/ecto_sql/Ecto.Migration.html#module-field-types"&gt;Ecto SQL Ecto.Migration docs and guides&lt;/a&gt; but in case you want to learn the possible types for your columns head out to &lt;a href="https://hexdocs.pm/ecto/Ecto.Schema.html#module-primitive-types"&gt;Ecto docs under Ecto.Schema&lt;/a&gt;. Yes, those are two different docs because Ecto does not necessarily means you need to use one of the default Ecto SQL databases, you could use &lt;a href="https://github.com/elixir-mongo/mongodb_ecto"&gt;Mongo&lt;/a&gt; adapter or even something like &lt;a href="https://github.com/clickhouse-elixir/clickhouse_ecto"&gt;ClickHouse&lt;/a&gt; which is also SQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating our first migration by hand
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mix help ecto.gen.migration

                             mix ecto.gen.migration                             

Generates a migration.

The repository must be set under :ecto_repos in the current app configuration
or given via the -r option.

## Examples

    $ mix ecto.gen.migration add_posts_table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once more generators come to the rescue. We could write it by hand but you'd be having to figure out the current timestamp, write some boilerplate code and all that is boring, let Ecto do that for you. The migration name doesn't impact it's effect but it's nice to be clear what you're going to do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mix ecto.gen.migration add_users_points
* creating priv/repo/migrations/20230617145124_add_users_points.exs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default code should be something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule Champions.Repo.Migrations.AddUsersPoints do
  use Ecto.Migration

  def change do

  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our only migration so far had used &lt;code&gt;create&lt;/code&gt; to create/delete a database table for us, what if we just want to add a column? &lt;a href="https://hexdocs.pm/ecto_sql/Ecto.Migration.html#alter/2"&gt;alter/2&lt;/a&gt; comes to the rescue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule Champions.Repo.Migrations.AddUsersPoints do
  use Ecto.Migration

  def change do
    alter table(:users) do
      add :points, :integer, default: 0, null: false
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No specific &lt;code&gt;up&lt;/code&gt; and &lt;code&gt;down&lt;/code&gt; needed, Ecto knows how to do these for you. We will be creating a field called points that default to 0 and can't be null. Let's run this migration. Here's a fun fact: if you pass &lt;code&gt;--log-migrations-sql&lt;/code&gt; you can see the SQL queries being run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mix ecto.migrate --log-migrations-sql

11:58:05.588 [info] == Running 20230617145124 Champions.Repo.Migrations.AddUsersPoints.change/0 forward

11:58:05.590 [info] alter table users

11:58:05.625 [debug] QUERY OK db=9.6ms
ALTER TABLE "users" ADD COLUMN "points" integer DEFAULT 0 NOT NULL []

11:58:05.626 [info] == Migrated 20230617145124 in 0.0s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, that should do… For our Postgres database at least. We still need to teach Ecto how to map this column to Elixir. Head out to &lt;code&gt;lib/champions/accounts/user.ex&lt;/code&gt; and let's add it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule Champions.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :email, :string
    field :password, :string, virtual: true, redact: true
    field :hashed_password, :string, redact: true
    field :confirmed_at, :naive_datetime
    field :points, :integer, default: 0

    timestamps()
  end

  # more stuff
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we 10x developers and we just added something meaningful to our code, we want to make sure we add tests for this! I didn't mention it to you but the Phoenix auth generator created a lot of test files. You can verify that with &lt;code&gt;mix test&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;$ mix test
................................................................................................................................
Finished in 1.9 seconds (0.6s async, 1.2s sync)
128 tests, 0 failures

Randomized with seed 490838
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's keep it simple and edit an existing test. Head out to &lt;code&gt;test/champions/accounts_test.exs&lt;/code&gt; and look for 'returns the user if the email exists'. A simple assertion should do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;test "returns the user if the email exists" do
&lt;/span&gt;  %{id: id} = user = user_fixture()
  assert %User{id: ^id} = Accounts.get_user_by_email(user.email)
&lt;span class="gi"&gt;+ assert user.points == 0
&lt;/span&gt;&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If running &lt;code&gt;mix test&lt;/code&gt; still works, we are done with Ecto. At least for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your first UI change
&lt;/h2&gt;

&lt;p&gt;Our users cannot see how many points they have so far. We can tweak our layout to show their points beside their email. Headout to &lt;code&gt;lib/champions_web/components/layouts/root.html.heex&lt;/code&gt; and look for &lt;code&gt;&amp;lt;%= @current_user do %&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;&amp;lt;%= if @current_user do %&amp;gt;
&lt;/span&gt;  &amp;lt;li class="text-[0.8125rem] leading-6 text-zinc-900"&amp;gt;
    &amp;lt;%= @current_user.email %&amp;gt;
&lt;span class="gi"&gt;+   (&amp;lt;%= @current_user.points %&amp;gt; points)
&lt;/span&gt;  &amp;lt;/li&amp;gt;
&lt;span class="err"&gt;...more&lt;/span&gt; stuff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  HEEx: your new best friend
&lt;/h2&gt;

&lt;p&gt;HEEx stands for HTML + Embedded Elixir. It's simply the way Phoenix uses to express HTML with Elixir code inside. There are a ton of amazing things behind the scenes to make HEEx a thing but we will defer talking about them to later. What you need to take away from it right now are a few key concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whenever you see a tag &lt;code&gt;&amp;lt;%= elixir code here %&amp;gt;&lt;/code&gt; that elixir code will be rendered into the HTML.&lt;/li&gt;
&lt;li&gt;To run Elixir code and not render anything in the HTML just omit the &lt;code&gt;=&lt;/code&gt; sign such as &lt;code&gt;&amp;lt;% x = 10 %&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Any variable inside tags that starts with &lt;code&gt;@&lt;/code&gt; are called assigns. They're special and we will talk about them a lot over the next chapters. For now, we used the &lt;code&gt;@current_user&lt;/code&gt; assign to render both the user email and points on the navbar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this change, we went to our &lt;code&gt;root.html.heex&lt;/code&gt; file which contains the outermost HTML of our web page, including the HTML, header, and body tags. Later on, we will be learning more about layouts when it comes to creating multiple of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Most Ecto migration codes know how to be migrated and rollbacked without needing two different code paths.&lt;/li&gt;
&lt;li&gt;Ecto &lt;code&gt;timestamps&lt;/code&gt; function creates &lt;code&gt;inserted_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt; fields unless the code opts out of one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mix ecto.gen.migration migration_name&lt;/code&gt; does the boilerplate bit of creating migration files with timestamps on names.&lt;/li&gt;
&lt;li&gt;One must also update Ecto models after doing migrations so Ecto knows which fields are available.&lt;/li&gt;
&lt;li&gt;HEEx is how Phoenix templates HTML for interfaces, it uses variables know as &lt;code&gt;assigns&lt;/code&gt; with names that start with &lt;code&gt;@&lt;/code&gt; like &lt;code&gt;@current_user&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;HEEx has special tags for rendering HTML content from Elixir code &lt;code&gt;&amp;lt;%= code %&amp;gt;&lt;/code&gt; and just running elixir code without rendering things &lt;code&gt;&amp;lt;% x = 10 %&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;root.html.heex&lt;/code&gt; contains a simple navbar that shows who's the currently logged user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-4-1nim"&gt;Read chapter 4: Adding points to users&lt;/a&gt;&lt;/p&gt;

</description>
      <category>liveview</category>
      <category>elixir</category>
      <category>phoenix</category>
    </item>
    <item>
      <title>The Lazy Programmer's Intro to LiveView: Chapter 2</title>
      <dc:creator>Lubien</dc:creator>
      <pubDate>Sun, 18 Jun 2023 15:03:07 +0000</pubDate>
      <link>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-2-2175</link>
      <guid>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-2-2175</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-1-1487"&gt;Go to Chapter 1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Authentication to Champions
&lt;/h2&gt;

&lt;p&gt;Authentication and authorization are a pain in the ass. It always felt like the most boring thing ever when I started new projects. Luckily Phoenix comes with a simple yet powerful auth system built-in. In this chapter, we are going to learn how to use Phoenix Auth Generator, aka &lt;code&gt;mix phx.gen.auth&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick tip for those who like details
&lt;/h2&gt;

&lt;p&gt;We are very soon going to run a command that generates a few files and modifies existing ones. If you haven't created a &lt;code&gt;git&lt;/code&gt; repository or committed your changes yet I strongly advise you to so you can use the diff to learn what changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git init
$ git add .
$ git commit -m Init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What does Champions App need?
&lt;/h2&gt;

&lt;p&gt;If you recall the last chapter I mentioned that this project was meant for people to record matches between themselves. That means we need to know who you are. We are going to build a way one can simply register and log in to our app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet &lt;code&gt;mix phx.gen.auth&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;As previously mentioned, &lt;code&gt;mix&lt;/code&gt; is also an automation tool for Elixir. The Phoenix framework added a few custom commands to help generate boilerplate Phoenix code that can go very far when you need to get things done. Feel free to run &lt;code&gt;mix help&lt;/code&gt; if you want to list all possible &lt;code&gt;mix&lt;/code&gt; commands. For now, we care only about one: &lt;code&gt;mix phx.gen.auth&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;$ mix help phx.gen.auth

                                mix phx.gen.auth                                

Generates authentication logic for a resource.

    $ mix phx.gen.auth Accounts User users

The first argument is the context module followed by the schema module and its
plural name (used as the schema table name).

Additional information and security considerations are detailed in the mix
phx.gen.auth guide (mix_phx_gen_auth.html).

# A lot more info below
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did you see what I just did? I ran &lt;code&gt;mix help phx.gen.auth&lt;/code&gt; and got a ton of useful information. Feel free to do that whenever you feel lost before running a command. What we really care about now is the usage: &lt;code&gt;mix phx.gen.auth Accounts User users&lt;/code&gt;. What are those 3 words that come after the command? I invite you to learn about…&lt;/p&gt;

&lt;h2&gt;
  
  
  Phoenix Contexts
&lt;/h2&gt;

&lt;p&gt;Phoenix fundamentally uses an abstraction where you separate the bones of your code into 3 places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Context modules: a place where you should put all your business logic.&lt;/li&gt;
&lt;li&gt;Ecto Models: a place where you should bridge Elixir and your database.&lt;/li&gt;
&lt;li&gt;Web modules: everything that handles user interactions ranging from HTML to JSON APIs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A lot of Phoenix generator commands follow the structure: &lt;code&gt;mix phx.gen.* [Context Name] [Model Name] [Model Table Name]&lt;/code&gt;. If we apply that knowledge into &lt;code&gt;mix phx.gen.auth Accounts User users&lt;/code&gt; we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mix phx.gen.auth&lt;/code&gt;: Hey Phoenix, please generate an authentication system for me.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Accounts&lt;/code&gt;: let's call the context module &lt;code&gt;Accounts&lt;/code&gt;, and put all my business logic there.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;User&lt;/code&gt;: the Ecto model name for our users should be &lt;code&gt;User&lt;/code&gt;, define its structure there.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;users&lt;/code&gt;: and make sure the database table name is &lt;code&gt;users&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;mix phx.gen.auth Accounts User users
An authentication system can be created in two different ways:
- Using Phoenix.LiveView (default)
- Using Phoenix.Controller only
Do you want to create a LiveView based authentication system? [Yn] 
* creating priv/repo/migrations/20230617121436_create_users_auth_tables.exs
* creating lib/champions/accounts/user_notifier.ex
* creating lib/champions/accounts/user.ex
* creating lib/champions/accounts/user_token.ex
* creating lib/champions_web/user_auth.ex
* creating test/champions_web/user_auth_test.exs
* creating lib/champions_web/controllers/user_session_controller.ex
* creating test/champions_web/controllers/user_session_controller_test.exs
* creating lib/champions_web/live/user_registration_live.ex
* creating test/champions_web/live/user_registration_live_test.exs
* creating lib/champions_web/live/user_login_live.ex
* creating test/champions_web/live/user_login_live_test.exs
* creating lib/champions_web/live/user_reset_password_live.ex
* creating test/champions_web/live/user_reset_password_live_test.exs
* creating lib/champions_web/live/user_forgot_password_live.ex
* creating test/champions_web/live/user_forgot_password_live_test.exs
* creating lib/champions_web/live/user_settings_live.ex
* creating test/champions_web/live/user_settings_live_test.exs
* creating lib/champions_web/live/user_confirmation_live.ex
* creating test/champions_web/live/user_confirmation_live_test.exs
* creating lib/champions_web/live/user_confirmation_instructions_live.ex
* creating test/champions_web/live/user_confirmation_instructions_live_test.exs
* creating lib/champions/accounts.ex
* injecting lib/champions/accounts.ex
* creating test/champions/accounts_test.exs
* injecting test/champions/accounts_test.exs
* creating test/support/fixtures/accounts_fixtures.ex
* injecting test/support/fixtures/accounts_fixtures.ex
* injecting test/support/conn_case.ex
* injecting config/test.exs
* injecting mix.exs
* injecting lib/champions_web/router.ex
* injecting lib/champions_web/router.ex - imports
* injecting lib/champions_web/router.ex - plug
* injecting lib/champions_web/components/layouts/root.html.heex

Please re-fetch your dependencies with the following command:

    $ mix deps.get

Remember to update your repository by running migrations:

    $ mix ecto.migrate

Once you are ready, visit "/users/register"
to create your account and then access "/dev/mailbox" to
see the account confirmation email.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fslabstatic.com%2Fprod%2Fuploads%2For5adfz5%2Fposts%2Fimages%2F-q2chm8TZwkVyJqiFiyznFIJ.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%2Fslabstatic.com%2Fprod%2Fuploads%2For5adfz5%2Fposts%2Fimages%2F-q2chm8TZwkVyJqiFiyznFIJ.png" alt="Visual Studio Code with Git tab open showing 30 modified or created files"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is a huge diff. But don't be scared: let's talk about the main things you should care about immediately now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;lib/champions&lt;/code&gt; directory
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lib
├── champions
│   ├── accounts
│   │   ├── user.ex
│   │   ├── user_notifier.ex
│   │   └── user_token.ex
│   ├── accounts.ex
│   ├── application.ex
│   ├── mailer.ex
│   └── repo.ex
├── champions.ex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since our project is called &lt;code&gt;champions&lt;/code&gt;, inside the &lt;code&gt;lib&lt;/code&gt; folder there's a folder with that very same name that will host our business logic. When we ran &lt;code&gt;mix phx.gen.auth&lt;/code&gt; we choose the context and model names to be, respectively, Accounts and User. We can see that here where &lt;code&gt;lib/champions/accounts.ex&lt;/code&gt; contains the context &lt;code&gt;Champions.Accounts&lt;/code&gt; and inside &lt;code&gt;lib/champions/accounts/user.ex&lt;/code&gt; there's our model &lt;code&gt;Champions.Accounts.User&lt;/code&gt;. You might have noticed but the folder and module names match.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule Champions.Accounts do
  def get_user_by_email(email) when is_binary(email) do
  def get_user_by_email_and_password(email, password)
  def get_user!(id), do: Repo.get!(User, id)
  def register_user(attrs) do
  def change_user_registration(%User{} = user, attrs \\ %{}) do
  def change_user_email(user, attrs \\ %{}) do
  def apply_user_email(user, password, attrs) do
  def update_user_email(user, token) do
  def deliver_user_update_email_instructions(%User{} = user, current_email, update_email_url_fun)
  def change_user_password(user, attrs \\ %{}) do
  def update_user_password(user, password, attrs) do
  def generate_user_session_token(user) do
  def get_user_by_session_token(token) do
  def delete_user_session_token(token) do
  def deliver_user_confirmation_instructions(%User{} = user, confirmation_url_fun)
  def confirm_user(token) do
  def deliver_user_reset_password_instructions(%User{} = user, reset_password_url_fun)
  def get_user_by_reset_password_token(token) do
  def reset_user_password(user, attrs) do
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've deleted a ton of code just so you can see the function names of our Accounts context purpose. When I said our business logic lives there I meant it. This context is all about doing things with user accounts. Ranging from getting data to sending recovery password emails, this will be very helpful in the future. Do note that this is just a plain Elixir module with a lot of functions, there's no magic that makes it a context, it's just a good practice on Phoenix. Later on, we will be creating more contexts and adding functions to existing ones so you can get more practice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule Champions.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :email, :string
    field :password, :string, virtual: true, redact: true
    field :hashed_password, :string, redact: true
    field :confirmed_at, :naive_datetime

    timestamps()
  end

  def registration_changeset(user, attrs, opts \\ []) do
  def email_changeset(user, attrs, opts \\ []) do
  def password_changeset(user, attrs, opts \\ []) do
  def confirm_changeset(user) do
  def valid_password?(%Champions.Accounts.User{hashed_password: hashed_password}, password)
  def validate_current_password(changeset, password) do
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you go to &lt;code&gt;lib/champions/accounts/user.ex&lt;/code&gt; you're going to see something like the code above. Once more I've deleted the function code so it's easier to see what does it have. Notice it starts with a simple mapping of what our model contains: &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;hashed_password&lt;/code&gt; and &lt;code&gt;confirmed_at&lt;/code&gt;. You can also see there's &lt;code&gt;timestamps()&lt;/code&gt; there, this macro adds two fields: &lt;code&gt;inserted_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt;. There's really a ton of unpacking here on the future but for now think of Ecto models as an abstraction to map our database data to Elixir and the other way around.&lt;/p&gt;

&lt;p&gt;For now we will ignore the other files created on &lt;code&gt;lib/champions&lt;/code&gt; and will talk about them as we need later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;lib/champions_web&lt;/code&gt; folder
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lib
├── champions_web
│   ├── components
│   │   ├── core_components.ex
│   │   ├── layouts
│   │   └── layouts.ex
│   ├── controllers
│   │   ├── error_html.ex
│   │   ├── error_json.ex
│   │   ├── page_controller.ex
│   │   ├── page_html/
│   │   ├── page_html.ex
│   │   └── user_session_controller.ex
│   ├── endpoint.ex
│   ├── gettext.ex
│   ├── live
│   │   ├── user_confirmation_instructions_live.ex
│   │   ├── user_confirmation_live.ex
│   │   ├── user_forgot_password_live.ex
│   │   ├── user_login_live.ex
│   │   ├── user_registration_live.ex
│   │   ├── user_reset_password_live.ex
│   │   └── user_settings_live.ex
│   ├── router.ex
│   ├── telemetry.ex
│   └── user_auth.ex
└── champions_web.ex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phoenix likes to separate business logic from user interfaces, which includes both HTML and APIs. It's a good practice for you to learn how to separate your concerns between your &lt;code&gt;app&lt;/code&gt; and &lt;code&gt;app_web&lt;/code&gt; folders once you understand better. For now I'd like to draw your focus on on these folders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;components&lt;/code&gt;: here we put generic Phoenix Components that can be reused over our application such as custom button, form inputs, data tables etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;controllers&lt;/code&gt;: here lies both API controllers (files ending with &lt;code&gt;_json.ex&lt;/code&gt;) and plain HTML controllers (ending with &lt;code&gt;_html.ex&lt;/code&gt;). Any HTML here is not a LiveView (known to some as dead views).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;live&lt;/code&gt;: the name says all, our LiveViews will be stored here. Phoenix auth generator even created some for you. Each LiveView can be reused in one or more pages. We'll talk more about that later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we got after all?
&lt;/h2&gt;

&lt;p&gt;We need to do two more things before we proceed: install the new dependencies (bcrypt_elixir) and migrate our database to create our users tables. Stop your server and run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
# more stuff...
* Getting bcrypt_elixir (Hex package)
* Getting comeonin (Hex package)
* Getting elixir_make (Hex package)

champions on  main [!?] via 💧 v1.14.1 took 3s 
[I] ➜ mix ecto.migrate
# Code compiling
Generated champions app

10:51:19.938 [info] == Running 20230617121436 Champions.Repo.Migrations.CreateUsersAuthTables.change/0 forward

10:51:19.942 [info] execute "CREATE EXTENSION IF NOT EXISTS citext"

10:51:20.072 [info] create table users

10:51:20.116 [info] create index users_email_index

10:51:20.124 [info] create table users_tokens

10:51:20.152 [info] create index users_tokens_user_id_index

10:51:20.159 [info] create index users_tokens_context_token_index

10:51:20.176 [info] == Migrated 20230617121436 in 0.2s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart your server once more with &lt;code&gt;mix phx.server&lt;/code&gt;, you should see two links to login and registration pages on the top right. Let's register our first user. After registering you should be automatically logged in. One neat feature you should look into immediately is that Phoenix comes with a fake mail delivery system. Head out to &lt;a href="http://localhost:4000/dev/mailbox" rel="noopener noreferrer"&gt;http://localhost:4000/dev/mailbox&lt;/a&gt; and you're going to see your email confirmation is there. Be aware that this is memory only, stopping/restarting the server will make your email preview be lost.&lt;/p&gt;

&lt;p&gt;Phoenix auth comes bundled with the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login&lt;/li&gt;
&lt;li&gt;Registration&lt;/li&gt;
&lt;li&gt;Token-based sessions with token invalidation&lt;/li&gt;
&lt;li&gt;Email confirmation&lt;/li&gt;
&lt;li&gt;Password reset&lt;/li&gt;
&lt;li&gt;Preview emails without having to setup anything locally&lt;/li&gt;
&lt;li&gt;User settings page&lt;/li&gt;
&lt;li&gt;Authenticated routes (we will talk a lot about this later)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go nuts with it, test it all and enjoy how a single terminal command wrote so much code anyone would hate to do over and over again. When you're done, don't forget to commit it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mix help COMMAND&lt;/code&gt; shows useful help for mix commands you don't know how to use.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mix phx.gen.auth&lt;/code&gt; setups an entire authentication system for your app with a single command.&lt;/li&gt;
&lt;li&gt;Phoenix contexts are where you store your business logic.&lt;/li&gt;
&lt;li&gt;Ecto models handle mapping your database data into Elixir data.&lt;/li&gt;
&lt;li&gt;Phoenix separates your app into two folders: &lt;code&gt;app_name&lt;/code&gt; for business logic and &lt;code&gt;app_name_web&lt;/code&gt; for handling user interactions from APIs and HTML interfaces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-3-4b59"&gt;Read chapter 3: Adding a points system&lt;/a&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>liveview</category>
      <category>phoenix</category>
    </item>
    <item>
      <title>The Lazy Programmer's Intro to LiveView: Chapter 1</title>
      <dc:creator>Lubien</dc:creator>
      <pubDate>Sun, 18 Jun 2023 15:02:23 +0000</pubDate>
      <link>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-1-1487</link>
      <guid>https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-1-1487</guid>
      <description>&lt;h2&gt;
  
  
  Chapters
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;You're here&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-2-2175"&gt;Adding Authentication to Champions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-3-4b59"&gt;Adding a points system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-4-1nim"&gt;Adding points to users&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-5-5cn3"&gt;Listing our users&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-6-17ef"&gt;Creating the user page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-7-47e0"&gt;Conceding points to the winner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-8-156k"&gt;Distributed systems are hard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;TODO 😉&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Endgame source code: &lt;a href="https://github.com/lubien/champions" rel="noopener noreferrer"&gt;https://github.com/lubien/champions&lt;/a&gt; (update as I write chapters).&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you're like me, you're lazy. When it comes to learning new things you rather take it incrementally than jump on docs and spend a lot of time learning what it takes to use this new thing. That's fine, I guess. I mean, worked for me so far. I love to learn things doing projects so I'd like to invite you to learn LiveView with me doing a side project.&lt;/p&gt;

&lt;p&gt;This project is going to be called &lt;strong&gt;Champions&lt;/strong&gt;. Back in the day one of the very first projects that I created that made it into production was a side project of mine where my friends could compete with each other and get points by winning matches. It was a very simple system where all you'd have to do was declare that you lost to someone and they'd get 3 points if they confirmed their win, each would win 1 point on a draw. It was a trust-based system but worked just fine for us.&lt;/p&gt;

&lt;p&gt;In these guides, I'm going to teach you how to rebuild that very system using Phoenix LiveView. My goal is to teach you LiveView concepts over time rather than dumping a ton of context you might not need at first. For that, you could feel like I'm not telling you the full history but I promise you by the end of it you're going to be able to make your very own side projects with the knowledge you get from here.&lt;/p&gt;

&lt;p&gt;I'll approach these chapters as if I was just doing the project by myself and talking to the voices in my head. That means I'm going to do things incrementally and at random times I'm just going to suddenly add new scope to the app or change the current scope on a whim. I hope with that that you'd get some experience on how to change things on LiveView as needed over time since life is not all about generators, you also need to learn how to maintain code.&lt;/p&gt;

&lt;p&gt;One more thing I'll be during throughout the entire process is that I'm going to link the documentation from Elixir, Phoenix, and Phoenix LiveView as much as possible. Those just happen to be one of the very best docs I've ever consumed as a developer and they also come packed with guides so don't feel scared of opening links from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;I love when a new thing I'm learning has a 'Get Started' page. That's usually where the maintainers put the minimal info you'd need to use something. I like it so much that I'm doing this section right here.&lt;/p&gt;

&lt;p&gt;First, I assume you've installed Elixir. I'm using 1.14.1 with Erlang/OTP 24 but anything higher should just work. If you never installed it I highly recommend you to do it using &lt;code&gt;asdf&lt;/code&gt;, there's a ton of guides out there about it. If you're a Windows user, please make your life easier and use WSL to run everything. Another assumption I'm going to do is that you have PostgreSQL installed and running on your machine. We will be using it as our main database.&lt;/p&gt;

&lt;p&gt;We will be using Phoenix 1.7.6 and that comes with LiveView v0.19.2 as of now. It was mentioned not much is going to change from LiveView as it's getting close to v1 so I'm confident I'm not going to need to rewrite this soon and you shouldn't be worried if you're reading this when Phoenix and LiveView are on higher versions.&lt;/p&gt;

&lt;p&gt;Whenever I forget how to start a new Phoenix project all I have to do is go to &lt;a href="https://www.phoenixframework.org/" rel="noopener noreferrer"&gt;https://www.phoenixframework.org/&lt;/a&gt;, click on Guides, and right on my left then open the 'Installation' page. To get the latest Phoenix installer run the command below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mix archive.install hex phx_new
Resolving Hex dependencies...
Dependency resolution completed:
New:
  phx_new 1.7.6
* Getting phx_new (Hex package)
All dependencies are up to date
Compiling 11 files (.ex)
Generated phx_new app
Generated archive "phx_new-1.7.6.ez" with MIX_ENV=prod
Found existing entry: /Users/lubien/.asdf/installs/elixir/1.14/.mix/archives/phx_new-1.7.3
Are you sure you want to replace it with "phx_new-1.7.6.ez"? [Yn]
* creating /Users/lubien/.asdf/installs/elixir/1.14/.mix/archives/phx_new-1.7.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see I already had v1.7.3 and it upgraded it to v1.7.6. You're also going to see a lot of paths like &lt;code&gt;/Users/lubien/.asdf/installs/elixir/1.14/&lt;/code&gt; since I'm using &lt;code&gt;asdf&lt;/code&gt;, don't worry about it. Let's create our project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mix phx.new champions
* creating champions/config/config.exs
* creating champions/config/dev.exs
* creating champions/config/prod.exs
* creating champions/config/runtime.exs
* creating champions/config/test.exs
* creating champions/lib/champions/application.ex
* creating champions/lib/champions.ex
* creating champions/lib/champions_web/controllers/error_json.ex
* creating champions/lib/champions_web/endpoint.ex
* creating champions/lib/champions_web/router.ex
* creating champions/lib/champions_web/telemetry.ex
* creating champions/lib/champions_web.ex
* creating champions/mix.exs
* creating champions/README.md
* creating champions/.formatter.exs
* creating champions/.gitignore
* creating champions/test/support/conn_case.ex
* creating champions/test/test_helper.exs
* creating champions/test/champions_web/controllers/error_json_test.exs
* creating champions/lib/champions/repo.ex
* creating champions/priv/repo/migrations/.formatter.exs
* creating champions/priv/repo/seeds.exs
* creating champions/test/support/data_case.ex
* creating champions/lib/champions_web/controllers/error_html.ex
* creating champions/test/champions_web/controllers/error_html_test.exs
* creating champions/lib/champions_web/components/core_components.ex
* creating champions/lib/champions_web/controllers/page_controller.ex
* creating champions/lib/champions_web/controllers/page_html.ex
* creating champions/lib/champions_web/controllers/page_html/home.html.heex
* creating champions/test/champions_web/controllers/page_controller_test.exs
* creating champions/lib/champions_web/components/layouts/root.html.heex
* creating champions/lib/champions_web/components/layouts/app.html.heex
* creating champions/lib/champions_web/components/layouts.ex
* creating champions/priv/static/images/logo.svg
* creating champions/lib/champions/mailer.ex
* creating champions/lib/champions_web/gettext.ex
* creating champions/priv/gettext/en/LC_MESSAGES/errors.po
* creating champions/priv/gettext/errors.pot
* creating champions/priv/static/robots.txt
* creating champions/priv/static/favicon.ico
* creating champions/assets/js/app.js
* creating champions/assets/vendor/topbar.js
* creating champions/assets/css/app.css
* creating champions/assets/tailwind.config.js
* creating champions/assets/vendor/heroicons/LICENSE.md
* creating champions/assets/vendor/heroicons/UPGRADE.md
* extracting champions/assets/vendor/heroicons/optimized

Fetch and install dependencies? [Yn]
* running mix deps.get
* running mix assets.setup
* running mix deps.compile

We are almost there! The following steps are missing:

    $ cd champions

Then configure your database in config/dev.exs and run:

    $ mix ecto.create

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This could take a while but our Elixir and JavaScript dependencies will come ready to be used. In the meantime feel free to open your favorite code editor on your project. Do pay attention that in the end, it will show commands that are useful to be run at first. Let's enter our project directory and create our database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd champions

$ mix ecto.create
Compiling 15 files (.ex)
Generated champions app
The database for Champions.Repo has been created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just in case you don't know, &lt;code&gt;mix&lt;/code&gt; is the build and automation tool Elixir comes bundled with. Libraries such as Phoenix and Ecto can add commands to it. And now we see the first mention of Ecto. In short, it's the thing that lets you access databases. We will expand more on that later. If Postgres is not running on localhost on port 5432 with a user and password equal to &lt;code&gt;postgres&lt;/code&gt;, you're going to see a database error when running &lt;code&gt;mix ecto.create&lt;/code&gt;. In that case, feel free to open &lt;code&gt;config/dev.exs&lt;/code&gt; and change the ecto config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config/dev.exs
import Config

# Configure your database
config :champions, Champions.Repo,
  username: "postgres",
  password: "postgres",
  hostname: "localhost",
  database: "champions_dev",
  stacktrace: true,
  show_sensitive_data_on_connection_error: true,
  pool_size: 10

# more stuff...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time to start the server!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mix phx.server
[info] Running ChampionsWeb.Endpoint with cowboy 2.10.0 at 127.0.0.1:4000 (http)
[info] Access ChampionsWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes...
Browserslist: caniuse-lite is outdated. Please run:
  npx update-browserslist-db@latest
  Why you should do it regularly: https://github.com/browserslist/update-db#readme

Rebuilding...

Done in 173ms.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt; and you're going to see your app running.&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%2Fslabstatic.com%2Fprod%2Fuploads%2For5adfz5%2Fposts%2Fimages%2FgUqKzlDrS4wO778SwGTxdaRv.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%2Fslabstatic.com%2Fprod%2Fuploads%2For5adfz5%2Fposts%2Fimages%2FgUqKzlDrS4wO778SwGTxdaRv.png" alt="Default Phoenix gome page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Phoenix is the web framework, and LiveView is a library that comes with it by default.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mix archive.install hex phx_new&lt;/code&gt; installs or update your Phoenix CLI to create new projects.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mix phx.new project_name&lt;/code&gt; creates a new Phoenix project.&lt;/li&gt;
&lt;li&gt;Phoenix uses Postgres by default for your database.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mix ecto.create&lt;/code&gt; creates your database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go to &lt;a href="https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-2-2175"&gt;Chapter 2: Adding Authentication to Champions&lt;/a&gt;&lt;/p&gt;

</description>
      <category>phoenix</category>
      <category>liveview</category>
      <category>elixir</category>
    </item>
    <item>
      <title>Elixir mix + fzf = 💜🧪</title>
      <dc:creator>Lubien</dc:creator>
      <pubDate>Sat, 09 Oct 2021 06:49:12 +0000</pubDate>
      <link>https://dev.to/lubien/elixir-mix-fzf-ohb</link>
      <guid>https://dev.to/lubien/elixir-mix-fzf-ohb</guid>
      <description>&lt;p&gt;&lt;a href="https://asciinema.org/a/441041"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UwCJARGZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://asciinema.org/a/441041.svg" alt="asciicast" width="708" height="510"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/junegunn/fzf"&gt;&lt;code&gt;fzf&lt;/code&gt;&lt;/a&gt; is such a powerful tool when it comes to make your terminal experiences so much better. One thing I like about it is how easy it is to extend it's features and one could make auto completions &lt;a href="https://github.com/junegunn/fzf/#custom-fuzzy-completion"&gt;work for any command with bare minimum code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For today's snippet I'd like to share, here's how to integrate Elixir's build tool &lt;code&gt;mix&lt;/code&gt; with it. Just paste it on your friendly neighborhood dotfiles (.bashrc, .zshrc...) and git it a go by writting &lt;code&gt;mix **&lt;/code&gt; then hitting &lt;code&gt;&amp;lt;Tab&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;_fzf_complete_mix&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  _fzf_complete &lt;span class="nt"&gt;--reverse&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;
    mix &lt;span class="nb"&gt;help&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"^mix [a-zA-Z]"&lt;/span&gt; &lt;span class="nt"&gt;--color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;never | &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/^[^ ]* //"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/#.*//"&lt;/span&gt; 
  &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

_fzf_complete_mix_post&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $NF}'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>elixir</category>
      <category>terminal</category>
      <category>bash</category>
      <category>fzf</category>
    </item>
  </channel>
</rss>
