<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>De Wet Blomerus</title>
    <description>Personal website and blog of De Wet Blomerus</description>
    <link>https://dewetblomerus.com/</link>
    <atom:link href="https://dewetblomerus.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 06 May 2026 20:46:11 +0000</pubDate>
    <lastBuildDate>Wed, 06 May 2026 20:46:11 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Deps I add to every new Phoenix app</title>
        <description>&lt;h2 id=&quot;i-learn-by-building-side-projects&quot;&gt;I learn by building side projects&lt;/h2&gt;

&lt;p&gt;I have found that I end up adding these deps to every project I build&lt;/p&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:credo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 1.7&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;only:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;runtime:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:ex_check&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 0.16.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;only:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;runtime:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:mix_audit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 2.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;only:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;runtime:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:mix_test_watch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 1.4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;only:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;runtime:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:sobelow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 0.13&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;only:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;runtime:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:tidewave&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 0.5&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;only:&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:ueberauth_auth0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 2.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Thu, 23 Oct 2025 00:00:00 +0000</pubDate>
        <link>https://dewetblomerus.com/2025/10/23/deps-to-add-to-phoenix.html</link>
        <guid isPermaLink="true">https://dewetblomerus.com/2025/10/23/deps-to-add-to-phoenix.html</guid>
        
        <category>Elixir Phoenix</category>
        
        
      </item>
    
      <item>
        <title>Connecting to a DigitalOcean Managed Postgres from your Elixir Phoenix app on Fly.io</title>
        <description>&lt;h2 id=&quot;bluf-bottom-line-up-front&quot;&gt;BLUF (Bottom Line Up Front)&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;There are no sponsored links on this page, I’m just trying to be helpful.&lt;/li&gt;
  &lt;li&gt;I’m running a DigitalOcean fully manged &amp;amp; backed up Postgres instance with 1GB of ram and 10GB of storage, which makes it powerful enough to run databases for all of my current side projects.&lt;/li&gt;
  &lt;li&gt;My Fly.io apps are connecting to this Postgres, and the latency from the network hop originating in Ashburn Virginia to New York is completely imperceivable.&lt;/li&gt;
  &lt;li&gt;At $15 per month, this is less than half the price of &lt;a href=&quot;https://fly.io/docs/mpg/&quot;&gt;Fly Managed Postgres&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;history&quot;&gt;History&lt;/h2&gt;

&lt;p&gt;I received the email months ago that Fly.io will no longer be supporting the automated-but-not-managed Postgres apps that were usually the default. I have one of these old DBs for &lt;a href=&quot;https://spellsightwords.com/&quot;&gt;spellsightwords.com&lt;/a&gt; which means that my daughter and a bunch of other kids have years of their tutoring progress stored in this DB. It would break their little hearts if it was every lost, so I should probably store it in a DB with properly automated backups.&lt;/p&gt;

&lt;h2 id=&quot;why-i-went-looking&quot;&gt;Why I went looking&lt;/h2&gt;

&lt;p&gt;When the new &lt;a href=&quot;https://fly.io/docs/mpg/&quot;&gt;Fly Managed Postgres&lt;/a&gt; finally launched at a $38, I evaluated for about 5 seconds before deciding to go looking for something cheaper. I considered &lt;a href=&quot;https://koyeb.com/&quot;&gt;Koyeb&lt;/a&gt; and &lt;a href=&quot;http://neon.com/&quot;&gt;Neon&lt;/a&gt;, but then I would be imposing an annoying startup latency on my users, and if I want &lt;a href=&quot;http://neon.com/&quot;&gt;Neon&lt;/a&gt; or &lt;a href=&quot;https://koyeb.com/&quot;&gt;Koyeb&lt;/a&gt; to always be on, it would cost about the same as Fly’s offering.&lt;/p&gt;

