<?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: Jakub Lambrych</title>
    <description>The latest articles on DEV Community by Jakub Lambrych (@utopos).</description>
    <link>https://dev.to/utopos</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%2F804364%2F4c03bd85-8d5a-494e-9f14-db0319d1aedb.jpeg</url>
      <title>DEV Community: Jakub Lambrych</title>
      <link>https://dev.to/utopos</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/utopos"/>
    <language>en</language>
    <item>
      <title>How to report Postgres custom errors in Ecto Changeset</title>
      <dc:creator>Jakub Lambrych</dc:creator>
      <pubDate>Thu, 04 Jul 2024 17:32:19 +0000</pubDate>
      <link>https://dev.to/utopos/how-to-report-postgres-custom-errors-in-ecto-changeset-54m</link>
      <guid>https://dev.to/utopos/how-to-report-postgres-custom-errors-in-ecto-changeset-54m</guid>
      <description>&lt;p&gt;Sometimes you may find yourself in the need to capture a &lt;code&gt;Postgres&lt;/code&gt; (or any other RDBMS) custom error in the &lt;code&gt;Ecto.Changeset&lt;/code&gt; without raising an exception. This enables you to handle all the errors in one place without braking your aesthetic Elixir functional code with “try/rescue” constructs.&lt;/p&gt;

&lt;p&gt;It has one big advantage: as the &lt;code&gt;Ecto.Changeset&lt;/code&gt; is a “lingua franca” of many libraries and frameworks (like Phoenix), embedding error reports in the changeset struct will work for you out of the box without any additional error handing burden. Less code, less maintenance!&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood
&lt;/h2&gt;

&lt;p&gt;Currently, the &lt;code&gt;Postgres Ecto Adapter&lt;/code&gt; (same as other adapters for major RDBMS) provide only limited support for reporting errors inside the &lt;code&gt;Ecto Changeset&lt;/code&gt;. Let’s have a glimpse into the Postgres adapter &lt;a href="https://dev.to/utopos/how-to-report-postgres-custom-errors-in-ecto-changeset-54m"&gt;source code&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="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;to_constraints&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Postgrex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;postgres:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;code:&lt;/span&gt; &lt;span class="ss"&gt;:unique_violation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;constraint:&lt;/span&gt; &lt;span class="n"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt; &lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;unique:&lt;/span&gt; &lt;span class="n"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;to_constraints&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Postgrex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;postgres:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;code:&lt;/span&gt; &lt;span class="ss"&gt;:foreign_key_violation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;constraint:&lt;/span&gt; &lt;span class="n"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt; &lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;foreign_key:&lt;/span&gt; &lt;span class="n"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;to_constraints&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Postgrex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;postgres:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;code:&lt;/span&gt; &lt;span class="ss"&gt;:exclusion_violation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;constraint:&lt;/span&gt; &lt;span class="n"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt; &lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;exclusion:&lt;/span&gt; &lt;span class="n"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;to_constraints&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Postgrex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;postgres:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;code:&lt;/span&gt; &lt;span class="ss"&gt;:check_violation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;constraint:&lt;/span&gt; &lt;span class="n"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt; &lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;check:&lt;/span&gt; &lt;span class="n"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that certain Postgres errors, namely those related to constraints, get a special treatment at the adapter level so that later could be transformed into relevant changeset errors on demand (by calling &lt;code&gt;*_constraint&lt;/code&gt; functions in the changeset). Meanwhile, the remaining errors will be let through and propagated to your code. There are only few constraint error codes that get intercepted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;:unique_violation&lt;/li&gt;
&lt;li&gt;:foreign_key_violation&lt;/li&gt;
&lt;li&gt;:exclusion_violation&lt;/li&gt;
&lt;li&gt;:check_violation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;The method I would like to propose is to disguise your custom database error as one of the constraints that is already implemented by default in the Postgres Ecto adapter (see above).&lt;/p&gt;

