<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Mike Zornek</title>
    <link>https://mikezornek.com/tags/elixir/</link>
    <description>Recent content in elixir on Mike Zornek</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>mike@mikezornek.com (Mike Zornek)</managingEditor>
    <webMaster>mike@mikezornek.com (Mike Zornek)</webMaster>
    <lastBuildDate>Tue, 27 May 2025 08:00:00 -0400</lastBuildDate>
    
	<atom:link href="https://mikezornek.com/tags/elixir/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>A Visual Tour of Phoenix&#39;s Updated &#39;magic link&#39; Authentication Code Generator</title>
      <link>https://mikezornek.com/posts/2025/5/phoenix-magic-link-authentication/</link>
      <pubDate>Tue, 27 May 2025 08:00:00 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2025/5/phoenix-magic-link-authentication/</guid>
      <description>&lt;p&gt;With Phoenix 1.8, the authentication code generator inside &lt;code&gt;phx.gen.auth&lt;/code&gt; has been revamped, favoring an emailed &amp;lsquo;magic link&amp;rsquo; authentication flow.&lt;/p&gt;
&lt;p&gt;In this blog post, I&amp;rsquo;ll give a visual tour of how this looks to the user, what the core schemas/contexts look like, and I&amp;rsquo;ll sprinkle in some personal commentary as we go.&lt;/p&gt;
&lt;h2 id=&#34;background-on-authentication&#34;&gt;Background on authentication&lt;/h2&gt;
&lt;p&gt;When building any web application, user registration, authentication, and authorization are extremely common needs. The Phoenix team wants to help and provides tools and patterns to help get you jump-started.&lt;/p&gt;
&lt;p&gt;The Mix task &lt;code&gt;phx.gen.auth&lt;/code&gt; is one such tool. Using it inside a fresh Phoenix project, you can set up a community-vetted starting point to allow user registration and authentication.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll also spot some small files created by the &lt;code&gt;phx.gen.auth&lt;/code&gt; task related to &lt;a href=&#34;https://hexdocs.pm/phoenix/1.8.0-rc.3/scopes.html&#34;&gt;Scopes&lt;/a&gt; a Phoenix 1.8 authorization pattern, but we will not be talking about authorization today.&lt;/p&gt;
&lt;p&gt;Creating user registration and authentication flows have a large surface area for security concerns, and using a community-vetted approach like this is highly recommended. Even if you decide to roll your own, generating the community&amp;rsquo;s recommended code can be a valuable reference tool to inform your custom design.&lt;/p&gt;
&lt;h2 id=&#34;the-sample-hello-project&#34;&gt;The sample &lt;code&gt;Hello&lt;/code&gt; project&lt;/h2&gt;
&lt;p&gt;The project we&amp;rsquo;ll be reviewing today was created by:&lt;/p&gt;
&lt;p&gt;Installing a prerelease &lt;code&gt;phx_new&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mix archive.install hex phx_new 1.8.0-rc.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creating a new project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mix phx.new hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And generate authentication logic:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mix phx.gen.auth Accounts User users
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can find a repo with this code at &lt;a href=&#34;https://github.com/zorn/magic-link-demo&#34;&gt;https://github.com/zorn/magic-link-demo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;user-experience&#34;&gt;User experience&lt;/h2&gt;
&lt;p&gt;I consider myself a user-focused developer, so let&amp;rsquo;s start with a visual review of how this registration and authentication feels from a user point of view.&lt;/p&gt;
&lt;h3 id=&#34;navigation-updates&#34;&gt;Navigation updates&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://mikezornek.com/posts/2025/5/phoenix-magic-link-authentication/1_register_log_in_navigation.png&#34; alt=&#34;Screenshot showing navigation bar with Register and Log in links&#34;&gt;&lt;/p&gt;
&lt;p&gt;One of the first outcomes of running the code generator is a change to the site navigation. In the upper-right you now have links for &lt;code&gt;Register&lt;/code&gt; and &lt;code&gt;Log in&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;registration-page&#34;&gt;Registration page&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://mikezornek.com/posts/2025/5/phoenix-magic-link-authentication/2_register_form.png&#34; alt=&#34;Screenshot showing a registration form asking for email only.&#34;&gt;&lt;/p&gt;
&lt;p&gt;On the registration page, you&amp;rsquo;ll see a form asking for a single input value, the user&amp;rsquo;s email.&lt;/p&gt;
&lt;p&gt;While password authentication is still an option, a password is not asked for at the time of registration.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://mikezornek.com/posts/2025/5/phoenix-magic-link-authentication/3_register_toast.png&#34; alt=&#34;Screenshot showing a toast notification telling the user an email was sent to their account.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Once filled out, the user is presented with a toast explaining that an email was sent to confirm their account.&lt;/p&gt;
&lt;h3 id=&#34;opening-the-confirmation-magic-link&#34;&gt;Opening the confirmation &amp;lsquo;magic link&amp;rsquo;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://mikezornek.com/posts/2025/5/phoenix-magic-link-authentication/5_dev_mailbox.png&#34; alt=&#34;Screenshot of a black and white minimal email browsing UI.&#34;&gt;&lt;/p&gt;
&lt;p&gt;When working in local dev, the Phoenix app runs with a web interface to a fake email inbox at &lt;code&gt;http://localhost:4000/dev/mailbox/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://mikezornek.com/posts/2025/5/phoenix-magic-link-authentication/6_magic_link_page_view.png&#34; alt=&#34;Screenshot of the page shown after clicking the magic link.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Upon clicking the magic link, the user is presented with a simple form that offers an optional checkbox to &lt;code&gt;Keep me logged in&lt;/code&gt; (which will last for 14 days; see module attribute &lt;code&gt;@session_validity_in_days&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This page is required because magic links can be &amp;ldquo;viewed&amp;rdquo; by automated systems, such as email link security tools or even URL preview cards seen in apps like Slack. We need to require a user action, and by using the form &lt;code&gt;POST&lt;/code&gt; (which automated tools should avoid), we have stronger confidence that the token in this magic link will not be used unexpectedly.&lt;/p&gt;
&lt;p&gt;Once the user clicks on the &lt;code&gt;Confirm my account&lt;/code&gt; button, they will, in fact, be authenticated AND their account confirmed. On subsequent flows, the button will say &lt;code&gt;Log in&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://mikezornek.com/posts/2025/5/phoenix-magic-link-authentication/7_authenticated_toast.png&#34; alt=&#34;Screenshot showing an authentication toast.&#34;&gt;&lt;/p&gt;
&lt;p&gt;After authentication, the user is redirected to a predetermined &lt;code&gt;signed_in_path&lt;/code&gt; or a stored &lt;code&gt;user_return_to&lt;/code&gt; path and is presented with a toast saying they have been authenticated.&lt;/p&gt;
&lt;h3 id=&#34;authenticated-navigation&#34;&gt;Authenticated navigation&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://mikezornek.com/posts/2025/5/phoenix-magic-link-authentication/8_settings_log_out_naviation.png&#34; alt=&#34;Screenshot showing a navigation with email, Settings and Log out&#34;&gt;&lt;/p&gt;
&lt;p&gt;After the user is authenticated, the navigation changes to show the account email address, a &lt;code&gt;Settings&lt;/code&gt; link, and a &lt;code&gt;Log out&lt;/code&gt; link.&lt;/p&gt;
&lt;h3 id=&#34;log-in-with-email&#34;&gt;Log in with email&lt;/h3&gt;
&lt;p&gt;To examine the log in form, we will first &lt;code&gt;Log out&lt;/code&gt; and see a simple toast with &lt;code&gt;Logged out successfully.&lt;/code&gt;. Then, upon clicking the &lt;code&gt;Log in&lt;/code&gt; link, you will see this page:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://mikezornek.com/posts/2025/5/phoenix-magic-link-authentication/4_log_in_form.png&#34; alt=&#34;Screenshot showing the log in form&#34;&gt;&lt;/p&gt;
&lt;p&gt;The upper half of the page invites you to &lt;code&gt;Log in with email&lt;/code&gt;. Filling out an email will result in a toast saying, &lt;code&gt;If your email is in our system, you will receive instructions for logging in shortly.&lt;/code&gt; That email looks very similar to the confirmed email we showed before.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aside:&lt;/strong&gt; It is good security practice to build systems so as not to leak user data. I suspect that is the intent of this message&amp;rsquo;s phrasing. However, the system is not doing this in all places. If you try to register with a known email address, it &lt;a href=&#34;has_already_been_taken.png&#34;&gt;leaks the existence&lt;/a&gt; of the email in the system with &lt;code&gt;has already been taken&lt;/code&gt;. There is a balance between pure security concerns and helping the user. You, as the system designer, should decide which path to take for your specific app. I wish the generated code was more consistent, however.&lt;/p&gt;
&lt;h3 id=&#34;log-in-with-emailpassword&#34;&gt;Log in with email/password&lt;/h3&gt;
&lt;p&gt;The bottom of the page offers a more traditional email/password form. It works as expected. If you are following along, you might be asking yourself, &amp;ldquo;But I did not create a password?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;An account can, in fact, have a password. It is set on the &lt;code&gt;Settings&lt;/code&gt; page we will view below. However, out of the box, the Settings page and its &lt;code&gt;Save Password&lt;/code&gt; form is an optional page, not all users would have seen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aside:&lt;/strong&gt; I think it is a bit odd that this generator is doing Log in with email &lt;strong&gt;and&lt;/strong&gt; a secondary (optional) Log in with email/password. In my own professional experiences, I&amp;rsquo;ve done each separately but never both at the same time. It feels like a UX decision that could lead to user confusion.&lt;/p&gt;
&lt;h3 id=&#34;settings&#34;&gt;Settings&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://mikezornek.com/posts/2025/5/phoenix-magic-link-authentication/9_settings.png&#34; alt=&#34;Screenshot showing two forms. One to Change Email and another to Save Password&#34;&gt;&lt;/p&gt;
&lt;p&gt;As an authenticated user, you&amp;rsquo;ll see that the &lt;code&gt;Settings&lt;/code&gt; page provides two forms, one for &lt;code&gt;Change Email&lt;/code&gt; and one for &lt;code&gt;Save Password&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;change-email&#34;&gt;Change Email&lt;/h3&gt;
&lt;p&gt;When you put in a new email value and click &lt;code&gt;Change Email&lt;/code&gt; the change does not happen immediately. Instead, a link with a token identity for a &lt;code&gt;UserToken&lt;/code&gt; record that contains the request is sent via email. Only when that link is activated is the change applied.&lt;/p&gt;
&lt;p&gt;The generator logic inside of &lt;a href=&#34;https://github.com/zorn/magic-link-demo/blob/main/lib/hello/accounts.ex#L119&#34;&gt;&lt;code&gt;update_user_email/2&lt;/code&gt;&lt;/a&gt; will destroy any previous &lt;code&gt;UserToken&lt;/code&gt; records, protecting against multiple change email links going out. All previous ones are deleted upon creation of a new &lt;code&gt;change:#{user.email}&lt;/code&gt; token context. More on the &lt;code&gt;UserToken&lt;/code&gt; schema below.&lt;/p&gt;
&lt;h3 id=&#34;save-password&#34;&gt;Save Password&lt;/h3&gt;
&lt;p&gt;Setting a password is not a requirement on the registration page.&lt;/p&gt;
&lt;p&gt;To set the initial password (or change the current one), you need to fill out this form with a password value and a confirmation. Out of the box, this will require a 12-character length value, but other requirements can be added, and the generated code offers suggestions inside of &lt;a href=&#34;https://github.com/zorn/magic-link-demo/blob/1de3ac787dc7e98721a2e3df468e454a3275d74b/lib/hello/accounts/user.ex#L81-L90&#34;&gt;&lt;code&gt;User.validate_password/2&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You might note this form does not ask for the current password. Instead, this whole settings page utilizes a function from &lt;code&gt;Accounts&lt;/code&gt; called &lt;a href=&#34;https://github.com/zorn/magic-link-demo/blob/1de3ac787dc7e98721a2e3df468e454a3275d74b/lib/hello/accounts.ex#L85-L97&#34;&gt;&lt;code&gt;sudo_mode?/2&lt;/code&gt;&lt;/a&gt;. The boolean function is documented as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@doc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  Checks whether the user is in sudo mode.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  The user is in sudo mode when the last authentication was done no further
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  than 20 minutes ago. The limit can be given as second argument in minutes.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; sudo_mode?(user, minutes \\ &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the user attempts to load the setting page using a more stale authenticated session, they&amp;rsquo;ll get a flash redirect saying, &lt;code&gt;You must re-authenticate to access this page.&lt;/code&gt;. Some other user experiences would ask for a confirmation of the current password; this is an alternative approach to that security need.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aside:&lt;/strong&gt; I don&amp;rsquo;t like the name &lt;code&gt;sudo_mode?/2&lt;/code&gt;. For me, &lt;code&gt;sudo&lt;/code&gt; implies an increase in privilege. I renamed the function &lt;code&gt;recently_authenticated?/2&lt;/code&gt; in my own fork of this logic.&lt;/p&gt;
&lt;h2 id=&#34;schemas&#34;&gt;Schemas&lt;/h2&gt;
&lt;p&gt;All of the core authentication logic is found inside &lt;code&gt;Hello.Accounts&lt;/code&gt;. There are two main persisted entities.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  schema &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;users&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    field &lt;span style=&#34;color:#e6db74&#34;&gt;:email&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    field &lt;span style=&#34;color:#e6db74&#34;&gt;:password&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:string&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;virtual&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;redact&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    field &lt;span style=&#34;color:#e6db74&#34;&gt;:hashed_password&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:string&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;redact&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    field &lt;span style=&#34;color:#e6db74&#34;&gt;:confirmed_at&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:utc_datetime&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    field &lt;span style=&#34;color:#e6db74&#34;&gt;:authenticated_at&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:utc_datetime&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;virtual&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    timestamps(&lt;span style=&#34;color:#e6db74&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;:utc_datetime&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first is &lt;code&gt;Hello.Accounts.User&lt;/code&gt;, and this is a schema for the persisted entity for the registered user. Most fields are pretty straightforward. Good to see the use of &lt;code&gt;redact: true&lt;/code&gt; for those password-related fields.&lt;/p&gt;
&lt;p&gt;I feel a little curious about the &lt;code&gt;virtual&lt;/code&gt; &lt;code&gt;authenticated_at&lt;/code&gt; field. This is a value that is patched into the &lt;code&gt;User&lt;/code&gt; struct &lt;a href=&#34;https://github.com/zorn/magic-link-demo/blob/1de3ac787dc7e98721a2e3df468e454a3275d74b/lib/hello/accounts/user_token.ex#L63&#34;&gt;during calls to&lt;/a&gt; &lt;code&gt;Hello.Accounts.get_user_by_session_token/1&lt;/code&gt; and later evaluated by &lt;a href=&#34;https://github.com/zorn/magic-link-demo/blob/1de3ac787dc7e98721a2e3df468e454a3275d74b/lib/hello/accounts.ex#L85-L97&#34;&gt;&lt;code&gt;sudo_mode?/2&lt;/code&gt;&lt;/a&gt;. This feels like a performance choice, connecting values earlier in the call stack instead of repeating a database call later, but I&amp;rsquo;m generally suspicious of &lt;code&gt;virtual&lt;/code&gt; fields as they lead to many questions when trying to understand how the fields are populated.&lt;/p&gt;
&lt;p&gt;The second entity is &lt;code&gt;Hello.Accounts.UserToken&lt;/code&gt;, and this schema creates a container of sorts that will be used for a variety of tasks.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  schema &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;users_tokens&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    field &lt;span style=&#34;color:#e6db74&#34;&gt;:token&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:binary&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    field &lt;span style=&#34;color:#e6db74&#34;&gt;:context&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    field &lt;span style=&#34;color:#e6db74&#34;&gt;:sent_to&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    field &lt;span style=&#34;color:#e6db74&#34;&gt;:authenticated_at&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:utc_datetime&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    belongs_to &lt;span style=&#34;color:#e6db74&#34;&gt;:user&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Hello.Accounts.User&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    timestamps(&lt;span style=&#34;color:#e6db74&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;:utc_datetime&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;updated_at&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you authenticate, in addition to the Phoenix session / cookie, a &lt;code&gt;UserToken&lt;/code&gt; record is created. The reasoning for this is documented in the &lt;code&gt;build_session_token/1&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@doc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  Generates a token that will be stored in a signed place,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  such as session or cookie. As they are signed, those
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  tokens do not need to be hashed.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  The reason why we store session tokens in the database, even
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  though Phoenix already provides a session cookie, is because
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  Phoenix&amp;#39; default session cookies are not persisted, they are
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  simply signed and potentially encrypted. This means they are
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  valid indefinitely, unless you change the signing/encryption
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  salt.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  Therefore, storing them allows individual user
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  sessions to be expired. The token system can also be extended
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  to store additional data, such as the device used for logging in.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  You could then use this information to display all valid sessions
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  and devices in the UI and allow users to explicitly expire any
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  session they deem invalid.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; build_session_token(user) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, this &lt;code&gt;UserToken&lt;/code&gt; schema is used for more than just sessions.&lt;/p&gt;
&lt;p&gt;When the system sends an emailed magic link to log in, that is a reference to a &lt;code&gt;UserToken&lt;/code&gt;. Again, the power to expire these is very handy.&lt;/p&gt;
&lt;p&gt;The system even uses &lt;code&gt;UserToken&lt;/code&gt; when changing an email. The wanted new email address is &lt;a href=&#34;https://github.com/zorn/magic-link-demo/blob/1de3ac787dc7e98721a2e3df468e454a3275d74b/lib/hello/accounts.ex#L268&#34;&gt;encoded in the link&lt;/a&gt; sent over email.&lt;/p&gt;
&lt;h2 id=&#34;closing-thoughts-and-nitpicks&#34;&gt;Closing thoughts and nitpicks&lt;/h2&gt;
&lt;p&gt;So that is a quick visual overview of what you&amp;rsquo;ll find in Phoenix 1.8&amp;rsquo;s update &lt;code&gt;phx.gen.auth&lt;/code&gt; code generator. &lt;a href=&#34;https://hexdocs.pm/phoenix/1.8.0-rc.3/mix_phx_gen_auth.html&#34;&gt;Formal documentation&lt;/a&gt; has more detailed information and if you want to follow the community discussions that led to this change see this &lt;a href=&#34;https://github.com/phoenixframework/phoenix/issues/6041&#34;&gt;issue&lt;/a&gt; and &lt;a href=&#34;https://github.com/phoenixframework/phoenix/pull/6081&#34;&gt;PR&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have mixed feelings about the change overall.&lt;/p&gt;
&lt;p&gt;I think my primary concern is that this adds friction to the deployment of Phoenix toy projects that want user registration. Requiring the developer to acquire transactional email services is one more thing to deploy, and there are plenty of projects where I&amp;rsquo;d like user registration but desire to keep the dependencies as small as possible.&lt;/p&gt;
&lt;p&gt;When I first heard about this change, I thought it would be opt-in, but &lt;a href=&#34;https://hexdocs.pm/phoenix/1.8.0-rc.3/Mix.Tasks.Phx.Gen.Auth.html&#34;&gt;there is no option&lt;/a&gt; to prefer email/password as the primary user flow. It is not a giant leap to generate code and then pull some of it back, remove the magic links, and introduce email/password during registration (I&amp;rsquo;m doing that for my current Phoenix sandbox), but an official option would be welcome.&lt;/p&gt;
&lt;p&gt;Other small nitpicks I&amp;rsquo;d encourage others to consider when adapting this code for your own projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I think button labels should have a consistent case. This project has a mix of title-cased buttons and first-word-only capitalization. Many also have a left-pointing arrow, which adds some visual flair to help promote a primary page action but can be visually overwhelming when you see multiple buttons using it, like on the log in page.&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;m not sure why it says &lt;code&gt;Change Email&lt;/code&gt; but &lt;code&gt;Save Password&lt;/code&gt;. I wish it were consistent with the verb &lt;code&gt;Change&lt;/code&gt;. I thought maybe it would only say &lt;code&gt;Save&lt;/code&gt; Password when the password was missing, but in my experience, it was a static button label.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;If you welcome this kind of nitpick criticism of your own project, I am available for &lt;a href=&#34;https://mikezornek.com/elixir-consulting/&#34;&gt;Elixir Consulting&lt;/a&gt;. 😅&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Ranked Vote Flick Demo</title>
      <link>https://mikezornek.com/posts/2025/5/ranked-vote-flick-demo/</link>
      <pubDate>Thu, 01 May 2025 19:58:04 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2025/5/ranked-vote-flick-demo/</guid>
      <description>&lt;p&gt;Took a few moments tonight to record a short demo of &lt;a href=&#34;https://rankedvote.app/&#34;&gt;RankedVote.app&lt;/a&gt; (Project name: &lt;a href=&#34;https://github.com/zorn/flick&#34;&gt;Flick&lt;/a&gt;) tonight.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Flick is a simple Elixir / Phoenix LiveView app that helps capture ranked votes.&lt;/p&gt;
&lt;p&gt;This project was built to help the &lt;a href=&#34;https://elixirbookclub.github.io/website/&#34;&gt;Elixir Book Club&lt;/a&gt; pick books, but it is open to all.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;rsquo;m going to moving on to other side projects and seems as good a time as any to capture this in action.&lt;/p&gt;





&lt;video
  controls
  class=&#34;mb-0 w-full&#34;
  title=&#34;Project Demo: RankedVote.app (Flick) -- Phoenix LiveView app to help people run ranked voting.&#34;
&gt;
  &lt;source src=&#34;https://f002.backblazeb2.com/file/mikezornek-com-media/flick-demo-2025-05-01.mp4&#34; type=&#34;video/mp4&#34;&gt;
  Your browser does not seem to support the video format. You can
  &lt;a href=&#34;https://f002.backblazeb2.com/file/mikezornek-com-media/flick-demo-2025-05-01.mp4&#34;&gt;download the MP4 file&lt;/a&gt;
  directly.
&lt;/video&gt;

&lt;div class=&#34;mt-2 flex justify-between&#34;&gt;
  &lt;a href=&#34;https://f002.backblazeb2.com/file/mikezornek-com-media/flick-demo-2025-05-01.mp4&#34;&gt;Download MP4&lt;/a&gt;
  
    &lt;a href=&#34;https://www.youtube.com/watch?v=pxE6AbaQuUM&#34;&gt;Watch on YouTube&lt;/a&gt;
  
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Conference Notes: CodeBEAM America (San Francisco) 2025</title>
      <link>https://mikezornek.com/posts/2025/3/code-beam-america-notes/</link>
      <pubDate>Mon, 10 Mar 2025 09:56:13 -0800</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2025/3/code-beam-america-notes/</guid>
      <description>&lt;p&gt;After a run of bad luck where I had to cancel Elixir-related conferences at the last minute (once because of the smoke storms that hit NY, and once because I got COVID before the conference), this week I enjoyed some time at &lt;a href=&#34;https://codebeamamerica.com/&#34;&gt;CodeBEAM America 2025 in San Francisco&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had a great time at the conference. The content was high quality and well presented. The in-person attendees totaled around 100 people, with another 150 online. I got some well-enjoyed real-world face time with a few old friends, online book club peeps, and maybe even some future clients.&lt;/p&gt;
&lt;h2 id=&#34;notable-talks&#34;&gt;Notable Talks&lt;/h2&gt;
&lt;p&gt;A few notable talks that landed for me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jenny Bramble and Adrian Dunston did an excellent talk, &amp;ldquo;Detective Hat: Investigating Production Issues,&amp;rdquo; reviewing how to approach production incidents, communicate with your peers, question assumptions, and ultimately learn from the experience. The pair was well prepared, and the content well delivered.&lt;/li&gt;
&lt;li&gt;James Arthur code dropped &lt;a href=&#34;https://hexdocs.pm/phoenix_sync/readme.html&#34;&gt;&lt;code&gt;Phoenix.Sync&lt;/code&gt;&lt;/a&gt; built on the back of &lt;a href=&#34;https://electric-sql.com/&#34;&gt;ElectricSQL&lt;/a&gt;. I recently learned of Electric while &lt;a href=&#34;https://www.youtube.com/watch?v=ZlHWSpIYixk&#34;&gt;catching up on the Local-first Conf videos&lt;/a&gt; just a few weeks ago. It was cool to learn that Electric is built using Elixir back then, but seeing some Phoenix-specific tooling drop was even more exciting. Kicking these tires will be high on my priority list.&lt;/li&gt;
&lt;li&gt;Eric Saxby had a talk called &amp;ldquo;Acceptable Upgrades,&amp;rdquo; jam-packed with great code organization and testing strategies. I captured many handwritten notes, but the last one was to get the slides, as I could not capture them all.&lt;/li&gt;
&lt;li&gt;Jason Axelson followed this up with another test-focused talk titled &amp;ldquo;Choosing an effective testing structure,&amp;rdquo; which had a bunch of great tips and library suggestions.&lt;/li&gt;
&lt;li&gt;Digit did a talk titled &amp;ldquo;Nerves ❤️ Flutter&amp;rdquo; reviewing how the SmartRent Thermostat/Smart Home hub was built, and it was awe-inspiring. The &lt;a href=&#34;https://github.com/nerves-flutter/nerves_flutter_support&#34;&gt;&lt;code&gt;nerves_flutter_support&lt;/code&gt;&lt;/a&gt; repo was made public as well to help people build on this.&lt;/li&gt;
&lt;li&gt;Mitchell Hanberg gave us an update on the new Elixir Language Server, now named &lt;a href=&#34;https://expert-lsp.org/&#34;&gt;Expert&lt;/a&gt;. The now &lt;a href=&#34;https://elixir-lang.org/blog/2024/08/15/welcome-elixir-language-server-team/&#34;&gt;blessed official team&lt;/a&gt; has been hard at work building a new language server foundation built on the best of the previous three projects. A release still feels a few months away, but the update and teaser were much appreciated.&lt;/li&gt;
&lt;li&gt;Kevin Barrett and Sloane Perrault did a talk, &amp;ldquo;CRDTs and the BEAM: Eventual Consistency Through Acronyms.&amp;rdquo; Having CRDT in the title, this was a session I&amp;rsquo;ve had circled on my calendar for months. The talk was an excellent review of how they are utilizing CRDTs to power the delivery of &lt;a href=&#34;https://screen.garden/&#34;&gt;screen.garden&lt;/a&gt;, which allows real-time collaboration of standard Obsidian Markdown.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;conference-format&#34;&gt;Conference Format&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The talks were a mix of 40-minute and 25-minute sessions with ample time for hallway track discussions. A great balance.&lt;/li&gt;
&lt;li&gt;There was a great 40-minute block each day for &amp;ldquo;Birds of a Feather&amp;rdquo; sessions. Early in the day, people voted on topics, and then the round tables where we ate lunch were signed for spontaneous discussions. I attended a table on WebAssembly, learning about &lt;a href=&#34;https://github.com/wasmx&#34;&gt;&lt;code&gt;wasmx&lt;/code&gt;&lt;/a&gt; on the first day, and then a table talking about &lt;a href=&#34;https://www.ash-hq.org/&#34;&gt;Ash Framework&lt;/a&gt; and &lt;a href=&#34;https://hexdocs.pm/igniter/readme.html&#34;&gt;Igniter&lt;/a&gt; on the second day.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;vibes&#34;&gt;Vibes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;While the majority of attendees seemed work-aligned with Elixir, there was also a healthy mix of Nerves, Gleam, and Erlang folks.&lt;/li&gt;
&lt;li&gt;Everyone was very friendly and welcoming. There were people who had history and clumped together, but at the same time, I observed many people making space for new people to enter the circle and join in.&lt;/li&gt;
&lt;li&gt;Presenting in pairs was popular and more so impressively executed. Everyone seemed well in sync with their presenting partner.&lt;/li&gt;
&lt;li&gt;The chaos of the real world was not front and center in most talks (which is fine with me as I enjoy the escape), but it did leak through at times. I recall a specific moment when Ingela Anderton Andin was doing an OTP update. She was remote, presenting from her home in Sweden over video. There was some chatter at the end about how the conference would love to see her return in person in the United States in the upcoming year. There was an uneasiness in her reaction. Acknowledging without fully verbalizing the state of international relations. Seeing the forthcoming calendar of BEAM-related events, many happening in Europe, I had a similar response. I don&amp;rsquo;t know if United States citizens will be welcome there in 6 months.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;followup-learning-projects&#34;&gt;Followup Learning Projects&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Related to some discussion and possible project work, I bought the &lt;a href=&#34;https://pragprog.com/titles/ldash/ash-framework/&#34;&gt;Ash book&lt;/a&gt; and started reading it on the plane ride home. I&amp;rsquo;ve worked with Ash before, but using the book to refresh my memory. I may even consider porting some of the &lt;a href=&#34;https://github.com/zorn/flick&#34;&gt;Flick&lt;/a&gt; domain to Ash as a space to play around.&lt;/li&gt;
&lt;li&gt;People were hyped about &lt;a href=&#34;https://www.cursor.com/&#34;&gt;Cursor&lt;/a&gt; well over GitHub Copilot for AI codegen things. I need to experiment more with it.&lt;/li&gt;
&lt;li&gt;Need to tinker with &lt;code&gt;Phoenix.Sync&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;I totally forgot about &lt;a href=&#34;https://github.com/burrito-elixir/burrito&#34;&gt;&lt;code&gt;burrito&lt;/code&gt;&lt;/a&gt;. It was mentioned during Digit&amp;rsquo;s talk, and I should totally check it out in relation to building local-first software.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Burrito is our answer to the problem of distributing Elixir CLI applications across varied environments, where we cannot guarantee that the Erlang runtime is installed, and where we lack the permissions to install it ourselves. In particular, we have CLI tooling that must be deployed on-premise by consultants into customer environments that may be running MacOS, Linux, or Windows.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;While I disliked the travel, and boy was I grumpy after the travel day on Wednesday, the conference was a tremendous success. This was my first time at a CodeBEAM event, and I highly recommend it.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Phoenix LiveView: Presenting DateTime in User&#39;s Time Zone</title>
      <link>https://mikezornek.com/posts/2025/1/presenting-datetime-in-user-time-zone-phoenix-live-view/</link>
      <pubDate>Wed, 22 Jan 2025 19:47:37 -0500</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2025/1/presenting-datetime-in-user-time-zone-phoenix-live-view/</guid>
      <description>&lt;p&gt;This past weekend, I added a feature to Flick (&lt;a href=&#34;https://rankedvote.app/&#34;&gt;RankedVote.app&lt;/a&gt;) where we now present domain-specific &lt;code&gt;DateTime&lt;/code&gt; values, like &lt;code&gt;published_at&lt;/code&gt; and &lt;code&gt;closed_at&lt;/code&gt;, on the live view page using the user&amp;rsquo;s time zone. I thought I&amp;rsquo;d capture some notes on how this was accomplished, some known limitations, ideas to solve those in your own work, and a set of resource links to learn more.&lt;/p&gt;
&lt;h2 id=&#34;elixir-time-zone-basics&#34;&gt;Elixir time zone basics&lt;/h2&gt;
&lt;p&gt;Out of the box, when working with Elixir &lt;a href=&#34;https://hexdocs.pm/elixir/DateTime.html&#34;&gt;&lt;code&gt;DateTime&lt;/code&gt;&lt;/a&gt; values, you can only create values relative to the &lt;code&gt;Etc/UTC&lt;/code&gt; time zone. If you want to represent values in other time zones, you will need a time zone database.&lt;/p&gt;
&lt;p&gt;The Elixir docs discuss this and &lt;a href=&#34;https://hexdocs.pm/elixir/DateTime.html#module-time-zone-database&#34;&gt;link a few options&lt;/a&gt;. For this post and the Flick project, I choose &lt;a href=&#34;https://hex.pm/packages/tzdata&#34;&gt;&lt;code&gt;tzdata&lt;/code&gt;&lt;/a&gt;, and the following paragraphs will reference it specifically. However, the Elixir runtime is very flexible if you prefer another.&lt;/p&gt;
&lt;p&gt;To add the dependency to your project, you&amp;rsquo;ll add it to the &lt;code&gt;deps&lt;/code&gt; list in the &lt;code&gt;mix.exs&lt;/code&gt; file.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# mix.exs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;defp&lt;/span&gt; deps &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#e6db74&#34;&gt;:tzdata&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;~&amp;gt; 1.1&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, you&amp;rsquo;ll need to configure it as your &lt;code&gt;:time_zone_database&lt;/code&gt; in the &lt;code&gt;config/config.exs&lt;/code&gt; file.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# config/config.exs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;config &lt;span style=&#34;color:#e6db74&#34;&gt;:elixir&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:time_zone_database&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Tzdata.TimeZoneDatabase&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With that in place, you can create values in a time zone.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;iex&amp;gt; DateTime.new(~D[2016-05-24], ~T[13:26:08.003], &amp;#34;America/New_York&amp;#34;)
{:ok, #DateTime&amp;lt;2016-05-24 13:26:08.003-04:00 EDT America/New_York&amp;gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;the-users-time-zone&#34;&gt;The user&amp;rsquo;s time zone&lt;/h2&gt;
&lt;p&gt;When I say &lt;code&gt;User&lt;/code&gt; here, I refer to the web browser client requesting a web page from our server.&lt;/p&gt;
&lt;p&gt;Sadly, when displaying a &lt;code&gt;DateTime&lt;/code&gt; value in this user&amp;rsquo;s response, we are hampered by the fact that we do not know their time zone. The time zone is not included with the HTTP request.&lt;/p&gt;
&lt;p&gt;Assuming the browser renders the page in an environment that executes JavaScript, the most approachable way to get this is via a function call like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Intl&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DateTimeFormat&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;resolvedOptions&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;timeZone&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat&#34;&gt;MDN Reference for &lt;code&gt;DateTimeFormat&lt;/code&gt;.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aside:&lt;/strong&gt; Curiously, the time zone and locale details of the user&amp;rsquo;s environment are not part of a permission prompt flow even though they leak sensitive data IMO. If viewing the page, assume you are giving up that info.&lt;/p&gt;
&lt;p&gt;So, how do we use this relative to our live view page?&lt;/p&gt;
&lt;p&gt;The first thing we will do is edit our &lt;code&gt;assets/js/app.js&lt;/code&gt; file. We want to query the browser for the time zone and send it to LiveView via the &lt;code&gt;params&lt;/code&gt; of the &lt;code&gt;LiveSocket&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// assets/js/app.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;csrfToken&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; document
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  .&lt;span style=&#34;color:#a6e22e&#34;&gt;querySelector&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;meta[name=&amp;#39;csrf-token&amp;#39;]&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  .&lt;span style=&#34;color:#a6e22e&#34;&gt;getAttribute&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;content&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Add this.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time_zone&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Intl&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DateTimeFormat&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;resolvedOptions&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;timeZone&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;liveSocket&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LiveSocket&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/live&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Socket&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;longPollFallbackMs&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2500&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// And `time_zone` here.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;params&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;_csrf_token&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;csrfToken&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;time_zone&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time_zone&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From your live view module&amp;rsquo;s &lt;code&gt;mount/3&lt;/code&gt; function, you can access this &lt;code&gt;time_zone&lt;/code&gt; value like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; mount(params, _session, socket) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# The `time_zone` value will only be available when the live view is&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# rendering a `connected?/1` socket, so make sure to define a default.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    time_zone &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; get_connect_params(socket)[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;time_zone&amp;#34;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;UTC&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once you have the time zone value, any domain-specific &lt;code&gt;DateTime&lt;/code&gt; you are storing in UTC can be converted using &lt;code&gt;DateTime.shift_zone/2&lt;/code&gt; for display to the user.&lt;/p&gt;
&lt;p&gt;For the needs of my side project, I made a dedicated module called &lt;code&gt;Flick.DateTimeFormatter&lt;/code&gt;, which helps me shift the &lt;code&gt;DateTime&lt;/code&gt; values and format them to an expected style. It looks something like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; display_string(date_time_value, time_zone) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DateTime&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;shift_zone(date_time_value, time_zone) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {&lt;span style=&#34;color:#e6db74&#34;&gt;:ok&lt;/span&gt;, date_time_in_time_zone} &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;Calendar&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;strftime(date_time_in_time_zone, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%B %-d, %Y %-I:%M %p %Z&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {&lt;span style=&#34;color:#e6db74&#34;&gt;:error&lt;/span&gt;, reason} &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {&lt;span style=&#34;color:#e6db74&#34;&gt;:error&lt;/span&gt;, reason}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With this in place, I can display a &lt;code&gt;DateTime&lt;/code&gt; value from any live view in the user&amp;rsquo;s time zone.&lt;/p&gt;
&lt;p&gt;A full implementation can be found in &lt;a href=&#34;https://github.com/zorn/flick/pull/137/files&#34;&gt;the Flick PR&lt;/a&gt; if you want more reference materials.&lt;/p&gt;
&lt;h2 id=&#34;this-is-a-half-solution&#34;&gt;This is a half solution.&lt;/h2&gt;
&lt;p&gt;You might recall in a code comment above, I said:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;The `time_zone` value will only be available when the live view is
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rendering a `connected?/1` socket, so make sure to define a default.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You might also recall that when a user requests a URL, the live view first renders a non-connected DOM state. This DOM is delivered to the browser, and only then is a WebSocket established, and thus the live view becomes &amp;ldquo;connected&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The outcome of this and the design of our solution is that the user will see the &lt;code&gt;DateTime&lt;/code&gt; value rendered in &lt;code&gt;UTC&lt;/code&gt; first (our default) and then flash into the browser&amp;rsquo;s time zone as the WebSocket becomes connected.&lt;/p&gt;
&lt;p&gt;That kind of sucks, but for the simple needs of Flick, this felt acceptable and is where I left it.&lt;/p&gt;
&lt;h2 id=&#34;designing-a-more-complete-solution&#34;&gt;Designing a more complete solution.&lt;/h2&gt;
&lt;p&gt;In a previous project, I built a meetup group platform. It had to display lots of &lt;code&gt;DateTime&lt;/code&gt; values, and this kind of flashing would not have been acceptable.&lt;/p&gt;
&lt;p&gt;For that project, we created a stack of fallback time zones.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When a user created their meetup group, they defined a default display time zone for the group. This was our fallback default.&lt;/li&gt;
&lt;li&gt;When a web visitor was on the page, we &lt;a href=&#34;#errata&#34;&gt;executed JavaScript&lt;/a&gt; that would &lt;code&gt;PUT&lt;/code&gt; the observed time zone we saw and then on the backend inside a standard controller we would &lt;a href=&#34;#errata&#34;&gt;store the time zone inside the session&lt;/a&gt;. This would be our ideal default, and would generally be available on everything except the user&amp;rsquo;s first page load of the site.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;a-hook-based-approach&#34;&gt;A Hook-based approach.&lt;/h2&gt;
&lt;p&gt;LittleAccountOfCalm of Reddit also &lt;a href=&#34;https://www.reddit.com/r/elixir/comments/1i84ptj/comment/m8qdgal/&#34;&gt;suggests&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pardon me, but why not render a &lt;code&gt;&amp;lt;local-time phx-hook=&amp;quot;LocalTime&amp;quot; id={@id} class=&amp;quot;invisible&amp;quot;&amp;gt;{@date}&amp;lt;/local-time&lt;/code&gt; component, that has a js-hook like:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Hooks&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;LocalTime&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;mounted&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;updated&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;updated&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dt&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Date(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;el&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;textContent&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;el&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;textContent&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Intl&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DateTimeFormat&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;default&amp;#34;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;dateStyle&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;medium&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// timeStyle: &amp;#34;short&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }).&lt;span style=&#34;color:#a6e22e&#34;&gt;format&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;dt&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;el&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;classList&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;remove&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;invisible&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I recall seeing approaches like this before and you correct that this is just as reasonable of a solution for Flick&amp;rsquo;s needs. I vaguely recall requirements that made it a non-option for my other project (maybe due to time zone presentation in email copy) but a great addition to the post. Thanks for sharing!&lt;/p&gt;
&lt;h2 id=&#34;other-resources&#34;&gt;Other Resources&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ll leave you out with some related resources. Good coding, and if you have any questions, &lt;a href=&#34;https://mikezornek.com/contact&#34;&gt;reach out&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Elixir&amp;rsquo;s &lt;a href=&#34;https://hexdocs.pm/elixir/DateTime.html&#34;&gt;&lt;code&gt;DateTime&lt;/code&gt;&lt;/a&gt; docs are great and worthy of re-read.&lt;/li&gt;
&lt;li&gt;My &lt;a href=&#34;https://github.com/zorn/flick/pull/137/files&#34;&gt;Flick PR&lt;/a&gt; where I added this feature.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://elixirschool.com/en/lessons/basics/date-time/&#34;&gt;Date and Time · Elixir School&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I gave a talk &lt;a href=&#34;https://mikezornek.com/posts/2020/3/working-with-time-zones-in-an-elixir-phoenix-app/&#34;&gt;Working With Time Zones in an Elixir Phoenix App&lt;/a&gt; back in 2020 at a local meetup. I have the video, but the embed on that page is currently down. Slides are there, which might be interesting.&lt;/li&gt;
&lt;li&gt;There was an amazing talk at ElixirConf 2022 &lt;a href=&#34;https://www.youtube.com/watch?v=4VfPvCI901c&#34;&gt;Kip Cole - Time algebra: a new way to think about and work with time&lt;/a&gt;. This strays from the practical demonstrations of this post but was a memorable talk that impressed me.&lt;/li&gt;
&lt;li&gt;DockYards had their own recent demo blog post, which aligns with many of the things shared here: &lt;a href=&#34;https://dockyard.com/blog/2024/10/15/getting-displaying-users-local-time-liveview&#34;&gt;Getting and Displaying the User&amp;rsquo;s Local Time in LiveView&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://simonwillison.net/2024/Nov/27/storing-times-for-human-events/&#34;&gt;Simon Willison: Storing times for human events&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;errata&#34;&gt;Errata&lt;/h2&gt;
&lt;p&gt;I originally posted this with some links to a private repo. I&amp;rsquo;m not in a place to make that repo public but will recreate the linked code below.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// assets/js/app.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// &amp;#34;When a web visitor was on the page, we executed JavaScript that would PUT the observed time zone we saw...&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;window.&lt;span style=&#34;color:#a6e22e&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;DOMContentLoaded&amp;#34;&lt;/span&gt;, (&lt;span style=&#34;color:#a6e22e&#34;&gt;event&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (document.&lt;span style=&#34;color:#a6e22e&#34;&gt;head&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;dataset&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timezone&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;user_timezone&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Intl&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DateTimeFormat&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;resolvedOptions&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;timeZone&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;User timezone was unknown on dom load. Sending user timezone (&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;user_timezone&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;) to server.&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;location&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;protocol&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;//&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;location&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;host&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/timezone?iana=&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        encodeURIComponent(&lt;span style=&#34;color:#a6e22e&#34;&gt;user_timezone&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ).&lt;span style=&#34;color:#a6e22e&#34;&gt;then&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;response&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;response&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# lib/guildflow_web/controllers/subdomain/timezone_controller.ex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# &amp;#34;... and then on the backend inside a standard controller we would store the time zone inside the session.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; index(conn, %{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;iana&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; iana}) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Enum&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;member?(&lt;span style=&#34;color:#a6e22e&#34;&gt;Timex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timezones(), iana) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    conn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; put_session(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;timezone&amp;#34;&lt;/span&gt;, iana)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; render(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;index.html&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;user_timezone&lt;/span&gt;: iana)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    user_timezone &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; get_session(conn, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;timezone&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    render(conn, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;index.html&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;user_timezone&lt;/span&gt;: user_timezone,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;page_title&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;User Session Timezone Status&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Personal Elixir Code Aesthetics</title>
      <link>https://mikezornek.com/posts/2024/9/elixir-code-aesthetic/</link>
      <pubDate>Sun, 29 Sep 2024 20:25:46 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2024/9/elixir-code-aesthetic/</guid>
      <description>&lt;p&gt;With my side project &lt;a href=&#34;https://github.com/zorn/flick&#34;&gt;Flick&lt;/a&gt; hitting an MVP milestone and inspired by some conversations during &lt;a href=&#34;https://elixirbookclub.github.io/website/&#34;&gt;Elixir Book Club&lt;/a&gt;, I thought I&amp;rsquo;d take a moment to document some code aesthetic choices I made in this project.&lt;/p&gt;
&lt;p&gt;The order below is not ranked in importance. In fact most of this is nitpicky, but still my preference.&lt;/p&gt;
&lt;h2 id=&#34;whitespace-between-import-and-alias&#34;&gt;Whitespace between import and alias.&lt;/h2&gt;
&lt;h2 id=&#34;alphabetically-ordered-alias-declarations&#34;&gt;Alphabetically ordered alias declarations.&lt;/h2&gt;
&lt;h2 id=&#34;avoiding-multi-alias-declarations&#34;&gt;Avoiding multi-alias declarations.&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# lib/flick/ranked_voting.ex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Ecto.Query&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Flick.RankedVoting.Ballot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Flick.RankedVoting.Vote&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Flick.Repo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Whitespace should be done with intention to separate distinct concepts; and for me, &lt;code&gt;import&lt;/code&gt; and &lt;code&gt;alias&lt;/code&gt; are distinct concepts.&lt;/p&gt;
&lt;p&gt;I also list my &lt;code&gt;alias&lt;/code&gt; declarations in alphabetical order, enforced via &lt;a href=&#34;https://hexdocs.pm/credo/Credo.Check.Readability.AliasOrder.html&#34;&gt;&lt;code&gt;AliasOrder&lt;/code&gt;&lt;/a&gt; and use a preferred order across &lt;code&gt;use&lt;/code&gt;, &lt;code&gt;import&lt;/code&gt;, &lt;code&gt;alias&lt;/code&gt;, and &lt;code&gt;require&lt;/code&gt; via &lt;a href=&#34;https://hexdocs.pm/credo/Credo.Check.Readability.StrictModuleLayout.html&#34;&gt;&lt;code&gt;StrictModuleLayout&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I prefer to avoid multi-&lt;code&gt;alias&lt;/code&gt; declarations like &lt;code&gt;alias Flick.RankedVoting.{Ballot, Vote}&lt;/code&gt; since it makes searching the code base for module names harder to do. This is enforced with &lt;a href=&#34;https://hexdocs.pm/credo/Credo.Check.Readability.MultiAlias.html&#34;&gt;&lt;code&gt;MultiAlias&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;generally-prefer-multiline-doend-functions&#34;&gt;(Generally) prefer multiline do/end functions.&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;defp&lt;/span&gt; page_title(&lt;span style=&#34;color:#e6db74&#34;&gt;:edit&lt;/span&gt;), &lt;span style=&#34;color:#e6db74&#34;&gt;do&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Edit Ballot&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;defp&lt;/span&gt; page_title(_), &lt;span style=&#34;color:#e6db74&#34;&gt;do&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Create a Ballot&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Unless I can express a group of functions in a simple stack (like the above code) I generally prefer multiline &lt;code&gt;do/end&lt;/code&gt; function declarations. One reason for this preference is that my code editor can collapse the whole module in a nice way, making during exploration easier.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# This could be one line, but I prefer multiline do/end.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; get_ballot!(ballot_id) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;Repo&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get!(&lt;span style=&#34;color:#a6e22e&#34;&gt;Ballot&lt;/span&gt;, ballot_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;use-consistent-dsl-layout-in-test-modules&#34;&gt;Use consistent DSL layout in test modules.&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# test/flick/ranked_voting_test.exs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;describe &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;update_ballot/1&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  test &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;success: updates a ballot title and questions&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  test &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;failure: `question_title` is required&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  test &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;failure: can not update a published ballot&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Test files can generally end up 2 or 3 times the line count of the module under test. To help keep this code organized and easy to navigate I use a style where I will list the function under test using &lt;code&gt;describe&lt;/code&gt; and then various &lt;code&gt;success&lt;/code&gt; and &lt;code&gt;failure&lt;/code&gt; expectations in each &lt;code&gt;test&lt;/code&gt;. For functions like &lt;code&gt;list_ballots/1&lt;/code&gt; that can&amp;rsquo;t fail, one might drop the &lt;code&gt;success&lt;/code&gt; label, but looking at my code as it stands today, it looks like I kept it. 🤷‍♂️&lt;/p&gt;
&lt;p&gt;I have far less consistency with my LiveView tests. Sometimes the &lt;code&gt;describe&lt;/code&gt; &lt;a href=&#34;https://github.com/zorn/flick/blob/main/test/flick_web/live/ballots/editor_live_test.exs&#34;&gt;breaks out new vs edit logic&lt;/a&gt;, other times it &lt;a href=&#34;https://github.com/zorn/flick/blob/main/test/flick_web/live/ballots/index_live_test.exs&#34;&gt;breaks out with or without authentication&lt;/a&gt; paths. I&amp;rsquo;m still evolving this and welcome ideas and good examples.&lt;/p&gt;
&lt;h2 id=&#34;invest-in-test-fixtures&#34;&gt;Invest in test fixtures&lt;/h2&gt;
&lt;p&gt;Arrange, Act, Assert. To help keep your arrange logic neat and tidy, invest in good test fixture tooling. These test fixtures should provide functions for entity creation in your test files as well as the known list of argument defaults (for tests that need to do something more custom but do not want to be burdened with a complete understanding of every argument).&lt;/p&gt;
&lt;p&gt;These fixtures should use the actual domain context paths for creation and not raw SQL injection unless absolutely needed for performance or edge cases.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# test/support/fixtures/ballot_fixture.ex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;defmodule&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Support.Fixtures.BallotFixture&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@moduledoc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  Provides functions to allows tests to easily create and stage
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  `Flick.RankedVoting.Ballot` entities for testing.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Flick.RankedVoting.Ballot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@doc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  Returns a map of valid attributes for a `Flick.RankedVoting.Ballot` entity,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  allowing for the passed in attributes to override defaults.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@spec&lt;/span&gt; valid_ballot_attributes(map()) &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; map()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; valid_ballot_attributes(attrs \\ %{}) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Enum&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;into(attrs, %{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;question_title&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;What day should have dinner?&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;possible_answers&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Monday, Tuesday, Wednesday, Thursday, Friday&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;url_slug&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;dinner-day-&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;System&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;unique_integer()&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;published_at&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@doc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  Creates a `Flick.RankedVoting.Ballot` entity in the `Flick.Repo` for the passed in
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  optional attributes.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  When not provided, all required attributes will be generated.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@spec&lt;/span&gt; ballot_fixture(map()) &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Ballot&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; ballot_fixture(attrs \\ %{}) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    attrs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; valid_ballot_attributes(attrs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#e6db74&#34;&gt;:ok&lt;/span&gt;, ballot} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Flick.RankedVoting&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;create_ballot(attrs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ballot
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@doc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  Creates a `Flick.RankedVoting.Ballot` entity in the `Flick.Repo` for the passed in
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  optional attributes and then publishes the ballot.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  When not provided, all required attributes will be generated.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@spec&lt;/span&gt; published_ballot_fixture(map()) &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Ballot&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; published_ballot_fixture(attrs \\ %{}) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    attrs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; valid_ballot_attributes(attrs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#e6db74&#34;&gt;:ok&lt;/span&gt;, ballot} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Flick.RankedVoting&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;create_ballot(attrs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#e6db74&#34;&gt;:ok&lt;/span&gt;, published_ballot} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Flick.RankedVoting&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;publish_ballot(ballot)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    published_ballot
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Aside: I hate how the typespecs here use &lt;code&gt;map()&lt;/code&gt; for the attribute map. I want to make that more detailed in the future, see &lt;a href=&#34;https://github.com/zorn/flick/issues/4&#34;&gt;issue #4&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;use-tiny_maps-to-save-horizontal-line-space-in-tests&#34;&gt;Use tiny_maps to save horizontal line space in tests.&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;success: submitting valid form creates ballot and redirects&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;~M{view, ballot}&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When composing my test descriptions, I like to get the &lt;code&gt;test &amp;quot;...&amp;quot; do&lt;/code&gt; on a single line. To help achieve that, I use the &lt;a href=&#34;https://github.com/abshierjoel/tiny_maps&#34;&gt;tiny_maps&lt;/a&gt; library to help me express repetitive argument maps with a more concise syntax: &lt;code&gt;~M{view, ballot}&lt;/code&gt; expands to &lt;code&gt;%{view: view, ballot: ballot}&lt;/code&gt;. I generally limit this syntax sugar to test files but would not be against using it in the main source in the future.&lt;/p&gt;
&lt;h2 id=&#34;compose-code-to-prefer-clean-line-breaks&#34;&gt;Compose code to prefer clean line breaks&lt;/h2&gt;
&lt;p&gt;I love that Elixir ships with an opinionated formatter. However, even with the formatter, you still have a lot of influence on how your code is composed. I prefer it greatly when expressions are clean one-liners or otherwise avoid excessive indentation when breaking up complex terms.&lt;/p&gt;
&lt;p&gt;To help explain, let me walk you through a test from Flick.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;success: submitting valid form creates ballot and redirects&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;~M{view}&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  payload &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; %{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;question_title&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;What&amp;#39;s your favorite color?&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;possible_answers&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Red, Green, Blue&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;url_slug&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;favorite-color&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    view
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; form(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;form&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;ballot&lt;/span&gt;: payload)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; render_submit()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Assert upon submit the page redirects, and the ballot was created.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  assert {&lt;span style=&#34;color:#e6db74&#34;&gt;:error&lt;/span&gt;, {&lt;span style=&#34;color:#e6db74&#34;&gt;:redirect&lt;/span&gt;, %{&lt;span style=&#34;color:#e6db74&#34;&gt;to&lt;/span&gt;: redirect_target}}} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; response
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  assert &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/ballot/favorite-color/&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt; secret &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; redirect_target
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  assert %&lt;span style=&#34;color:#a6e22e&#34;&gt;Ballot&lt;/span&gt;{} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RankedVoting&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_ballot_by_url_slug_and_secret!(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;favorite-color&amp;#34;&lt;/span&gt;, secret)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First, let&amp;rsquo;s take note that &lt;code&gt;response&lt;/code&gt; is captured in its own line group. Technically, you could compose &lt;code&gt;render_submit()&lt;/code&gt; to happen on the same line as &lt;code&gt;assert&lt;/code&gt;, but by capturing &lt;code&gt;response&lt;/code&gt; using its own line group, it helps separate the &lt;code&gt;act&lt;/code&gt; vs. &lt;code&gt;assert&lt;/code&gt; test concepts and avoids a very complex indentation variant.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# A complex multiline expression with lots of indentation, I am trying to avoid.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;assert {&lt;span style=&#34;color:#e6db74&#34;&gt;:error&lt;/span&gt;, {&lt;span style=&#34;color:#e6db74&#34;&gt;:redirect&lt;/span&gt;, %{&lt;span style=&#34;color:#e6db74&#34;&gt;to&lt;/span&gt;: redirect_target}}} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         view
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; form(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;form&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;ballot&lt;/span&gt;: payload)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; render_submit()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, let&amp;rsquo;s look at the pipe feeding &lt;code&gt;response&lt;/code&gt;. You could compose the code like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  view
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; form(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;form&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;ballot&lt;/span&gt;: %{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;question_title&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;What&amp;#39;s your favorite color?&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;possible_answers&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Red, Green, Blue&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;url_slug&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;favorite-color&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; render_submit()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I dislike this composition since it breaks the pipe. By moving the &lt;code&gt;payload&lt;/code&gt; value assignment to its own line group, we end up with a cleaner pipe that, in my opinion, reads better.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;payload &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; %{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;question_title&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;What&amp;#39;s your favorite color?&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;possible_answers&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Red, Green, Blue&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;url_slug&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;favorite-color&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  view
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; form(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;form&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;ballot&lt;/span&gt;: payload)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; render_submit()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;embrace-pipelines-with-custom-utility-functions&#34;&gt;Embrace pipelines with custom utility functions.&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# lib/flick_web/live/ballots/index_live.ex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; mount(_params, _session, socket) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  socket
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; assign(&lt;span style=&#34;color:#e6db74&#34;&gt;:page_title&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Admin: Ballots&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; assign(&lt;span style=&#34;color:#e6db74&#34;&gt;:ballots&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Flick.RankedVoting&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;list_ballots())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; ok()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;defmodule&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FlickWeb.LiveViewPipes&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@moduledoc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  A collection of functions to help express pipes when processing live view responses.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Phoenix.LiveView.Socket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@spec&lt;/span&gt; ok(&lt;span style=&#34;color:#a6e22e&#34;&gt;Socket&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()) &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; {&lt;span style=&#34;color:#e6db74&#34;&gt;:ok&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Socket&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; ok(%&lt;span style=&#34;color:#a6e22e&#34;&gt;Socket&lt;/span&gt;{} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; socket), &lt;span style=&#34;color:#e6db74&#34;&gt;do&lt;/span&gt;: {&lt;span style=&#34;color:#e6db74&#34;&gt;:ok&lt;/span&gt;, socket}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@spec&lt;/span&gt; noreply(&lt;span style=&#34;color:#a6e22e&#34;&gt;Socket&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()) &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; {&lt;span style=&#34;color:#e6db74&#34;&gt;:noreply&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Socket&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; noreply(%&lt;span style=&#34;color:#a6e22e&#34;&gt;Socket&lt;/span&gt;{} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; socket), &lt;span style=&#34;color:#e6db74&#34;&gt;do&lt;/span&gt;: {&lt;span style=&#34;color:#e6db74&#34;&gt;:noreply&lt;/span&gt;, socket}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Many LiveView functions require tuple return values like &lt;code&gt;{:ok, socket}&lt;/code&gt; or &lt;code&gt;{:noreply, socket}&lt;/code&gt;. To help allow call sites to be composed as a single pipeline, I use some utility functions like &lt;code&gt;ok()&lt;/code&gt; and &lt;code&gt;noreply()&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;be-consistent-between-module-names-and-filenames&#34;&gt;Be consistent between module names and filenames.&lt;/h2&gt;
&lt;p&gt;If I have a module called &lt;code&gt;Flick.RankedVoting.RankedAnswer&lt;/code&gt; it lives at the filepath &lt;code&gt;lib/flick/ranked_voting/ranked_answer.ex&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;While I have good consistency inside my core domain contexts, this consistency fails with various Phoenix things. For example, &lt;code&gt;FlickWeb.Ballots.EditorLive&lt;/code&gt; lives at &lt;code&gt;lib/flick_web/live/ballots/editor_live.ex&lt;/code&gt;. I dislike that Phoenix generators put these modules in a folder called &lt;code&gt;live&lt;/code&gt; that the module path does not express. I may fix that in the future.&lt;/p&gt;
&lt;p&gt;Related blog post: &lt;a href=&#34;https://mikezornek.com/posts/2024/9/liveview-modules-must-end-in-live/&#34;&gt;LiveView Modules Must End in &lt;code&gt;Live&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;write-professional-documentation&#34;&gt;Write professional documentation.&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@doc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  Publishes the given `Flick.RankedVoting.Ballot` entity.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  Once a `Flick.RankedVoting.Ballot` entity is published, it can no longer be updated.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  Only a published ballot can be voted on.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All public functions, especially those that represent the formalized domain API for your system, should get documentation.&lt;/p&gt;
&lt;p&gt;Each documentation block should start with a single-line, terse summary, as those are used by &lt;code&gt;ex_doc&lt;/code&gt; and other developer tooling to summarize function indexes. Every so often, look at the collection of function summaries of a module and try to make them all use consistent phrasing like &amp;ldquo;Returns noun given thing&amp;hellip;&amp;rdquo; or &amp;ldquo;Raises &lt;code&gt;Blah&lt;/code&gt; when stuff&amp;hellip;&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;If referencing another module, function, or callback, use backticks to help the documentation system generate hyperlinks.&lt;/p&gt;
&lt;p&gt;Use &lt;a href=&#34;https://github.com/stkb/Rewrap&#34;&gt;rewrap tools&lt;/a&gt; to hardwrap characters to 80 columns to help with GitHub diffing and more presentable Markdown.&lt;/p&gt;
&lt;h2 id=&#34;document-your-decisions&#34;&gt;Document your decisions.&lt;/h2&gt;
&lt;p&gt;Programming is all about tradeoffs. When making an intentional design or process decision with other valid approaches available, consider documenting what was considered and why you went with your approach. Your future self and peers will thank you.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve &lt;a href=&#34;https://github.com/zorn/flick/tree/main/docs/decisions&#34;&gt;documented some things&lt;/a&gt; related to timestamps, schema shape, and fixme/todo so far in Flick.&lt;/p&gt;
&lt;h2 id=&#34;make-sure-each-fixme-has-an-issue-url&#34;&gt;Make sure each FIXME has an issue URL.&lt;/h2&gt;
&lt;p&gt;Related to the above decisions, Flick &lt;a href=&#34;https://github.com/zorn/flick/blob/main/docs/decisions/3-fixme-and-todo.md&#34;&gt;allows FIXME comments&lt;/a&gt; but requests all &lt;code&gt;FIXME&lt;/code&gt;s include a link to a GitHub issue documenting the concern.&lt;/p&gt;
&lt;h2 id=&#34;avoid-abbreviations-and-prefer-expressiveness&#34;&gt;Avoid abbreviations and prefer expressiveness.&lt;/h2&gt;
&lt;p&gt;Prefer expressive variable names like &lt;code&gt;_ballot&lt;/code&gt; over &lt;code&gt;_&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Prefer expressive variable names like &lt;code&gt;ballot_params&lt;/code&gt; over &lt;code&gt;params&lt;/code&gt; when you think it helps improve clarity.&lt;/p&gt;
&lt;h2 id=&#34;words-matter&#34;&gt;Words matter.&lt;/h2&gt;
&lt;p&gt;Invest time in an expressive and consistent &lt;a href=&#34;https://github.com/zorn/flick/blob/main/docs/ubiquitous_language.md&#34;&gt;ubiquitous language&lt;/a&gt; for your project. Continue to edit it over time as the terms evolve.&lt;/p&gt;
&lt;p&gt;Find consistency in your project code regarding terms like &lt;code&gt;create&lt;/code&gt; vs. &lt;code&gt;new&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt; vs. &lt;code&gt;edit&lt;/code&gt;, &lt;code&gt;submit&lt;/code&gt; vs. &lt;code&gt;save&lt;/code&gt;, &lt;code&gt;params&lt;/code&gt; vs. &lt;code&gt;attributes&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Do your best to align with existing Elixir community norms.&lt;/p&gt;
&lt;h2 id=&#34;craft-typespecs-to-express-your-domain&#34;&gt;Craft typespecs to express your domain.&lt;/h2&gt;
&lt;p&gt;All public functions should have a typespec. Private functions can also have typespecs, depending on whether they will help with code clarity or change confidence.&lt;/p&gt;
&lt;p&gt;Spend time and make those typespecs match the domain. For example, when accepting the identity of a &lt;code&gt;Ballot&lt;/code&gt; use the &lt;code&gt;Ballot.id()&lt;/code&gt; not a &lt;code&gt;Ecto.UUID.t()&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# lib/flick/ranked_voting/ballot.ex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@type&lt;/span&gt; id &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Ecto.UUID&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When building types for my main domain entities, I tend to build the main &lt;code&gt;t()&lt;/code&gt; around a persisted entity value that is brought into memory from a &lt;code&gt;Repo&lt;/code&gt;, and thus all post-creation values like &lt;code&gt;id&lt;/code&gt; or &lt;code&gt;updated_at&lt;/code&gt; are typed to their expected value, without the need for an &lt;code&gt;| nil&lt;/code&gt; addendum.&lt;/p&gt;
&lt;p&gt;To help write typespecs for non-persisted structs of this schema type, I make a &lt;code&gt;schema_t()&lt;/code&gt; variant.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@typedoc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  A type for a persisted `Flick.RankedVoting.Ballot` entity.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@type&lt;/span&gt; t &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;%&lt;/span&gt;__MODULE__{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;Ecto.UUID&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;question_title&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;String&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;description&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;String&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t() &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;url_slug&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;String&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;secret&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;Ecto.UUID&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;possible_answers&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;String&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;published_at&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;DateTime&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t() &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@typedoc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  A type for the empty `Flick.RankedVoting.Ballot` struct.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  This type is helpful when you want to typespec a function that needs to accept
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  a non-persisted `Flick.RankedVoting.Ballot` struct value.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@type&lt;/span&gt; struct_t &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;%&lt;/span&gt;__MODULE__{}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Spend time writing &lt;code&gt;typedoc&lt;/code&gt; documentation, it can be a helpful space to talk out the reasoning behind some of the types.&lt;/p&gt;
&lt;p&gt;In addition to writing typespecs, when composing functions I also prefer pattern matching the struct and using guards to be explicit about incoming argument expectations.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; change_ballot(%&lt;span style=&#34;color:#a6e22e&#34;&gt;Ballot&lt;/span&gt;{} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ballot, attrs) &lt;span style=&#34;color:#f92672&#34;&gt;when&lt;/span&gt; is_map(attrs) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dialyzer is not a runtime enforcement tool, but these are. They help enforce expectations earlier in the call stack and thus help you become aware of when things are not as they seem sooner.&lt;/p&gt;
&lt;h2 id=&#34;document-your-dependencies&#34;&gt;Document your dependencies.&lt;/h2&gt;
&lt;p&gt;To help future you know why various dependencies were added to the project, add a minimal description before listing it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;defp&lt;/span&gt; deps &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# For Observability.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#e6db74&#34;&gt;:appsignal_phoenix&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;~&amp;gt; 2.5&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# To Render Markdown.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#e6db74&#34;&gt;:earmark&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;~&amp;gt; 1.4&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# For security scans.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#e6db74&#34;&gt;:sobelow&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;~&amp;gt; 0.13&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;only&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;:dev&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:test&lt;/span&gt;], &lt;span style=&#34;color:#e6db74&#34;&gt;runtime&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;document-and-validate-function-options&#34;&gt;Document and validate function options.&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# lib/flick/ranked_voting.ex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@doc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;##&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; Options
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  * `:action` - An optional atom applied to the changeset, useful for forms that
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    look to a changeset&amp;#39;s action to influence form behavior.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; change_vote(%&lt;span style=&#34;color:#a6e22e&#34;&gt;Vote&lt;/span&gt;{} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; vote, attrs, opts \\ []) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    opts &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Keyword&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;validate!(opts, &lt;span style=&#34;color:#e6db74&#34;&gt;action&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you offer a function that accepts options as the final argument, document them and validate them.&lt;/p&gt;
&lt;p&gt;Validation helps ensure call sites do not include unexpected or typoed option keys and offers a clean space to provide a default value for the said option.&lt;/p&gt;
&lt;p&gt;Aside: Not currently demonstrable in Flick, but is in some of my work project, I usually write rich typespecs for my options as well.&lt;/p&gt;
&lt;h2 id=&#34;compose-pr-titles-for-clarity-and-consistency&#34;&gt;Compose PR titles for clarity and consistency.&lt;/h2&gt;
&lt;p&gt;With Flick, I work in focused PRs and have those PRs titled for clarity regarding what is changing. I like using prefixes like &lt;code&gt;fix&lt;/code&gt; &lt;code&gt;chore&lt;/code&gt; &lt;code&gt;feat&lt;/code&gt;. These PR titles are enforced with a specific &lt;a href=&#34;https://github.com/zorn/flick/blob/main/.github/workflows/lint-pr.yaml&#34;&gt;GitHub Action workflow&lt;/a&gt;. These PRs are squash merged and make for a (hopefully) readable &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;
&lt;p&gt;In the future, I might even be able to automate this to help with release notes.&lt;/p&gt;
&lt;h2 id=&#34;strive-for-database-precision&#34;&gt;Strive for database precision.&lt;/h2&gt;
&lt;p&gt;Be detail-oriented when building out your database tables.&lt;/p&gt;
&lt;p&gt;If something can not be null, be explicit &lt;code&gt;NOT NULL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If a string can be really long, use &lt;code&gt;:text&lt;/code&gt;. If you do use &lt;code&gt;:string&lt;/code&gt; (which has length limits), then enforce those length limitations in &lt;code&gt;Ecto.Changeset&lt;/code&gt; so the value is not trimmed quietly.&lt;/p&gt;
&lt;p&gt;Virtual fields on Ecto schemas are almost never the right answer.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;That was quite the hodgepodge of suggestions and tips. I had others, but they did not fit, or I don&amp;rsquo;t have good open source references yet.&lt;/p&gt;
&lt;p&gt;If you liked these or disagree, &lt;a href=&#34;https://mikezornek.com/contact&#34;&gt;let me know&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>LiveView Modules Must End in `Live`</title>
      <link>https://mikezornek.com/posts/2024/9/liveview-modules-must-end-in-live/</link>
      <pubDate>Tue, 03 Sep 2024 20:32:57 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2024/9/liveview-modules-must-end-in-live/</guid>
      <description>&lt;p&gt;When working with &lt;a href=&#34;https://hexdocs.pm/phoenix_live_view/welcome.html&#34;&gt;LiveView&lt;/a&gt; you&amp;rsquo;ll notice people naming modules with the suffix &lt;code&gt;Live&lt;/code&gt;, as seen in module names like &lt;code&gt;FlickWeb.Ballots.IndexLive&lt;/code&gt; or &lt;code&gt;FlickWeb.Vote.VoteCaptureLive&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A student I was mentoring once asked me what that &lt;code&gt;Live&lt;/code&gt; suffix was for and if it was required.&lt;/p&gt;
&lt;p&gt;We drafted some simple LiveView demos with modules that did not end in &lt;code&gt;Live,&lt;/code&gt; and things worked fine. I had a gut feeling that this was required, but I could not put my finger on it.&lt;/p&gt;
&lt;p&gt;Today, I was reminded when and why you need the &lt;code&gt;Live&lt;/code&gt; suffix.&lt;/p&gt;
&lt;p&gt;When using the &lt;a href=&#34;https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Router.html#live/4&#34;&gt;&lt;code&gt;live/4&lt;/code&gt;&lt;/a&gt; router macro, you can optionally include an action like &lt;code&gt;:new&lt;/code&gt; and &lt;code&gt;:edit&lt;/code&gt; as seen in the examples below.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;live &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/ballot/new&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Ballots.EditorLive&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:new&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;live &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/ballot/:url_slug/:secret/edit&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Ballots.EditorLive&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:edit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This &lt;code&gt;action&lt;/code&gt; name will be available in the live view&amp;rsquo;s &lt;code&gt;socket.assigns.live_action&lt;/code&gt; and can be a helpful signal when you are utilizing a live view for multiple purposes, such as a form for creation or a form for mutation, depending on the URL.&lt;/p&gt;
&lt;p&gt;Should you use &lt;code&gt;action&lt;/code&gt; names, your &lt;code&gt;LiveView&lt;/code&gt; &lt;strong&gt;must&lt;/strong&gt; end with &lt;code&gt;Live&lt;/code&gt;. If it does not, you&amp;rsquo;ll see a compiler error like:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;== Compilation error in file lib/flick_web/router.ex ==
** (ArgumentError) could not infer :as option because a live action was given
and the LiveView does not have a &amp;#34;Live&amp;#34; suffix. Please pass :as explicitly or
make sure your LiveView is named like &amp;#34;FooLive&amp;#34; or &amp;#34;FooLive.Index&amp;#34;
  (phoenix_live_view 0.20.17) lib/phoenix_live_view/router.ex:479: Phoenix.LiveView.Router.inferred_as/3
  (phoenix_live_view 0.20.17) lib/phoenix_live_view/router.ex:409: Phoenix.LiveView.Router.__live__/4
  lib/flick_web/router.ex:36: (module)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Looking over the &lt;a href=&#34;https://github.com/phoenixframework/phoenix_live_view/blob/f778e5bb1a4b0a29f8d688bbc6c0b7182dea51ca/lib/phoenix_live_view/router.ex#L470-L487&#34;&gt;related source code&lt;/a&gt; I kind of suspect this assumption could be removed in the future, but seems like a low impact and pain level so, meh.&lt;/p&gt;
&lt;p&gt;I am happy to rediscover and document this. Hopefully, the explanation was helpful.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Announcing my New Full-Time Job</title>
      <link>https://mikezornek.com/posts/2023/11/new-job/</link>
      <pubDate>Mon, 27 Nov 2023 18:05:27 -0500</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2023/11/new-job/</guid>
      <description>&lt;p&gt;I have some big news: Starting December 4th, I&amp;rsquo;ll be working full-time as a Senior Elixir Developer for &lt;a href=&#34;https://www.allovue.com&#34;&gt;Allovue&lt;/a&gt;, where they build web-based accounting software for school districts.&lt;/p&gt;
&lt;p&gt;This change is particularly significant because I&amp;rsquo;ve been self-employed as a freelance consultant for most of my career.&lt;/p&gt;
&lt;p&gt;Frankly, finding consistent Elixir contracts over the last 18 months has been challenging. I&amp;rsquo;ve had some ongoing client work, but I have generally described the situation as underemployed to friends and family. While I continue to enjoy Elixir, compared to my historic iOS and Rails contracting, I found companies preferring full-time employees or more extensive consultancy team assignments. Sometimes, I was lucky enough to get subcontracts for such teams, but being outside of those relationships disadvantaged me.&lt;/p&gt;
&lt;p&gt;With that context, I started to keep an open mind and consider different job openings a few months ago. When it came to Allovue, I felt aligned with what they were doing and looking for. The interview process took some time, and I kept my expectations in check because you never really know how these things will turn out.&lt;/p&gt;
&lt;p&gt;When I eventually got the offer and considered all my observations about the opportunity, it was a good fit. My primary responsibilities will include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mentoring the junior contributors.&lt;/li&gt;
&lt;li&gt;Owning individual threads of Elixir and LiveView work.&lt;/li&gt;
&lt;li&gt;Helping explore larger product and architecture decisions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll share more as things get going. It&amp;rsquo;s going to be an exciting new year for sure.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Shutting Down ElixirClub</title>
      <link>https://mikezornek.com/posts/2023/7/shutting-down-elixir-club/</link>
      <pubDate>Tue, 04 Jul 2023 12:00:00 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2023/7/shutting-down-elixir-club/</guid>
      <description>&lt;p&gt;After six months, today I announced ElixirClub&amp;rsquo;s closure. For context and for those many souls who did not know:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ElixirClub is (was/hopped to be) an outcome-oriented community that helps Elixir developers finish their side projects.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I ran the site on Circle, hosted some weekly events and async project update threads &amp;ndash; but the hard truth is that I was not able to create even the modest-sized community that I was aiming for.&lt;/p&gt;
&lt;p&gt;There are reasons and lessons to be learned about pricing, pitches and what not. Ultimately it was very niche target and did not have a strong marketing angle.&lt;/p&gt;
&lt;p&gt;I thought it was best to close it down, issue refunds (automated and manual) as best I can and move on. To what? I have no idea.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;There is a awful bad taste anytime you put you and a side project of yours out there, &amp;hellip;and it fails.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s been many years since I had a solid side project win and I&amp;rsquo;d be lying if I did not also share, I feel like I&amp;rsquo;ve lost some of the side project drive I&amp;rsquo;ve had in the past.&lt;/p&gt;
&lt;p&gt;I am trying to &amp;ldquo;just keep swimming&amp;rdquo; as it were and even today have a new little side project in the wings, but I&amp;rsquo;m really hoping for a win soon™.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elixir Rap Song</title>
      <link>https://mikezornek.com/posts/2023/3/elixir-rap-song/</link>
      <pubDate>Tue, 14 Mar 2023 14:43:35 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2023/3/elixir-rap-song/</guid>
      <description>&lt;p&gt;Had too much fun asking ChatGPT to generate haiku poems and rap songs about programming tools and languages. Here is one about Elixir.&lt;/p&gt;
&lt;h2 id=&#34;compose-a-rap-song-about-how-much-programmers-love-the-functional-programming-language-elixir&#34;&gt;Compose a rap song about how much programmers love the functional programming language Elixir.&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Verse 1:
Listen up y&amp;#39;all, let me tell you &amp;#39;bout a language that&amp;#39;s dope
It&amp;#39;s called Elixir, and it&amp;#39;s more than just a little bit of hope
It&amp;#39;s functional programming, and it&amp;#39;s built to scale
You can build web apps, IoT, and anything else you wanna unveil

Chorus:
Elixir, Elixir, it&amp;#39;s the language of the gods
Programmers love it, they&amp;#39;re never gonna stop
With its easy syntax and its powerful tools
Elixir&amp;#39;s the one that rules, yeah, Elixir&amp;#39;s the one that rules

Verse 2:
Elixir&amp;#39;s got it all, from concurrency to fault tolerance
And the syntax is so sweet, you&amp;#39;ll never want to go back to nonsense
It&amp;#39;s got pipes, macros, and pattern matching too
Elixir&amp;#39;s so efficient, it&amp;#39;s gonna make your code dreams come true

Chorus:
Elixir, Elixir, it&amp;#39;s the language of the gods
Programmers love it, they&amp;#39;re never gonna stop
With its easy syntax and its powerful tools
Elixir&amp;#39;s the one that rules, yeah, Elixir&amp;#39;s the one that rules

Verse 3:
So if you want to build something that&amp;#39;s fast and robust
Elixir&amp;#39;s the language that you can trust
You can build real-time apps and scale with ease
Elixir&amp;#39;s the future, it&amp;#39;s the one that&amp;#39;s gonna please

Chorus:
Elixir, Elixir, it&amp;#39;s the language of the gods
Programmers love it, they&amp;#39;re never gonna stop
With its easy syntax and its powerful tools
Elixir&amp;#39;s the one that rules, yeah, Elixir&amp;#39;s the one that rules

Outro:
So now you know why programmers love Elixir so much
It&amp;#39;s the language that&amp;#39;s got the perfect touch
If you&amp;#39;re not using it yet, you&amp;#39;re missing out
Elixir&amp;#39;s the future, it&amp;#39;s what programming&amp;#39;s all about.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&#34;rapping.webp&#34; alt=&#34;Rabbit on stage&#34;&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elixir Context Accessor Function: Which Return Type Do You Prefer?</title>
      <link>https://mikezornek.com/posts/2023/2/elixir-get-noun-return-type/</link>
      <pubDate>Sun, 12 Feb 2023 13:55:42 -0500</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2023/2/elixir-get-noun-return-type/</guid>
      <description>&lt;p&gt;During a recent &lt;a href=&#34;https://elixirbookclub.github.io/website/&#34;&gt;Elixir Book Club&lt;/a&gt; meeting we had a back and forth discussion on a simple code style question:&lt;/p&gt;
&lt;p&gt;When providing an accessor function inside your domain context, do you prefer a return type of &lt;code&gt;{:ok, noun} || {:error, :not_found}&lt;/code&gt; or &lt;code&gt;noun || nil&lt;/code&gt; ?&lt;/p&gt;
&lt;p&gt;In my own historical work I&amp;rsquo;ve generally followed along with common Phoenix generator style of building out functions like &lt;code&gt;get_noun/1&lt;/code&gt; that return the &lt;code&gt;noun&lt;/code&gt; or &lt;code&gt;nil&lt;/code&gt; but moving forward I think I&amp;rsquo;ll be preferring &lt;code&gt;fetch_noun/1&lt;/code&gt; functions that use an &lt;code&gt;:ok&lt;/code&gt; / &lt;code&gt;:error&lt;/code&gt; tuple style return type.&lt;/p&gt;
&lt;p&gt;The primary reason is expressiveness in the crash logs. Eventually there will be some crash and the stacktrace is going to show &lt;code&gt;nil&lt;/code&gt; being passed into some function, and the function having an expectation of a real value type. In those moments it can take some time to understand where the hell &lt;code&gt;nil&lt;/code&gt; is coming from. I think an error like &lt;code&gt;noun_not_found&lt;/code&gt; will be more helpful.&lt;/p&gt;
&lt;p&gt;I want to start keeping track of these personal preferences and so today I&amp;rsquo;ve also made a new guide for the Franklin project documenting &lt;a href=&#34;https://github.com/zorn/franklin/blob/main/guides/code_style/context_accessors.md&#34;&gt;Code Style: Context Accessors&lt;/a&gt;. There is some more background and reasoning about my preferences inside should you be interested.&lt;/p&gt;
&lt;p&gt;While that guide expresses a preference towards &lt;code&gt;fetch_noun/1&lt;/code&gt; and the &lt;code&gt;:ok&lt;/code&gt; / &lt;code&gt;:error&lt;/code&gt; tuple return type, it also acknowledges that it might be helpful and supportive for the context to offer &lt;strong&gt;multiple&lt;/strong&gt; accessor functions allowing the call sites to use whichever one is more appropriate.
`
How about yourself? What return type do you prefer and why? &lt;a href=&#34;https://mikezornek.com/contact&#34;&gt;Let me know.&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>A New Project: ElixirClub</title>
      <link>https://mikezornek.com/posts/2022/11/elixir-club-intro/</link>
      <pubDate>Sun, 27 Nov 2022 21:14:14 -0500</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/11/elixir-club-intro/</guid>
      <description>&lt;p&gt;Today I am announcing a new project of mine taking shape. It&amp;rsquo;s called ElixirClub.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/6WvHGGqfggM?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;ElixirClub is an outcome-oriented community that &lt;strong&gt;helps Elixir developers finish their side projects&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Working on a side project by yourself is &lt;strong&gt;surrounded by risks&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;personal loss of focus&lt;/li&gt;
&lt;li&gt;no accountability&lt;/li&gt;
&lt;li&gt;endless time extensions&lt;/li&gt;
&lt;li&gt;exploding scope&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even a completed side project might be considered a failure if it does not find its audience or fails to provide a &lt;strong&gt;reasonable return on your time investment&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;ElixirClub is a community where people can plant their flag, declare their side project goals, and &lt;strong&gt;meet fellow Elixir developers climbing the same mountain&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Through a mixture of &lt;strong&gt;accountability&lt;/strong&gt;, &lt;strong&gt;education&lt;/strong&gt;, and &lt;strong&gt;celebrations&lt;/strong&gt;, you&amp;rsquo;ll be part of a group that fosters &lt;strong&gt;successful habits&lt;/strong&gt; to an ultimately successful side project.&lt;/p&gt;
&lt;h2 id=&#34;what-to-expect-as-a-member&#34;&gt;What to expect as a member?&lt;/h2&gt;
&lt;p&gt;All the below community heartbeats are designed to help you shape the work needed to see your side project to its planned completion and build a habit loop for regular progress.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Weekly &lt;strong&gt;async project check-ins&lt;/strong&gt; to share wins, plan next week&amp;rsquo;s work and ask questions.&lt;/li&gt;
&lt;li&gt;📅 Weekly &lt;strong&gt;live coworking&lt;/strong&gt; sessions, offering a mix of face-to-face accountability and dedicated mic-off time-boxed work time.&lt;/li&gt;
&lt;li&gt;📺 Themed &lt;strong&gt;live (and recorded) events&lt;/strong&gt; around side project management concerns and Elixir-specific programming interests.&lt;/li&gt;
&lt;li&gt;📝 Written &lt;strong&gt;guides&lt;/strong&gt; and suggested &lt;strong&gt;tasks&lt;/strong&gt; to wrangle your project and find your flow.&lt;/li&gt;
&lt;li&gt;🤝 A &lt;strong&gt;code review trading board&lt;/strong&gt; that allows members to exchange code reviews.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Membership to the community will require a modest monthly subscription. This choice helps pay for the community software, helps people solidify their dedication to their projects, and helps fund my work creating content for the group.&lt;/p&gt;
&lt;p&gt;Right now, my priorities are to chat with folks who are good candidates for such a group and work at preparing a productive home to host everything.&lt;/p&gt;
&lt;p&gt;If you would like to hear more about this in the future, please email me &lt;a href=&#34;mailto:zorn@zornlabs.com?subject=ElixirClubSubscribe&#34;&gt;mailto:zorn@zornlabs.com?subject=ElixirClubSubscribe&lt;/a&gt;, and I&amp;rsquo;ll add you to my manual notification list.&lt;/p&gt;
&lt;p&gt;If you are really excited about the group, please reach out and book some time on my calendar: &lt;a href=&#34;https://savvycal.com/zorn/chat&#34;&gt;https://savvycal.com/zorn/chat&lt;/a&gt; &amp;ndash; I&amp;rsquo;d love to hear what side project you are working on.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elixir Book Club: Testing Elixir, Starts November 13th</title>
      <link>https://mikezornek.com/posts/2022/10/elixir-book-club-testing-book/</link>
      <pubDate>Sun, 30 Oct 2022 11:48:17 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/10/elixir-book-club-testing-book/</guid>
      <description>&lt;p&gt;I am pleased to share the reboot of the Elixir Book Club.&lt;/p&gt;
&lt;p&gt;Starting Sunday, November 13th at 10:30am (Eastern Time), we&amp;rsquo;ll meet for an hour via Discord to talk about our next book&amp;hellip;&lt;/p&gt;
&lt;h2 id=&#34;testing-elixir&#34;&gt;Testing Elixir&lt;/h2&gt;
&lt;figure style=&#34;width: 40%; margin: 0 auto;&#34;&gt;
&lt;img src=&#34;testing-elixir.jpg&#34; alt=&#34;Testing Elixir Book Cover&#34; data-action=&#34;zoom&#34;&gt;
&lt;figcaption&gt;Testing Elixir Book Cover&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&#34;https://pragprog.com/titles/lmelixir/testing-elixir/&#34;&gt;https://pragprog.com/titles/lmelixir/testing-elixir/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I read this back in April of 2021 and thought well of it. It&amp;rsquo;s a good overview of the various testing tools available and how to apply them to Elixir scenarios and patterns (OTP, Ecto, Phoenix).&lt;/p&gt;
&lt;p&gt;During the first meeting will review chapters 1 and 2. After that, we will meet to review two or so more chapters every two weeks.&lt;/p&gt;
&lt;p&gt;To participate, you&amp;rsquo;ll need to &lt;a href=&#34;https://discord.gg/6WJqHkY66x&#34;&gt;join the Elixir Book Club Discord&lt;/a&gt;, and shortly I expect the main &lt;a href=&#34;https://elixirbookclub.github.io/website/&#34;&gt;book club website&lt;/a&gt; to be current with our new book and dates.&lt;/p&gt;
&lt;h2 id=&#34;extra-credit&#34;&gt;Extra Credit?&lt;/h2&gt;
&lt;p&gt;If you are interested in software testing, I HIGHLY recommend this other book as well: U&lt;strong&gt;nit Testing Principles, Practices, and Patterns&lt;/strong&gt;.&lt;/p&gt;
&lt;figure style=&#34;width: 40%; margin: 0 auto;&#34;&gt;
&lt;img src=&#34;unit-testing.jpg&#34; alt=&#34;Unit Testing Principles, Practices, and Patterns Book Cover&#34; data-action=&#34;zoom&#34;&gt;
&lt;figcaption&gt;Unit Testing Principles, Practices, and Patterns Book Cover&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&#34;https://www.manning.com/books/unit-testing&#34;&gt;https://www.manning.com/books/unit-testing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Where Testing Elixir will help you learn the tooling and explain the &lt;strong&gt;how&lt;/strong&gt;, Unit Testing Principles, Practices, and Patterns will help you discover the &lt;strong&gt;why&lt;/strong&gt; and ultimately assist you in &lt;strong&gt;crafting a resilient testing strategy&lt;/strong&gt; for your projects.&lt;/p&gt;
&lt;p&gt;I read this book in June 2022, and it hugely influenced me and how I want to test moving forward. It could serve as a great companion or follow-up book for people in our club, though the club&amp;rsquo;s focus is officially and exclusively on Testing Elixir.&lt;/p&gt;
&lt;p&gt;Hope to see you at the book club!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Closing Thoughts on my Exercism Video Project</title>
      <link>https://mikezornek.com/posts/2022/10/completing-exercism-project/</link>
      <pubDate>Mon, 24 Oct 2022 16:34:58 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/10/completing-exercism-project/</guid>
      <description>&lt;p&gt;It was August 2, 2022, when I recorded the &lt;a href=&#34;https://mikezornek.com/posts/2022/8/exercism-elixir-cohort/&#34;&gt;first video and blogged&lt;/a&gt; about my Exercism Elixir video project. I decided to join an &lt;a href=&#34;https://exercism.org/cohorts/exhort-aug-22&#34;&gt;upcoming cohort&lt;/a&gt; that would be working through the &lt;a href=&#34;https://exercism.org/tracks/elixir&#34;&gt;Elixir track on Exercism&lt;/a&gt;, a place to learn programming languages via coding challenges. I decided to video record myself working through the exercises, hoping to share the experience with others.&lt;/p&gt;
&lt;p&gt;And now it&amp;rsquo;s all done! I posted my &lt;a href=&#34;https://mikezornek.com/posts/2022/10/exercism-take-a-number-deluxe/&#34;&gt;final &amp;ldquo;Learning Exercise&amp;rdquo; video&lt;/a&gt; on October 19, 2022. I unlocked the full Elixir syllabus and consider the project closed (for now).&lt;/p&gt;
&lt;p&gt;Not overlooking an opportunity to recap the project, I figure I&amp;rsquo;ll capture a few thoughts to celebrate.&lt;/p&gt;
&lt;p&gt;First, it feels incredible to &lt;strong&gt;FINISH&lt;/strong&gt; something. I&amp;rsquo;ve had many project stall or otherwise end in a &lt;a href=&#34;https://mikezornek.com/posts/2021/10/guildflow-shutdown/&#34;&gt;disappointing manner&lt;/a&gt;. Being able to see this through was very fulfilling.&lt;/p&gt;
&lt;p&gt;I chose the project scope with meaningful intent. I wanted something more than a weekend but not longer than a few months. Technically this took about ten weeks with some vacation and moving distractions mixed in. It turned out to be a pretty good length of time.&lt;/p&gt;
&lt;p&gt;The cohort was ok. We had a shared Discord server and encouraged daily check-ins and questions. People did help each other, but I feel like there was a lost opportunity to get more face time events. I attended a few video meetings, but they were more presentations than socializing. It&amp;rsquo;s a shame I didn&amp;rsquo;t make a stronger connection with anyone. Hard to do that over text-only chat. In the end, they did post a survey, and I recall sharing some suggestions. With luck, they will continue to mature the cohort idea and offer other languages.&lt;/p&gt;
&lt;p&gt;Recording the videos was more stressful than expected. A handful of exercises stumped me, and the sessions would go long. Even the simple sessions went pretty long. I would not expect many people to sit through those unless they were notably stuck on an issue and found the video matching an exercise title. I know at least one person who did; he mentioned he recognized me during a Dockyard Academy call and shared as much. That was pretty cool.&lt;/p&gt;
&lt;p&gt;As I consider more video content, I must condense the value better. If I do record longer sessions (or streams?) I should break out clips as the primary form of sharing and less so the full archive.&lt;/p&gt;
&lt;p&gt;For now, I consider the project closed and will focus on other things. I could see myself coming back to work on more exercises someday. There are a ton. I completed 40 of them to unlock the syllabus, and there are still 116 available. My hat off to anyone who finishes all of them.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Take-A-Number Deluxe (GenServer)</title>
      <link>https://mikezornek.com/posts/2022/10/exercism-take-a-number-deluxe/</link>
      <pubDate>Wed, 19 Oct 2022 16:19:28 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/10/exercism-take-a-number-deluxe/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about GenServer while completing the Take-A-Number Deluxe exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/RTf8GMgjU1s?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: RPN Calculator Inspection (Links and Tasks)</title>
      <link>https://mikezornek.com/posts/2022/10/exercism-rpn-calculator-inspection/</link>
      <pubDate>Tue, 18 Oct 2022 12:27:46 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/10/exercism-rpn-calculator-inspection/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Links and Tasks while completing the RPN Calculator Inspection exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/ZgyJMV329ug?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Dancing Dots (Behaviours and Use)</title>
      <link>https://mikezornek.com/posts/2022/10/exercism-dancing-dots/</link>
      <pubDate>Mon, 17 Oct 2022 15:36:56 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/10/exercism-dancing-dots/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Behaviours and Use while completing the Dancing Dots exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/62Ibpa5Z5Gc?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Lucas Numbers (Streams)</title>
      <link>https://mikezornek.com/posts/2022/10/exercism-lucas-numbers/</link>
      <pubDate>Fri, 14 Oct 2022 11:56:22 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/10/exercism-lucas-numbers/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Streams while completing the Lucas Numbers exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/EjxBvtAuZwk?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Stack Underflow (Exceptions)</title>
      <link>https://mikezornek.com/posts/2022/10/exercism-stack-underflow/</link>
      <pubDate>Tue, 11 Oct 2022 11:52:35 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/10/exercism-stack-underflow/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Exceptions while completing the Stack Underflow exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/VLUUUgya6wo?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: New Passport (With)</title>
      <link>https://mikezornek.com/posts/2022/10/exercism-new-passport/</link>
      <pubDate>Mon, 10 Oct 2022 12:01:02 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/10/exercism-new-passport/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about With while completing the New Passport exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/ABdobeAFuKc?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: RPN Calculator (Errors and Try/Rescue)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-rpn-calculator/</link>
      <pubDate>Fri, 30 Sep 2022 14:05:42 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-rpn-calculator/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Errors and Try/Rescue while completing the RPN Calculator exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/WU0rIKHl30s?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Need For Speed (Alias and Import)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-need-for-speed/</link>
      <pubDate>Thu, 29 Sep 2022 13:31:02 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-need-for-speed/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Alias and Import while completing the Need For Speed exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/5SE3_AXa8mo?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Some Early Thoughts on LiveView Native</title>
      <link>https://mikezornek.com/posts/2022/9/26-liveview-native/</link>
      <pubDate>Mon, 26 Sep 2022 08:00:00 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/26-liveview-native/</guid>
      <description>&lt;p&gt;This weekend I finally had some time to experiment with &lt;a href=&#34;https://github.com/liveviewnative&#34;&gt;LiveView Native&lt;/a&gt;, working through the &lt;a href=&#34;https://liveviewnative.github.io/liveview-client-swiftui/tutorials/yourfirstapp/&#34;&gt;introduction tutorial&lt;/a&gt; during a Side Project Saturday event run by my local Apple developer group. After working through the tutorial, I demoed the results to my Apple friends. We had some great discussions about the pros and cons of the approach. Those discussions locked in some of my early hot takes, and in this post, I&amp;rsquo;ll try to capture and share some of my early thoughts and concerns.&lt;/p&gt;
&lt;h2 id=&#34;what-is-liveview-native&#34;&gt;What is LiveView Native?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/liveviewnative&#34;&gt;LiveView Native&lt;/a&gt; is a project that aims to empower exsiting LiveView developers with the ability to deploy native applications using LiveView, with an initial focus on iOS and Android apps.&lt;/p&gt;
&lt;p&gt;There are a lot of technologies at play, so I&amp;rsquo;m going to take a moment to summarize the players involved:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Elixir&lt;/strong&gt; is a functional programming language that runs in the Erlang/BEAM virtual machine. This runtime excels at providing concurrent and fault-tolerant systems.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phoenix&lt;/strong&gt; is a popular framework used to build web applications in Elixir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LiveView&lt;/strong&gt; is a feature of Phoenix that lets people build interactive, real-time, and collaborative web experiences. It leans on an architecture where a websocket keeps the browser connected to a process on the server. As a user interacts with the page, events are sent to the server process, and then a declarative/reactive UI is updated based on the new process state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Swift&lt;/strong&gt; and &lt;strong&gt;SwiftUI&lt;/strong&gt; are the primary programming language and UI framework pair for building modern/native &lt;strong&gt;iOS&lt;/strong&gt; applications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kotlin&lt;/strong&gt; and &lt;strong&gt;Jetpack Compose&lt;/strong&gt; are the primary programming language and UI framework pair for building modern/native &lt;strong&gt;Android&lt;/strong&gt; applications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;React&lt;/strong&gt; is a frontend JavaScript framework used to create single-page web applications, a competitor to LiveView.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;React Native&lt;/strong&gt; is a framework used to build React-powered apps inside a web view wrapper running on iOS and Android. Because of its web view wrapper, React Native is known to be memory expensive and sometimes exhibit sluggish performance.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Correction:&lt;/strong&gt; I&amp;rsquo;ve been &lt;a href=&#34;https://mastodon.technology/@CodingItWrong/109065827489289642&#34;&gt;corrected by friends&lt;/a&gt; that &amp;ldquo;[React Native] doesn&amp;rsquo;t run inside a web view, but instead renders native iOS/Android views.&amp;rdquo; and thus my ignorance spotlighted. I feel like I got my understanding of this via as the comparison of LiveView Native using &amp;ldquo;native views&amp;rdquo; and React Native using &amp;ldquo;web views&amp;rdquo; so I want to walk back and try to be accurate and say once again, I don&amp;rsquo;t use these frameworks and &lt;a href=&#34;https://twitter.com/zorn/status/1522237365175136259&#34;&gt;I do not know what I am talking about&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update 2:&lt;/strong&gt; &lt;a href=&#34;https://www.youtube.com/watch?v=gvkqT_Uoahw&#34;&gt;This seems like a fair technical review of how React Native works.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dart&lt;/strong&gt; and &lt;strong&gt;Flutter&lt;/strong&gt; are a programming language and UI framework pair that lets you build cross-platform mobile applications. The unique take of Flutter is that it does all of its own UI drawing, allowing for a much more controlled and speedy UI render. It can use component libraries to emulate its host OS, with varying degrees of success (re: uncanny valley effect).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, with all that said, what is &lt;strong&gt;LiveView Native&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;In a traditional LiveView, we have a server process and a client web browser. The LiveView renders a template of HTML influenced by the process state and sends it down the wire. As new events are processed and the state changes, very terse diffs are sent to the client browser, allowing the user&amp;rsquo;s UI to be updated.&lt;/p&gt;
&lt;p&gt;LiveView Native substitutes the client web browser for a native client view running on iOS or Android. Instead of writing templates in HTML, we write them in SwiftUI or Jetpack Compose. LiveView will continue to own responsibility for figuring out the view diffs while the mobile apps will render these SwiftUI or Jetpack Compose views just like they would if built more traditionally.&lt;/p&gt;
&lt;p&gt;There is no web view like you&amp;rsquo;d see in a React Native app, nor is there any new UI framework like you&amp;rsquo;d see in Flutter.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a unique take on the problem that unlocks potentially impressive solutions, but like any technology, you must understand your compromises.&lt;/p&gt;
&lt;h2 id=&#34;worlds-are-colliding&#34;&gt;Worlds Are Colliding!&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been working with Elixir since 2017 and LiveView since it was announced in 2019. Before Elixir, I spent more than a decade doing Apple platform development, first on the Mac and then on the iPhone. By the end, I was teaching iOS programming classes at places like Amazon and Google via my job at Big Nerd Ranch. So when this announcement came out, I felt like worlds were colliding and said as much.&lt;/p&gt;
&lt;figure style=&#34;width: 70%; margin: 0 auto;&#34;&gt;
&lt;a href=&#34;https://twitter.com/zorn/status/1565370172311060482&#34;&gt;
  &lt;img src=&#34;worlds-colliding.png&#34; alt=&#34;Worlds Colliding Tweet&#34; data-action=&#34;zoom&#34;&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;I have many historic pessimistic opinions on multi-platform frameworks that try to sell &amp;ldquo;write once, deploy everywhere.&amp;rdquo; I&amp;rsquo;ll get into some of those later, but first, I&amp;rsquo;ll share my tutorial observations.&lt;/p&gt;
&lt;h2 id=&#34;my-day-with-liveview-native&#34;&gt;My Day with LiveView Native&lt;/h2&gt;
&lt;p&gt;During the first half of Side Project Saturday, I worked through the &lt;a href=&#34;https://liveviewnative.github.io/liveview-client-swiftui/tutorials/yourfirstapp/&#34;&gt;Your First LiveViewNative App&lt;/a&gt; tutorial. This tutorial has you build both a Phoenix project and an iOS project.&lt;/p&gt;
&lt;p&gt;In the Phoenix project, you build a &lt;code&gt;CatsListLive&lt;/code&gt; live view with SwiftUI-specific &lt;code&gt;heex&lt;/code&gt; template content.&lt;/p&gt;
&lt;figure style=&#34;width: 90%; margin: 0 auto;&#34;&gt;
&lt;a href=&#34;swiftui-in-heex.png&#34;&gt;
  &lt;img src=&#34;swiftui-in-heex.png&#34; alt=&#34;LiveView with SwiftUI inside a `heex` template.&#34;&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;In the iOS project, you&amp;rsquo;ll add some of the LiveView Native package dependencies and then configure the default &lt;code&gt;ContentView&lt;/code&gt; to load the swifty &lt;code&gt;LiveView&lt;/code&gt; view class, which points at a &lt;code&gt;localhost:4000&lt;/code&gt; URI via configuration.&lt;/p&gt;
&lt;p&gt;When you launch the iOS app, the &lt;code&gt;LiveView&lt;/code&gt; view class inside of SwiftUI will connect to the Phoenix server, which makes a live view server process for &lt;code&gt;CatsListLive&lt;/code&gt; and then sends down the template. When received inside of iOS, the view hierarchy is injected, and then regular old SwiftUI will render it just as if it had complied typically.&lt;/p&gt;
&lt;figure style=&#34;width: 90%; margin: 0 auto;&#34;&gt;
&lt;a href=&#34;xcode.png&#34;&gt;
  &lt;img src=&#34;xcode.png&#34; alt=&#34;The Cats Demo in Xcode&#34;&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;You may notice that the SwiftUI code templated in &lt;code&gt;heex&lt;/code&gt; has things like &lt;code&gt;phx-click=&amp;quot;toggle-favorite&amp;quot;&lt;/code&gt;. When the button is &amp;ldquo;clicked&amp;rdquo; in the iOS app, the event is sent via the websocket to the live view process on the server, the state is updated per standard LiveView lifecycle mechanics, and then the template is re-rendered, and diffs are applied.&lt;/p&gt;
&lt;p&gt;Another interesting thing in the demo is the use of &lt;code&gt;navigationlink&lt;/code&gt; and traditional Phoenix routes and redirects to handle the &amp;ldquo;NavigationController&amp;rdquo; like push and back button behavior all iOS users are accustomed to.&lt;/p&gt;
&lt;p&gt;Once I completed the tutorial, I presented the project to my peers. As I expected, there was general curiosity about how it worked compared to other multi-platform solutions (like React Native and Flutter). We clicked around the LiveView Native dependencies and reviewed how things were being built. I explained how the view diffs were very tight and not just some text-based XML diff. They asked questions about other SwiftUI view modifiers and how animations would be handled, which I did not know yet.&lt;/p&gt;
&lt;p&gt;There was also a reaction of &amp;ldquo;this feels like a lot of complexity with many failure points&amp;rdquo; and &amp;ldquo;is this worth the complexity cost?&amp;rdquo; For them, I think that is an expected response. These people have embraced Apple platforms and are already building with Swift and SwiftUI. They have no appetite for learning Elixir, Phoenix, or LiveView for this new way to run an app or any genuine appreciation for the unique niche it can solve (real-time and collaboration experiences).&lt;/p&gt;
&lt;p&gt;However, this tool is not really built for them. So who is this being built for? Time for hot takes.&lt;/p&gt;
&lt;h1 id=&#34;what-pain-is-liveview-native-is-trying-to-solve&#34;&gt;What pain is LiveView Native is trying to solve?&lt;/h1&gt;
&lt;figure style=&#34;width: 70%; margin: 0 auto;&#34;&gt;
&lt;a href=&#34;hot-take.jpg&#34;&gt;
  &lt;img src=&#34;hot-take.jpg&#34; alt=&#34;Hot Take House&#34;&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;a href=&#34;https://native.live&#34;&gt;marketing site&lt;/a&gt; for the project states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;LiveView Native empowers developers to build native and web applications seamlessly with Phoenix. Now there&amp;rsquo;s no need to manage multiple teams or juggle multiple frameworks: The same developers can manage both, cutting down delivery timelines and freeing up resources to focus on innovation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Given my experience, I find this statement to be suspect.&lt;/p&gt;
&lt;p&gt;Any developer trying to build a LiveView Native app with no historic native mobile development experience will need to overcome a significant list of concerns.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They will need to compose templates with pseudo-SwiftUI/Jetpack Compose layout code. These UI/layout frameworks are drastically different from the HTML/Tailwind layout code they use. It&amp;rsquo;s a steep learning curve.&lt;/li&gt;
&lt;li&gt;They will need to work around the lack of client-side JavaScript events (coming soon, maybe?). They may miss the inability to leverage existing JavaScript solutions already in use in their current LiveView solutions.&lt;/li&gt;
&lt;li&gt;They will need to resolve offline experiences and real-world spotty connectivity/reconnection issues for phones in buildings/tunnels/elevators. (This is true of web-based LiveView as well.)&lt;/li&gt;
&lt;li&gt;They will need to overcome the complexities of the Xcode and Android Studio IDEs, both extremely deep topics.&lt;/li&gt;
&lt;li&gt;They will need to learn all the trials and tribulations of app store review and deployment. This may be the most significant risk (see below).&lt;/li&gt;
&lt;li&gt;They will encounter harder-to-debug problems because of the complexities of this stack. There is no browser console to inspect/change things. Debugging is very different in Xcode/Android Studio.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll also add that I have many friends already working in SwiftUI natively in Xcode daily. While SwiftUI can provide an unexpected boost to productivity for basic needs, there is a point where it falls down, and it is not easy to compensate for. If you observe the iOS developer community, this is a massive point of conflict and discussion. Depending on your LiveView Native layout&amp;rsquo;s complexity, you might be in for a world of pain.&lt;/p&gt;
&lt;h2 id=&#34;will-apple-approve-this&#34;&gt;Will Apple approve this?&lt;/h2&gt;
&lt;figure style=&#34;width: 40%; margin: 0 auto;&#34;&gt;
&lt;a href=&#34;app-store.jpg&#34;&gt;
  &lt;img src=&#34;app-store.jpg&#34; alt=&#34;App Store Review meme&#34;&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;I hate app store review and distribution exclusivity. It was one of the primary reasons why I left the platform years ago.&lt;/p&gt;
&lt;p&gt;My concerns around &amp;ldquo;will Apple approve this?&amp;rdquo; was one of the first questions that came to mind watching the ElixirConf keynote announcement. The fact DockYard had already gotten an &lt;a href=&#34;https://apps.apple.com/us/app/elixirconf-2022-chat/id1641807562&#34;&gt;ElixirConf chat app&lt;/a&gt; through review was impressive, but unfortunately, past review approval does not guarantee future outcomes. I&amp;rsquo;ve seen many apps approved for years that suddenly get blocked for doing something they&amp;rsquo;ve always done.&lt;/p&gt;
&lt;p&gt;Looking at the &lt;a href=&#34;https://developer.apple.com/app-store/review/guidelines/#software-requirements&#34;&gt;App Store Review Guidelines&lt;/a&gt;, we find section &lt;code&gt;2.5.2&lt;/code&gt;, which states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2.5.2&lt;/strong&gt; Apps should be self-contained in their bundles, and may not read or write data outside the designated container area, nor may they download, install, or execute code which introduces or changes features or functionality of the app, including other apps. Educational apps designed to teach, develop, or allow students to test executable code may, in limited circumstances, download code provided that such code is not used for other purposes. Such apps must make the source code provided by the app completely viewable and editable by the user.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The fact that we are editing the UI in real-time, potentially adding, removing, or changing features, feels like we violate this section.&lt;/p&gt;
&lt;p&gt;However, if you search the web, you&amp;rsquo;ll find plenty of posts talking about &lt;a href=&#34;https://duckduckgo.com/?q=ios+server-driven+ui&#34;&gt;iOS server-driven UI&lt;/a&gt; and even this recent &lt;a href=&#34;https://360idev.com/session/building-server-driven-ui-in-ios/&#34;&gt;conference talk&lt;/a&gt; that explains:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Companies like Airbnb, Spotify, Flipkart, Swiggy and several others use a technique called Server Driven UI, which allows them to remotely update their user interfaces based on the response received from the server. This allows them to bypass the review process and keep their apps sync on multiple platforms.&lt;/p&gt;
&lt;p&gt;In this session, you are going to learn how to get started with building a server driven UI in iOS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So I don&amp;rsquo;t know. 🤷‍♂️ It&amp;rsquo;s one of those things Apple keeps vague and open to interpretation. Having chosen to leave the platform because of these controls, I am incredibly hesitant to return and bet my business on app store whims.&lt;/p&gt;
&lt;h2 id=&#34;conclusions&#34;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;LiveView Native is an interesting project with a unique take on the multi-platform framework problem space. I have doubts it can overcome the &amp;ldquo;lets LiveView people do mobile without knowing mobile&amp;rdquo; shtick, but I am happy to be proven wrong.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t plan to be too active around the project (have no interest bowing down to Apple for app approvals ever again) and instead will invest my LiveView learning time into the new component attributes / UI library opportunities.&lt;/p&gt;
&lt;p&gt;Based on my podcast listening habits and general community awareness, LiveView Native is a significant project for the LiveView community. I expect many to tinker with this in the coming months, and I look forward to reading their blog posts and findings.&lt;/p&gt;
&lt;p&gt;PS: I hope people don&amp;rsquo;t take my positioning concerns about the project as a personal attack. The tech itself is very impressive. I fear this project won&amp;rsquo;t be able to honor the simplicity promise it is being promoted with. Teams who DO accept the extra complexity and tradeoffs of this &amp;ldquo;stack&amp;rdquo; may be empowered to build software that would otherwise take more time &amp;ndash; it&amp;rsquo;s just there is no free lunch; they will still have to know a lot about mobile development to do so.&lt;/p&gt;
&lt;h2 id=&#34;random-leftover-draft-notes&#34;&gt;Random Leftover Draft Notes&lt;/h2&gt;
&lt;p&gt;I recorded a companion video walking through some of the code:&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/2GXh9hP-_WU?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;One company that comes to mind for this sort of thing is &lt;a href=&#34;https://www.sketch.com&#34;&gt;Sketch&lt;/a&gt;. They build a native Mac experience AND lean on websockets with an Elixir backend to enable a real-time collaborative experience. They do not use LiveView but do benefit from the BEAM.&lt;/li&gt;
&lt;li&gt;I was disappointed with Xcode that when I copied/pasted Swift code from the tutorial into Xcode, it did not run a code formatter on save. My friends say you can configure plugins to help with that, but wow, I&amp;rsquo;m bummed that it hasn&amp;rsquo;t been defaulted by Xcode/Apple yet.&lt;/li&gt;
&lt;li&gt;When composing SwiftUI templates in a &lt;code&gt;heex&lt;/code&gt; file, we intentionally use things like &lt;code&gt;phx-click&lt;/code&gt;, which make sense given the goals of the project. Still, I can&amp;rsquo;t help but note that when I was a mobile app developer, most frameworks took pride in using terms like &amp;ldquo;touch&amp;rdquo; and &amp;ldquo;tap,&amp;rdquo; so it can feel odd to see &amp;ldquo;click&amp;rdquo; in this space. Seeing people say &amp;ldquo;click&amp;rdquo; was always a smell that something did not belong. Like when people say &lt;code&gt;Iphone&lt;/code&gt; or &lt;code&gt;xCode&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;One niche I think is perfect for Flutter is when you want to build a very styled/themed UI and want it to be the same across iPhone/Android. Things like video game menu UIs or brand/movie tie-in things.&lt;/li&gt;
&lt;li&gt;It was neat to see the LiveView Native team use Apple&amp;rsquo;s own &lt;code&gt;DocC&lt;/code&gt; &lt;a href=&#34;https://developer.apple.com/documentation/docc/tutorial-syntax&#34;&gt;tutorial template tooling&lt;/a&gt;. I was aware this was out but had not seen it firsthand.&lt;/li&gt;
&lt;li&gt;One pro for LiveView Native is that it does have a path to migrate to traditional SwiftUI should the project benefit from a more conventional approach. That is not true of the others.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Captain&#39;s Log (Erlang Libraries, Randomness)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-captains-log/</link>
      <pubDate>Fri, 23 Sep 2022 13:36:32 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-captains-log/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Erlang Libraries and Randomness while completing the Captain&amp;rsquo;s Log exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/waoDMlqT8lY?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Bread And Potions (Protocols)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-bread-and-potions/</link>
      <pubDate>Thu, 22 Sep 2022 11:32:09 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-bread-and-potions/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Protocols while completing the Bread And Potions exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/2fsMUNsXRpE?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Community Garden (Agent)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-community-garden/</link>
      <pubDate>Wed, 21 Sep 2022 11:41:57 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-community-garden/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Agent while completing the Community Garden exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/W9YB2mbVtao?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Boutique Suggestions (List Comprehensions)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-boutique-suggestions/</link>
      <pubDate>Tue, 20 Sep 2022 11:07:10 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-boutique-suggestions/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about List Comprehensions while completing the Boutique Suggestions exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/JRRv7wgV7-I?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Remote Control Car (Structs)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-remote-control-car/</link>
      <pubDate>Mon, 19 Sep 2022 10:45:55 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-remote-control-car/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Structs while completing the Remote Control Car exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/g2yE5zo0TPE?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Chessboard (Ranges)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-chessboard/</link>
      <pubDate>Fri, 16 Sep 2022 12:23:28 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-chessboard/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Ranges while completing the Chessboard exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/qpxlWnBM-II?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Newsletter (File)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-newsletter/</link>
      <pubDate>Wed, 14 Sep 2022 13:25:01 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-newsletter/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about the File module while completing the Newsletter exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/Sh4pdGxKL2A?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Top Secret (AST: Abstract Syntax Tree)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-top-secret/</link>
      <pubDate>Tue, 13 Sep 2022 11:44:14 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-top-secret/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about AST (Abstract Syntax Tree) while completing the Top Secret exercise.&lt;/p&gt;
&lt;p&gt;This exercise was a tougher one for me. I cut out a bunch of head scratching, but it still ended up being almost an hour. Ugh.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/RSY1nnoUnDA?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: File Sniffer (Binaries)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-file-sniffer/</link>
      <pubDate>Mon, 12 Sep 2022 16:15:55 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-file-sniffer/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Binaries while completing the File Sniffer exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/Sh4pdGxKL2A?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Boutique Inventory (Enum)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-boutique-inventory/</link>
      <pubDate>Sat, 10 Sep 2022 14:41:52 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-boutique-inventory/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Enum while completing the Boutique Inventory exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/_sdv5UKwXoY?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Basketball Website (Access Behaviour)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-basketball-website/</link>
      <pubDate>Wed, 07 Sep 2022 17:07:30 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-basketball-website/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Access Behaviour while completing the Basketball Website exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/nv9AV0UvV4U?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Take-A-Number (PIDs and Processes)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-take-a-number/</link>
      <pubDate>Tue, 06 Sep 2022 11:14:37 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-take-a-number/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about PIDs and Processes while completing the Take-A-Number exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/BsC3OGFa8OI?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Library Fees (Dates and Time)</title>
      <link>https://mikezornek.com/posts/2022/9/exercism-library-fees/</link>
      <pubDate>Mon, 05 Sep 2022 11:57:33 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/exercism-library-fees/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Dates and Time while completing the Library Fees exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/jpP5o_rZruQ?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>ElixirConf Recap</title>
      <link>https://mikezornek.com/posts/2022/9/elixir-conf-recap/</link>
      <pubDate>Sun, 04 Sep 2022 13:25:37 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/9/elixir-conf-recap/</guid>
      <description>&lt;p&gt;This past week I attended &lt;a href=&#34;https://2022.elixirconf.com/&#34;&gt;ElixirConf&lt;/a&gt; in Colorado. It was my first time out at an in-person conference since COVID lockdowns. As much as I hated the travel, it was great to see everyone.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;main-stage.jpeg&#34; alt=&#34;Main Stage&#34;&gt;&lt;/p&gt;
&lt;p&gt;I got a lot of face time with a handful of people I had worked with over the last two years and a bunch of others whose company I have enjoyed via online meetups, book clubs, and Twitter. Even a tiny in-person conversation adds an extra level to those ongoing &amp;ldquo;online peer relationships,&amp;rdquo; and I&amp;rsquo;m happy to have made many new connections.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;selfie.jpeg&#34; alt=&#34;ElixirConf Selfie&#34;&gt;&lt;/p&gt;
&lt;p&gt;* Yes, it would seem many Elixir companies are still hiring; though I&amp;rsquo;d say only half the talks mentioned that when historically, maybe 75% of the talks would so 🤷‍♂️ maybe there is a slight dip.&lt;/p&gt;
&lt;h3 id=&#34;for-more-photos-check-out-this-album-started-by-todd-resudek&#34;&gt;For more photos, check out &lt;a href=&#34;https://photos.google.com/share/AF1QipMotSnbzx_Wy0B_f4dD9Ejip4eXS9SMbArcLGQae4H_eIN-81qoeMyeK_TYr_eUMw?pli=1&amp;amp;key=UUcxQjFFYUw0SWROaFJHYjJCYjBMeHdDc0RKVmxn&#34;&gt;this album&lt;/a&gt; started by Todd Resudek.&lt;/h3&gt;
&lt;p&gt;The conference content was fantastic, almost overwhelming.. I&amp;rsquo;ll note a few big takeaways below, but a complete review would be quite extensive and is out of scope for this post.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LiveView Native&lt;/strong&gt; &amp;ndash; Brian Cardarella of DockYard shared a new project that aims to provide a way to build mobile applications that can act as a LiveView client. Using this, you can connect a mobile application to a LiveView process on the server and then render native views based on heex templates, intermixing LiveView Native view renders with &amp;ldquo;normal&amp;rdquo; platform views (such as SwiftUI used in the demo). More info can be found on the &lt;a href=&#34;https://native.live/&#34;&gt;LiveView Native marketing site&lt;/a&gt;, &lt;a href=&#34;https://github.com/liveviewnative&#34;&gt;GitHub project&lt;/a&gt;, and related &lt;a href=&#34;https://news.ycombinator.com/item?id=32694668&#34;&gt;Hacker News thread&lt;/a&gt;. It is still pretty early tech but has a lot of people buzzing. I&amp;rsquo;ll share more thoughts after I get a chance to kick the tires.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;liveview-native.jpeg&#34; alt=&#34;LiveView Native Slide&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;liveview-native-demo.jpeg&#34; alt=&#34;LiveView Native Demo&#34;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Elixir 1.14&lt;/strong&gt; &lt;a href=&#34;https://hexdocs.pm/elixir/1.14.0/changelog.html&#34;&gt;is out&lt;/a&gt;! It includes &lt;code&gt;Kernel.dbg/2&lt;/code&gt;, a new macro for debugging, &lt;code&gt;PartitionSupervisor&lt;/code&gt; for handling some bottleneck scenarios, improved errors on binaries and evaluation, slicing with steps, expression-based inspection and more.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phoenix 1.7 is immanent.&lt;/strong&gt; It will include verified routes and component properties. It also has Tailwind built-in as the default. Chris also shared a roadmap for future work which included plans to integrate some aspects of the &lt;code&gt;phx_live_storybook&lt;/code&gt; project as well as a rethink of LiveView/Component messaging (which was a point of issue Miki Rezentes brought up in her talk and I&amp;rsquo;m in total agreement with &amp;ndash; right now it&amp;rsquo;s a bit clunky).&lt;/li&gt;
&lt;li&gt;I had an excellent time in &lt;strong&gt;my telemetry class&lt;/strong&gt; on Tuesday. Still have to finish my last lesson, but I had some beneficial experiences adding custom events and attaching common library events through a informative collection of sample projects.&lt;/li&gt;
&lt;li&gt;I sat in on many sessions showcasing the continued evolution of &lt;strong&gt;Livebook&lt;/strong&gt;. Nice to see this tool continue to mature and empower education and knowledge sharing.&lt;/li&gt;
&lt;li&gt;Mark Ericksen did a presentation walking through &lt;a href=&#34;https://github.com/superfly/fly_postgres_elixir&#34;&gt;some tools&lt;/a&gt; that can help make &lt;strong&gt;normal Ecto usage apply to a globally distributed database&lt;/strong&gt; environment, as one would see on fly.io. I have not done a fly deployment (yet), but this is something I should check out.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/juvet&#34;&gt;juvet&lt;/a&gt; is a cool little in-dev framework for making &lt;strong&gt;Slack bots&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Kip Cole&amp;rsquo;s talk on &lt;strong&gt;Time Algebra&lt;/strong&gt; and a new library called &lt;a href=&#34;https://github.com/kipcole9/tempo&#34;&gt;Tempo&lt;/a&gt; was an unexpected and impressive talk.&lt;/li&gt;
&lt;li&gt;David Bernheisel is preparing a paid-for extension to his &lt;strong&gt;&lt;a href=&#34;https://www.ectoinproduction.com/&#34;&gt;Ecto in Production&lt;/a&gt;&lt;/strong&gt; resource. I can&amp;rsquo;t wait to support it.&lt;/li&gt;
&lt;li&gt;Owen Bickford talked about his new &lt;strong&gt;&lt;a href=&#34;https://github.com/liveshowy/webauthn_live_component&#34;&gt;WebAuthn LiveView component&lt;/a&gt;&lt;/strong&gt;, which simplifies integrations that want to lean on this new auth tech. Looks extremely useful, and as iOS starts to promote PassKeys, I think many will seek this out.&lt;/li&gt;
&lt;li&gt;And a lot more which I don&amp;rsquo;t have time to document.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Conferencing during COVID is stressful. I masked up aggressively during the plane trip to and from the show. I did not mask while attending. Most people did not mask. I&amp;rsquo;d guess maybe 5% did mask.&lt;/p&gt;
&lt;p&gt;The big value of the show for me was socializing with people, from hallway chats to lunches and even some code pairing. Masking during these interactions would have been a notable hindrance. I have a hard enough time hearing people with my bad ear in these rooms, let alone adding a mask to my own voice (or theirs). If the conference required masking or if I observed a community norm, I was prepared to do so, but in the end, I did not. I&amp;rsquo;ll live with that outcome, keep an eye out for symptoms, and take an at-home test over the next few days. Bummed this stress point is even here, but those are the cards we are dealt.&lt;/p&gt;
&lt;p&gt;The last takeaway I&amp;rsquo;ll note is a personal observation I need to return to sharing. As an iOS developer, I did many presentations and conference talks but have not mirrored this behavior with my presence in the Elixir ecosystem. In part, during the early years, it was mostly imposter syndrome &amp;ndash; as an Elixir newbie, who am I to tell these people anything &amp;ndash; but those days are past, and I really need to start giving back some.&lt;/p&gt;
&lt;p&gt;I expect the ElixirConf videos to be shared publicly on YouTube in the next few months. Keep an eye out for more info on &lt;a href=&#34;https://www.youtube.com/c/ElixirConf&#34;&gt;their channel&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>ElixirConf Talks of Interest</title>
      <link>https://mikezornek.com/posts/2022/8/elixir-conf-plans/</link>
      <pubDate>Mon, 29 Aug 2022 16:38:56 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/elixir-conf-plans/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m attending ElixirConf this week, and in preparation, I looked over the session schedule to see what talks are of interest to me. I figured I&amp;rsquo;d share my notes in case anyone is curious about what&amp;rsquo;s on my mind. If you are also in attendance, &lt;a href=&#34;https://twitter.com/zorn/status/1563965125278765060&#34;&gt;please say hi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://2022.elixirconf.com/schedule&#34;&gt;https://2022.elixirconf.com/schedule&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;tuesday&#34;&gt;Tuesday&lt;/h2&gt;
&lt;p&gt;Tuesday is the training day. I picked &lt;a href=&#34;https://2022.elixirconf.com/training/#instrumenting-elixir-applications&#34;&gt;Instrumenting Elixir Applications&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been on projects using observation tools like open telemetry and DataDog but have never been personally responsible for setting it up, so I have a lot to learn.&lt;/p&gt;
&lt;h2 id=&#34;wednesday&#34;&gt;Wednesday&lt;/h2&gt;
&lt;p&gt;Wednesday starts with a curious &amp;ldquo;virtual-only&amp;rdquo; morning session list. I think this was done to allow the sister &lt;a href=&#34;https://nervesconf.com/&#34;&gt;NervesConf&lt;/a&gt; &amp;ldquo;sub-conference&amp;rdquo; to do its thing in the meeting spaces. I am not attending, so will most likely look to watch some virtual sessions in the hotel lobby and make friends with other stragglers.&lt;/p&gt;
&lt;p&gt;After José Valim keynote, we have tracks of three topics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Robust, Distributed, and Parallel Processing for Enormous Images Using SuperVisor, Node, Flow, Nx, and evision.&lt;/li&gt;
&lt;li&gt;Porting Legacy Backend Services into Elixir, Seamlessly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Building a HEEX component library&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll be attending the HEEX component library talk. Improving my skill set at building components is high on my interest list right now, and I&amp;rsquo;m curious to see what Matthew has to share.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m likely to watch the recording of the Porting Legacy Backend Services later as that is also something of interest to me. So often, I try to encourage people to avoid the big rewrite, and this sounds like a good match for that since the summary includes &amp;ldquo;while keeping the user experience the same during the whole migration process.&amp;rdquo; 👍&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t have a current need for scalable image processing but will keep the talk recording in mind in the future.&lt;/p&gt;
&lt;p&gt;Next up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Livebook smart cells are amazing&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Classifying Bot IP Addresses in Phoenix&lt;/li&gt;
&lt;li&gt;The Elixir Of Web Scraping&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think I&amp;rsquo;ll be going to the Livebook talk. I&amp;rsquo;ve only done some basic Livebook writing in the past, but I have some ideas about building LiveView/collaborative UX demo tools and it would be helpful to see what&amp;rsquo;s new.&lt;/p&gt;
&lt;p&gt;The other talks sound useful but are not active topics for me right now.&lt;/p&gt;
&lt;p&gt;For the final Wednesday morning choices, we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Make a Soundcloud like app in a week and make it fit in a seven dollar box&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Dx - a pragmatic inference system based on your Ecto schema&lt;/li&gt;
&lt;li&gt;Cryptography and Elixir&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll be attending the Soundcloud app talk. Curious to hear how that project has progressed and very interested in the topics around file uploading.&lt;/p&gt;
&lt;p&gt;After lunch, we have two choices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ecto in Production - Migration Edition&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Let&amp;rsquo;s talk to Industrial devices with Elixir &amp;amp; Nerves&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m not doing an IoT or Nerves stuff, so this is an easy call for me. Improving large-impact Ecto migrations is a welcome focus, particularly given my client&amp;rsquo;s data sizes.&lt;/p&gt;
&lt;p&gt;Next up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Delightful Multiplayer Editing with Phoenix&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Workflows and use cases for Elixir Nerves&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Anxious to see how the Felt team is doing UI collaboration. This is one of the big reasons a project should choose Phoenix LiveView. It&amp;rsquo;s a great fit.&lt;/p&gt;
&lt;p&gt;Next up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Going Global with a Normal Phoenix App&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Keeping your IoT fleet afloat with Sink&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m curious to hear how Fly.io deployments are working out and will surely appreciate people sharing their edge cases and other considerations.&lt;/p&gt;
&lt;p&gt;Next up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Match Specs: Fast Functional Filtering with Matcha&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Introducing Juvet: Building Bots in Elixir&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That Match Specs talk seems too deep so for me. The Juvet one sounds interesting. Some of my product ideas are tools to make Slack more reasonable, so bot programming seems like a topic I would enjoy.&lt;/p&gt;
&lt;p&gt;Wednesday&amp;rsquo;s final sessions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Time algebra - a new way to think about and work with time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Making Elixir Honk: Implementing An Elixir Audio Library In Zig&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m interested in both of these talks, though I&amp;rsquo;ll probably give the audio talk my attention. I&amp;rsquo;ve worked alongside Chris and am curious about what he has come up with. Also, have a passive interest in continued interest in Zig and what people are up to.&lt;/p&gt;
&lt;h2 id=&#34;thursday&#34;&gt;Thursday&lt;/h2&gt;
&lt;p&gt;Thursday starts with a morning keynote from Brian Cardarella.&lt;/p&gt;
&lt;p&gt;Afterward, session tracks start with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Onboarding New Elixir Members&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;In Production with Elixir, Rust, and WebAssembly&lt;/li&gt;
&lt;li&gt;Caching with the Postgres WAL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll probably watch the recording of the WebAssembly talk and attend the onboarding talk. I&amp;rsquo;m less confident I&amp;rsquo;ll be doing education as a business moving forward, but I&amp;rsquo;m still big on mentoring and curious about what this talk has to share.&lt;/p&gt;
&lt;p&gt;In-between sessions, Test Double is offering a &amp;ldquo;Double Up with Test Double&amp;rdquo; session, and I might stop by to say hi and get some advice on my CQRS project.&lt;/p&gt;
&lt;p&gt;Next track choices include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;How to Grow your own Juniors&amp;rdquo;: A guide to mentoring in an Elixir Environment&lt;/li&gt;
&lt;li&gt;Quick Iteration in Elixir - Tips from 6 Years of Professional &amp;amp; Hobby Elixir Development&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WebAuthn + LiveView&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m interested in all of these talks, but WebAuthn is high on my interest level. I&amp;rsquo;ll be there.&lt;/p&gt;
&lt;p&gt;Next up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Selling Elixir&lt;/li&gt;
&lt;li&gt;Seems Good Enough to Me: Working with Testers to Derisk Elixir Upgrades&lt;/li&gt;
&lt;li&gt;Wordle: Elixir Flavour&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m curious about these talks, but none are on fire for me. I might take this slot as an opportunity to mingle a bit. We&amp;rsquo;ll see.&lt;/p&gt;
&lt;p&gt;Next up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Roll Cages, Pit Stops, and Victory Laps: The Launch of Elixir and LiveView at Scale on the New Cars.com&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Self Taught to First Job!&lt;/li&gt;
&lt;li&gt;E2E Reactivity - using Svelte with Phoenix LiveView&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a contributor to the cars.com project, I&amp;rsquo;m anxious to see Zack&amp;rsquo;s retrospective.&lt;/p&gt;
&lt;p&gt;Next up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Axon: Functional Programming for Deep Learning&lt;/li&gt;
&lt;li&gt;Serverless to Serving Elixir: Migrating Serverless app to run on Phoenix&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Have you got the ExFactor? Writing a refactor helper&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll probably go to the refactor talk. Kind of curious how that project works, though again, I might use this time for mingling.&lt;/p&gt;
&lt;p&gt;Next up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SOLID code isn&amp;rsquo;t flexible&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Using Livebook to Teach Elixir&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Review of using Elixir for 4 years in production&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Very anxious to see Brooklin Myers&amp;rsquo;s Livebook talk. I hear he is using it a ton in the upcoming DockYard class. The other talks look cool too but I will have to wait till the recordings.&lt;/p&gt;
&lt;p&gt;Final tracks for Thursday:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Learn you some pattern matching for great good!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect the Docs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Shifting Left: Secure Coding in Elixir Livebooks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m interested in all of these talks, but the one on documentation is close to my heart, and I will attend.&lt;/p&gt;
&lt;h2 id=&#34;friday&#34;&gt;Friday&lt;/h2&gt;
&lt;p&gt;Morning track choices start with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To MVP and beyond, a year in LiveView&lt;/li&gt;
&lt;li&gt;A Javascripter&amp;rsquo;s trip to Phoenix and LiveView&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exploring Elixir Codebases with Archeometer&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Probably going to stop by the Archeometer talk. Curious to see what that project is all about.&lt;/p&gt;
&lt;p&gt;Next up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Flame On: Profiling Elixir and Phoenix apps with Flame Graphs in Live Dashboard&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Designing Data Intensive Applications in Elixir with Flow&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Understanding common network protocols with Elixir&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I would like to try out that flame graph tool but will probably attend the Flow talk since it aligns with my event-sourcing interests.&lt;/p&gt;
&lt;p&gt;Next up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;I was wrong about LiveView&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Searching Your Elixir Code&lt;/li&gt;
&lt;li&gt;Embedded Web Apps in Elixir with LiveState&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll probably sit in on Jason&amp;rsquo;s talk. While I like LiveView and think it&amp;rsquo;s an excellent choice for many projects, I&amp;rsquo;m not sure I&amp;rsquo;m as all in (as it sounds like he is). I still feel like if the app lends itself to request-response it can be better to embrace that simplicity, particularly for user-facing pages that are often presented on mobile phones.&lt;/p&gt;
&lt;p&gt;The day closes with closing keynotes from Chris Grainger and then Chris McCord.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;It feels like this is going to be a packed conference. While I&amp;rsquo;ve outlined talks that interest me, I need to remember that the value of going in person is seeing others, so if I have to skip out on some of these and catch the recordings, I think that is fine too. I suspect the recordings will have a quick turnaround since they are already doing the virtual component, but we&amp;rsquo;ll see.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: DNA Encoding (Bitstrings and Tail Call Recursion)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-dna-encoding/</link>
      <pubDate>Sun, 28 Aug 2022 14:38:27 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-dna-encoding/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Bitstrings and Tail Call Recursion while completing the DNA Encoding exercise.&lt;/p&gt;
&lt;p&gt;Today&amp;rsquo;s recording did not go nearly as bad as the Regular Expressions one but I still struggled with the Elixir Bitstring syntax and this took longer than needed. Bonus round of disappointment, you&amp;rsquo;ll get to see me with a haircut I did not like (and has since been &amp;lsquo;fixed&amp;rsquo;).&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/1Fpe-lticQ8?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Wine Cellar (Keyword Lists)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-wine-cellar/</link>
      <pubDate>Fri, 26 Aug 2022 11:43:10 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-wine-cellar/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Keyword Lists while completing the Wine Cellar exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/kyzEN7qoHBY?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Log Parser (Regular Expressions)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-log-parser/</link>
      <pubDate>Thu, 25 Aug 2022 13:26:42 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-log-parser/</guid>
      <description>&lt;p&gt;This one went badly and took forever. I am awful with regular expressions and the lesson was painful for me to do. I can only imagine what it might be like to watch for others. However, in the spirit of transparency, here is the 1 hour 25 minute recording. Good luck with it.&lt;/p&gt;
&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we ATTEMPT to learn about Regular Expressions while completing the Log Parser exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/Wstpqt9SsSw?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Name Badge (If and Nil)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-name-badge/</link>
      <pubDate>Wed, 24 Aug 2022 13:00:36 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-name-badge/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about If and Nil while completing the Name Badge exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/OBrJmmbnzKA?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: RPG Character Sheet (IO)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-rpg-character-sheet/</link>
      <pubDate>Tue, 23 Aug 2022 12:40:46 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-rpg-character-sheet/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about IO while completing the RPG Character Sheet exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/sB23AXp026A?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: German Sysadmin (Case and Charlist)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-german-sysadmin/</link>
      <pubDate>Mon, 22 Aug 2022 15:54:16 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-german-sysadmin/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Case and Charlist while completing the German Sysadmin exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/jkFnwqlhLoA?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: City Office (Docs and Typespecs)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-city-office/</link>
      <pubDate>Sat, 20 Aug 2022 14:38:17 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-city-office/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Docs and Typespecs while completing the City Office exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/hhZOTrD9K_k?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Bird Count (Recursion)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-bird-count/</link>
      <pubDate>Fri, 19 Aug 2022 10:54:32 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-bird-count/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Recursion while completing the Bird Count exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/eVEZAz5Xhhc?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: High Score (Maps and Module Attributes as Constants)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-high-score/</link>
      <pubDate>Thu, 18 Aug 2022 11:30:56 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-high-score/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Maps and Module Attributes as Constants while completing the High Score exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/iCuFQu-C1-w?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: High School Sweetheart (Pipe Operator and Strings)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-high-school-sweetheart/</link>
      <pubDate>Wed, 17 Aug 2022 12:24:27 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-high-school-sweetheart/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Pipe Operator and Strings while completing the High School Sweetheart exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/nzPSrvcGFew?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Kitchen Calculator (Pattern Matching and Tuples)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-kitchen-calculator/</link>
      <pubDate>Tue, 16 Aug 2022 11:30:51 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-kitchen-calculator/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Pattern Matching and Tuples while completing the Kitchen Calculator exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/wLcuCxifRqg?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Guessing Game (Default Arguments, Guards, and Multiple Clause Functions)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-guessing-game/</link>
      <pubDate>Mon, 15 Aug 2022 11:05:37 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-guessing-game/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Default Arguments, Guards, and Multiple Clause Functions while completing the Guessing Game exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/rcvvtDbKW2Q?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Log Level (Atoms and Cond)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-log-level/</link>
      <pubDate>Fri, 12 Aug 2022 15:05:52 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-log-level/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video, we learn about Atoms and Cond while completing the Log Level exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/2GwBl6qm088?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Language List (Lists)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-language-list/</link>
      <pubDate>Thu, 11 Aug 2022 16:56:19 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-language-list/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video we learn about Lists while completing the Language List exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/lLGLORH-g_8?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Freelancer Rates (Floating Point Numbers and Integers)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-freelancer-rates/</link>
      <pubDate>Fri, 05 Aug 2022 13:14:55 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-freelancer-rates/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video we learn about Floating Point Numbers and Integers while completing the Freelancer Rates exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/rk-La3WVpN0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Pacman Rules (Booleans)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-pacman-rules/</link>
      <pubDate>Thu, 04 Aug 2022 11:16:06 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-pacman-rules/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video we learn about Booleans while completing the Pacman Rules exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/S0mBLc4YVMk?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Track: Secrets (Anonymous Functions and Bit Manipulation)</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-secrets/</link>
      <pubDate>Wed, 03 Aug 2022 11:50:51 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-secrets/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s Exercism Elixir Track video we learn about Anonymous Functions and Bit Manipulation while completing the Secrets exercise.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/mTUKSKTyiTo?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Exercism Elixir Cohort, New Video Series</title>
      <link>https://mikezornek.com/posts/2022/8/exercism-elixir-cohort/</link>
      <pubDate>Tue, 02 Aug 2022 19:39:16 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/8/exercism-elixir-cohort/</guid>
      <description>&lt;p&gt;Exercism, the popular platform that teaches programming via structured coding exercises, is going to be hosting &lt;a href=&#34;https://exercism.org/cohorts/exhort-aug-22&#34;&gt;a free August cohort for their Elixir track&lt;/a&gt;. They brand the endeavour an &amp;ldquo;Exhort&amp;rdquo;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Exhort is simple. We&amp;rsquo;ll give you and a bunch of other learners a space to learn together, add in some of our best mentors and Exercism experts, and support you as you work together through the Exercism Elixir syllabus over 30 days.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;100% free&lt;/li&gt;
&lt;li&gt;Starts August 8th. Lasts for 30 days.&lt;/li&gt;
&lt;li&gt;Work through the track in your own time&lt;/li&gt;
&lt;li&gt;Pair with each other or work as individuals&lt;/li&gt;
&lt;li&gt;Commit to sharing your journey, and supporting others&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;I did a few lessons of the Elixir track last year when the new version of Exercism was launched and was impressed with how it all worked. The cohort sounds fun and a nice way for me to mingle with other looking to learn Elixir.&lt;/p&gt;
&lt;p&gt;My plan is to record my work through the sessions and the first video / exercise is shared below.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/P4Fw8m7Z3cQ?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;For now I&amp;rsquo;m distributing these on my &lt;a href=&#34;https://www.youtube.com/c/MikeZornek&#34;&gt;personal YouTube channel&lt;/a&gt;. The videos are pretty raw and longer than some historical educational content I&amp;rsquo;ve produced. I sharing my failures as well as my victories. It could be longer content than some might expect but I could see others enjoying seeing me work through the errors and typos too. We&amp;rsquo;ll see how it goes.&lt;/p&gt;
&lt;p&gt;I also think the 30-day scope of the cohort helps me invest in the video series without worrying about a long-term content commitment to people. I&amp;rsquo;m still on the fence how I want to split my time in the coming months.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elixir Style: Prefer Unnested Aliases</title>
      <link>https://mikezornek.com/posts/2022/4/style-aliases/</link>
      <pubDate>Fri, 01 Apr 2022 12:31:06 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2022/4/style-aliases/</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally written for my old ElixirFocus blog, and transfer here after its closure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In Elixir, &lt;code&gt;alias&lt;/code&gt; is a keyword we often use to create shorthand references to long-form module names allowing us to refer to &lt;code&gt;RetroTaxi.Boards.Board&lt;/code&gt; simply as &lt;code&gt;Board&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll often end up with a collection of &lt;code&gt;alias&lt;/code&gt; declarations at the top of a module, and Elixir offers two ways to format these.&lt;/p&gt;
&lt;p&gt;One way is simple list such as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RetroTaxi.Boards.Board&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RetroTaxi.Boards.Column&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RetroTaxi.Boards.TopicCard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The other way is an optional nested list like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RetroTaxi.Boards&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;Board&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Column&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;TopicCard&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sometimes this nested list is broken across separate lines and presented like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RetroTaxi.Boards&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Board&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Column&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;TopicCard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;style-recommendation&#34;&gt;Style Recommendation&lt;/h2&gt;
&lt;p&gt;I highly recommend avoiding this nested presentation. The primary reason is to help keep the codebase plain-text searchable. By avoiding the nesting, you can quickly find which modules are referencing any module simply by searching for its name string like &lt;code&gt;RetroTaxi.Boards.Column&lt;/code&gt;. Finding nested aliases would require a more complex regular expression or language server tooling.&lt;/p&gt;
&lt;p&gt;If you want to enforce this style with a credo, check out &lt;a href=&#34;https://hexdocs.pm/credo/Credo.Check.Readability.MultiAlias.html&#34;&gt;Credo.Check.Readability.MultiAlias&lt;/a&gt; which is usually disabled on a default credo install.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Personal Phoenix 1.6 Upgrade Notes</title>
      <link>https://mikezornek.com/posts/2021/10/phoenix-1.6-upgrade-notes/</link>
      <pubDate>Mon, 18 Oct 2021 12:00:00 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2021/10/phoenix-1.6-upgrade-notes/</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally written for my old ElixirFocus blog, and transfer here after its closure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Over the past few days I&amp;rsquo;ve been &lt;a href=&#34;https://github.com/elixirfocus/retro_taxi/pull/24&#34;&gt;upgrading my projects to Phoenix 1.6&lt;/a&gt; and like any project that comes out of a template-based generator, migrating a Phoenix project to a new version can be a little scary and error prone, particularly for people new to Elixir and Phoenix. Today I&amp;rsquo;ll share some personal notes and tips to hopefully make the process a little smoother.&lt;/p&gt;
&lt;h2 id=&#34;step-0-skip-the-manual-upgrade-maybe&#34;&gt;Step 0: Skip the Manual Upgrade (Maybe?)&lt;/h2&gt;
&lt;p&gt;Depending on the scale and circumstances of your project you might consider just re-generating a new project using &lt;code&gt;mix phx.new&lt;/code&gt; and bringing over your old code into the fresh template. I wouldn&amp;rsquo;t recommend this for any project that had meaningful git history or other collaborators, but for smaller personal projects it might be the path of least resistance.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll probably want the new project generator regardless and can install it with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ mix archive.install hex phx_new
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;step-1-familiarize-yourself-with-the-new-version&#34;&gt;Step 1: Familiarize Yourself with the New Version&lt;/h2&gt;
&lt;p&gt;Before blindly attempting the upgrade it would be best to read the &lt;a href=&#34;https://www.phoenixframework.org/blog/phoenix-1.6-released&#34;&gt;official blog announcement&lt;/a&gt;, review the &lt;a href=&#34;https://github.com/phoenixframework/phoenix/blob/3ba0f6fc3407d4ddc08c05715ff8b24cb367d8bd/CHANGELOG.md#160-rc0-2021-08-26&#34;&gt;project changelog&lt;/a&gt; and preview &lt;a href=&#34;https://gist.github.com/chrismccord/2ab350f154235ad4a4d0f4de6decba7b&#34;&gt;Chris McCord&amp;rsquo;s personal upgrade notes&lt;/a&gt;. You might want to also bookmark documentation for specific new features, like &lt;a href=&#34;https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#sigil_H/2&#34;&gt;heex templates&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;step-2-get-your-project-is-a-stable-state&#34;&gt;Step 2: Get Your Project is a Stable State&lt;/h2&gt;
&lt;p&gt;Make sure your project is building clean, the tests are passing and the typespecs are um, type spec-ing. Push your changes to remote and start a new &lt;code&gt;upgrade-phoenix&lt;/code&gt; branch. It would be best if you work through the upgrade in chunks and verify these things as you go.&lt;/p&gt;
&lt;h2 id=&#34;step-3-update-elixir-and-project-dependencies&#34;&gt;Step 3: Update Elixir and Project Dependencies&lt;/h2&gt;
&lt;p&gt;If you can, now is a good time to explicitly require Elixir 1.12 as the minimum version of your project. This is not an absolute requirement, but as described in the &lt;a href=&#34;https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#sigil_H/2&#34;&gt;heex docs&lt;/a&gt;, it will improve your error messages:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: HEEx requires Elixir &amp;gt;= 1.12.0 in order to provide accurate file:line:column information in error messages. Earlier Elixir versions will work but will show inaccurate error messages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# mix.exs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; project &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;elixir&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;~&amp;gt; 1.12&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you are using an &lt;a href=&#34;https://asdf-vm.com/&#34;&gt;asdf&lt;/a&gt; for version management you might also want to update your &lt;code&gt;.tool-versions&lt;/code&gt; file as well.&lt;/p&gt;
&lt;p&gt;Next run &lt;code&gt;mix hex.outdated&lt;/code&gt; to see what dependency updates are available. You&amp;rsquo;ll see a bunch of stuff for the new Phoenix 1.6 release, which at the time of this post is up to &lt;code&gt;1.6.2&lt;/code&gt;. For one of our currently outdated projects I get the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ mix hex.outdated
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Dependency              Current  Latest   Status
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;credo                   1.5.6    1.5.6    Up-to-date
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dialyxir                1.1.0    1.1.0    Up-to-date
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ecto_sql                3.6.2    3.7.1    Update possible
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;floki                   0.29.0   0.31.0   Update not possible
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gettext                 0.18.2   0.18.2   Up-to-date
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;jason                   1.2.2    1.2.2    Up-to-date
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mix_test_watch          1.0.3    1.1.0    Update possible
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;phoenix                 1.5.9    1.6.2    Update not possible
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;phoenix_ecto            4.3.0    4.4.0    Update possible
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;phoenix_html            2.14.3   3.0.4    Update not possible
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;phoenix_live_dashboard  0.4.0    0.5.3    Update possible
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;phoenix_live_reload     1.3.3    1.3.3    Up-to-date
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plug_cowboy             2.5.1    2.5.2    Update possible
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postgrex                0.15.10  0.15.12  Update possible
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;telemetry_metrics       0.6.1    0.6.1    Up-to-date
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;telemetry_poller        0.5.1    1.0.0    Update not possible
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Using the above collection of versions you can update your &lt;code&gt;mix.exs&lt;/code&gt; deps list. To verify these work try running the following commands in series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mix deps.clean --all&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mix clean&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mix deps.get&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mix compile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mix test&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fix any issues that popup and then you can commit your progress and start to consider some of the optional parts of the 1.6 upgrade.&lt;/p&gt;
&lt;h2 id=&#34;step-4-migrate-to-heex-templates-optional&#34;&gt;Step 4: Migrate to &lt;code&gt;heex&lt;/code&gt; Templates (optional)&lt;/h2&gt;
&lt;p&gt;The new template system is called &lt;code&gt;heex&lt;/code&gt; (pronounced &lt;code&gt;heaks&lt;/code&gt;) and is more HTML-aware, helping report issues like missing &lt;code&gt;div&lt;/code&gt; tags at compile time. The new &lt;code&gt;heex&lt;/code&gt; templates will help resolve a notable collection of common LiveView and CSS bugs as well as provide nicer inline functions.&lt;/p&gt;
&lt;p&gt;While &lt;code&gt;heex&lt;/code&gt; templates are great they are optional for now. Previous &lt;code&gt;eex&lt;/code&gt; and &lt;code&gt;leex&lt;/code&gt; templates still work but are considered deprecated. If you want to start to migrate to &lt;code&gt;heex&lt;/code&gt; I would check out &lt;a href=&#34;https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#sigil_H/2&#34;&gt;the docs&lt;/a&gt; for a quick overview and then start renaming your file extensions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;app.html.eex&lt;/code&gt; to &lt;code&gt;app.html.heex&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;live.html.leex&lt;/code&gt; to &lt;code&gt;app.html.heex&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For inline templates you&amp;rsquo;ll use the new &lt;code&gt;~H&lt;/code&gt; sigil instead of &lt;code&gt;~L&lt;/code&gt; sigil.&lt;/p&gt;
&lt;p&gt;As you make these changes the compiler will inform you what needs updating. For my own RetroTaxi project most &lt;code&gt;heex&lt;/code&gt; updates involved migrating HTML attributes like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;- &amp;lt;div id=&amp;#34;topic-card-&amp;lt;%= @topic_card.id %&amp;gt;&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+ &amp;lt;div id={&amp;#34;topic-card#{@topic_card.id}&amp;#34;}&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;- &amp;lt;button phx-click=&amp;#34;start_discussion_phase&amp;#34; phx-target=&amp;#34;&amp;lt;%= @myself %&amp;gt;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+ &amp;lt;button phx-click=&amp;#34;start_discussion_phase&amp;#34; phx-target={@myself}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I also ended up using the new &lt;code&gt;&amp;lt;.form&amp;gt;&lt;/code&gt; component, which honors the tag aware templates while also addressing a &lt;a href=&#34;https://hexdocs.pm/phoenix_live_view/Phoenix.LiveComponent.html#module-change-tracking-requirement&#34;&gt;known technical limitation&lt;/a&gt; of &lt;code&gt;live_components&lt;/code&gt; being wrapped in a block, which is a common pattern inside forms.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;- &amp;lt;%= f = form_for @changeset, &amp;#34;#&amp;#34;, phx_submit: &amp;#34;save&amp;#34;, phx_target: @myself %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;- &amp;lt;!-- stuff --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;- &amp;lt;/form&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+ &amp;lt;.form let={f} for={@changeset} phx_submit={&amp;#34;save&amp;#34;} phx_target={@myself}&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+ &amp;lt;!-- stuff --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+ &amp;lt;/.form&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;step-5-migrate-to-esbuild-optional&#34;&gt;Step 5: Migrate to esbuild (optional)&lt;/h2&gt;
&lt;p&gt;With Phoenix 1.6 all new project templates will default to &lt;a href=&#34;https://esbuild.github.io/&#34;&gt;esbuild&lt;/a&gt; over the historic webpack for asset bundling. This was a change made in an effort to improve long term stability, as npm and webpack have been a huge source of bugs and maintenance issues inside the Phoenix project. This is however an optional change when upgrading older Phoenix projects.&lt;/p&gt;
&lt;p&gt;If you already have invested in any webpack customizations you might just want to keep using it.&lt;/p&gt;
&lt;p&gt;If your needs are simple and you&amp;rsquo;d like to give esbuild a spin it can be swapped in without too much work. Chris McCord&amp;rsquo;s &lt;a href=&#34;https://gist.github.com/chrismccord/2ab350f154235ad4a4d0f4de6decba7b&#34;&gt;upgrade document&lt;/a&gt; has an in-depth set of edits you can follow. I did those steps myself with RetroTaxi and then once I got it all working, went back and re-added my Tailwind dependencies following &lt;a href=&#34;https://pragmaticstudio.com/tutorials/adding-tailwind-css-to-phoenix&#34;&gt;Mike Clark&amp;rsquo;s blog post&lt;/a&gt;. I ended up with a very basic &lt;a href=&#34;https://github.com/elixirfocus/retro_taxi/blob/main/assets/package.json&#34;&gt;package.json&lt;/a&gt; file.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Aside: One notable question I still have regarding esbuild is the new command: &lt;code&gt;mix assets.deploy&lt;/code&gt;. It seems curious to me that running this with the new &lt;code&gt;.gitignore&lt;/code&gt; patterns results in a dirty git state. Is this wanted/expected behavior? If you have any thoughts let me know.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;step-6-the-little-things&#34;&gt;Step 6: The Little Things&lt;/h2&gt;
&lt;p&gt;There are lots of little things you can notice comparing your current Phoenix 1.5 project with a freshly generated 1.6 project. In fact I recommend using &lt;code&gt;mix phx.new hello&lt;/code&gt; (substituting your own project name for &lt;code&gt;hello&lt;/code&gt; to help with easier copy and paste) and then compare the new project files 1:1 against your current project. You&amp;rsquo;ll no doubt notice things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the move from &lt;code&gt;use Mix.Config&lt;/code&gt; to &lt;code&gt;import Config&lt;/code&gt; and other related config file changes.&lt;/li&gt;
&lt;li&gt;the addition of &lt;a href=&#34;https://github.com/swoosh/swoosh&#34;&gt;Swoosh&lt;/a&gt;, an email composition and delivery tool, to your project dependency list.&lt;/li&gt;
&lt;li&gt;how the path of assets in an esbuild setup will effect your &lt;code&gt;.gitignore&lt;/code&gt; file and other template references.&lt;/li&gt;
&lt;li&gt;the introduction of &lt;code&gt;topbar&lt;/code&gt; as a static JS library replacing the previous &lt;code&gt;NProgress&lt;/code&gt; dependency.&lt;/li&gt;
&lt;li&gt;slight changes to the generated telemetry descriptions.&lt;/li&gt;
&lt;li&gt;slight changes to how the Ecto sandbox is started during test mode.&lt;/li&gt;
&lt;li&gt;new complier warning suppressions for LiveDashboard, which will intentionally not be available in production by default.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For RetroTaxi I tried to honor these changes as much as possible.&lt;/p&gt;
&lt;p&gt;One helpful tool to do this kind of template comparison at a glace is PhoenixDiffs:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.phoenixdiff.org/?source=1.5.12&amp;amp;target=1.6.0&#34;&gt;https://www.phoenixdiff.org/?source=1.5.12&amp;amp;target=1.6.0&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;my-final-upgrade-pr-and-next-steps&#34;&gt;My Final Upgrade PR and Next Steps&lt;/h2&gt;
&lt;p&gt;The final RetroTaxi Phoenix 1.6 upgrade PR is here if you want to take a peek:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/elixirfocus/retro_taxi/pull/24/&#34;&gt;https://github.com/elixirfocus/retro_taxi/pull/24/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;One new feature I have my eye on but did not get around too (yet) is &lt;a href=&#34;https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html&#34;&gt;Phoenix.Component&lt;/a&gt;. I should be able to use this in favor of some historic stateless &lt;code&gt;LiveComponent&lt;/code&gt; modules, which is no longer recommended, as seen in the docs quoted below:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: previous LiveView versions allowed the &lt;code&gt;:id&lt;/code&gt; to be skipped on &lt;code&gt;live_component&lt;/code&gt; but those are now discouraged since the addition of function components, outlined in &lt;code&gt;Phoenix.Component&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;Hopefully these notes provide some help and comfort as you upgrade your own Phoenix projects to 1.6. I&amp;rsquo;m curious how we can continue to improve this process in the future and even have my eye on some new Elixir &lt;a href=&#34;https://hexdocs.pm/elixir/master/changelog.html#extended-code-formatting&#34;&gt;1.13 changes&lt;/a&gt; that could help by &amp;ldquo;supporting developers who wish to create tools that directly manipulate and custom format Elixir source code&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Thanks again to the entire Phoenix team for an outstanding release!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elixir Terminology: Parameters vs Attributes</title>
      <link>https://mikezornek.com/posts/2021/9/parameters-vs-attributes/</link>
      <pubDate>Mon, 13 Sep 2021 10:00:00 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2021/9/parameters-vs-attributes/</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally written for my old ElixirFocus blog, and transfer here after its closure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve written about being &lt;a href=&#34;https://mikezornek.com/posts/2021/6/programming-terminology/&#34;&gt;explicit and mindful about programming terminology&lt;/a&gt; before and today we&amp;rsquo;ll look at another specific Elixir terminology example: parameters vs attributes.&lt;/p&gt;
&lt;p&gt;If you create a new Phoenix app and use the generators to stub out a simple CRUD experience you will no doubt observe web controller functions like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;defmodule&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;HelloWeb.PostController&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; create(conn, %{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;post&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; post_params}) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Blog&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;create_post(post_params) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And internal schema module functions like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;defmodule&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Hello.Blog.Post&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; changeset(post, attrs) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    post
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; cast(attrs, [&lt;span style=&#34;color:#e6db74&#34;&gt;:body&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:word_count&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; validate_required([&lt;span style=&#34;color:#e6db74&#34;&gt;:body&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:word_count&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Observing function argument names like &lt;code&gt;post_params&lt;/code&gt; (short for parameters) and &lt;code&gt;attr&lt;/code&gt; (short for attributes) how do we reconcile those naming choices?&lt;/p&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;p&gt;When we say &amp;ldquo;parameters&amp;rdquo; we are usually talking about data coming into the system from external actors, like a user POST-ing data from a web form or an API accepting a request. Parameters are generally considered unsafe. Since the contents are dynamic they will almost always be made from maps using string keys to avoid the known runtime capacity limitations of &lt;code&gt;:atom&lt;/code&gt; keys.&lt;/p&gt;
&lt;h2 id=&#34;attributes&#34;&gt;Attributes&lt;/h2&gt;
&lt;p&gt;When we say &amp;ldquo;attributes&amp;rdquo; we are usually talking about internal Elixir structures. When a function accepts a simple map argument labeled &lt;code&gt;attr&lt;/code&gt;, as seen in our example, I think we can lean on the &lt;a href=&#34;https://www.merriam-webster.com/dictionary/attribute&#34;&gt;definition for attribute&lt;/a&gt; which says, &amp;ldquo;a quality, character, or characteristic ascribed to someone or something&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;While &lt;code&gt;attr&lt;/code&gt; is a community norm for incoming function arguments, there is an unfortunate overlap with some official Elixir terminology. A &lt;a href=&#34;https://hexdocs.pm/elixir/Module.html#module-module-attributes&#34;&gt;module attribute&lt;/a&gt; is how we describe those at sign (&lt;code&gt;@&lt;/code&gt;) declarations like &lt;code&gt;@email&lt;/code&gt; below.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;defmodule&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ContactSupport&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@email&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mike@mikezornek.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;p&gt;Short post today, but hopefully some helpful context about some community naming norms.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Using Schemaless Changesets to Separate Concerns Between the Web Context and the Business Context</title>
      <link>https://mikezornek.com/posts/2021/9/ecto-schemaless-changesets/</link>
      <pubDate>Tue, 07 Sep 2021 08:00:00 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2021/9/ecto-schemaless-changesets/</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally written for my old ElixirFocus blog, and transfer here after its closure.&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With schemaless changesets you have the power to hand craft validations for specific web form presentations and define firm boundaries of responsibilities between your web presentation layer and the business-specific contexts of your app.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;rsquo;s a story we can all relate with. A new app is born using &lt;code&gt;mix phx.new hello&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Some &lt;code&gt;mix phx.gen&lt;/code&gt; generators are used to begin to shape a solution, perhaps something like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ mix phx.gen.html Blog Post posts body:string word_count:integer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then over time the &lt;code&gt;Post&lt;/code&gt; schema grows. Different web forms are needed and so multiple &lt;code&gt;changeset&lt;/code&gt; functions are added for the diverging user tasks. You might even start adding &lt;code&gt;virtual&lt;/code&gt; fields to schemas just to handle web form needs.&lt;/p&gt;
&lt;p&gt;It gets to a point where no one wants to edit or refactor the schema since it has grown too large and complex.&lt;/p&gt;
&lt;p&gt;How do get we get out of this mess?&lt;/p&gt;
&lt;h2 id=&#34;part-one-break-things-down&#34;&gt;Part One: Break Things Down&lt;/h2&gt;
&lt;p&gt;When you find yourself with a large schema I would first look to how you can break down this &lt;strong&gt;BIG&lt;/strong&gt; noun into other smaller nouns.&lt;/p&gt;
&lt;p&gt;Perhaps you have a large &lt;code&gt;User&lt;/code&gt; entity. Maybe you could isolate and extract some of its responsibilities. Maybe introduce a &lt;code&gt;Credentials&lt;/code&gt; entity and a user has many of them. Maybe break out a &lt;code&gt;Profile&lt;/code&gt; and a user has one of those.&lt;/p&gt;
&lt;p&gt;Perhaps you have an &lt;code&gt;Notification&lt;/code&gt; entity that is being mutated to track its progress through some kind of delivery process to a third party. Maybe instead of an ever mutating string value of &lt;code&gt;status&lt;/code&gt; you introduce a new entity for &lt;code&gt;DeliveryAttempt&lt;/code&gt; and then notifications can have many of those and derive a &lt;code&gt;status&lt;/code&gt;. With less mutation you likely will need less changesets functions and the code will naturally be more focused.&lt;/p&gt;
&lt;p&gt;Breaking down domain concepts into smaller structures can improve the expressiveness and clarity of a codebase. Now, while I hope this little reminder can help brainstorm ideas for your own project there is another sneaky issue at play in our story and that is the changeset dependency problem.&lt;/p&gt;
&lt;h2 id=&#34;part-two-breaking-your-changeset-dependencies&#34;&gt;Part Two: Breaking Your Changeset Dependencies&lt;/h2&gt;
&lt;p&gt;As useful as Phoenix generators can be to stand up a basic app, or to get through a tutorial introducing you to concepts about the framework, there is a dark pattern you would be wise to acknowledge if accepting its compromise.&lt;/p&gt;
&lt;p&gt;In an ideal scenario an &lt;code&gt;Ecto.Changeset&lt;/code&gt; should &lt;strong&gt;NOT&lt;/strong&gt; have a dependency through line from an app&amp;rsquo;s business context into the web controller and onto the web form of the page.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Letting a changeset that low in the stack influence the web forms is dangerous.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Lets explain.&lt;/p&gt;
&lt;p&gt;First, having any chunk of code touch too many layers begins to handicap your ability to refactor. I tend to align with the thought that &amp;ldquo;Good Code Is Easy To Change Code&amp;rdquo;. If you are looking for a simple measurement when evaluating the quality of a codebase, changeability is as good as any principle to lean on.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src=&#34;layers.png&#34; alt=&#34;Visual showing four layers of a typical Phoenix app.&#34;&gt;
 &lt;figcaption&gt;A common but brittle approach that creates strong dependencies across all the layers.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Second, having low stack changesets dictate web form behavior creates a dysfunctional approach to user experience design where you force the user to represent their work as it will (eventually) be persisted in a database table row. In an ideal situation you&amp;rsquo;ll be crafting custom web forms to capture user intent and then transform the incoming data into the needed persistance format. Do not let the database dictate the user interface!&lt;/p&gt;
&lt;h3 id=&#34;using-schemaless-changesets&#34;&gt;Using Schemaless Changesets&lt;/h3&gt;
&lt;p&gt;So with all of that said, how does one use a &lt;a href=&#34;https://hexdocs.pm/ecto/data-mapping-and-validation.html#schemaless-changesets&#34;&gt;schemaless changesets&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;Normally if you had a defined an Ecto schema like &lt;code&gt;Registration&lt;/code&gt; you&amp;rsquo;d build a changeset with code like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fields &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;:first_name&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:last_name&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:email&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;changeset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  %&lt;span style=&#34;color:#a6e22e&#34;&gt;Registration&lt;/span&gt;{}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Ecto.Changeset&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;cast(params[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sign_up&amp;#34;&lt;/span&gt;], fields)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; validate_required(...)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; validate_length(...)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Creating a schemaless version is super easy and very much the same. Instead of starting the pipe with a schema type you instead start it with a simple tuple containing the data and some metadata about the data&amp;rsquo;s types.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; %{}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;types &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; %{&lt;span style=&#34;color:#e6db74&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;:string&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;email&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;:string&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# The data+types tuple is equivalent to %Registration{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;changeset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {data, types}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Ecto.Changeset&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;cast(params[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sign_up&amp;#34;&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;keys(types))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; validate_required(...)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; validate_length(...)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;data&lt;/code&gt; in this case can be a simple &lt;code&gt;Map&lt;/code&gt; or a more defined struct. For example, in the RetroTaxi project there is a web form presented on the home page and so I built a &lt;code&gt;Struct&lt;/code&gt; to represent &lt;a href=&#34;https://github.com/elixirfocus/retro_taxi/blob/main/lib/retro_taxi/board_creation/request.ex&#34;&gt;the request&lt;/a&gt; like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;defmodule&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RetroTaxi.BoardCreation.Request&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;defstruct&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;:board_name&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:facilitator_name&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@type&lt;/span&gt; t &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;%&lt;/span&gt;__MODULE__{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;board_name&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;String&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t() &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;facilitator_name&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;String&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t() &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Later when creating a changeset we use this schemaless pattern:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; change_request(%&lt;span style=&#34;color:#a6e22e&#34;&gt;Request&lt;/span&gt;{} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; request, attrs \\ %{}) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  types &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; %{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;board_name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;:string&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;facilitator_name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;:string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {request, types}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Changeset&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;cast(attrs, &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;keys(types))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Changeset&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;validate_required([&lt;span style=&#34;color:#e6db74&#34;&gt;:board_name&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:facilitator_name&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Changeset&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;validate_length(&lt;span style=&#34;color:#e6db74&#34;&gt;:board_name&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;min&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;max&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;255&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Changeset&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;validate_length(&lt;span style=&#34;color:#e6db74&#34;&gt;:facilitator_name&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;min&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;max&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;255&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Other than this difference, you can use the changeset with the Phoenix form tool just like before.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;ll also take a moment to give a nod to the &lt;code&gt;embedded_schema&lt;/code&gt; Ecto type which can be used here in place of the arbitrary &lt;code&gt;Struct&lt;/code&gt; or &lt;code&gt;Map&lt;/code&gt;; see &lt;a href=&#34;https://hexdocs.pm/ecto/data-mapping-and-validation.html&#34;&gt;the documentation&lt;/a&gt; for a full sample. My gut tells me to isolate usage of things called &amp;ldquo;schemas&amp;rdquo; to entities or values that will end up in the database for a greenfield project, but it&amp;rsquo;s a small preference. I also like the idea that the concepts of &amp;ldquo;changeset&amp;rdquo; need not be limited to the Ecto library.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;the-missing-error-gotcha&#34;&gt;The Missing Error Gotcha&lt;/h3&gt;
&lt;p&gt;The one issue you may run into with this schemaless changeset approach is that as you test validations on your web forms you will not see any errors. The reason for this is that traditionally the changeset you render in a &lt;code&gt;POST&lt;/code&gt; request will come out of a &lt;code&gt;Repo.insert/2&lt;/code&gt; or &lt;code&gt;Repo.update/2&lt;/code&gt; error tuple and that changeset will have its &lt;code&gt;action&lt;/code&gt; attribute set to a non-nil &lt;code&gt;:insert&lt;/code&gt; or &lt;code&gt;:update&lt;/code&gt; value. The Phoenix web form tools use the presence of this value as a signal to render the errors. For us, this means we need to set &lt;code&gt;action&lt;/code&gt; explicitly like the sample below.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; process_request(request, user_id) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    changeset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; change_request(request, %{})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; changeset&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;valid? &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# In the first published version of this post we edited the `action` directly but&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# it is probably safer to use the defined `apply_action` function, though the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# result is pretty much the same.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# {:error, %{changeset | action: :insert}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# We need to force an action value so the Phoenix forms will display the errors.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Code is more explicit here for demonstration purposes.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {&lt;span style=&#34;color:#e6db74&#34;&gt;:error&lt;/span&gt;, changeset_with_action} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Ecto.Changeset&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;apply_action(changeset, &lt;span style=&#34;color:#e6db74&#34;&gt;:insert&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {&lt;span style=&#34;color:#e6db74&#34;&gt;:error&lt;/span&gt;, changeset_with_action}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Perform the actual request.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;With schemaless changesets you have the power to hand craft validations for specific web form presentations and define firm boundaries of responsibilities between your web presentation layer and the business-specific contexts of your app. Lots of people will not invest in this separation of concerns, and depending on the life-cycle of the application and the needs of your users that might be fine. If however, you find yourself making multiple flavors of changeset or adding lots of &lt;code&gt;virtual&lt;/code&gt; fields to a schema just to make the web forms fit in &amp;ndash; it might be time to rethink your approach.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Retro Taxi: September 2021 Update</title>
      <link>https://mikezornek.com/posts/2021/9/retro-taxi-project-sept-2021-update/</link>
      <pubDate>Thu, 02 Sep 2021 09:00:41 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2021/9/retro-taxi-project-sept-2021-update/</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally written for my old ElixirFocus blog, and transfer here after its closure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A few months ago I kicked off the next sample project for this website, a team retrospective board built using Phoenix LiveView called &lt;a href=&#34;https://github.com/elixirfocus/retro_taxi/&#34;&gt;RetroTaxi&lt;/a&gt;. More info about the project is available in the &lt;a href=&#34;https://mikezornek.com/posts/retro-taxi-project-kickoff/&#34;&gt;project kickoff&lt;/a&gt; and today I&amp;rsquo;ll share how it has been coming along.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/0PPIX2biRVc?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;From the scope of the &lt;a href=&#34;https://github.com/elixirfocus/retro_taxi/blob/main/docs/c1/feature_post_and_vote.md&#34;&gt;original pitch document&lt;/a&gt; you&amp;rsquo;d like to think I would have gotten further along by now but a mix of client responsibilities and then a &lt;a href=&#34;https://mikezornek.com/posts/2021/7/vacation/&#34;&gt;extended personal vacation&lt;/a&gt; kept me distracted. Over the past week however I have gotten back into the project and can show some basic things working.&lt;/p&gt;
&lt;p&gt;First, a user can visit the home page and create a new board. This create board form also asks for their name, since they will be displayed as the meeting&amp;rsquo;s facilitator to future collaborators.&lt;/p&gt;
&lt;figure&gt;
 &lt;a href=&#34;new-board.png&#34;&gt;
 &lt;img src=&#34;new-board-thumb.png&#34; alt=&#34;Create new board form.&#34;&gt;&lt;/a&gt;
 &lt;figcaption&gt;Create new board form.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;With the board created you are redirected to the board page, which shows the custom title and displays the facilitator in the to-be-fleshed-out &amp;ldquo;who&amp;rsquo;s here&amp;rdquo; section.&lt;/p&gt;
&lt;p&gt;Each board has four columns and each column has content cards. You can add cards to columns and you can edit cards. There is no active pub/sub between multiple people viewing the board and seeing the cards change (yet), that is still forthcoming.&lt;/p&gt;
&lt;figure&gt;
 &lt;a href=&#34;board-columns.png&#34;&gt;
 &lt;img src=&#34;board-columns-thumb.png&#34; alt=&#34;Sample of current board with editable content cards.&#34;&gt;&lt;/a&gt;
 &lt;figcaption&gt;Sample of current board with editable content cards.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The site is styled with Tailwind CSS through I wouldn&amp;rsquo;t say it represents a final visual language by any means. I hope to finish the project with a vanilla Phoenix LiveView setup and then move on to experiment with &lt;a href=&#34;https://surface-ui.org/&#34;&gt;Surface&lt;/a&gt; for a fuller UI component experience.&lt;/p&gt;
&lt;p&gt;Since I wrote the &lt;a href=&#34;https://github.com/elixirfocus/retro_taxi/blob/main/docs/c1/feature_post_and_vote.md&#34;&gt;original cycle one pitch&lt;/a&gt; I feel like my understanding of what I want to build and how it will all work has been very fluid. I have pretty much dropped the concept of cycles and have just started tagging issues with a &lt;code&gt;MVP1&lt;/code&gt; label since this project&amp;rsquo;s progress has clearly not been constrained by time.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to try to be more deliberate with &lt;a href=&#34;https://github.com/elixirfocus/retro_taxi/issues&#34;&gt;issues&lt;/a&gt; and pull requests moving forward so if you want a peek or contribute, check them out.&lt;/p&gt;
&lt;p&gt;Hopefully I&amp;rsquo;ll also be able to start extracting some more code-specific blog posts from the project soon. If you have any specific questions, let me know &lt;a href=&#34;mailto:mike@mikezornek.com&#34;&gt;mike@mikezornek.com&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Resources and Suggestions to Find Elixir-based Employment</title>
      <link>https://mikezornek.com/posts/2021/7/elixir-job-resources/</link>
      <pubDate>Fri, 16 Jul 2021 16:12:03 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2021/7/elixir-job-resources/</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally written for my old ElixirFocus blog, and transfer here after its closure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The following is a collection of resources and suggestions for people looking to acquire full time or project specific employment writing Elixir. I &lt;a href=&#34;https://mikezornek.com/for-hire/&#34;&gt;personally lean more on consulting&lt;/a&gt; but the topic of Elixir employment comes up enough that I thought I&amp;rsquo;d share what I&amp;rsquo;m aware of.&lt;/p&gt;
&lt;h2 id=&#34;elixir-job-listings&#34;&gt;Elixir Job Listings&lt;/h2&gt;
&lt;p&gt;First we have the aptly named &lt;a href=&#34;https://elixirjobs.net/&#34;&gt;elixirjobs.net&lt;/a&gt;, which provides an active catalog of known job listings.&lt;/p&gt;
&lt;p&gt;I also highly recommend the &lt;code&gt;#jobs&lt;/code&gt;, &lt;code&gt;#jobs_chat&lt;/code&gt;, and &lt;code&gt;#looking_for_contract&lt;/code&gt; rooms of the official &lt;a href=&#34;https://join.slack.com/t/elixir-lang/shared_invite/zt-eivteker-k_nArD59XHjjN_r8qeH6dw&#34;&gt;Elixir Slack&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Elixir Radar, which may be more well known for its weekly newsletter, also hosts &lt;a href=&#34;https://elixir-radar.com/jobs&#34;&gt;a job board&lt;/a&gt;, which is usually promoted in said newsletter emails.&lt;/p&gt;
&lt;p&gt;The Elixir Forum has a dedicated &lt;a href=&#34;https://elixirforum.com/c/community/elixir-jobs/16&#34;&gt;jobs tag&lt;/a&gt; that is less active then the above can still be a helpful link to track.&lt;/p&gt;
&lt;h2 id=&#34;dont-let-requirements-scare-you-away&#34;&gt;Don&amp;rsquo;t Let &amp;ldquo;Requirements&amp;rdquo; Scare You Away&lt;/h2&gt;
&lt;p&gt;One of my first web development jobs was with a company that at the time was looking for full time / experienced developer. I applied despite my limited experience and with only part time availability (I was still in college at the time). They hired me anyways, found a project that I could contribute to and ultimately provided a great environment to improve my skills.&lt;/p&gt;
&lt;p&gt;The point being, many of these job descriptions do not fully convey the situation at these companies. If there is a company or opportunity that interests you, put yourself out there, introduce yourself and share what you are looking for. You never know how it might turn out.&lt;/p&gt;
&lt;h2 id=&#34;elixir-meetups--personal-networking&#34;&gt;Elixir Meetups / Personal Networking&lt;/h2&gt;
&lt;p&gt;While applying to a specific job listing can be the most direct approach to finding a new gig, there is something to be said for personal networking. Many &lt;a href=&#34;https://www.meetup.com/topics/elixir/&#34;&gt;Elixir meetups&lt;/a&gt; and &lt;a href=&#34;https://github.com/elixir-lang/elixir/wiki&#34;&gt;communities&lt;/a&gt; are very approachable online right now. Get out there, introduce yourself (maybe even do a talk!) and be shamelessly direct, explaining that you are looking for work opportunities. Be like &lt;a href=&#34;https://www.youtube.com/watch?v=z0-W4Sozr4I&#34;&gt;Bob Vance, Vance Refrigeration&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;elixir-companies&#34;&gt;Elixir Companies&lt;/h2&gt;
&lt;p&gt;If you observe a company sponsoring an &lt;a href=&#34;https://github.com/elixir-lang/elixir/wiki/Conferences&#34;&gt;Elixir conference&lt;/a&gt; or other kind of &lt;a href=&#34;https://erlef.org/events/&#34;&gt;Elixir event&lt;/a&gt;, they are most likely looking to hire people. You don&amp;rsquo;t even need to limit your search to current events; if that company sponsored an Elixir event years ago they are still a good target to contact.&lt;/p&gt;
&lt;p&gt;You can also look for &lt;a href=&#34;https://elixir-lang.org/cases.html&#34;&gt;companies that have been promoted for their Elixir usage&lt;/a&gt; or are otherwise &lt;a href=&#34;https://elixir-companies.com/en&#34;&gt;known to be using Elixir&lt;/a&gt;. If you spot a company that might be a good fit, reach out to them &amp;ndash; even if they don&amp;rsquo;t have a job listing.&lt;/p&gt;
&lt;h2 id=&#34;self-promotion&#34;&gt;Self Promotion&lt;/h2&gt;
&lt;p&gt;If you have even a mildly active personal website or social media account, consider adding a static header or profile link to a blog post explaining what kind of job you are looking for. Rename yourself on Twitter or Slack to &amp;ldquo;Billy Beta is looking for Elixir work&amp;rdquo; to make it clear, even in unrelated posts that you are available for hire.&lt;/p&gt;
&lt;p&gt;If you need to be more stealthy, consider a few private emails to friends explaining your situation and that you are looking for something new. So much of my own works comes from friends of friends. It is very helpful to have wide social nets to allow for good things to happen.&lt;/p&gt;
&lt;h2 id=&#34;be-prepared&#34;&gt;Be Prepared&lt;/h2&gt;
&lt;p&gt;Finally, be prepared. &lt;a href=&#34;https://thewebivore.com/mnam-a-method-for-defining-your-next-role/&#34;&gt;Know what you want and don&amp;rsquo;t want in a new gig.&lt;/a&gt; Write it all down (paper is better) and prioritize your interview questions to align with those needs.&lt;/p&gt;
&lt;p&gt;I personally am very behind the &lt;a href=&#34;https://wildbit.com/people-first/business&#34;&gt;People-first Business&lt;/a&gt; movement, see also the &lt;a href=&#34;https://peoplefirstjobs.com/&#34;&gt;People-first Job Board&lt;/a&gt;, and so I align my own questions around those values and concerns.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Your ability to land an Elixir gig has never been better. Get out there and find something that is a good fit for you. If I missed any resources, let me know.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Improve the Clarity of Your Elixir Code Through Expressive and Consistent Language</title>
      <link>https://mikezornek.com/posts/2021/6/programming-terminology/</link>
      <pubDate>Sun, 06 Jun 2021 12:00:00 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2021/6/programming-terminology/</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally written for my old ElixirFocus blog, and transfer here after its closure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One of our primary day-to-day responsibilities as programmers is to communicate. We need to express our ideas, explain our work and document our code. Having a strong vocabulary and explicit intentions behind the words you (and your team) choose can greatly help avoid confusion.&lt;/p&gt;
&lt;p&gt;Take some time as a team to talk about how you name and describe things. Embrace productive confrontation when you identify inconsistencies and work towards creating (and documenting!) as much as a ubiquitous language as you can.&lt;/p&gt;
&lt;p&gt;The following is a collection of terms I try to be mindful of in my own work. Hopefully this list helps inspire some of your own terminology choices.&lt;/p&gt;
&lt;h2 id=&#34;programming-terminology&#34;&gt;Programming Terminology&lt;/h2&gt;
&lt;h3 id=&#34;entity-vs-value&#34;&gt;Entity vs Value&lt;/h3&gt;
&lt;p&gt;An &lt;strong&gt;entity&lt;/strong&gt; is a structure that has identity. An &lt;code&gt;Account&lt;/code&gt; in my application has a unique identifier and is thus is considered an entity.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;value&lt;/strong&gt; is a structure without identity. A &lt;code&gt;Color&lt;/code&gt; in my application might be made up of three scalars like red, green and blue. Collectively they make up the &lt;code&gt;Color&lt;/code&gt; value but there is no identity.&lt;/p&gt;
&lt;p&gt;Some people might prefer the term &lt;strong&gt;record&lt;/strong&gt; instead of entity in this context. I consider the terms fairly similar so would honor my team&amp;rsquo;s chosen preference.&lt;/p&gt;
&lt;h3 id=&#34;entity-vs-row&#34;&gt;Entity vs Row&lt;/h3&gt;
&lt;p&gt;Sometimes when we talk about entities we&amp;rsquo;ll get into the deeper discussions of how the entities are persisted, usually inside a database At that point I make a special case to change how I talk, being more explicit about the &lt;strong&gt;database row&lt;/strong&gt;. Entity is how I refer to the in-memory representation of the structure, but if we are talking about database storage or SQL-specific things I want to use the term &lt;strong&gt;row&lt;/strong&gt; to be more explicit.&lt;/p&gt;
&lt;h3 id=&#34;maps-vs-structs-vs-ecto-schemas&#34;&gt;Maps vs Structs vs (Ecto) Schemas&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Maps&lt;/strong&gt;, &lt;strong&gt;Structs&lt;/strong&gt; and &lt;strong&gt;(Ecto) Schemas&lt;/strong&gt; are all Elixir structure tools that we use to create entities and values. Each of these tools has their own purpose and constrains so I try to be explicit when talking about which tool we are using.&lt;/p&gt;
&lt;h3 id=&#34;create-vs-new-vs-insert-and-remove-vs-delete&#34;&gt;Create vs New vs Insert and Remove vs Delete&lt;/h3&gt;
&lt;p&gt;As you define the core domain nouns of your app you&amp;rsquo;ll inevitably start to build out a series of modules that help you manage these nouns. When designing the interfaces of these modules have an explicit pattern for how to name behaviors and try when possible to lean on community patterns. Ask yourself, &amp;ldquo;how does the Elixir language or popular frameworks use these terms?&amp;rdquo;&lt;/p&gt;
&lt;figure class=&#34;&#34;&gt;
 &lt;a href=&#34;delete-new-search.png&#34;&gt;
 &lt;img class=&#34;&#34; style=&#34;max-width: 50%; &#34;src=&#34;delete-new-search.png&#34; alt=&#34;Delete and New as used inside of Elixir.&#34;&gt;&lt;/a&gt;
 &lt;figcaption class=&#34;&#34;&gt;Delete and New as used inside of Elixir.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You can also take advantage of the various Phoenix/Ecto generates for a peek at some pattern recommendations from the framework authors. These are usually a great place to start, although I don&amp;rsquo;t consider the patterns to be gospel.&lt;/p&gt;
&lt;p&gt;In addition to the working on the names of the modules functions themselves, you may also be involved in the user experience of an app and its own terminology. Ideally the nouns and terms from the interface down through the code will align but this is not always possible. Sometimes the needs of the user experience design require the terminologies to deviate. While not ideal, I&amp;rsquo;m fine with this &amp;ndash; it just has to be done with intent and consistency. Write down the terms you have chosen and why.&lt;/p&gt;
&lt;h3 id=&#34;get-vs-fetch&#34;&gt;Get vs Fetch&lt;/h3&gt;
&lt;p&gt;There are a few notable Elixir patterns to be mindful of with the terms &lt;code&gt;get&lt;/code&gt; vs &lt;code&gt;fetch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First let&amp;rsquo;s check out &lt;a href=&#34;https://hexdocs.pm/elixir/Map.html#get/3&#34;&gt;Map.get/3&lt;/a&gt; which will return a value for the given key or a default value if the key is not found.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;iex&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(%{}, &lt;span style=&#34;color:#e6db74&#34;&gt;:a&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;iex&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(%{&lt;span style=&#34;color:#e6db74&#34;&gt;a&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;}, &lt;span style=&#34;color:#e6db74&#34;&gt;:a&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;iex&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(%{&lt;span style=&#34;color:#e6db74&#34;&gt;a&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;}, &lt;span style=&#34;color:#e6db74&#34;&gt;:b&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;iex&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(%{&lt;span style=&#34;color:#e6db74&#34;&gt;a&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;}, &lt;span style=&#34;color:#e6db74&#34;&gt;:b&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next we have &lt;a href=&#34;https://hexdocs.pm/elixir/Map.html#fetch/2&#34;&gt;Map.fetch/2&lt;/a&gt; which will return an :ok tuple or :error if the key is not found.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;iex&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fetch(%{&lt;span style=&#34;color:#e6db74&#34;&gt;a&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;}, &lt;span style=&#34;color:#e6db74&#34;&gt;:a&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;:ok&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;iex&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fetch(%{&lt;span style=&#34;color:#e6db74&#34;&gt;a&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;}, &lt;span style=&#34;color:#e6db74&#34;&gt;:b&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There is also a bang version of fetch, &lt;a href=&#34;https://hexdocs.pm/elixir/Map.html#fetch!/2&#34;&gt;Map.fetch!/2&lt;/a&gt; which will return the value or raise an exception if the key is not found.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;iex&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fetch!(%{&lt;span style=&#34;color:#e6db74&#34;&gt;a&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;}, &lt;span style=&#34;color:#e6db74&#34;&gt;:a&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The takeaways from these language patterns I take note of are the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;get_noun&lt;/code&gt; should return the value or a default if not found.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetch_noun&lt;/code&gt; should return a &lt;code&gt;:ok&lt;/code&gt; tuple or an &lt;code&gt;:error&lt;/code&gt; tuple.&lt;/li&gt;
&lt;li&gt;Any function with a bang (exclamation point) will throw an exception, and that exception should be documented inside the inline documentation.&lt;/li&gt;
&lt;li&gt;In general I try to avoid creating bang style functions as I feel like Elixir exceptions should be exceptional. You may notice that the there are Phoenix code generators out there that will create a &lt;code&gt;get!&lt;/code&gt; style functions in generated contexts. That is pattern I am aware of but question in newer code.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;list-vs-array&#34;&gt;List vs Array&lt;/h3&gt;
&lt;p&gt;I do my best to be mindful of and embrace the terminology of the language I am working in, moment to moment. This means if we are dealing with a collection in Elixir I&amp;rsquo;ll say list and when we are dealing with a collection is JavaScript I&amp;rsquo;ll say array. In an Elixir context I&amp;rsquo;ll say map and in a JavaScript context I&amp;rsquo;ll say dictionary.&lt;/p&gt;
&lt;p&gt;For a junior programmer this might seem like symantec nonsense but I think its an important distinction. Related, I honor the styles of the host language, for example using camel case for variables in JavaScript and underscores in Elixir. With luck a linter of some kind can help remind me if I loose my place.&lt;/p&gt;
&lt;h3 id=&#34;behavior-vs-feature&#34;&gt;Behavior vs Feature&lt;/h3&gt;
&lt;p&gt;When talking about what my code does I use the term behavior. I describe the behaviors I want to build and I document the behaviors I observe.&lt;/p&gt;
&lt;p&gt;The term feature is more a product or marketing term, usually describing a collection of end user observable behaviors.&lt;/p&gt;
&lt;h2 id=&#34;app-and-business-domain-consistency&#34;&gt;App and Business Domain Consistency&lt;/h2&gt;
&lt;h3 id=&#34;document-your-apps-custom-terminology&#34;&gt;Document Your App&amp;rsquo;s Custom Terminology&lt;/h3&gt;
&lt;p&gt;While I have some personal preferences to share below, ultimately every app will have a custom vernacular for how they label things in the system. Spend some time getting team agreement on the meaning and intent behind these terms. Write them down. Review and evolve the glossary when needed. Having alignment on this language will be extremely helpful over time as you discuss what to code needs to do.&lt;/p&gt;
&lt;h3 id=&#34;authentication-vs-authorization&#34;&gt;Authentication vs Authorization&lt;/h3&gt;
&lt;p&gt;Many people simply talk about &amp;ldquo;auth&amp;rdquo; systems without being explicit but it&amp;rsquo;s important to distinguish between authentication, figuring out who you are talking to, and authorization, figuring out if someone should be allowed to do something.&lt;/p&gt;
&lt;h3 id=&#34;sign-in-vs-log-in&#34;&gt;Sign in vs Log in&lt;/h3&gt;
&lt;p&gt;My own preference here is to use Sign in, Sign out and Sign up. I&amp;rsquo;m also ok with other variants like &amp;ldquo;Create Account&amp;rdquo; in the user experience as long as we are consistent.&lt;/p&gt;
&lt;p&gt;I dislike log in since it is too easy to mix up the term &amp;ldquo;login&amp;rdquo; vs &amp;ldquo;log in&amp;rdquo;.&lt;/p&gt;
&lt;h3 id=&#34;account-vs-user-and-settings-vs-preferences&#34;&gt;Account vs User and Settings vs Preferences&lt;/h3&gt;
&lt;p&gt;No strong personal preferences here, would have to consider the application context &amp;ndash; but once again I am looking for consistency. If I were debating choices for a new app with my team I&amp;rsquo;d look at some industry competition to see what they do.&lt;/p&gt;
&lt;h2 id=&#34;language-i-try-to-avoid&#34;&gt;Language I Try to Avoid&lt;/h2&gt;
&lt;h3 id=&#34;bad-code-and-code-smell&#34;&gt;Bad Code and Code Smell&lt;/h3&gt;
&lt;p&gt;I do my best to follow the &lt;a href=&#34;https://easyretro.io/retrospective-prime-directive/&#34;&gt;retrospective prime directive&lt;/a&gt; which states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Regardless of what we discover, we understand and truly believe that everyone did the best job they could, given what they knew at the time, their skills and abilities, the resources available, and the situation at hand.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Believing this, I try to avoid saying something is &amp;ldquo;bad code&amp;rdquo; or has a &amp;ldquo;code smell&amp;rdquo;. Those phrases package a lot of negative judgement which is not very constructive. Instead let&amp;rsquo;s look for opportunities to improve testability, or documentation or abstractions. No one wrote this &amp;ldquo;bad code&amp;rdquo; intentionally to sabotage the system. We are all members of an extremely immature and fast moving industry with diverse backgrounds and levels of experience. It is messy out there and everyone is learning as we go.&lt;/p&gt;
&lt;p&gt;If you are doing a code review, instead of saying something has a &amp;ldquo;code smell&amp;rdquo;, kickstart a discussion around getting more context around the decisions and constrains that led to this code&amp;rsquo;s design. Ask if they considered an alternative or are aware of your concerns. These pull requests are opportunities for education and starting the process with judgement language is not going to result in many positive outcomes.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;You might also enjoy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=5MBGDM8xSQg&#34;&gt;Video: Building Beautiful Systems With Phoenix Contexts and Domain-Driven Design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://devonburriss.me/ddd-glossary/&#34;&gt;Domain-Driven Design Glossary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;What did I miss? What terminology are you protective of in your own code? Let me know: &lt;a href=&#34;mailto:mike@mikezornek.com&#34;&gt;mike@mikezornek.com&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Understanding when to use String-based Maps vs Atom-based Maps</title>
      <link>https://mikezornek.com/posts/2021/5/string-vs-atom-maps/</link>
      <pubDate>Mon, 31 May 2021 10:56:17 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2021/5/string-vs-atom-maps/</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally written for my old ElixirFocus blog, and transfer here after its closure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While browsing the ElixirForum the other day I spotted &lt;a href=&#34;https://elixirforum.com/t/map-with-atom-string-keyword-list-its-blocking-me-so-much-as-a-beginner-elixr-enthusiast/39954&#34;&gt;this question&lt;/a&gt; from Dominic which resonated deeply with my own experiences learning Elixir. Dominic asks:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;m constantly being blocked by maps&amp;rsquo; different construct &lt;code&gt;%{:atom =&amp;gt; &amp;quot;value&amp;quot;}&lt;/code&gt; vs. &lt;code&gt;%{&amp;quot;string&amp;quot; =&amp;gt; &amp;quot;value&amp;quot;}&lt;/code&gt; vs &lt;code&gt;%{name: &amp;quot;value&amp;quot;}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It does not appears to be consistent enough and in my case are often the cause of all &amp;ldquo;blockage&amp;rdquo; I have.&lt;/p&gt;
&lt;p&gt;I understand them, I&amp;rsquo;m a long time Elm dev/fan and used to FP, but I don&amp;rsquo;t know why Elixir&amp;rsquo;s maps are so weird to me.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Later in the post Dominic notes he gets an error complaining about mixed keys: &lt;code&gt;(Ecto.CastError) expected params to be a map with atoms or string keys&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So with all that said, why the lack of consistency? Why don&amp;rsquo;t people just use String-based Maps or just use Atom-based Maps? Why do all these Phoenix tutorials jump from one to the other?&lt;/p&gt;
&lt;p&gt;The short answer is, if given the opportunity I think most Elixir developers would lean on Atom-based Maps at all times, but we need to work within the constrains of our deployments. As preferable as &lt;code&gt;:atoms&lt;/code&gt; may be they are not garbage collected and if you are allowing dynamic content into the system (from say a web request) and converting that content to Atom-based Maps you leave yourself open to running out of memory from a bad actor or even just natural system behavior. It is for this reason the web request tooling within Phoenix will transform those payloads into String-based Maps.&lt;/p&gt;
&lt;p&gt;With that explanation out of the way, let&amp;rsquo;s talk about Map syntax. While Dominic quotes three styles of map syntax there are really only two patterns to keep in your head:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;%{&amp;quot;name&amp;quot; =&amp;gt; value}&lt;/code&gt; which is a Map syntax using the arrow &lt;code&gt;=&amp;gt;&lt;/code&gt; token; and,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%{name: value}&lt;/code&gt; which is an arrow-less version of the Map syntax made exclusively for Atom-based Maps.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The third style Dominic shared, &lt;code&gt;%{:atom =&amp;gt; value}&lt;/code&gt; I would group into the arrow &lt;code&gt;=&amp;gt;&lt;/code&gt; camp. Maps can use Strings or Atoms as keys and so no real syntax sugar is present in this example.&lt;/p&gt;
&lt;p&gt;I think one of the real jarring issues for people new to Elixir when it comes to the Atom-exclusive Map syntax is that it has the colon of the Atom key on the right-hand side. You&amp;rsquo;ll see this with &lt;a href=&#34;https://elixirschool.com/en/lessons/basics/collections/#keyword-lists&#34;&gt;Keyword lists&lt;/a&gt; as well. Having multiple ways to express a Map can be one level of confusion, but then to combine it with multiple ways to express an &lt;code&gt;:atom&lt;/code&gt; feels like a compounding problem.&lt;/p&gt;
&lt;p&gt;The good news, I can share as someone who has been doing Elixir for a few years now, is that this confusion is &lt;strong&gt;not&lt;/strong&gt; something I&amp;rsquo;ve experienced elsewhere in the language. On the whole I&amp;rsquo;ve found Elixir syntax to be straightforward and approachable. Getting comfortable with the Atom syntax sugar of Maps and Keyword lists took a little time but eventually it stuck.&lt;/p&gt;
&lt;p&gt;One of the deeper concepts that comes out of this discussion however is the need for String-based keys from content outside the system. I think it is very helpful to have that external boundary in mind when building your code. Let the web or API contexts handle validating or shaping those String-based Map values and then have a meaningful transformation into the Atom-based Maps that will accept values for your core layer. With that pattern in mind your tests can follow along, simulating String-based Map payloads for controllers and Atom-based Map values for core context testing, and thus avoiding the kind of Ecto error Dominic shared.&lt;/p&gt;
&lt;p&gt;For more on boundaries check out the &lt;a href=&#34;https://medium.com/very-big-things/towards-maintainable-elixir-the-core-and-the-interface-c267f0da43&#34;&gt;The Core and the Interface&lt;/a&gt; from the excellent Towards Maintainable Elixir series.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>RetroTaxi Project Kickoff</title>
      <link>https://mikezornek.com/posts/2021/3/retro-taxi-project-kickoff/</link>
      <pubDate>Sun, 14 Mar 2021 16:55:21 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2021/3/retro-taxi-project-kickoff/</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally written for my old ElixirFocus blog, and transfer here after its closure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Phoenix by Example is all about crafting educational content extracted from open source example projects. Today I am kicking off my latest example project, RetroTaxi!&lt;/p&gt;
&lt;p&gt;From the &lt;a href=&#34;https://github.com/phoenix-by-example/retro_taxi/blob/main/docs/c1/feature_post_and_vote.md&#34;&gt;pitch document&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A retrospective meeting is a regular practice of agile teams or any group of people who are looking for continued improvement to their team process, communication or habits. Successful retro meetings give the team an opportunity to reflect on what is working, what is not and what they want to do next.&lt;/p&gt;
&lt;p&gt;Retrospective meetings can be executed in many different ways but with the advent of more people working remotely having an online collaborative tool to help execute the meeting and capture feedback is often advantageous. Some people will initially lean on collaborative tools like Google Docs to help execute a retro meeting and while it can work, it is not very elegant or structured. There are also dedicated retro tools but most are locked behind account registration, paywalls or try to do too many things.&lt;/p&gt;
&lt;p&gt;This project aims to provide a simple, focused tool to help people run a retro meeting online. Our solution will value:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A registrations-free experience&lt;/strong&gt; over formalized team management.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One-time use boards&lt;/strong&gt; over detailed historic archives.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conversations&lt;/strong&gt; over analytical measurements or note taking.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A responsive web experience&lt;/strong&gt; over platform-specific apps.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;RetroTaxi will utilize &lt;a href=&#34;https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html&#34;&gt;Phoenix LiveView&lt;/a&gt; fairly heavily and should be a useful source of topics to discuss.&lt;/p&gt;
&lt;h2 id=&#34;project-goals&#34;&gt;Project Goals&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&#34;https://github.com/phoenix-by-example/greeter&#34;&gt;first&lt;/a&gt; &lt;a href=&#34;https://github.com/phoenix-by-example/get_shorty&#34;&gt;two&lt;/a&gt; projects of Phoenix by Example were fairly simplistic in nature and one of the first goals is of RetroTaxi is to do &lt;strong&gt;a more through exploration of a real world project process&lt;/strong&gt;. As such you&amp;rsquo;ll see some early discovery time has been spent in preparing &lt;a href=&#34;https://github.com/phoenix-by-example/retro_taxi/blob/main/docs/c1/feature_post_and_vote.md&#34;&gt;a pitch document&lt;/a&gt;, which is a slice of the larger Shape Up process.&lt;/p&gt;
&lt;figure&gt;
 &lt;a href=&#34;breadboards.jpeg&#34;&gt;
 &lt;img src=&#34;breadboards-thumb.jpeg&#34; alt=&#34;Sample of some early discovery sketching and interface breadboarding.&#34;&gt;&lt;/a&gt;
 &lt;figcaption&gt;Sample of some early discovery sketching and interface &lt;a href=&#34;https://basecamp.com/shapeup/1.3-chapter-04#breadboarding&#34;&gt;breadboarding&lt;/a&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Some mockups from the pitch document:&lt;/p&gt;
&lt;figure&gt;
 &lt;a href=&#34;board-layout.png&#34;&gt;
 &lt;img src=&#34;board-layout-thumb.jpeg&#34; alt=&#34;Board layout.&#34;&gt;&lt;/a&gt;
 &lt;figcaption&gt;Board layout.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
 &lt;a href=&#34;card-state.png&#34;&gt;
 &lt;img src=&#34;card-state-thumb.jpeg&#34; alt=&#34;Card state.&#34;&gt;&lt;/a&gt;
 &lt;figcaption&gt;Card state.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The second project goal is to (hopefully) &lt;strong&gt;provide more useful and usable solutions to real world problems&lt;/strong&gt;. When I built the &lt;a href=&#34;https://github.com/phoenix-by-example/get_shorty&#34;&gt;GetShorty&lt;/a&gt; link shortener example there were no real aspirations that anyone would actually use the tool day-to-day, but moving forward I&amp;rsquo;d like to find small slices of opinionated, usefulness solutions to model the examples after. If you find this and have thoughts on retro meeting and the tools involved, please &lt;a href=&#34;mailto:mike@mikezornek.com&#34;&gt;reach out&lt;/a&gt;. I&amp;rsquo;d love to hear your observations.&lt;/p&gt;
&lt;h2 id=&#34;next-steps&#34;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;With the early discovery and my &lt;a href=&#34;https://github.com/phoenix-by-example/retro_taxi/blob/main/docs/c1/feature_post_and_vote.md&#34;&gt;first pitch&lt;/a&gt; written I see the next steps as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sketch out the HTML/TailwindCSS needed to execute the design.&lt;/li&gt;
&lt;li&gt;Build out the core contexts needed to model the behaviors of the board.&lt;/li&gt;
&lt;li&gt;Create the LiveView to wire up the behaviors to the UI, breaking the interface into simple, testable components.&lt;/li&gt;
&lt;li&gt;Refine the interactions and test non-happy path experiences (browser reload/rejoin).&lt;/li&gt;
&lt;li&gt;Deploy and test the app to be used on the public internet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The plan is to do this project in 6 weeks, with about 10 hours per week of dedicated time. I&amp;rsquo;ll be blogging my progress here so if you want to stay informed be sure to subscribe.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Securing Webhook Payload Delivery in Phoenix</title>
      <link>https://mikezornek.com/posts/2021/2/securing-webhook-payload-delivery/</link>
      <pubDate>Mon, 22 Feb 2021 14:00:00 -0500</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2021/2/securing-webhook-payload-delivery/</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally written for my old ElixirFocus blog, and transfer here after its closure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Some of the most interesting and valuable behaviors we can build as web developers are through connecting different web applications together. Often times this comes in the form of webhooks, where one web app will POST events to your own web app. Keeping that communication secure is important and today we will take a peak into how you can secure webhook payloads in a Phoenix application.&lt;/p&gt;
&lt;h2 id=&#34;webhook-security-101&#34;&gt;Webhook Security 101&lt;/h2&gt;
&lt;p&gt;Webhook might sound like some scary, exotic term but at the end of the day webhooks are nothing more that a web server endpoint configured to receive HTTP POST requests in some agreed upon format. The concern however is, without any additional security, anyone could discover the endpoint and start sending their own malicious payloads to your web application.&lt;/p&gt;
&lt;p&gt;To help secure a webhook, when you first set it up you&amp;rsquo;ll give the third party some generated secret. As an example, here is where you can store the secret while configuring a GitHub webhook:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;github-secret.png&#34;&gt;&lt;img src=&#34;github-secret-thumb.png&#34; alt=&#34;GitHub Webhook configuration screen, showing a field for &amp;ldquo;secret&amp;rdquo;.&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When GitHub then sends the HTTP POST request they will include, in the headers of that request, a signature of the payload using the previously shared secret. As you process the HTTP POST request you can then generate your own signature, based on the incoming payload and the known secret, to verify the payload&amp;rsquo;s authenticity. If the secrets match then you have much more trust that the payload came from the correct third party.&lt;/p&gt;
&lt;h2 id=&#34;validating-a-payload-signature-in-phoenix&#34;&gt;Validating a Payload Signature in Phoenix&lt;/h2&gt;
&lt;p&gt;To get this to work in Phoenix we&amp;rsquo;ll need a few core behaviors.&lt;/p&gt;
&lt;p&gt;First, we will want the ability to generate a signature based on a payload and a secret.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; generate_payload_signature(_, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#e6db74&#34;&gt;:error&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:missing_app_secret&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; generate_payload_signature(payload, app_secret) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#e6db74&#34;&gt;:ok&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:crypto&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mac(&lt;span style=&#34;color:#e6db74&#34;&gt;:hmac&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:sha256&lt;/span&gt;, app_secret, payload) &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Base&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;encode16(&lt;span style=&#34;color:#e6db74&#34;&gt;case&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;:lower&lt;/span&gt;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Second we&amp;rsquo;ll need a way to pull out the signature from the connection&amp;rsquo;s request headers, if present:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;defp&lt;/span&gt; signature_from_req_headers(req_headers) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;List&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;keyfind(req_headers, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;x-hub-signature&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;x-hub-signature&amp;#34;&lt;/span&gt;, full_signature} &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sha256=&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt; signature &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; full_signature
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        signature
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      _ &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally we&amp;rsquo;ll connect the dots creating a simple call site function where someone can pass in a &lt;code&gt;Plug.Conn&lt;/code&gt; and a payload and ask if it is authentic.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; is_authentic_payload?(%&lt;span style=&#34;color:#a6e22e&#34;&gt;Conn&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;req_headers&lt;/span&gt;: req_headers}, payload) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; signature_from_req_headers(req_headers) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      signature &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        is_payload_signature_valid?(signature, payload)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;defp&lt;/span&gt; is_payload_signature_valid?(payload_signature, payload) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; generate_payload_signature(payload, webhook_secret()) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {&lt;span style=&#34;color:#e6db74&#34;&gt;:ok&lt;/span&gt;, generated_payload_signature} &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;Plug.Crypto&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;secure_compare(generated_payload_signature, payload_signature)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      _ &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;defp&lt;/span&gt; webhook_secret &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Keyword&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fetch!(&lt;span style=&#34;color:#a6e22e&#34;&gt;Application&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fetch_env!(&lt;span style=&#34;color:#e6db74&#34;&gt;:webhook_signature&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:github&lt;/span&gt;), &lt;span style=&#34;color:#e6db74&#34;&gt;:webhook_secret&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are a few ways you could go about getting this check into your controller code. The most challenging part is that out of the box Phoenix does not make the raw body of an HTTP POST request available to you through the &lt;code&gt;Plug.Conn&lt;/code&gt; struct. Instead a default Phoenix app uses parsers and converts such payloads into native Elixir value types.&lt;/p&gt;
&lt;p&gt;One way to turn off this behavior is to edit your Phoenix app&amp;rsquo;s &lt;code&gt;endpoint.ex&lt;/code&gt; file and remove the auto-parsing behavior for all routes of that endpoint. You can find some more info about doing this in the &lt;a href=&#34;https://hexdocs.pm/plug/Plug.Parsers.html#module-custom-body-reader&#34;&gt;Plug docs&lt;/a&gt; in a section titled &amp;ldquo;Custom body reader&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Since this parsing behavior is helpful for non-webhook requests in our example app, we will relocate the parsing configurations out of the &lt;code&gt;endpoint.ex&lt;/code&gt; file and into the pipeline definition on the &lt;code&gt;router.ex&lt;/code&gt; file. This way we can keep the parsing behavior working for the rest of the example app and do something custom for our webhook endpoint.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;defmodule WebhookSignatureWeb.Endpoint do
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  # CONDENSED FOR DEMONSTRATION
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-  plug Plug.Parsers,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    parsers: [:urlencoded, :multipart, :json],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    pass: [&amp;#34;*/*&amp;#34;],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    json_decoder: Phoenix.json_library()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;defmodule WebhookSignatureWeb.Router do
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  # CONDENSED FOR DEMONSTRATION
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pipeline :browser do
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    plug :accepts, [&amp;#34;html&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    plug :fetch_session
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    plug :fetch_flash
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    plug :protect_from_forgery
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    plug :put_secure_browser_headers
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    plug Plug.Parsers,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+      parsers: [:urlencoded, :multipart, :json],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+      pass: [&amp;#34;*/*&amp;#34;],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+      json_decoder: Phoenix.json_library()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Our next question is where do we this verification check? In our example app, we will keep the controller code focused on the behavior of processing the payload and put the behaviors for signature verification inside some plugs as part of the router&amp;rsquo;s pipeline for this specific endpoint.&lt;/p&gt;
&lt;p&gt;The first plug will read the contents of the &lt;code&gt;Plug.Conn&lt;/code&gt; body into a new assigns value &lt;code&gt;:raw_body&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;defmodule&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WebhookSignatureWeb.Plugs.RawBodyPassthrough&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@moduledoc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  This plug will read the body for `POST` and PUT` request and store it into a
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  new assigns key `:raw_body`.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  This plug is used on certain routes in preference to the default Phoenix
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  behaviors that would automatically decode the params and request body into
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  native elixir values for a controller. It is a required choice since the body
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  of a `Plug.Conn` can only be read from once.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Plug.Conn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Plug.Conn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@spec&lt;/span&gt; init(&lt;span style=&#34;color:#a6e22e&#34;&gt;Keyword&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()) &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Keyword&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; init(options), &lt;span style=&#34;color:#e6db74&#34;&gt;do&lt;/span&gt;: options
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@spec&lt;/span&gt; call(&lt;span style=&#34;color:#a6e22e&#34;&gt;Conn&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t(), term()) &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Conn&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; call(%&lt;span style=&#34;color:#a6e22e&#34;&gt;Conn&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;method&lt;/span&gt;: method} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; conn, opts) &lt;span style=&#34;color:#f92672&#34;&gt;when&lt;/span&gt; method &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;POST&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; method &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;PUT&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Conn&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read_body(conn, opts) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {&lt;span style=&#34;color:#e6db74&#34;&gt;:ok&lt;/span&gt;, body, _conn_details} &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;Conn&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;assign(conn, &lt;span style=&#34;color:#e6db74&#34;&gt;:raw_body&lt;/span&gt;, body)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {&lt;span style=&#34;color:#e6db74&#34;&gt;:more&lt;/span&gt;, _partial_body, _conn_details} &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        conn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; send_resp(&lt;span style=&#34;color:#ae81ff&#34;&gt;413&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;PAYLOAD TOO LARGE&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; halt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; call(conn, _opts), &lt;span style=&#34;color:#e6db74&#34;&gt;do&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;Conn&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;assign(conn, &lt;span style=&#34;color:#e6db74&#34;&gt;:cached_body&lt;/span&gt;, %{})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The second plug will pull out the signature from the HTTP request headers and then verify it matches our expectations.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;defmodule&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WebhookSignatureWeb.Plugs.RequirePayloadSignatureMatch&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@moduledoc&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  This plug will verify that the payload from a webhook request matches the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  accompanying header signature, based on a previously shared `webhook_secret`.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  When the payload is verified the connection continues as normal.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  When the payload is unverifiable the connection is halted with a 403 response.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Plug.Conn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Plug.Conn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WebhookSignature.PayloadValidator&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@spec&lt;/span&gt; init(&lt;span style=&#34;color:#a6e22e&#34;&gt;Keyword&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()) &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Keyword&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; init(options), &lt;span style=&#34;color:#e6db74&#34;&gt;do&lt;/span&gt;: options
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@spec&lt;/span&gt; call(&lt;span style=&#34;color:#a6e22e&#34;&gt;Conn&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t(), term()) &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Conn&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;t()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; call(%&lt;span style=&#34;color:#a6e22e&#34;&gt;Conn&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;method&lt;/span&gt;: method} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; conn, _opts) &lt;span style=&#34;color:#f92672&#34;&gt;when&lt;/span&gt; method &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;POST&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; method &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;PUT&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;PayloadValidator&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;is_authentic_payload?(conn, conn&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;assigns&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;raw_body) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        conn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        conn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; put_resp_content_type(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; send_resp(&lt;span style=&#34;color:#ae81ff&#34;&gt;403&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;{\&amp;#34;error\&amp;#34;:\&amp;#34;PAYLOAD SIGNATURE FAILED\&amp;#34;}&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;|&amp;gt;&lt;/span&gt; halt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; call(conn, _opts), &lt;span style=&#34;color:#e6db74&#34;&gt;do&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;Conn&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;assign(conn, &lt;span style=&#34;color:#e6db74&#34;&gt;:cached_body&lt;/span&gt;, %{})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally we&amp;rsquo;ll bring it all together, defining an &lt;code&gt;:api&lt;/code&gt; pipeline and using that pipeline for our &lt;code&gt;GitHubWebhookController&lt;/code&gt; route.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;defmodule&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WebhookSignatureWeb.Router&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# CONDENSED FOR DEMONSTRATION&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pipeline &lt;span style=&#34;color:#e6db74&#34;&gt;:api&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    plug &lt;span style=&#34;color:#e6db74&#34;&gt;:accepts&lt;/span&gt;, [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;json&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    plug &lt;span style=&#34;color:#a6e22e&#34;&gt;WebhookSignatureWeb.Plugs.RawBodyPassthrough&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;length&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;4_000_000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# It is important that this comes after&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# `WebhookSignatureWeb.Plugs.RawBodyPassthrough` as it relies&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# on the `:raw_body` being inside the `conn.assigns`.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    plug &lt;span style=&#34;color:#a6e22e&#34;&gt;WebhookSignatureWeb.Plugs.RequirePayloadSignatureMatch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  scope &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/github&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;WebhookSignatureWeb&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pipe_through &lt;span style=&#34;color:#e6db74&#34;&gt;:api&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    post &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/webhook&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;GitHubWebhookController&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:webhook&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the signature match is successful the controller will be allowed to do its processing of the payload values.&lt;/p&gt;
&lt;p&gt;If the signature match is not successful we return a &lt;a href=&#34;https://httpstatuses.com/403&#34;&gt;403 response&lt;/a&gt; since &amp;ldquo;The server understood the request but refuses to authorize it.&amp;rdquo;&lt;/p&gt;
&lt;h2 id=&#34;show-me-the-code&#34;&gt;Show Me The Code&lt;/h2&gt;
&lt;p&gt;There is an example project with the behaviors expressed in this post on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/phoenix-by-example/webhook_signature&#34;&gt;https://github.com/phoenix-by-example/webhook_signature&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;further-reading&#34;&gt;Further Reading&lt;/h2&gt;
&lt;p&gt;For more on webhooks, you can check out &lt;a href=&#34;https://docs.github.com/en/developers/webhooks-and-events/webhooks&#34;&gt;GitHub&amp;rsquo;s documentation&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Understanding the Tradeoffs with Elixir Typespecs and Dialyzer</title>
      <link>https://mikezornek.com/posts/2021/1/typespecs-and-dialyzer/</link>
      <pubDate>Wed, 20 Jan 2021 08:51:42 -0500</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2021/1/typespecs-and-dialyzer/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s post I want to start a conversation around the tradeoffs of using &lt;a href=&#34;https://hexdocs.pm/elixir/typespecs.html&#34;&gt;Typespecs&lt;/a&gt; and &lt;a href=&#34;http://erlang.org/doc/man/dialyzer.html&#34;&gt;Dialyzer&lt;/a&gt; use in your Elixir code.&lt;/p&gt;
&lt;p&gt;Elixir is a dynamic, not a statically-typed language &amp;ndash; and just to preemptively avoid the programming wars that might ensue, the given type system of a language is not a &amp;ldquo;good&amp;rdquo; or &amp;ldquo;bad&amp;rdquo; attribute by itself, but it will none-the-less influences the code we write.&lt;/p&gt;
&lt;p&gt;For those who prefer or come to Elixir from a statically-typed language background you may be interested in learning more about &lt;a href=&#34;https://hexdocs.pm/elixir/typespecs.html&#34;&gt;Elixir Typespecs&lt;/a&gt;, described in the documentation as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Elixir comes with a notation for declaring types and specifications. Elixir is a dynamically typed language, and as such, type specifications are never used by the compiler to optimize or modify code. Still, using type specifications is useful because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;they provide documentation (for example, tools such as &lt;a href=&#34;https://github.com/elixir-lang/ex_doc&#34;&gt;ExDoc&lt;/a&gt; show type specifications in the documentation)&lt;/li&gt;
&lt;li&gt;they&amp;rsquo;re used by tools such as &lt;a href=&#34;http://www.erlang.org/doc/man/dialyzer.html&#34;&gt;Dialyzer&lt;/a&gt; , that can analyze code with typespec to find type inconsistencies and possible bugs&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;While I have done some limited work with typespecs in my side projects, in my current client project the decree has been made we will define typespecs for all functions, both public and private, and we have dialyzer checks built into the CI to report any reported issues.&lt;/p&gt;
&lt;p&gt;On the face of this requirement I do not have a problem, it is very well meaning towards building a stable codebase. Who can disagree with such a goal? The realties however of seeing it through day-to-day are more complex. First, let&amp;rsquo;s talk about how dialyzer errors can sometimes be hard to debug.&lt;/p&gt;
&lt;p&gt;One recent error I got back from dialyzer was something like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;:0:unknown_type
Unknown type: Widget.t/0.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There was no function name, nor file name, nor line number to help me understand where this error was coming from. I was on my own. Luckily I knew the dialyzer check was clean before my I started my branch so I could at least look at my git diff for some ideas. This was not fun.&lt;/p&gt;
&lt;p&gt;Another time I had a whole list of dialyzer errors reported back to me. Now if I was dealing with traditional complier errors I would work from the top of the list down, since sometimes when those first complier issues are resolved the later issues will disappear. Sadly when it comes to dialyzer errors that first error need not be what actually needs to get fixed. Many times you need to fix issues from the middle of the list first and knowing what to fix from that list is a learned art with its own dedicated learning curve.&lt;/p&gt;
&lt;p&gt;Obtuse errors are bad enough but the second big issue for me is time. On a bigger project like ours it can sometime takes 10+ minutes to do a full dialyzer check the project. That long feedback loop can really slow down your day.&lt;/p&gt;
&lt;p&gt;So where do we find the balance?&lt;/p&gt;
&lt;p&gt;When it comes to Elixir code, my priorities of practices to enable a stable code base are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, write tests. Validate your code behaviors and assumptions. Check the happy paths and check the sad paths. Do this for every project you ever write.&lt;/li&gt;
&lt;li&gt;Second, use &lt;a href=&#34;https://github.com/rrrene/credo&#34;&gt;credo&lt;/a&gt;. Credo can analyze your project for Elixir style and consistency. Credo is extremely fast with helpful error messages that teach you not only how to fix something but why it should be fixed. Use this on any project that you work on for more than a few days.&lt;/li&gt;
&lt;li&gt;Finally, use typespecs and dialyzer, but only for your public functions, and with a heavy leaning towards your own contexts and not the Phoenix-based modules that will be called based on dynamic behaviors like routes calling controllers. Use dialyzer for customer facing apps or anything open source.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course every project has unique needs and tradeoffs, but this is where I land in the general case.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d love to hear your thoughts on typespecs and dialyzer.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Introducing Phoenix by Example</title>
      <link>https://mikezornek.com/posts/2020/10/introducing-phoenix-by-example/</link>
      <pubDate>Mon, 19 Oct 2020 18:23:47 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2020/10/introducing-phoenix-by-example/</guid>
      <description>&lt;p&gt;Teaching has always been a big part of my self identity. Be it teaching at university, Big Nerd Ranch workshops or even my work with local tech meetups.&lt;/p&gt;
&lt;p&gt;With my increasing enjoyment of &lt;a href=&#34;https://elixir-lang.org&#34;&gt;Elixir&lt;/a&gt; and &lt;a href=&#34;https://www.phoenixframework.org/&#34;&gt;Phoenix&lt;/a&gt; I&amp;rsquo;ve been anxious to find a teaching outlet using these technologies. Additionally I want to find a unique spin on whatever I come up with, since there are already lots of great resources for &lt;a href=&#34;https://elixir-lang.org/learning.html&#34;&gt;learning Elixir&lt;/a&gt; and &lt;a href=&#34;https://hexdocs.pm/phoenix/community.html&#34;&gt;Phoenix&lt;/a&gt; and I don&amp;rsquo;t want steal anyone&amp;rsquo;s thunder.&lt;/p&gt;
&lt;p&gt;My first result towards this effort is a &amp;ldquo;hello world&amp;rdquo; style example app and tutorial called &lt;a href=&#34;https://github.com/phoenix-by-example/greeter&#34;&gt;greeter&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Greeter is a &amp;ldquo;hello world&amp;rdquo; example app to help introduce the core concepts of the Phoenix Framework written in Elixir.&lt;/p&gt;
&lt;p&gt;This tutorial assumes no previous Elixir experience. In fact the first section will help you install and setup Elixir and Phoenix.&lt;/p&gt;
&lt;p&gt;By the end of this example, you&amp;rsquo;ll have created a Phoenix app that can display a custom greeting message with a formatted name.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;rsquo;ve published the example repo under the Phoenix by Example organization name. I&amp;rsquo;ve also nabbed a web domain &lt;code&gt;phoenixbyexample.com&lt;/code&gt; and a twitter account &lt;code&gt;@PhoenixExamples&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m still not sure how this will work out over time but my plan is to record a few videos to accompany the example project. I would also like to work out some shorter tweet videos with some tips and tricks content.&lt;/p&gt;
&lt;p&gt;The next project I&amp;rsquo;m thinking about would be a link shortener. I suspect I will not do a step-by-step tutorial for that one, but instead lean on code snippets pointing out the big ideas and then more videos.&lt;/p&gt;
&lt;p&gt;The only really &amp;ldquo;bad news&amp;rdquo; is that I have a new Elixir Phoenix client project starting up so progress on these examples will be a little slower than I expected when I started pulling it together.&lt;/p&gt;
&lt;p&gt;Feedback and project ideas very welcome. &lt;a href=&#34;https://mikezornek.com/contact&#34;&gt;Let me know&lt;/a&gt; what you think.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Using Postmark&#39;s new Broadcast Email Stream via Elixir and Swoosh</title>
      <link>https://mikezornek.com/posts/2020/10/postmark-broadcast-via-elixir-swoosh/</link>
      <pubDate>Mon, 12 Oct 2020 13:51:26 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2020/10/postmark-broadcast-via-elixir-swoosh/</guid>
      <description>&lt;p&gt;In advance of adding some new broadcast email features to Guildflow I&amp;rsquo;ve built some additions to the Swoosh Elixir email library that allow one to use the new broadcast message stream feature of Postmark. Brief video tour and links below. Let me know if you have any questions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://mikezornek.com/projects/guildflow/&#34;&gt;Guildflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://postmarkapp.com/message-streams&#34;&gt;Postmark Message Streams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/swoosh/swoosh&#34;&gt;Swoosh: Compose, deliver and test your emails easily in Elixir&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/swoosh/swoosh/pull/533&#34;&gt;My Swoosh Pull Request&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/Guildflow/mcfeely&#34;&gt;My demo McFeely app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;





&lt;video
  controls
  class=&#34;mb-0 w-full&#34;
  title=&#34;Using Postmark&amp;#39;s new Broadcast Email Stream via Elixir and Swoosh&#34;
&gt;
  &lt;source src=&#34;https://f002.backblazeb2.com/file/mikezornek-com-media/using_postmarks_new_broadcast_email_stream_via_elixir_and_swoosh.mp4&#34; type=&#34;video/mp4&#34;&gt;
  Your browser does not seem to support the video format. You can
  &lt;a href=&#34;https://f002.backblazeb2.com/file/mikezornek-com-media/using_postmarks_new_broadcast_email_stream_via_elixir_and_swoosh.mp4&#34;&gt;download the MP4 file&lt;/a&gt;
  directly.
&lt;/video&gt;

&lt;div class=&#34;mt-2 flex justify-between&#34;&gt;
  &lt;a href=&#34;https://f002.backblazeb2.com/file/mikezornek-com-media/using_postmarks_new_broadcast_email_stream_via_elixir_and_swoosh.mp4&#34;&gt;Download MP4&lt;/a&gt;
  
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Video: An Introduction to Elixir</title>
      <link>https://mikezornek.com/posts/2020/4/video-an-introduction-to-elixir/</link>
      <pubDate>Tue, 14 Apr 2020 08:46:05 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2020/4/video-an-introduction-to-elixir/</guid>
      <description>&lt;p&gt;This talk was originally presented at the &lt;a href=&#34;https://www.meetup.com/PhillyElixir/&#34;&gt;Philly Elixir Group&lt;/a&gt; April meeting.&lt;/p&gt;
&lt;p&gt;Covers the basics of the language, what it can be used for and why it&amp;rsquo;s particularly helpful for certain situations. A great talk for people new to or curious about Elixir.&lt;/p&gt;





&lt;video
  controls
  class=&#34;mb-0 w-full&#34;
  title=&#34;An Introduction to Elixir&#34;
&gt;
  &lt;source src=&#34;https://f002.backblazeb2.com/file/mikezornek-com-media/introduction_to_elixir.mp4&#34; type=&#34;video/mp4&#34;&gt;
  Your browser does not seem to support the video format. You can
  &lt;a href=&#34;https://f002.backblazeb2.com/file/mikezornek-com-media/introduction_to_elixir.mp4&#34;&gt;download the MP4 file&lt;/a&gt;
  directly.
&lt;/video&gt;

&lt;div class=&#34;mt-2 flex justify-between&#34;&gt;
  &lt;a href=&#34;https://f002.backblazeb2.com/file/mikezornek-com-media/introduction_to_elixir.mp4&#34;&gt;Download MP4&lt;/a&gt;
  
    &lt;a href=&#34;https://www.youtube.com/watch?v=CIpbQoENrO4&#34;&gt;Watch on YouTube&lt;/a&gt;
  
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Slides &lt;a href=&#34;mike-zornek-an-introduction-to-elixir.pdf&#34;&gt;PDF&lt;/a&gt; / &lt;a href=&#34;https://speakerdeck.com/zorn/an-introduction-to-elixir&#34;&gt;SpeakerDeck&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://mikezornek.com/for-hire/&#34;&gt;Mike Zornek: For Hire&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://elixir-lang.org/getting-started/introduction.html&#34;&gt;Elixir Language Website Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://elixirschool.com/en/&#34;&gt;Elixir School&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.manning.com/books/elixir-in-action&#34;&gt;Elixir in Action (Book)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pragmaticstudio.com/elixir&#34;&gt;The Pragmatic Studio (Videos)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/channel/UC0l2QTnO1P2iph-86HHilMQ/videos&#34;&gt;ElixirConf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Video: Working With Time Zones in an Elixir Phoenix App</title>
      <link>https://mikezornek.com/posts/2020/3/working-with-time-zones-in-an-elixir-phoenix-app/</link>
      <pubDate>Thu, 19 Mar 2020 11:14:21 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2020/3/working-with-time-zones-in-an-elixir-phoenix-app/</guid>
      <description>&lt;p&gt;This talk was originally presented at the &lt;a href=&#34;https://www.meetup.com/PhillyElixir/&#34;&gt;Philly Elixir Group&lt;/a&gt; February meeting.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Today we are going to talk about time zones, specifically what they are, how Elixir handles them and then talk about some strategies you can apply when working with time zones in the context of an Elixir Phoenix app backed by an Ecto database.&amp;rdquo;&lt;/p&gt;
&lt;video controls&gt;
  &lt;source src=&#34;https://f002.backblazeb2.com/file/mikezornek-com-media/working_with_time_zones_in_an_elixir_phoenix_app.mp4&#34; type=&#34;video/mp4&#34;&gt;
  &lt;a href=&#34;https://f002.backblazeb2.com/file/mikezornek-com-media/working_with_time_zones_in_an_elixir_phoenix_app.mp4&#34;&gt;Download MP4.&lt;/a&gt;
&lt;/video&gt;
&lt;p&gt;Slides &lt;a href=&#34;mike-zornek-working-with-time-zones-in-an-elixir-phoenix-app-slides.pdf&#34;&gt;PDF&lt;/a&gt; / &lt;a href=&#34;https://speakerdeck.com/zorn/working-with-time-zones-in-an-elixir-phoenix-app&#34;&gt;SpeakerDeck&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here are some of the reference links mentioned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=_E988mvPIzU&#34;&gt;ElixirConf 2019 - Date, Time, and Time Zones in Elixir 1.9 - Lau Taarnskov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://elixirschool.com/en/lessons/basics/date-time/&#34;&gt;Date and Time · Elixir School&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/lau/tzdata&#34;&gt;GitHub - lau/tzdata: tzdata for Elixir&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/lau/calendar&#34;&gt;GitHub - lau/calendar: date-time and time zone handling in Elixir&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/bitwalker/timex&#34;&gt;GitHub - bitwalker/timex: A complete date/time library for Elixir projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://elixirforum.com/t/tz-time-zone-support-for-elixir-alternative-to-tzdata-that-comes-with-a-lot-of-bugfixes/29358&#34;&gt;Elixir Forum: Tz, time zone support for Elixir (alternative to Tzdata)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>First ElixirConf, and Then Azeroth</title>
      <link>https://mikezornek.com/posts/2019/8/first-elixirconf-and-then-azeroth/</link>
      <pubDate>Fri, 23 Aug 2019 15:20:41 -0400</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2019/8/first-elixirconf-and-then-azeroth/</guid>
      <description>&lt;p&gt;Happy Friday everyone. Hope you all had a good and productive week. I myself am feeling pretty good.&lt;/p&gt;
&lt;p&gt;Went heads down this week and made some project progress for both clients and personal endeavors. Today, Friday, I&amp;rsquo;m working from home. First cleaning up a bit and then probably some more client work. Would like to close a few threads before attending &lt;a href=&#34;https://elixirconf.com/2019&#34;&gt;ElixirConf&lt;/a&gt; next week.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going for four days. First two days are workshops and then two days of conference talks. My workshops will be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://elixirconf.com/2019/training-classes/6&#34;&gt;Build Performant, Real-Time UIs with Phoenix LiveView&lt;/a&gt; - LiveView is something I have experimented with but have yet to get going in my projects. I am excited to get past the simple demos into more complex use cases.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://elixirconf.com/2019/training-classes/8&#34;&gt;Build a Smart Camera with Nerves, Phoenix, and Absinthe&lt;/a&gt; - I have almost no hardware experience and this is more of an experimental thing for me. Lots to learn!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s going to be a fun week. Over the last year I&amp;rsquo;ve really started to get over the beginner hump of Elixir and am ready to sink my teeth into some of the deeper aspects (or aspects like Nerves and embedded software I&amp;rsquo;ve never touched).&lt;/p&gt;
&lt;p&gt;Sadly while I&amp;rsquo;m away at ElixirConf I&amp;rsquo;m also going to miss the opening of &lt;a href=&#34;https://worldofwarcraft.com/en-us/wowclassic&#34;&gt;WoW Classic&lt;/a&gt;. I&amp;rsquo;m really excited to see how Classic works out as I&amp;rsquo;m not a fan of current retail WoW but still enjoy the lore and atmosphere of Azeroth. It sounds like the early days are going to result in a fair amount of login queues so maybe my Elixir distractions will be for the best.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pretty sure I&amp;rsquo;m going to level a Gnome Mage as my main. Server is Herod (PvP).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Regardless to all this, I am also looking forward to September and a no more trips for a little while. As much as I like to get out I also love being home and enjoying my routines. I also need to start getting more time into my side project and start it&amp;rsquo;s marketing / validation path to see if it&amp;rsquo;s a real product or not. More to come on that.&lt;/p&gt;
&lt;p&gt;Enjoy the weekend and have some fun.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Philly Elixir Meetup is Rebooting</title>
      <link>https://mikezornek.com/posts/2019/4/philly-elixir-meetup-reboot/</link>
      <pubDate>Mon, 22 Apr 2019 10:00:00 -0500</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2019/4/philly-elixir-meetup-reboot/</guid>
      <description>&lt;p&gt;After too long of a hiatus, the Philly Elixir Meetup group is rebooting!&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.meetup.com/PhillyElixir&#34;&gt;https://www.meetup.com/PhillyElixir&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Elixir is a functional programming language that is popular for building web apps and services using a library called Phoenix as well as GraphQL APIs using Absinthe.&lt;/p&gt;
&lt;p&gt;Our first meeting back will be:&lt;/p&gt;
&lt;p&gt;Monday, May 6, 2019&lt;br&gt;
6:00 PM to 8:00 PM&lt;/p&gt;
&lt;p&gt;PromptWorks&lt;br&gt;
1211 Chestnut Street #400&lt;/p&gt;
&lt;p&gt;RSVP: &lt;a href=&#34;https://www.meetup.com/PhillyElixir&#34;&gt;https://www.meetup.com/PhillyElixir&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Even if you can&amp;rsquo;t make this first meeting, please join the group as we&amp;rsquo;ll have many more to come. If you have questions, &lt;a href=&#34;https://mikezornek.com/contact/&#34;&gt;shoot me an email&lt;/a&gt;. Thanks!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>ElixirConf 2018 Notes</title>
      <link>https://mikezornek.com/posts/2018/9/elixirconf-2018-notes/</link>
      <pubDate>Sun, 09 Sep 2018 19:54:59 +0000</pubDate>
      <author>mike@mikezornek.com (Mike Zornek)</author>
      <guid>https://mikezornek.com/posts/2018/9/elixirconf-2018-notes/</guid>
      <description>&lt;p&gt;After attending &lt;a href=&#34;https://elixirconf.com&#34;&gt;ElixirConf&lt;/a&gt; I am as confident as ever that &lt;a href=&#34;https://elixir-lang.org&#34;&gt;Elixir&lt;/a&gt; is a language, community and ecosystem I want to continue to personally invest and participate in.&lt;/p&gt;
&lt;p&gt;About two years ago I made the decision that I needed to diversify my technical skills outside the Apple ecosystem. I then went on to experiment and research lots of different languages and frameworks, including EmberJS, Go, Rust, Elm, HTML5 updates, React and Elixir.&lt;/p&gt;
&lt;p&gt;The Elixir interest started from a broad recommendation from Dave Thomas who I had years before followed heavily while doing Ruby on Rails development. Elixir also had gotten momentum from my interests in Functional Programming and looking to solve problems outside of traditional Object Oriented Programming design patterns. Over the last few months I’ve gotten deeper into Elixir and I really like what I’ve found.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’ll do a post in the future about why I’m liking Elixir so much. It’s a potentially large topic and I want to give it the space it deservers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ElixirConf was a great event. Two days of training and two days of conference sessions; I took it all in. My personal estimate would put the training day attendance at around 150 and the full conference at around 500.&lt;/p&gt;
&lt;p&gt;While educational, I found the class pacing to be mixed. I felt one went a little too slow and the other a little too fast. There was a wide gamut of Elixir experience in the audience so I think it’s challenging for the instructors to find a pace everyone can agree with. That said, I learned a ton in each of the two more introduction-based classes I attended. I was envious of the more advanced classes that were covering GraphQL and hardware development using &lt;a href=&#34;https://nerves-project.org&#34;&gt;Nerves&lt;/a&gt; — I heard people were very impressed with them. Maybe next year.&lt;/p&gt;
&lt;p&gt;For the conference days we had some great keynotes and session. I loved hearing José Valim (creator of Elixir) talk about the future of the language including the core teams failed experiments with adding a type system and why it’s not on the horizon. Chris McCord (creator of Phoenix) did a closing keynote, reviewing progress with the framework including a preview of Phoenix LiveView which was very impressive and has an opportunity to shake things up in the single-page app space. Aaron Renner had a great talk on taming complexity which mirrored some of my previous iOS code patters with way better naming. Aaron Votre’s excitement about GraphQL is contagious and I’m anxious to get my hands dirty. Andrew Bennett has some great tips in his Sustainable Testing talk. Daniel Azuma did a great job showing how we can mix and match Docker with traditional OTP deployments for unique benefits. Some time slots were competitive for my attention. I sadly missed Boyd Multerer’s Introduction of Scenic and Eric Oestrich’s Going Multi-Node session which both were well received from chat in the hallways. I’ll be sure to watch them on YouTube in the week ahead. In fact the majority of the conference keynotes and sessions are &lt;a href=&#34;https://www.youtube.com/playlist?list=PLqj39LCvnOWaxI87jVkxSdtjG8tlhl7U6&#34;&gt;already posted on YouTube&lt;/a&gt; if you want to take a look.&lt;/p&gt;
&lt;p&gt;Despite my general shyness, the community was very welcoming and friendly when I put myself out there. I had some great conversations during breaks and lunch. Hopefully these will continue on the community Slack and forums — I need to spend some more time with those.&lt;/p&gt;
&lt;p&gt;Finally, everyone is hiring. Almost every speaker who represented a company said they were hiring. While I’m not looking for full time employment its relieving to see such hiring interest in a more niche language than say my current source of income, iOS.&lt;/p&gt;
&lt;p&gt;Next year ElixirConf will be in Denver and assuming I find a way to keep Elixir active in my development schedule (I have a potential Elixir subcontract in the fall as well as some personal projects) I plan to be there.&lt;/p&gt;
&lt;p&gt;For more on Elixir check out &lt;a href=&#34;https://elixir-lang.org&#34;&gt;its homepage&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;photos&#34;&gt;Photos&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/3A4EF5D9-80A5-4152-8FDC-29AC506B4549.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/4D2123E1-E2AE-4611-8422-94357C55D53D.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/689BB9EA-A470-49E8-A359-6C037AB2A7C3.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/E2B786CE-0A56-43F9-AA6F-95F347AF57FE.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/IMG_3228.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/IMG_3230.jpg&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/IMG_3232.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/IMG_3247.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/IMG_3248.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/IMG_3249.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/IMG_3250.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/IMG_3251.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/IMG_3252.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/IMG_3253.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&#34;http://mikezornek.com/media/images/elixirconf-2018-photos/IMG_3254.JPG&#34; alt=&#34;&#34;&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>