&lt;p&gt;Eventually, I settled on &lt;a href=&quot;https://www.digitalocean.com/products/managed-databases-postgresql&quot;&gt;DigitalOcean Managed Postgres&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;cost-comparison&quot;&gt;Cost Comparison&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Memory (RAM)&lt;/th&gt;
      &lt;th style=&quot;background-color: #e1bee7; color: #333;&quot;&gt;Fly.io Plan&lt;/th&gt;
      &lt;th style=&quot;background-color: #e1bee7; color: #333;&quot;&gt;Fly.io CPU&lt;/th&gt;
      &lt;th style=&quot;background-color: #e1bee7; color: #333;&quot;&gt;Fly.io Monthly&lt;/th&gt;
      &lt;th style=&quot;background-color: #bbdefb; color: #333;&quot;&gt;DigitalOcean vCPU&lt;/th&gt;
      &lt;th style=&quot;background-color: #bbdefb; color: #333;&quot;&gt;DO Storage Min&lt;/th&gt;
      &lt;th style=&quot;background-color: #bbdefb; color: #333;&quot;&gt;DO Conn Limit&lt;/th&gt;
      &lt;th style=&quot;background-color: #bbdefb; color: #333;&quot;&gt;DO Monthly&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1 GB&lt;/td&gt;
      &lt;td style=&quot;background-color: #e1bee7; color: #333;&quot;&gt;Basic&lt;/td&gt;
      &lt;td style=&quot;background-color: #e1bee7; color: #333;&quot;&gt;Shared-2x&lt;/td&gt;
      &lt;td style=&quot;background-color: #e1bee7; color: #333; text-align: right;&quot;&gt;&lt;strong&gt;$38.00&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;background-color: #bbdefb; color: #333;&quot;&gt;1 vCPU&lt;/td&gt;
      &lt;td style=&quot;background-color: #bbdefb; color: #333;&quot;&gt;15 GiB&lt;/td&gt;
      &lt;td style=&quot;background-color: #bbdefb; color: #333;&quot;&gt;22&lt;/td&gt;
      &lt;td style=&quot;background-color: #bbdefb; color: #333; text-align: right;&quot;&gt;&lt;strong&gt;$12.00&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;Fly.io is providing a 2-node High-Availability cluster with automatic fallback. So the actual value you will be receiving is 100% comparable, and there is a reason for the $38 price tag.&lt;/li&gt;
  &lt;li&gt;The above is only compute, storage is billed per-GB on both providers, but DigitalOcean makes you buy a minimum of 10GB on the smallest plan, which works out to about $2.15 which gets added to the $12 and how I ended at ~$15.&lt;/li&gt;
  &lt;li&gt;I’m assuming data integrity &amp;amp; safety with the backup solution is comparable.&lt;/li&gt;
  &lt;li&gt;I’m assuming the 2-node HA cluster from Fly would have higher uptime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;how-to-actually-connect-to-the-digitalocean-database-from-your-flyio-elixir-app&quot;&gt;How to actually connect to the DigitalOcean database from your Fly.io Elixir app.&lt;/h2&gt;

&lt;p&gt;If you ran &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fly launch&lt;/code&gt; on your Phoenix app (I will never get over how easy that is), you will basically have &lt;a href=&quot;https://fly.io/docs/elixir/the-basics/clustering/&quot;&gt;this clustering setup, which is 100% up to date as Oct 21, 2025&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You will also have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATABASE_URL&lt;/code&gt; secret environment variable that your app reads at boot to connect to the DB via connection string.&lt;/p&gt;

&lt;p&gt;Change the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Change this one line in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rel/env.sh.eex&lt;/code&gt; from:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ECTO_IPV6&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ECTO_IPV6&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;false&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;Get the DB connection string from DigitalOcean, and set it on your Fly.io app. Makes sure to put quotes around the connection string before running the command with:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fly secrets &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DATABASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;your-connection-string-in-quotes&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime.exs&lt;/code&gt; you will have Repo config for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:prod&lt;/code&gt;. It will look something like this:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:mobile_worship&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MobileWorship&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Repo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;c1&quot;&gt;# ssl: true,&lt;/span&gt;
     &lt;span class=&quot;ss&quot;&gt;url:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;database_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;ss&quot;&gt;pool_size:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;POOL_SIZE&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;10&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
     &lt;span class=&quot;c1&quot;&gt;# For machines with several cores, consider starting multiple pools of `pool_size`&lt;/span&gt;
     &lt;span class=&quot;c1&quot;&gt;# pool_count: 4,&lt;/span&gt;
     &lt;span class=&quot;ss&quot;&gt;socket_options:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maybe_ipv6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You need to add a line of config to make an SSL connection to DigitalOcean:&lt;/p&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;ss&quot;&gt;ssl:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;verify:&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:verify_none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it. You are done. You will need to re-deploy your app, but it should start working immediately after the first boot with these settings changed. Try &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fly logs&lt;/code&gt; to keep an eye on what is happening.&lt;/p&gt;

&lt;h2 id=&quot;password-only-security&quot;&gt;Password Only Security&lt;/h2&gt;

&lt;p&gt;This is just for my side projects, and I am storing zero personal or sensitive data. So I have made the decision for now to live with the fact that if someone had my connection string, they could steal the data, or even write bad data to the DB. In a scenario where your business is making any money, you should set up at least IP allow-listing or some other firewall layer so that a bad actor would need more than just your DB connection string.&lt;/p&gt;