&lt;p&gt;In this example, I will define and raise a custom error from within a PL/pgSQL trigger function using Postgres’ &lt;code&gt;check_contraint&lt;/code&gt; ERRCODE, but you can use any of the four, whichever makes more sense to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1. Raise error in Postgres codebase.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;custom_check&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;SOME&lt;/span&gt; &lt;span class="n"&gt;CONDITION&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
        &lt;span class="n"&gt;RAISE&lt;/span&gt; &lt;span class="n"&gt;EXCEPTION&lt;/span&gt; &lt;span class="s1"&gt;'CUSTOM ERROR'&lt;/span&gt; 
        &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;ERRCODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'check_violation'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'name_of_your_contraint'&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;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&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="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CUSTOM ERROR&lt;/code&gt; is a custom string lateral of &lt;strong&gt;your choice&lt;/strong&gt; that will be passed to Ecto as the error message text.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ERRCODE&lt;/code&gt; must be one of the following:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;unique_violation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;foreign_key_violation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exclusion_violation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;check_violation&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CONSTRAINT&lt;/code&gt; &lt;strong&gt;must have&lt;/strong&gt; a name of your choice that will uniquely identify the custom error in the Ecto Changeset.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Please note:&lt;/strong&gt; a comprehensive list of Postgres error codes can be found in the Postgres Documentation — Errors and Messages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 2 Define standard constraint in Ecto Changeset.
&lt;/h2&gt;

&lt;p&gt;In this case, I consistently follow check_contraint error code raised in Postgres and call check_constraint function in the changeset to capture it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;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;schema&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;schema&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;check_constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:some_field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="ss"&gt;name_of_your_contraint:&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="s2"&gt;"custom error message"&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;where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:some_field&lt;/code&gt; is a key of associated with the model struct. It is particularly useful when working with Phoenix forms.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:name_of_your_contraint&lt;/code&gt; is an atom reflecting the same name as the one defined in the Postgres codebase.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;message&lt;/code&gt; is an error message on Ecto side that will from additional contextual information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To make your Elixir code more readable, you could consider some refactoring:&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;changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&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;schema&lt;/span&gt;
   &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;my_custom_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:some_field&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;defp&lt;/span&gt; &lt;span class="n"&gt;my_custom_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&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;schema&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;check_constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="ss"&gt;name_of_your_contraint:&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="s2"&gt;"custom error message"&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;A minor trade off is that a potential error description in the changeset has to be related to a key in the existing Schema struct. This is because changesets are designed on field level. If you use Phoenix form, you can compensate this drawback with an accurate error message propagated to the user.&lt;/p&gt;

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

&lt;p&gt;In this article, I tried to propose a fairly easy technique to intercept custom database errors and turn them into a &lt;code&gt;Ecto Changeset&lt;/code&gt; errors. This all without the need to override your &lt;code&gt;Repo&lt;/code&gt; module functionality nor forking the adapter’s code, which would be way more difficult to maintain with new &lt;code&gt;Ecto&lt;/code&gt; library updates.&lt;/p&gt;

&lt;p&gt;Please, feel free to leave your comment and share other approaches that you came across.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>postgressql</category>
      <category>elixir</category>
    </item>
    <item>
      <title>Elixir: map casting to structs (with checks at compile time!)</title>
      <dc:creator>Jakub Lambrych</dc:creator>
      <pubDate>Mon, 10 Jun 2024 08:04:28 +0000</pubDate>
      <link>https://dev.to/utopos/elixir-validate-map-and-structs-keys-for-merging-at-compile-time-4dli</link>
      <guid>https://dev.to/utopos/elixir-validate-map-and-structs-keys-for-merging-at-compile-time-4dli</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;In this article you will learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to leverage compile-time validation of maps to be merged later with %struct{}, that can save you time and hassle after the deployment&lt;/li&gt;
&lt;li&gt;review popular runtime validation functions&lt;/li&gt;
&lt;li&gt;how to use a macro to do the same &lt;strong&gt;at compile time (!)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;import KeyValidator library to your project from HEX: &lt;a href="https://hex.pm/packages/key_validator"&gt;https://hex.pm/packages/key_validator&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Runtime validations
&lt;/h2&gt;