&lt;p&gt;One layer of security that I have in place to limit the possible impact is that I use Auth0 for all my apps, so if my database was compromised and my users used the same password on my app as they did on Gmail… the bad guys would not have my users’ Gmail passwords. Those would still be safe with Auth0.&lt;/p&gt;

&lt;p&gt;If one of my side projects grew to the point where it mattered, I could always move the database or move the app so that they are co-located in the same cloud and then have the DB set up to only allow connections from the app that is supposed to access it.&lt;/p&gt;
</description>
        <pubDate>Tue, 21 Oct 2025 00:00:00 +0000</pubDate>
        <link>https://dewetblomerus.com/2025/10/21/digital-ocean-postgres-from-fly.html</link>
        <guid isPermaLink="true">https://dewetblomerus.com/2025/10/21/digital-ocean-postgres-from-fly.html</guid>
        
        <category>Cloud Elixir Phoenix Postgres Fly DigitalOcean</category>
        
        
      </item>
    
      <item>
        <title>Building with Ash, Before &amp; After AI</title>
        <description>&lt;h2 id=&quot;bluf-bottom-line-up-front&quot;&gt;BLUF (Bottom Line Up Front)&lt;/h2&gt;

&lt;p&gt;AI assisted coding with the Ash framework is surprisingly good.&lt;/p&gt;

&lt;h2 id=&quot;history&quot;&gt;History&lt;/h2&gt;

&lt;p&gt;I built a side project with Ash two years ago &lt;a href=&quot;https://dewetblomerus.com/2023/11/26/first-thoughts-on-ash.html&quot;&gt;and wrote about it here&lt;/a&gt;. I might have had an early version of GitHub CoPilot at the time, but how quickly I write code with the assistance of AI has increased massively. I haven’t really done much with Ash until I reached for it again on a side-project a couple of months ago.&lt;/p&gt;

&lt;h2 id=&quot;my-assumptionhypothesis&quot;&gt;My assumption/hypothesis&lt;/h2&gt;

&lt;p&gt;I’ve heard that LLMs are best at writing code in &lt;a href=&quot;https://www.tiobe.com/tiobe-index/&quot;&gt;popular languages&lt;/a&gt;. And with Ash not being very popular at all &lt;em&gt;yet&lt;/em&gt;, I figured LLMs might be quite terrible at it. But I decided to give it a try, and if it didn’t work well, I would just drop down to Elixir.&lt;/p&gt;

&lt;h2 id=&quot;new-findings--opinion&quot;&gt;New Findings &amp;amp; Opinion&lt;/h2&gt;

&lt;p&gt;One of my biggest struggles with Ash two years ago was the code interface, whether I used an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ash.Query&lt;/code&gt; or exposed a proper code interface, and the error messages when I got it wrong were not as clear as vanilla Elixir code. Surprisingly, the LLM got through this quite quickly. It would either get it right the first time, or when it made a mistake, Cursor could run the code with Tidewave, understand the error more quickly than I could, and fix the code.&lt;/p&gt;

&lt;p&gt;One of the strongest points in favor of Ash is that the modeling of the business domain &amp;amp; logic is extremely dense and clear (very few short lines of code). So that if someone new to the project wrote some code that might contain mistakes, one person that understands the business domain can very quickly read &amp;amp; review what was written and if it lines up with business requirements. This benefit lines up PERFECTLY with an LLM writing code.&lt;/p&gt;

&lt;h2 id=&quot;my-new-workflow-with-llm-assisted-ash-coding&quot;&gt;My new workflow with LLM assisted Ash coding&lt;/h2&gt;

&lt;p&gt;I would start with the Ash resources (as you should). And I would very carefully read &amp;amp; review every line that was written by the LLM and sometimes just hand-write small changes. Migrations are generated by &lt;a href=&quot;https://hex.pm/packages/ash_postgres&quot;&gt;ash_postgres&lt;/a&gt;, those are even more carefully reviewed. Then define the code interface by which the UI would access the resources. Add a few tests if I have any concern that an authorization bug could one day cause a security issue. Ash policies with passing the user as an actor is usually the right way to do it. Then I would just vibe-code the UI. I might have strong opinions about how the routes should be constructed, and I’ll carefully review every function call that accesses my resources, but the actual UI code is LLM generated and just lightly reviewed.&lt;/p&gt;

&lt;p&gt;This has gone very quickly, because the pieces of code that I need to carefully review, is a lot less code than vanilla Elixir (or any other programming language I’ve ever read). So the speed of the LLM being able to generate lots of code and sometimes make a mistake, becomes less of a liability when there is less code for me as a human to review. The danger of me getting bored and just committing &amp;amp; merging code that I don’t fully understand is reduced.&lt;/p&gt;

&lt;h2 id=&quot;context-switching--ash-policies&quot;&gt;Context Switching &amp;amp; Ash Policies&lt;/h2&gt;

&lt;p&gt;When you decide to review the authorization flow, trying to make sure that each user can only see and do what they are supposed to, &lt;a href=&quot;https://hexdocs.pm/ash/policies.html&quot;&gt;Ash Policies&lt;/a&gt; is implemented with a minimal amount of code that is all organized into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;policies&lt;/code&gt; blocks. This allows your human reviewer brain to peruse all the authorization code while staying in the zone of thinking through authorization.&lt;/p&gt;

&lt;h2 id=&quot;downsides&quot;&gt;Downsides&lt;/h2&gt;

&lt;p&gt;If you are considering using Ash, you need to weigh the downsides/costs to make an informed decision. Learning curve. I went back and read the &lt;a href=&quot;https://dewetblomerus.com/2023/11/26/first-thoughts-on-ash.html&quot;&gt;Cons I listed here&lt;/a&gt;. I think they all still hold true, but maybe with less weight. The documentation has improved a lot in the last two years. A few times, I looked up a module on HexDocs and only found a description &amp;amp; typespec, but no examples. I think error messages &amp;amp; learning curve might not have changed, but an LLM definitely reduces the time &amp;amp; pain. Using Claude in agent mode with Tidewave, it would very quickly see the errors and understand them and fix the code.&lt;/p&gt;

&lt;h2 id=&quot;what-did-i-build&quot;&gt;What did I build?&lt;/h2&gt;

&lt;p&gt;I built &lt;a href=&quot;https://eventblast.dev/&quot;&gt;Server-Sent Events as a Service called EventBlast&lt;/a&gt;. Ash made it really easy to model that each user can create and belong to multiple organizations, an Organization can have one Plan with a monthly price &amp;amp; various rate limits, and each Org can have many API keys. And all the authentication &amp;amp; authorization required to ensure who can see and do what. This is all boilerplate stuff, and not particularly interesting to work on. I didn’t want to spend a bunch of time writing/reviewing all the code by hand. Ash allowed me to very quickly get it done, with minimal code to maintain going forward.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h2&gt;

&lt;p&gt;Would an LLM be better at writing Python? Maybe. But that isn’t the bottleneck with delivering software in 2025. A human needing to review all of the code that the LLM generates and ensuring that every bit of it lines up with the expectation, and that human feeling confident to just hit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git reset --hard HEAD&lt;/code&gt; and try again with a new prompt is what ultimately builds good software on clean code when an LLM is in the mix.&lt;/p&gt;
</description>
        <pubDate>Mon, 22 Sep 2025 00:00:00 +0000</pubDate>
        <link>https://dewetblomerus.com/2025/09/22/ash-after-ai.html</link>
        <guid isPermaLink="true">https://dewetblomerus.com/2025/09/22/ash-after-ai.html</guid>
        
        <category>Elixir</category>
        
        
      </item>
    
      <item>
        <title>Cursor Background Agents and Elixir</title>
        <description>&lt;h1 id=&quot;bluf-bottom-line-up-front&quot;&gt;BLUF Bottom Line Up Front&lt;/h1&gt;

&lt;p&gt;This blog post assume that you know what Cursor Background agents are. As of this writing, Cursor background agents are useful, but not ready to replace regular agent mode and certainly not ready to replace an experienced programmer (who is probably using Cursor in Agent mode, or something similar at this point).&lt;/p&gt;

&lt;h1 id=&quot;my-prediction-of-the-future&quot;&gt;My prediction of the future&lt;/h1&gt;

&lt;p&gt;It might be years away, but eventually, the cost in $ and time of AI generated code will be a lot lower than it is in 2025. Either the models will stop getting better, and someone will build specialized hardware on which to run them many times more efficiently (like what happened with specialized hardware for Bitcoin mining). Or the models will keep getting better, and will generate higher quality code per $ of electricity invested. Both of these options eventually gets us to the same destination, background agents that work independently, and humans verifying the work. How long we reside at that destination before AI reviews the code as well… There’s only one right answer at this point: “I don’t know.”&lt;/p&gt;

&lt;h1 id=&quot;shortcomings-of-cursor-background-agents&quot;&gt;Shortcomings of Cursor Background Agents&lt;/h1&gt;

&lt;p&gt;Setup feels really immature. You can either manually set up an Ubuntu box that they spin up, and then you have to click to take a snapshot, from which it will start all future runs. (This made me feel like I’m doing cutting edge server setup in 2005).
The other option is using Docker, but it kept building my entire docker image from the beginning of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; every time I start an agent.&lt;/p&gt;

&lt;h1 id=&quot;database&quot;&gt;Database&lt;/h1&gt;