&lt;p&gt;Elixir and Ecto has built-in functions that perform the key validity check of maps, but only at runtime: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Kernel.struct/2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Kernel.struct!/2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ecto.Query.select_merge/3&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Kernel.struct!/2
&lt;/h3&gt;

&lt;p&gt;Let's take a look at the following 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;defmodule&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="k"&gt;defstruct&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"john"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Following line is a runtime only check:&lt;/span&gt;

&lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;struct!&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="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Jakub"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; %User{name: "Jakub"}&lt;/span&gt;

&lt;span class="c1"&gt;# Runtime error on key typo:&lt;/span&gt;

&lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;struct!&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="ss"&gt;nam__e:&lt;/span&gt; &lt;span class="s2"&gt;"Jakub"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; ** (KeyError) key :nam__e not found&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expression &lt;code&gt;Kernel.struct!(User, %{name: "Jakub"})&lt;/code&gt; uses a map literal (&lt;code&gt;%{name: "Jakub"}&lt;/code&gt;). The User struct definition is known beforehand, as well as the map structure. However, the comparison between the keys in the &lt;code&gt;User&lt;/code&gt; struct and the map literal will only take place during when the app is running and the code getting actually executed. Thus, any potential typo in the key will be discovered only at runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ecto.Query.API.select_merge/3
&lt;/h3&gt;

&lt;p&gt;A similar situation takes place when using a popular  &lt;code&gt;Ecto.Query.select_merge/3&lt;/code&gt; for populating &lt;code&gt;virtual fields&lt;/code&gt; in schemas:&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;Post&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="n"&gt;schema&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;:author_firstname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
      &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:author_lastname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
      &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;virtual_field:&lt;/span&gt; &lt;span class="no"&gt;true&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;defmodule&lt;/span&gt; &lt;span class="no"&gt;Posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;KeyValidator&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;list_posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Post&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;select_merge&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;author:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author_firstname&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;" "&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author_lastname&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;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Posts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_posts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;In the provided example, &lt;code&gt;Post&lt;/code&gt; schema contains a &lt;code&gt;:author&lt;/code&gt; virtual field. We want to store a concatenated value of the post's author first name and the last name populated in the query. The &lt;code&gt;select_merge&lt;/code&gt; will merge the given expression with query results. &lt;/p&gt;

&lt;p&gt;If the result of the query is a struct (in our case &lt;code&gt;Post&lt;/code&gt;) and in we are merging a map (in our case &lt;code&gt;%{author: data}&lt;/code&gt; literal), we need to assert that all the keys from the map exist in the struct. To achieve this, Ecto uses &lt;a href="https://hexdocs.pm/ecto/Ecto.Query.API.html#merge/2"&gt;&lt;code&gt;Ecto.Query.API.merge/2&lt;/code&gt;&lt;/a&gt; underneath:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the map on the left side is a struct, Ecto will check all the fields on the right previously exist on the left before merging.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This, however, is done in &lt;strong&gt;runtime only&lt;/strong&gt; again. You will learn whether the map keys conform to the struct key when the particular line of code got executed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compile-time validation
&lt;/h2&gt;

&lt;p&gt;In certain situations, the conformity between map/keyword keys could be already checked at the compile-time.&lt;/p&gt;

&lt;p&gt;One example when we can potentially leverage the compile-time validations is when we work with map/keyword &lt;strong&gt;literals&lt;/strong&gt; in our code. These literals are provided directly in the Elixir's AST structure during compilation process.  We can build on this fact if our &lt;strong&gt;intention is to use the map for casting onto structs at some point in our code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We deal with map or keyword literals when the structure is given &lt;strong&gt;inline&lt;/strong&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;# Map literal:&lt;/span&gt;

&lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Jakub"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;lastname:&lt;/span&gt; &lt;span class="s2"&gt;"Lambrych"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Keyword literal&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Jakub"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;lastname:&lt;/span&gt; &lt;span class="s2"&gt;"Lambrych"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In contrast, the following expressions do not allow us to work with literals when invoking merging maps with struct:&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;# map is assigned to a variable&lt;/span&gt;
&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Artur"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Function invoked with variable "m" and not a "map literal".&lt;/span&gt;
&lt;span class="c1"&gt;# No opportunity for compile check.&lt;/span&gt;
&lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;struct&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="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use &lt;code&gt;KeyValidator&lt;/code&gt; macro for compile-time checks
&lt;/h2&gt;

&lt;p&gt;In the following example, &lt;code&gt;User&lt;/code&gt; module together with the map literal is defined at the compile time.  We will leverage the power of compile-time macros whenever our intention is to use a particular map/keyword literal to be merged at some point with a struct. &lt;/p&gt;

&lt;p&gt;To support this, I developed a small &lt;code&gt;KeyValidator.for_struct/2&lt;/code&gt; macro (available on &lt;a href="https://hex.pm/packages/key_validator"&gt;Hex&lt;/a&gt;). Let's see how we can use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="k"&gt;defstruct&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"john"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;KeyValidator&lt;/span&gt;

&lt;span class="c1"&gt;# Succesfull validation. Returns the map:&lt;/span&gt;

&lt;span class="n"&gt;user_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;for_struct&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="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Jakub"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; %{name: "Jakub"}&lt;/span&gt;

&lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;struct!&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="n"&gt;user_map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; %User{name: "Jakub"}&lt;/span&gt;

&lt;span class="c1"&gt;# Compile time error on "nam__e:" key typo:&lt;/span&gt;

&lt;span class="n"&gt;user_map2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;for_struct&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="ss"&gt;nam__e:&lt;/span&gt; &lt;span class="s2"&gt;"Jakub"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt;** (KeyError) Key :name_e not found in User&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;KeyValidator.for_struct/2&lt;/code&gt; can be also used with &lt;code&gt;Ecto.Query.select_merge/3&lt;/code&gt; to populate &lt;code&gt;virtual_fields&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;Posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;KeyValidator&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;list_posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;Post&lt;/span&gt;
        &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;select_merge&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;for_struct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;authorrr:&lt;/span&gt; &lt;span class="s2"&gt;"Typo"&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;span class="k"&gt;end&lt;/span&gt;


&lt;span class="no"&gt;Posts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_posts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; ** (KeyError) Key :authorrr not found in Post&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code will raise a &lt;code&gt;Key Error&lt;/code&gt; during compilation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install &lt;code&gt;KeyValidator&lt;/code&gt; from Hex
&lt;/h2&gt;

&lt;p&gt;For convenience, I wrapped the macro in library. It is available on hex with documentation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hex.pm/packages/key_validator"&gt;https://hex.pm/packages/key_validator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can add it as a dependency to your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:key_validator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 0.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;runtime:&lt;/span&gt; &lt;span class="no"&gt;false&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;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;Macro code together with &lt;code&gt;ExUnit&lt;/code&gt; tests can be accessed at the GitHub repo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/utopos/key_validator"&gt;https://github.com/utopos/key_validator&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Using metaprogramming in Elixir (macros) give you an opportunity to analyze the Elixir's AST and perform some checks of structs, maps and keywords before deploying your code.&lt;/p&gt;

&lt;p&gt;Adding &lt;code&gt;KeyValidator.for_struct/2&lt;/code&gt; macro to your project, allows some category of key conformity errors to be caught at an early stage in the development workflow. No need to wait until the code to crashes at runtime. It can be expensive and time-consuming to fix bugs caused by simple typos - i.e. when map keys are misspelled.&lt;/p&gt;