&lt;p&gt;I don’t like to set stuff up, so I signed up for a free-tier Postgres database on &lt;a href=&quot;https://www.koyeb.com&quot;&gt;Koyeb&lt;/a&gt;. The Koyeb free DB scales to 0 when not receiving any requests for 5 minutes, so it needs some warm-up time, I had to set the timeout really long.
I also really don’t like to wait for computers when I’m working on them, so I wanted my local development to still use my local DB and run dev &amp;amp; test as fast as possible. After some trial &amp;amp; error, here is what I came up with in my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime.exs&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config_env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:dev&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config_env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;database_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config_env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;my_app_test&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;MIX_TEST_PARTITION&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;my_app_dev&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;database_config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fetch_env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;DATABASE_URL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;database_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inspect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;DATABASE_URL is set 🤫&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;120_000&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;connect_timeout:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;handshake_timeout:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;pool_size:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;show_sensitive_data_on_connection_error:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;ssl:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;verify:&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:verify_none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;timeout:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;url:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;database_url&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

      &lt;span class=&quot;ss&quot;&gt;:error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;pool_size:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;schedulers_online&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;show_sensitive_data_on_connection_error:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;url:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;postgres://postgres:postgres@localhost:5432/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;database_name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:my_app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MyApp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Repo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;database_config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;stacktrace:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

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

&lt;h1 id=&quot;dockerfile&quot;&gt;Dockerfile&lt;/h1&gt;

&lt;p&gt;Since Cursor re-builds the docker image from the Dockerfile on each start of a background agent, I made an &lt;a href=&quot;https://github.com/dewetblomerus/cursor-background-agent-base-image-elixir&quot;&gt;Elixir development image that you can find here with instructions on how to use it in the README&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 11 Sep 2025 00:00:00 +0000</pubDate>
        <link>https://dewetblomerus.com/2025/09/11/cursor-background-agents-elixir.html</link>
        <guid isPermaLink="true">https://dewetblomerus.com/2025/09/11/cursor-background-agents-elixir.html</guid>
        
        <category>Elixir</category>
        
        
      </item>
    
      <item>
        <title>Elixir on Digital Ocean App Platform</title>
        <description>&lt;h1 id=&quot;bluf-bottom-line-up-front&quot;&gt;BLUF Bottom Line Up Front&lt;/h1&gt;

&lt;p&gt;DO NOT do this unless you absolutely have to. Fly.io is much simpler and more fully featured.&lt;/p&gt;

&lt;h1 id=&quot;what-i-was-up-to&quot;&gt;What I was up to&lt;/h1&gt;

&lt;p&gt;I recently had an idea to build Server-Sent-Events-As-A-Service, which would enable uni-directional real-time public content delivery for a fraction of the cost of WebSocket-As-A-Service providers.&lt;/p&gt;

&lt;h1 id=&quot;reasons-to-consider-digital-ocean-app-platform-vs-flyio&quot;&gt;Reasons to consider Digital Ocean App Platform vs Fly.io?&lt;/h1&gt;

&lt;p&gt;Most business ideas end up making no money, but because I like to dream, I dreamt that if this one makes it, my main cost would be my cloud bill, especially my egress networking bill. Even before factoring in the included free bandwidth DigitalOcean has much lower egress bandwidth costs.&lt;/p&gt;

&lt;h1 id=&quot;erlang-distribution-woes&quot;&gt;Erlang Distribution Woes&lt;/h1&gt;

&lt;p&gt;The nodes can not connect to each other. Each individual service has an internal public address, so your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web&lt;/code&gt; service can be accessed internally by your other services in App Platform on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://web&lt;/code&gt; with &lt;a href=&quot;https://docs.digitalocean.com/products/app-platform/how-to/manage-internal-routing/&quot;&gt;an internal port that you specify&lt;/a&gt;. What they can not do is connect to other nodes within the same service.&lt;/p&gt;

&lt;h1 id=&quot;elixir-is-not-a-first-class-citizen&quot;&gt;Elixir is not a first-class citizen&lt;/h1&gt;

&lt;p&gt;They have two ways of deploying code onto App platform. Looking at a Git repo and building it when new code is pushed to a branch (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; would make sense for Prod). Looking at a container registry and deploying it when a new image is pushed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;latest&lt;/code&gt; would make sense for Prod. I actually like the image push solution, so I don’t mind that they can’t build the Elixir app from source. The concerning fact is that there are guides for several other popular languages, but no guides on how to deploy an Elixir apps and all the googling online described how to deploy on DigitalOcean droplets is what ultimately leaves me recommending that you stick with Fly.io&lt;/p&gt;
</description>
        <pubDate>Sat, 06 Sep 2025 00:00:00 +0000</pubDate>
        <link>https://dewetblomerus.com/2025/09/06/elixir-on-do-app-platform.html</link>
        <guid isPermaLink="true">https://dewetblomerus.com/2025/09/06/elixir-on-do-app-platform.html</guid>
        
        <category>Elixir</category>
        
        
      </item>
    
      <item>
        <title>My First Impressions of Ash</title>
        <description>&lt;p&gt;I recently built &lt;a href=&quot;https://spellsightwords.com/&quot; target=&quot;\_blank&quot;&gt;SpellSightWords.com&lt;/a&gt;