&lt;p&gt;Although the macro usage covers some scenarios, we need to bear in mind that it is not a silver bullet. The &lt;code&gt;KeyValidator&lt;/code&gt; cannot accept dynamic variables  due to the nature of Elixir macros. Only map/keyword literals are accepted as their content can be accessed during AST expansion phase of Elixir compilation process.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Adding stream_async() to Phoenix LiveView</title>
      <dc:creator>Jakub Lambrych</dc:creator>
      <pubDate>Thu, 06 Jun 2024 16:42:15 +0000</pubDate>
      <link>https://dev.to/utopos/adding-streamasync-to-phoenix-liveview-2kii</link>
      <guid>https://dev.to/utopos/adding-streamasync-to-phoenix-liveview-2kii</guid>
      <description>&lt;p&gt;In this article, you will learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to implement in LiveView asynchronous assign for &lt;code&gt;streams&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;how to use the &lt;code&gt;async_result&lt;/code&gt; function component (UI) for &lt;code&gt;streams&lt;/code&gt; in Heex templates&lt;/li&gt;
&lt;li&gt;how to use meta programming to auto generate boilerplate for asynchronous stream handling  - &lt;code&gt;stream_async()&lt;/code&gt; macro&lt;/li&gt;
&lt;li&gt;simply add &lt;code&gt;stream_async&lt;/code&gt; via &lt;a href="https://hex.pm/packages/live_stream_async"&gt;Hex package&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  New asynchronous operations in LiveView
&lt;/h2&gt;

&lt;p&gt;A new release of LiveView library — v 0.20.0 — introduced built-in functions for &lt;a href="https://hexdocs.pm/phoenix_live_view/0.20.14/Phoenix.LiveView.html#module-async-operations"&gt;asynchronous work&lt;/a&gt;.  It's a perfect solution to deliver a snappy user experience by delegating some time-consuming tasks (ex. fetching from external services) to background jobs without blocking the UI or event handlers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;assign_sync/3&lt;/code&gt; - a straight forward way to load the results asynchronously from these background tasks into socket assigns. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;start_async/4&lt;/code&gt; - a more granular control over async task result handling. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;.async_result&lt;/code&gt; ...&amp;gt; -  component to handle the asynchronous operation state on the UI side - Heex templates (for success, loading, and errors).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Missing companion: &lt;code&gt;stream_async()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Sometimes you may need to work asynchronously with &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#stream/4"&gt;&lt;code&gt;streams&lt;/code&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Streaming results allow working with large collections without keeping them on the server. This functionality is not available out of the box yet.&lt;/p&gt;

&lt;p&gt;I will demonstrate further on how to manually implement an asynchronous &lt;code&gt;stream&lt;/code&gt; assign to the LivewView socket and wrap the whole boilerplate into a reusable &lt;code&gt;stream_async()&lt;/code&gt; macro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asynchronous streaming in LiveView
&lt;/h2&gt;

&lt;p&gt;In the following example, LiveView loads a list of hotels in a specified location. To load the data, we will use  &lt;code&gt;Hotels.fetch(location)&lt;/code&gt; function. Here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="nv"&gt;@hotels&lt;/span&gt; &lt;span class="ss"&gt;:hotels&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="s2"&gt;"location"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;location&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="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="nv"&gt;@hotels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AsyncResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loading&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;start_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@hotels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Hotels&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&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="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_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@hotels&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;hotels&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="nv"&gt;@hotels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AsyncResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@hotels&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="nv"&gt;@hotels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hotels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reset:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@hotels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:exit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&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;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@hotels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;async_result&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;AsyncResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;async_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:exit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&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="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 core of the solution in the presented code revolves around using a pair of assigns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;socket.assigns.hotels&lt;/code&gt;: instance of &lt;code&gt;%AsyncResult{ }&lt;/code&gt; struct handling the async result. 