to the point where my only user (my daughter) is using it every day and finding it useful. &lt;a href=&quot;https://github.com/dewetblomerus/red&quot;&gt;Here is the code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was my first experience with the &lt;a href=&quot;https://ash-hq.org/&quot; target=&quot;\_blank&quot;&gt;Ash Framework&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ash was enjoyable to use, I would definitely want to build with it again.&lt;/p&gt;

&lt;h3 id=&quot;pros&quot;&gt;Pros&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;The community is incredible. I have never seen a community so ready to help
when I get stuck&lt;/li&gt;
  &lt;li&gt;You get all the proven cost savings of LiveView, without the
cost of building a JSON or GraphQL API from scratch if you later need an API
for a mobile app or integration.&lt;/li&gt;
  &lt;li&gt;Understandability is really cheap. There is less code to read, and it is
very declarative, so it is easy to understand what the code is doing.&lt;/li&gt;
  &lt;li&gt;Less code to change when requirements change.&lt;/li&gt;
  &lt;li&gt;Even with the learning curve, I believe that I will spend less time
maintaining/owning the code than if I built it without Ash. So if the app
ends up living for years, I will have eventually saved time, even on the first
thing I built.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;cons&quot;&gt;Cons&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;The documentation is off to a great start, but still needs to mature. I have
been able to get by with the help of
the community, but I would like to see more documentation. Especially, more
code examples, which is helpful for someone using it for the first time.&lt;/li&gt;
  &lt;li&gt;Error messages are not as clear as I would like. Often times, the error message
made it clear to me which of my code caused the error, but I missed the normal
Elixir experience where I get a stack trace that points me to the exact line
of code that caused the error. If the error happened inside macro code, I would
like the Phoenix experience where my config caused an error deep inside some
library code, but the error includes suggestions on how to fix it.&lt;/li&gt;
  &lt;li&gt;There is a learning curve. This is true for any framework, but because Ash
does so many things, there are many things to learn. I could have built my app
in less time without ash. I believe that building my second app with Ash
could be faster than building without Ash, but that is just an educated hunch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;biggest-surprise&quot;&gt;Biggest Surprise&lt;/h3&gt;

&lt;p&gt;When I made a significant change to my data model, the changes to my UI code
were extremely minimal. I was able to make the change in what felt like minutes, and
then I was able to move on to the next feature. If I had written the code by hand,
I would probably have had a lot more code to move around when changing the data model.&lt;/p&gt;

&lt;h3 id=&quot;who-would-i-recommend-it-to&quot;&gt;Who would I recommend it to?&lt;/h3&gt;

&lt;p&gt;If you already know Elixir, Phoenix and LiveView, and you like using the latest
and greatest tools, then I highly recommend giving Ash a try. If you are still learning Elixir,
I would recommend you learn the basics first, then come back to Ash. Using it in
a business would carry some risk. The only real risk I see is if something happened
to Zach Daniel and Josh Price and it stopped being maintained. Taking that risk
into account, the benefits of shipping value in less time
and then having less code to maintain and all the cost savings of how much code
needs to be read to understand the system, I would still recommend it.&lt;/p&gt;
</description>
        <pubDate>Sun, 26 Nov 2023 00:00:00 +0000</pubDate>
        <link>https://dewetblomerus.com/2023/11/26/first-thoughts-on-ash.html</link>
        <guid isPermaLink="true">https://dewetblomerus.com/2023/11/26/first-thoughts-on-ash.html</guid>
        
        <category>Book</category>
        
        
      </item>
    
      <item>
        <title>The Phoenix Project Book</title>
        <description>&lt;p&gt;I recently finished listening to &lt;a href=&quot;https://www.audible.com/mk/t/title-3?asin=B00VAZZY32&quot; target=&quot;\_blank&quot;&gt;The Phoenix Project&lt;/a&gt;.
no relation to &lt;a href=&quot;https://www.phoenixframework.org/&quot; target=&quot;\_blank&quot;&gt;The Phoenix I Would Normally Write About&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Shoutout to &lt;a href=&quot;https://www.linkedin.com/in/adiiyengar/&quot; target=&quot;\_blank&quot;&gt;Adi Iengar&lt;/a&gt; for recommending it.&lt;/p&gt;

&lt;h3 id=&quot;key-takeaways&quot;&gt;Key takeaways&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Delivering software (or any digital) value, has a lot in common with an assembly line. That is why Agile processes and tools have names like “Lean” or “Kanban”, which are manufacturing terms.&lt;/li&gt;
  &lt;li&gt;The amount of Work In Progress is the single greatest indicator of poor quality and missed deadlines. Reducing batch sizes and cycle times is the answer.&lt;/li&gt;
  &lt;li&gt;Any improvement made, not at the constraint, is just an illusion. I have thought about this a lot while having some deployment issues at work, and when I see any of our Kanban board columns stacked with several cards.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;biggest-surprise&quot;&gt;Biggest Surprise&lt;/h3&gt;

&lt;p&gt;I enjoyed the story. The book is written as a Fable. When I first realized that, I assumed I would be annoyed at the extra time wasted through building the story around what I want to learn, to the contrary, I enjoyed the story and my brain created a context to store the lessons in. I keep seeing many connections from the book to how Salesloft operates (Salesloft is doing pretty great, but the book has principles we could still learn from).&lt;/p&gt;

&lt;h3 id=&quot;who-would-i-recommend-it-to&quot;&gt;Who would I recommend it to?&lt;/h3&gt;

&lt;p&gt;If you work in software, and your technical abilities are no longer the limiting factor (bottleneck or constraint in factory terms) to how much value you can create, then I highly recommend this book.&lt;/p&gt;
</description>
        <pubDate>Fri, 05 May 2023 00:00:00 +0000</pubDate>
        <link>https://dewetblomerus.com/2023/05/05/phoenix-project-book.html</link>
        <guid isPermaLink="true">https://dewetblomerus.com/2023/05/05/phoenix-project-book.html</guid>
        
        <category>Book</category>
        
        
      </item>
    
      <item>
        <title>LiveView Timed Flash</title>
        <description>&lt;p&gt;If you need to put a flash message in LiveView but want it to disappear after a few seconds, you might be surprised that LiveView does not have this built-in.&lt;/p&gt;

&lt;p&gt;Here is how to do it:&lt;/p&gt;

&lt;p&gt;All these changes go into your LiveView file.&lt;/p&gt;

&lt;p&gt;In your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mount&lt;/code&gt; function, assign an empty &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flash_timer&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;flash_timer:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Define a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:clear_flash&lt;/code&gt; handle info so we can send ourselves a message to clear the flash.&lt;/p&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;@impl&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:clear_flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:noreply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clear_flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Define a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;put_timed_flash&lt;/code&gt; function.&lt;/p&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;put_timed_flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;\\&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;assigns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flash_timer&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Process&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cancel_timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;assigns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flash_timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Process&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send_after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:clear_flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;put_flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;flash_timer:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’re done!&lt;/p&gt;

&lt;p&gt;Now you can use it with the same interface as &lt;a href=&quot;https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#put_flash/3&quot; target=&quot;\_blank&quot; rel=&quot;noopener&quot;&gt;put_flash/3&lt;/a&gt; but it also accepts an optional 4th timeout argument.&lt;/p&gt;

&lt;p&gt;Example usage:&lt;/p&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;put_timed_flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;:info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;This message will disappear after the default of 5 seconds ⏳.&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can &lt;a href=&quot;https://quickaverage.com/&quot; target=&quot;\_blank&quot; rel=&quot;noopener&quot;&gt;see it in action on quickaverage&lt;/a&gt; by clicking to copy the URL at the bottom or by clicking the “Clear Numbers” button.&lt;/p&gt;
</description>
        <pubDate>Sun, 19 Mar 2023 00:00:00 +0000</pubDate>
        <link>https://dewetblomerus.com/2023/03/19/timed-flash-liveview.html</link>
        <guid isPermaLink="true">https://dewetblomerus.com/2023/03/19/timed-flash-liveview.html</guid>
        
        <category>elixir</category>
        
        
      </item>
    
      <item>
        <title>Learning Agile Book</title>
        <description>&lt;p&gt;I have worked inside healthy software delivery processes for a long time and learned a lot from my surroundings and books like &lt;a href=&quot;https://www.amazon.com/Pragmatic-Programmer-Anniversary-Journey-Mastery/dp/B0833FBNHV&quot; target=&quot;_blank&quot;&gt;The Pragmatic Programmer&lt;/a&gt; and &lt;a href=&quot;https://www.amazon.com/Clean-Coder-Conduct-Professional-Programmers/dp/B08X7MNTCX&quot; target=&quot;_blank&quot;&gt;The Clean Coder&lt;/a&gt;. The shortcoming of these kinds of books is that they are aimed at programmers. They told me what I needed to know to follow the process like an expert programmer and why the processes and methodologies existed, but they did not prepare me for leadership in improving my team’s processes. I wanted to know some of the same things the experts know so that I could have an intelligent conversation with an agile expert and even stand in as the next-best-expert when no expert was around.&lt;/p&gt;