&lt;ul&gt;
&lt;li&gt;(line: &lt;code&gt;assign(@hotels, AsyncResult.loading())&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;socket.assigns.streams.hotels&lt;/code&gt; - &lt;code&gt;stream&lt;/code&gt; for target large collection

&lt;ul&gt;
&lt;li&gt;(line: &lt;code&gt;stream(@hotels, hotels, reset: true)&lt;/code&gt;). &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use two different maps in the socket to store necessary data (&lt;code&gt;assigns&lt;/code&gt; and the nested map: &lt;code&gt;assignes.streams&lt;/code&gt;). Note I use the same key in both: &lt;code&gt;:hotels&lt;/code&gt; (the reason will become clear in a moment). Using a combination of a direct assign and a stream assign solves the following challenge: how to store async loading state for a stream that is not yet populated. &lt;/p&gt;

&lt;p&gt;According to the documentation, a stream assign must contain only collections and nothing else. We cannot store temporarily any other type of data (here: loading state) as it will produce errors. Thus, need an additional assign to inform whether the stream content is ready to render, or maybe there was an error and stream is not assigned. &lt;/p&gt;

&lt;p&gt;It might look tempting to resign from using &lt;code&gt;%AsyncResult{}&lt;/code&gt; assign and simply stream en empty collection, meanwhile we are fetching "the real data" asynchronously. The role of the empty collection would be to signal "loading state" for the duration of the async operation. &lt;strong&gt;Personally, I don't find this approach right&lt;/strong&gt;. An empty stream may indicate to the UI and the user that there's no data returned as a response to their request (in the example: there are no hotels in the indicated location).  Furthermore, we would lose the ability to differentiate between "loading state" and "failed state" of collection fetching. As a result, we lose as well the opportunity to provide a meaningful error message to the user. &lt;/p&gt;

&lt;h3&gt;
  
  
  Anatomy of the solution
&lt;/h3&gt;

&lt;p&gt;Let's dig deeper into the proposed code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;start_async/4&lt;/code&gt; function - &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#start_async/4"&gt;asynchronous task&lt;/a&gt; wrapper. Used to get result that will be later streamed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;handle_async&lt;/code&gt; - 2x callbacks on the LiveView process to deal with &lt;code&gt;start_async/4&lt;/code&gt; task results:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{:ok, results}&lt;/code&gt; - success; collection to stream available in &lt;code&gt;results&lt;/code&gt; variable,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{:exit, reeason}&lt;/code&gt;- failure.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt; &lt;code&gt;Phoenix.LiveView.AsyncResult&lt;/code&gt; - LiveView &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.AsyncResult.html"&gt;struct&lt;/a&gt; to track state of an async assign.

&lt;ul&gt;
&lt;li&gt;Set the state and assign results on the struct via functions: ok(), loading() and failed()&lt;/li&gt;
&lt;li&gt;Read the state by accessing three boolean state fields : &lt;code&gt;:ok?&lt;/code&gt; (success), &lt;code&gt;:loading&lt;/code&gt; and &lt;code&gt;:failed&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Read the results of the async operation be accessing &lt;code&gt;:result&lt;/code&gt; field. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Accessing async stream in Heex
&lt;/h3&gt;

&lt;p&gt;When the async collection loading is successful, our socket structure will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;%{&lt;/span&gt;
  &lt;span class="ss"&gt;Stream:&lt;/span&gt; &lt;span class="c1"&gt;#Phoenix.LiveView.Socket&amp;lt;&lt;/span&gt;
  &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="s2"&gt;"phx-F9YIUoGp9R8towwB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
      &lt;span class="ss"&gt;hotels:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LiveView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;AsyncResult&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;ok?:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;loading:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;failed:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;result:&lt;/span&gt; &lt;span class="ss"&gt;:hotels&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;streams:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
      &lt;span class="ss"&gt;hotels:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LiveView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LiveStream&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="ss"&gt;:hotels&lt;/span&gt;&lt;span class="p"&gt;,&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;Pay attention that use the same atom (&lt;code&gt;:hotels&lt;/code&gt;) as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;key to access &lt;code&gt;socket.assigns&lt;/code&gt; referring to the async state data structure (&lt;code&gt;%AsyncResult{}&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;result value of the &lt;code&gt;AsyncResult.result&lt;/code&gt; field,&lt;/li&gt;
&lt;li&gt;key to access &lt;code&gt;socket.assigns.streams&lt;/code&gt; where the stream for the large collection eventually ends up. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although it may look confusing at a first glance, you will see soon that this helps us to produce a highly reusable code in LiveView's&lt;code&gt;render()&lt;/code&gt; to access the stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight eex"&gt;&lt;code&gt;def render(assigns) do
~H"""
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async_result&lt;/span&gt; &lt;span class="na"&gt;:let=&lt;/span&gt;&lt;span class="s"&gt;{stream_key}&lt;/span&gt; &lt;span class="na"&gt;assign=&lt;/span&gt;&lt;span class="s"&gt;{@hotels}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;:loading&amp;gt;&lt;/span&gt;Loading hotels...&lt;span class="nt"&gt;&amp;lt;/:loading&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;:failed&lt;/span&gt; &lt;span class="na"&gt;:let=&lt;/span&gt;&lt;span class="s"&gt;{_failure}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;There was an error loading the hotels. Please try again later.&lt;span class="nt"&gt;&amp;lt;/:failed&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"hotels_stream"&lt;/span&gt; &lt;span class="na"&gt;phx-update=&lt;/span&gt;&lt;span class="s"&gt;"stream"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;:for=&lt;/span&gt;&lt;span class="s"&gt;{{id,&lt;/span&gt; &lt;span class="na"&gt;hotel&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;streams&lt;/span&gt;&lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="na"&gt;stream_key&lt;/span&gt;&lt;span class="err"&gt;]}&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;{id}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nt"&gt;async_result&amp;gt;&lt;/span&gt;
"""
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;LiveView built-in &lt;code&gt;&amp;lt;.async_result ...&amp;gt;&lt;/code&gt; component is designed to work with the &lt;code&gt;%AsyncResult{}&lt;/code&gt; structs. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;%AsyncStruct{}&lt;/code&gt; is passed via "&lt;code&gt;assign={}&lt;/code&gt;" attribute of the component. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;.async_result ...&amp;gt;&lt;/code&gt; component @inner_block receives &lt;code&gt;stream_key&lt;/code&gt; which is used to fetch the correct stream from the &lt;code&gt;socket.assigns.streams&lt;/code&gt; (accessed by &lt;code&gt;@streams&lt;/code&gt; in the code). The &lt;code&gt;stream_key&lt;/code&gt; becomes available in the @inner_block via &lt;code&gt;:let={}&lt;/code&gt; attribute.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use &lt;code&gt;stream_async()&lt;/code&gt; function
&lt;/h2&gt;

&lt;p&gt;As we could observe, working manually with steams asynchronously adds a repetitive boilerplate to our LiveView. For every collection that we would like to stream, we need to add explicitly &lt;code&gt;handle_async()&lt;/code&gt; callbacks.&lt;/p&gt;

&lt;p&gt;The solution proposed in this article can be easily wrapped in a macro that will auto-generate all the necessary handling callbacks behind the scenes. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;stream_async/4&lt;/code&gt; macro can be used as follows:&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="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;LiveStreamAsync&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="s2"&gt;"location"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;location&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="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;stream_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:hotels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Hotels&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&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="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;stream_async/4&lt;/code&gt; macro supports &lt;code&gt;opts&lt;/code&gt; keyword. Options will be piped accordingly to the &lt;code&gt;start_async/4&lt;/code&gt; and &lt;code&gt;stream/3&lt;/code&gt; functions. To learn more about these options, check the official LiveView documentation (in the reference section at the end of the article).&lt;/p&gt;

&lt;p&gt;We're rendering results in &lt;code&gt;Heex&lt;/code&gt; template same as before (repeating):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight eex"&gt;&lt;code&gt;def render(assigns) do
~H"""
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async_result&lt;/span&gt; &lt;span class="na"&gt;:let=&lt;/span&gt;&lt;span class="s"&gt;{stream_key}&lt;/span&gt; &lt;span class="na"&gt;assign=&lt;/span&gt;&lt;span class="s"&gt;{@hotels}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;:loading&amp;gt;&lt;/span&gt;Loading hotels...&lt;span class="nt"&gt;&amp;lt;/:loading&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;:failed&lt;/span&gt; &lt;span class="na"&gt;:let=&lt;/span&gt;&lt;span class="s"&gt;{_failure}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;There was an error loading the hotels. Please try again later.&lt;span class="nt"&gt;&amp;lt;/:failed&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"hotels_stream"&lt;/span&gt; &lt;span class="na"&gt;phx-update=&lt;/span&gt;&lt;span class="s"&gt;"stream"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;:for=&lt;/span&gt;&lt;span class="s"&gt;{{id,&lt;/span&gt; &lt;span class="na"&gt;hotel&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;streams&lt;/span&gt;&lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="na"&gt;stream_key&lt;/span&gt;&lt;span class="err"&gt;]}&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;{id}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nt"&gt;async_result&amp;gt;&lt;/span&gt;
"""
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Macro code
&lt;/h3&gt;

&lt;p&gt;Just add the following macro to your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;LiveStreamAsync&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;LiveView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;AsyncResult&lt;/span&gt;

  &lt;span class="k"&gt;defmacro&lt;/span&gt; &lt;span class="n"&gt;__using__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kn"&gt;quote&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="kn"&gt;unquote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nv"&gt;@before_compile&lt;/span&gt; &lt;span class="kn"&gt;unquote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Module&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:async_streams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;accumulate:&lt;/span&gt; &lt;span class="no"&gt;true&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;defmacro&lt;/span&gt; &lt;span class="n"&gt;__before_compile__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_env&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;streams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Module&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__CALLER__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:async_streams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;streams&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="kn"&gt;quote&lt;/span&gt; &lt;span class="ss"&gt;bind_quoted:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;stream:&lt;/span&gt; &lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;opts:&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&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;results&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;socket&lt;/span&gt;
            &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AsyncResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&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="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kn"&gt;unquote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

          &lt;span class="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_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:exit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&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;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;async_result&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
             &lt;span class="no"&gt;AsyncResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;async_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:exit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
           &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;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;span class="k"&gt;defmacro&lt;/span&gt; &lt;span class="n"&gt;stream_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Module&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__CALLER__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:async_streams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="kn"&gt;quote&lt;/span&gt; &lt;span class="ss"&gt;bind_quoted:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;socket:&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;key:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;func:&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;opts:&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;socket&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AsyncResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loading&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;start_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;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;h2&gt;
  
  
  Hex package &lt;code&gt;live_stream_async&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;For ease, I packaged this macro on &lt;a href="https://hex.pm/packages/live_stream_async"&gt;hex.pm&lt;/a&gt;, so you can easily add as your project dependency in the &lt;code&gt;mix.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;defp&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:live_stream_async&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 0.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;runtime:&lt;/span&gt; &lt;span class="no"&gt;false&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;Use it the same way as described in this article Use &lt;code&gt;stream_async()&lt;/code&gt; function&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

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

&lt;p&gt;In this article, we learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;new functions in LiveView v 0.20.0 to work with asynchronous tasks&lt;/li&gt;
&lt;li&gt;fetching big collection for streaming require using low level &lt;code&gt;start_async/4&lt;/code&gt; function combined with &lt;code&gt;handle_async()&lt;/code&gt; callbacks&lt;/li&gt;
&lt;li&gt;combining &lt;code&gt;%AsyncResult{}&lt;/code&gt; struct with async streaming allows to control the state of loading big collections in the UI&lt;/li&gt;
&lt;li&gt;import &lt;code&gt;stream_async()&lt;/code&gt; functionality to your project with &lt;a href="https://hex.pm/packages/live_stream_async"&gt;hex.pm&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#module-async-operations"&gt;Phoenix LiveView Async Operations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#async_result/1"&gt;Phoenix.LiveView.AsyncResult&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#start_async/4"&gt;Phoenix LiveView.start_async/4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#stream/4"&gt;Phoenix.LiveView.stream/4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
      <category>liveview</category>
    </item>
  </channel>
</rss>