&lt;p&gt;I recently finished listening to &lt;a href=&quot;https://www.amazon.com/Learning-Agile-Understanding-Scrum-Kanban/dp/B094NYWWFN&quot; target=&quot;_blank&quot;&gt;Learning Agile&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;key-takeaways&quot;&gt;Key takeaways&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;There is no best way to build software. Each organization and team will define success differently and find that success by different means. Still, understanding how others have found success and which are generally good and bad practices will help drive success.&lt;/li&gt;
  &lt;li&gt;Agile is a mindset laid out in the &lt;a href=&quot;https://agilemanifesto.org/&quot; target=&quot;_blank&quot;&gt;Agile Manifesto&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Scrum is a software development methodology that has specific practices to follow. My favorite is to deliver working software every two weeks.&lt;/li&gt;
  &lt;li&gt;Kanban is a system for continual process improvement. It does not prescribe specific practices but lets you evaluate and improve the process you already have.&lt;/li&gt;
  &lt;li&gt;XP (Extreme Programming) is a set of specific practices for programmers.&lt;/li&gt;
  &lt;li&gt;Make decisions at the last responsible moment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;biggest-surprise&quot;&gt;Biggest Surprise&lt;/h3&gt;
&lt;p&gt;XP suggests pair programming all the time. Needing to align schedules to pair on an easy-to-implement programming task seems extreme (pun intended).&lt;/p&gt;

&lt;h3 id=&quot;who-would-i-recommend-it-to&quot;&gt;Who would I recommend it to?&lt;/h3&gt;
&lt;p&gt;Anyone working in software.&lt;/p&gt;
</description>
        <pubDate>Sat, 25 Feb 2023 00:00:00 +0000</pubDate>
        <link>https://dewetblomerus.com/2023/02/25/learning-agile-book.html</link>
        <guid isPermaLink="true">https://dewetblomerus.com/2023/02/25/learning-agile-book.html</guid>
        
        <category>Book</category>
        
        
      </item>
    
      <item>
        <title>Essential Postgres Book</title>
        <description>&lt;p&gt;I recently finished reading &lt;a href=&quot;https://www.amazon.com/gp/product/B08KH136G4&quot; target=&quot;_blank&quot;&gt;Essential Postgres by Rick Silva&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I aimed to strengthen in an area where I have yet to spend time focused on learning. I was pleasantly surprised to learn that I already knew most of the basics covered in the book.&lt;/p&gt;

&lt;h3 id=&quot;key-takeaways&quot;&gt;Key takeaways&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;ActiveRecord and Ecto are so good at abstracting SQL that there are a lot of SQL examples I get to look at and think, “That’s nice, but I won’t be writing that.”&lt;/li&gt;
  &lt;li&gt;Looking at complex joins with multiple tables in simple examples made them more approachable than trying to understand it in the middle of shipping a complex feature.&lt;/li&gt;
  &lt;li&gt;From Postgres’s perspective, a Primary Key is just a combination of “Not NULL”, Unique, and indexed. A sequence to auto-increment the primary key for each new row is a completely different Postgres thing. ActiveRecord and Ecto handle creating it for you on a primary key column.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;biggest-surprise&quot;&gt;Biggest Surprise&lt;/h3&gt;
&lt;p&gt;I have never considered myself &lt;em&gt;strong&lt;/em&gt; at Postgres, but I needed a more advanced book. This book provided lots of examples on how to get data in and out of Postgres using SQL and a bit of PL/pgSQL, but when I find myself needing more Postgres knowledge, it is always around indexes and how to make sure the query optimizer uses them. None of that was covered here.&lt;/p&gt;

&lt;h3 id=&quot;who-would-i-recommend-it-to&quot;&gt;Who would I recommend it to?&lt;/h3&gt;
&lt;p&gt;If you are new to using any kind of database and new to SQL, I would 100% recommend this book.&lt;/p&gt;
</description>
        <pubDate>Thu, 23 Feb 2023 00:00:00 +0000</pubDate>
        <link>https://dewetblomerus.com/2023/02/23/essential-postgres-book.html</link>
        <guid isPermaLink="true">https://dewetblomerus.com/2023/02/23/essential-postgres-book.html</guid>
        
        <category>Book</category>
        
        
      </item>
    
  </channel>
</rss>
