<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Paweł Świątkowski</title>
    <description>The latest articles on DEV Community by Paweł Świątkowski (@katafrakt).</description>
    <link>https://dev.to/katafrakt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F64988%2Faa899e15-5b05-4754-87d3-8fe54291a3bc.jpeg</url>
      <title>DEV Community: Paweł Świątkowski</title>
      <link>https://dev.to/katafrakt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/katafrakt"/>
    <language>en</language>
    <item>
      <title>Portable mruby binaries with Cosmopolitan</title>
      <dc:creator>Paweł Świątkowski</dc:creator>
      <pubDate>Wed, 07 Jan 2026 11:59:23 +0000</pubDate>
      <link>https://dev.to/katafrakt/portable-mruby-binaries-with-cosmopolitan-3md6</link>
      <guid>https://dev.to/katafrakt/portable-mruby-binaries-with-cosmopolitan-3md6</guid>
      <description>&lt;p&gt;One of my main interests with mruby is its ability to create standalone executables. As explored in &lt;a href="https://katafrakt.me/2024/10/05/mruby-beyond-hello-world/" rel="noopener noreferrer"&gt;this post&lt;/a&gt;, this is absolutely possible and works well. However, there is a classic issue of most standalone binaries: they can only be run on the same, or very similar, system as the one they were built on.&lt;/p&gt;

&lt;p&gt;For example, if I take an executable from my &lt;a href="https://github.com/katafrakt/mruby-hello-world" rel="noopener noreferrer"&gt;mruby Hello World project&lt;/a&gt;, built on amd64 Linux, it won't work on Silicon Mac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./hello
exec: Failed to execute process: './hello' the file could not be run by the operating system.
exec: Maybe the interpreter directive (#! line) is broken?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mruby &lt;strong&gt;does&lt;/strong&gt; have some &lt;a href="https://github.com/mruby/mruby/blob/master/doc/guides/compile.md#cross-compilation-1" rel="noopener noreferrer"&gt;cross-compilation capabilities&lt;/a&gt;, but I'll be honest: it always felt intimidating, complected and I never actually got it working from Linux to MacOS. So, are we doomed here? Is it possible to build standalone binaries for MacOS without a MacOS docker image or something?&lt;/p&gt;

&lt;p&gt;While reading &lt;a href="https://github.com/mruby/mruby/blob/master/NEWS.md" rel="noopener noreferrer"&gt;the changelog&lt;/a&gt; of mruby I noticed one entry there that got me curious:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;New Platform: Cosmopolitan Libc&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I never heard about Cosmopolitan, but a quick search revealed that it is what might address the issue mentioned above. From &lt;a href="https://justine.lol/cosmopolitan/index.html" rel="noopener noreferrer"&gt;the project's website&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cosmopolitan Libc makes C a build-anywhere run-anywhere language, like Java, except it doesn't need an interpreter or virtual machine. Instead, it reconfigures stock GCC and Clang to output a POSIX-approved polyglot format that runs natively on Linux + Mac + Windows + FreeBSD + OpenBSD + NetBSD + BIOS on AMD64 and ARM64 with the best possible performance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Alright, let's give it a try then!&lt;/p&gt;

&lt;h2&gt;
  
  
  Building an mruby project with Cosmopolitan
&lt;/h2&gt;

&lt;p&gt;The regular steps to build a standalone binary are as shown in the &lt;a href="https://github.com/katafrakt/mruby-hello-world/blob/808d8c8786bf6e9340d2da6d82dc689349548cbf/Rakefile" rel="noopener noreferrer"&gt;Rakefile&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build mruby&lt;/li&gt;
&lt;li&gt;Generate bytecode from the Ruby code, using &lt;code&gt;mrbc&lt;/code&gt; built in step 1&lt;/li&gt;
&lt;li&gt;Compile the binary using a C entry file and the bytecode generated in step 2&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For Cosmopolitan, it's simple, but we need some initial setup.&lt;/p&gt;

&lt;p&gt;First, let's download the Cosmopolitan toolkit from &lt;a href="https://cosmo.zip/pub/cosmocc/" rel="noopener noreferrer"&gt;https://cosmo.zip/pub/cosmocc/&lt;/a&gt;. I extracted it to &lt;code&gt;~/Downloads/cosmocc&lt;/code&gt; for the purpose of this article.&lt;/p&gt;

&lt;p&gt;Then we can build mruby using it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COSMO_ROOT=~/Downloads/cosmocc rake MRUBY_CONFIG=cosmopolitan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: At the time of writing, I needed to remove mirb (&lt;a href="https://github.com/mruby/mruby/blob/c10a9ec571d43da8a883c5d7bfbbe61ab05457f9/build_config/cosmopolitan.rb#L84" rel="noopener noreferrer"&gt;this line&lt;/a&gt;) from the config, because it failed to compile with Cosmopolitan.&lt;/p&gt;

&lt;p&gt;Then we can generate the bytecode. Note the different executable name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./mruby/bin/mrbc.com -Bhello_world hello.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, compile the entry file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/Downloads/cosmocc/bin/cosmocc -std=c99 -Imruby/include main.c -o hello mruby/build/host/lib/libmruby.a -lm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that we get a binary &lt;code&gt;hello&lt;/code&gt;. And if I copy it over to my Mac, it just works there! The binary size is larger, of course, which is the understandable price. But if it is fine for the project you are working on, for sure this is a simpler way than providing multiple binaries targeting different platforms.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>mruby</category>
      <category>cosmopolitan</category>
    </item>
    <item>
      <title>Using Elixir head version with Mise</title>
      <dc:creator>Paweł Świątkowski</dc:creator>
      <pubDate>Mon, 05 Jan 2026 09:46:27 +0000</pubDate>
      <link>https://dev.to/katafrakt/using-elixir-head-version-with-mise-4bjc</link>
      <guid>https://dev.to/katafrakt/using-elixir-head-version-with-mise-4bjc</guid>
      <description>&lt;p&gt;In 2025 I followed the direction many people headed in and switched my version manager to &lt;a href="https://mise.jdx.dev" rel="noopener noreferrer"&gt;mise-en-place&lt;/a&gt;. It has been going really well for me, but recently I came across a problem which took me a bit to figure out (and few &lt;em&gt;very&lt;/em&gt; frustrating conversations with LLM, which did not lead to anything). I wanted to test some code with unreleased version 1.20 of Elixir, straight from git.&lt;/p&gt;

&lt;p&gt;How to do it? It's really simple in retrospect. Here are the steps:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Build Elixir
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git clone https://github.com/elixir-lang/elixir.git 
$ cd elixir
$ make
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it's built correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bin/elixir --version
&amp;gt; Elixir 1.20.0-dev (88cbabf) (compiled with Erlang/OTP 28)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Link it in mise
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mise link elixir@head /path/to/elixir
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Verify this works
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mise x elixir@head -- elixir --version
&amp;gt; Elixir 1.20.0-dev (88cbabf) (compiled with Erlang/OTP 28)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it's done. Whenever you want to run something with Elixir head, you can just &lt;code&gt;mise use elixir@head&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Of course, this should work in a similar way for everything that is handles by mise version control.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>mise</category>
    </item>
    <item>
      <title>Integrating Pagy with Hanami (2025 edition)</title>
      <dc:creator>Paweł Świątkowski</dc:creator>
      <pubDate>Mon, 10 Nov 2025 10:53:43 +0000</pubDate>
      <link>https://dev.to/katafrakt/integrating-pagy-with-hanami-2025-edition-20f2</link>
      <guid>https://dev.to/katafrakt/integrating-pagy-with-hanami-2025-edition-20f2</guid>
      <description>&lt;p&gt;Back in 2018 &lt;a href="https://katafrakt.me/2018/06/01/integrating-pagy-with-hanami/" rel="noopener noreferrer"&gt;I wrote a post&lt;/a&gt; about connecting Hanami (then 1.x) and Pagy gem together. My verdict was not that favorable. I had to write quite a lot of glue code to make it work and perhaps the worst thing was that I had to pass the &lt;code&gt;request&lt;/code&gt; object to the template to make it work.&lt;/p&gt;

&lt;p&gt;However, things have changed since then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hanami is now 2.3, its persistence layer is more mature, based on ROM and allows to fall back to almost-bare-Sequel via relations.&lt;/li&gt;
&lt;li&gt;Pagy &lt;a href="https://github.com/ddnexus/pagy/releases/tag/43.0.0" rel="noopener noreferrer"&gt;released version 43&lt;/a&gt; this week. It's advertised as a complete rewrite of its internals and APIs.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;We needed a leap version to unequivocally signaling that it's not just a major version: it's a complete redesign of the legacy code at all levels, usage and API included.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There's no better time to take it for another spin then!&lt;/p&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;I quickly spun up a fresh Hanami app, created a migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ROM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;migration&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;change&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:people&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;primary_key&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;
      &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:birth_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:added_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then created a relation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Pagynation&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Relations&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;People&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Hanami&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Relation&lt;/span&gt;
      &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="ss"&gt;:people&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;infer: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next I generated the action and added Pagy to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"pagy"&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Pagynation&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Actions&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;People&lt;/span&gt;
      &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Pagynation&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Action&lt;/span&gt;
        &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Deps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"relations.people"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Pagy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Method&lt;/span&gt;

        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;pagy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pagy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;people&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;client_max_limit: &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;pagy: &lt;/span&gt;&lt;span class="n"&gt;pagy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;people: &lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks more or less like the tutorial code from the Pagy website. The only exception is that I have to pass the &lt;code&gt;request&lt;/code&gt; directy. If not passed, Pagy expects &lt;code&gt;self.request&lt;/code&gt; to be defined. This does not work with Hanami, where the request is passed as an argument (in a functional flavour), not burned into the controller instance.&lt;/p&gt;

&lt;p&gt;Let's now examine the view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Pagynation&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Views&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;People&lt;/span&gt;
      &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Pagynation&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;View&lt;/span&gt;
        &lt;span class="n"&gt;expose&lt;/span&gt; &lt;span class="ss"&gt;:people&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;decorate: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;

        &lt;span class="n"&gt;expose&lt;/span&gt; &lt;span class="ss"&gt;:paginator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;decorate: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;pagy&lt;/span&gt;&lt;span class="ss"&gt;:|&lt;/span&gt;
          &lt;span class="n"&gt;pagy&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is standard Hanami stuff. We passed &lt;code&gt;:pagy&lt;/code&gt; and &lt;code&gt;:people&lt;/code&gt; from the action via the &lt;code&gt;render&lt;/code&gt; method, now we need to pass it on to the template. I used &lt;code&gt;decorate: false&lt;/code&gt; here to keep the things as simple as possible (data is a simple hash, not objects). With &lt;code&gt;people&lt;/code&gt; and &lt;code&gt;paginator&lt;/code&gt; exposed, all that's left is to craft a template using it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;People list&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Name&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Date of Birth&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;people.each&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="na"&gt;person&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;person&lt;/span&gt;&lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="na"&gt;:name&lt;/span&gt;&lt;span class="err"&gt;]&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;person&lt;/span&gt;&lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="na"&gt;:email&lt;/span&gt;&lt;span class="err"&gt;]&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;person&lt;/span&gt;&lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="na"&gt;:birth_date&lt;/span&gt;&lt;span class="err"&gt;]&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;raw&lt;/span&gt; &lt;span class="na"&gt;paginator.info_tag&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;raw&lt;/span&gt; &lt;span class="na"&gt;paginator.series_nav&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;paginator.info_tag&lt;/code&gt; and &lt;code&gt;paginator.series_nav&lt;/code&gt; are Pagy's HTML helper to render some status info and pagination links. They are, of course, quite barebones, but more than enough to test stuff.&lt;/p&gt;

&lt;p&gt;This all led me to this beauty, with working &lt;code&gt;limit&lt;/code&gt; URL params, working links etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwhl3nmz1w2ow0brbqs7v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwhl3nmz1w2ow0brbqs7v.png" alt="Screenshot of a barebones HTML table with pagination links underneath" width="563" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New verdict
&lt;/h2&gt;

&lt;p&gt;This is basically great.&lt;/p&gt;

&lt;p&gt;Everything worked pretty much out-of-the-box. If I were to imagine how seamless a pagination library for Hanami could be, this would likely look like that. Great improvements from Pagy maintainer.&lt;/p&gt;

&lt;p&gt;The repository to try it out yourself is available here &lt;a href="https://codeberg.org/katafrakt/hanami-pagy" rel="noopener noreferrer"&gt;on Codeberg&lt;/a&gt;.&lt;/p&gt;

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

</description>
      <category>ruby</category>
      <category>hanami</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
    <item>
      <title>Chekhov's gun principle for testing</title>
      <dc:creator>Paweł Świątkowski</dc:creator>
      <pubDate>Sat, 02 Nov 2024 12:53:33 +0000</pubDate>
      <link>https://dev.to/katafrakt/chekhovs-gun-principle-for-testing-aa4</link>
      <guid>https://dev.to/katafrakt/chekhovs-gun-principle-for-testing-aa4</guid>
      <description>&lt;p&gt;It’s not an uncommon notion that writing tests is more of a storytelling task than a technical one. Most recently I encountered it in &lt;a href="https://bikeshed.thoughtbot.com/438" rel="noopener noreferrer"&gt;The Bike Shed podcast&lt;/a&gt;, but you can find &lt;a href="https://www.innoq.com/en/blog/2018/10/testing-is-storytelling/" rel="noopener noreferrer"&gt;blog posts&lt;/a&gt; and &lt;a href="https://av.tib.eu/media/37338" rel="noopener noreferrer"&gt;conference talks&lt;/a&gt; about it as well. And if it is a storytelling act, perhaps we should look into narrative principles to make our tests better?&lt;/p&gt;

&lt;p&gt;One of the first rules that comes to my mind when thinking about storytelling is &lt;strong&gt;Chekhov’s gun rule&lt;/strong&gt;. What’s it about? In Anton Chekhov’s own words (I’m quoting after Britannica):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One must never place a loaded rifle on the stage if it isn’t going to go off. It’s wrong to make promises you don’t mean to keep.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Aforementioned Britannica also provides a definition as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;principle in drama, literature, and other narrative forms asserting that every element introduced in a story should be necessary to the plot&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;How does that apply to our tests-storytelling? Imagine you are writing a test for a piece of e-commerce functionality. You have products in categories, and these categories have some kind of constraints on the buyer. The most obvious example would be that alcohol cannot be sold to people under the age of eighteen. In our case, such people cannot add such products to the cart.&lt;/p&gt;

&lt;p&gt;Let’s see an example test code - I’m using Elixir here, but this applies to most languages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"don't allow adding products to cart when age constraint is not met"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;buyer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;age:&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;country:&lt;/span&gt; &lt;span class="ss"&gt;:uk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;registered_on:&lt;/span&gt; &lt;span class="sx"&gt;~U[2023-09-16T18:17:22Z]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Alcohol"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;external_id:&lt;/span&gt; &lt;span class="mi"&gt;3242&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;constraints:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;AgeConstraint&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;min:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Triple Hazy IPA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;category:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;sku:&lt;/span&gt; &lt;span class="s2"&gt;"TRI-557"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;added_at:&lt;/span&gt; &lt;span class="sx"&gt;~U[2022-01-01T12:16:54Z]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Cart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buyer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;Cart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;quantity:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:constraint_violated&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not exactly a bad test, but it violates Chekhov’s gun principle in multiple places. Every scalar introduced in our test “arrange” part is a gun in Chekhov’s terms. It’s there on the stage. The reader might expect it to go off. We have 10 scalars here, 11 guns on stage. What does going-off mean? That by changing this value to some other, the test should start failing. Let’s examine our scalars:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;name: "John Smith"&lt;/code&gt;: no matter to what we change it, it won’t make the test fail&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;age: 17&lt;/code&gt;: if we change it to 18 or 22, the test will fail&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;country: :uk&lt;/code&gt;: will not fail (unless we implement country-based constraints, but we do not for now)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;registered_on: &amp;lt;date&amp;gt;&lt;/code&gt;: does not matter&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;name: "Alcohol"&lt;/code&gt;: does not matter&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;external_id: 3242&lt;/code&gt;: what’s even that? does not matter&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;min: 18&lt;/code&gt;: changing it to &lt;code&gt;15&lt;/code&gt; will make the test fail&lt;/li&gt;
&lt;li&gt;  The remaining free from product, name, sku and date of adding do not affect the test&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;quantity: 2&lt;/code&gt;: again, does not matter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Summing up, only two out of our eleven guns can go off here. About 18%. The rest is a pure distraction, leading the reader astray if they try to understand the test dynamics.&lt;/p&gt;

&lt;p&gt;How do we fix it? By using abstractions! Tests need their abstractions just like your regular code does. And just like with regular code, you should make sure your abstractions are right in a given context. Here one potential abstraction is a factory. Let’s see how the improved version could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"don't allow adding products to cart when age constraint is not met"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;buyer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;person_factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;age:&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;category_factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;constraints:&lt;/span&gt; &lt;span class="p"&gt;[%&lt;/span&gt;&lt;span class="no"&gt;AgeConstraint&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;min:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;
  &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;product_factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;category:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Cart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buyer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;Cart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:constraint_violated&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test is visibly shorter now. It contains just 5 non-empty lines. Every scalar here matters and there are just two of them. This way we don’t overwhelm the reader and they can likely quickly draw a conclusion: aha, so the buyer is 17 years old, but the constraint is that they need to be at least 18, so the &lt;code&gt;:constraint_violated&lt;/code&gt; error is returned. Makes sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Corollary: Make your guns visible
&lt;/h2&gt;

&lt;p&gt;This is just my addition to the narrative principles:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If something is to go off, show it before.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;People rather don’t like these &lt;em&gt;deus ex machina&lt;/em&gt; moments. If there is something that is important for the test to pass, show it explicitly. Don’t hide it under the abstraction.&lt;/p&gt;

&lt;p&gt;Imagine that in the test above the first line just says &lt;code&gt;buyer = person_factory()&lt;/code&gt;. Asked during the code review about it, the developer says that in the factory default age is actually 17, so there’s no need to repeat it. &lt;strong&gt;This is a misuse of the abstraction&lt;/strong&gt;. Don’t rely on what’s hidden. In theory everyone should be able to go into the factory and change the values - and tests should still pass. Some people even swear that defaults in factories should be random because of that.&lt;/p&gt;

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

&lt;p&gt;To make your tests better and more readable (telling story in a better way), eliminate all the data that is irrelevant for the test flow. Leave only things that are important - values that, when changed in a certain way, will make the test fail. On the other hand, don’t hide important stuff in the abstractions. Make the narrative flow without readers scratching their heads.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>elixir</category>
    </item>
    <item>
      <title>Enhancing Your Elixir Codebase with Gleam</title>
      <dc:creator>Paweł Świątkowski</dc:creator>
      <pubDate>Tue, 06 Aug 2024 12:00:00 +0000</pubDate>
      <link>https://dev.to/appsignal/enhancing-your-elixir-codebase-with-gleam-329c</link>
      <guid>https://dev.to/appsignal/enhancing-your-elixir-codebase-with-gleam-329c</guid>
      <description>&lt;p&gt;Do you write Elixir but sometimes miss the benefits of type safety we have in other languages? If the answer is "yes", you may want to take a look at Gleam. It is a relatively young language that runs on top of the BEAM platform, which means it can be added as an enhancement to an Elixir codebase without you having to rewrite everything.&lt;/p&gt;

&lt;p&gt;In this article, we will add Gleam code to an Elixir project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Gleam for Elixir?
&lt;/h2&gt;

&lt;p&gt;First, let's ask ourselves a question: why would we use Gleam? The Elixir ecosystem is mature at this point, with a multitude of great solutions for everyday problems. Ecto and Phoenix make a really powerful combination for writing web applications. Elixir itself is a dynamically typed language, which is great for some applications but opens the door to a whole class of errors.&lt;/p&gt;

&lt;p&gt;Imagine you're working on a critical payment processing system in Elixir and need to ensure your core logic's absolute reliability&lt;br&gt;
and correctness. Despite Elixir's strengths, its dynamic typing sometimes leaves room for subtle bugs. Enter Gleam. It's a statically typed language for the BEAM platform that promises to enhance your system's robustness. Where Elixir falls a bit short, Gleam can shine. Let's explore how to integrate Gleam with an Elixir project, so we can have the best from both languages.&lt;/p&gt;

&lt;p&gt;We want our core business logic to be written in Gleam while the surrounding code is in Elixir (somewhat akin to the idea of a "functional core, imperative shell", although in the functional world, it's rather a "pure core, stateful shell"). We will take this idea very far, establishing a physical boundary between the two worlds.&lt;/p&gt;

&lt;p&gt;Let's see how to get there.&lt;/p&gt;
&lt;h2&gt;
  
  
  About The Project
&lt;/h2&gt;

&lt;p&gt;To witness the power of Gleam in action, we will implement a feature in a made-up application to manage students at a university. We will take care of their enrollment in courses. Our business rules will look as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A student can be enrolled in a course.&lt;/li&gt;
&lt;li&gt;Every course has a limited number of seats and a finite-length waitlist. If a student tries to enroll but all the seats are already taken, they are put on the waitlist (if there are spots there; if not — the enrollment is rejected).&lt;/li&gt;
&lt;li&gt;If someone with a reserved seat cancels their enrollment, the first person on the waitlist takes their place.&lt;/li&gt;
&lt;li&gt;Some courses have age limits: only students older than a certain age can enroll.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will start the implementation by creating a fairly standard Phoenix and Ecto application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix phx.new student_roll
mix ecto.create
mix phx.gen.live Enrollment Student students name:string date_of_birth:date
mix phx.gen.live Enrollment Course courses name:string max_students:integer waitlist_size:integer min_age:integer
mix phx.gen.schema Enrollment.Enrollment enrollments student_id:integer course_id:integer waitlisted_at:datetime
mix ecto.migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this basic structure in place, we can add Gleam into the mix using the &lt;a href="https://github.com/gleam-lang/mix_gleam" rel="noopener noreferrer"&gt;&lt;code&gt;mix_gleam&lt;/code&gt;&lt;/a&gt; library. We will follow the steps from the project README. First, install &lt;code&gt;mix_gleam&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix archive.install hex mix_gleam
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now prepare the project to use Gleam by adding some entries to &lt;code&gt;mix.exs&lt;/code&gt;'s project definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="nv"&gt;@app_name&lt;/span&gt; &lt;span class="ss"&gt;:student_roll&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="ss"&gt;archives:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;mix_gleam:&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 0.6"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;app:&lt;/span&gt; &lt;span class="nv"&gt;@app_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;compilers:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:gleam&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;Mix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compilers&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
    &lt;span class="ss"&gt;aliases:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"deps.get"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"deps.get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"gleam.deps.get"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;erlc_paths:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"build/dev/erlang/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;@app_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/_gleam_artefacts"&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;erlc_include_path:&lt;/span&gt; &lt;span class="s2"&gt;"build/dev/erlang/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;@app_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/include"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;prune_code_paths:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# rest of the function&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that this assumes you are changing an existing &lt;code&gt;mix.exs&lt;/code&gt; file generated by &lt;code&gt;phx.new&lt;/code&gt;. These are required steps to make the Gleam code work with Elixir, as outlined in the documentation. You don't need to understand every single change here (I, for instance, don't). The important thing is &lt;code&gt;erlc_path&lt;/code&gt; which tells the compiler where to find compiled Gleam files, so we can use them in our project.&lt;/p&gt;

&lt;p&gt;Next, also in &lt;code&gt;mix.exs&lt;/code&gt;, we need to add the Gleam standard library and testing framework to our dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# others&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:gleam_stdlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt; 1.0"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:gleeunit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:dev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;runtime:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, fetch dependencies and create the &lt;code&gt;src&lt;/code&gt; directory where the Gleam code will live:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix deps.get
&lt;span class="nb"&gt;mkdir &lt;/span&gt;src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let's Write Some Gleam
&lt;/h2&gt;

&lt;p&gt;With all the setup in place, we can now start writing our business logic in Gleam! In the first iteration, we will skip the waitlist functionality, but we will still create a system that checks if enrollment is possible. Let's start with a &lt;code&gt;src/enrollment.gleam&lt;/code&gt; file. &lt;code&gt;src&lt;/code&gt; is the directory at the top level of the project, where &lt;code&gt;mix_gleam&lt;/code&gt; expects you to keep your Gleam code.&lt;/p&gt;

&lt;p&gt;We will put the following code in the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub type Student {
  Student(id: Int, age: Int)
}

pub type Course {
  Course(id: Int, min_age: Int, seats: Int, seats_used: Int)
}

pub type RejectionReason {
  NoSeats
  AgeRequirementNotMet
}

pub type EnrollmentDecision {
  Enrolled
  Rejected(reason: RejectionReason)
}

pub fn enroll(student: Student, course: Course) -&amp;gt; EnrollmentDecision {
  case student.age &amp;gt;= course.min_age {
    False -&amp;gt; Rejected(AgeRequirementNotMet)
    True -&amp;gt;
      case course.seats &amp;gt; course.seats_used {
        False -&amp;gt; Rejected(NoSeats)
        True -&amp;gt; Enrolled
      }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are quite a few things to unpack here, so let's take a quick crash course on Gleam.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Happening Here?
&lt;/h3&gt;

&lt;p&gt;In the first part, we define a bunch of types. Types are the heart of typed languages. Here we need a &lt;code&gt;Student&lt;/code&gt;, a &lt;code&gt;Course&lt;/code&gt;, an &lt;code&gt;EnrollmentDecision&lt;/code&gt;, and a &lt;code&gt;RejectionReason&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Taking a &lt;code&gt;Student&lt;/code&gt; declaration, we define not only a type itself but also its &lt;strong&gt;constructor&lt;/strong&gt;, a &lt;code&gt;Student&lt;/code&gt; with two arguments: &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;age&lt;/code&gt;. You can note that we don't include the &lt;code&gt;name&lt;/code&gt; here, even though we defined it before in the Elixir schema definition. The name is just side-data. We don't use it for anything related to business logic (unless you have a requirement, such as that only people with names starting with E can take a course).&lt;/p&gt;

&lt;p&gt;A type like &lt;code&gt;RejectionReason&lt;/code&gt; defines two constructors, which we can basically read as no seats left or the age requirement not being met.&lt;/p&gt;

&lt;p&gt;At the end of the code block, we define an actual &lt;code&gt;enroll&lt;/code&gt; function. It takes a student and a course, and using a &lt;code&gt;case&lt;/code&gt; statement, makes some decisions about the pending enrollment command. &lt;code&gt;case&lt;/code&gt; is the only flow control structure in Gleam (there is no &lt;code&gt;if&lt;/code&gt;, for example). In this example, we first check the age requirement, and if it's met, we check if seats are left.&lt;/p&gt;

&lt;p&gt;With that code ready, we can now write a failing unit test. Let's add a &lt;code&gt;student_roll_test.gleam&lt;/code&gt; file in the &lt;code&gt;test&lt;/code&gt; directory (this is a naming convention that &lt;code&gt;mix_gleam&lt;/code&gt; expects). In that file, add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import enrollment.{Course, Enrolled, Student}
import gleeunit

pub fn main() {
  gleeunit.main()
}

pub fn enrolled_test() {
  let course = Course(id: 1, min_age: 21, seats: 10, seats_used: 9)
  let student = Student(id: 10, age: 20)
  let assert Enrolled = enrollment.enroll(student, course)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a bit of boilerplate here, but in essence, we create a test function that builds a course with &lt;code&gt;id = 1&lt;/code&gt;, &lt;code&gt;min_age = 21&lt;/code&gt;, &lt;code&gt;seats_limit = 10&lt;/code&gt; and &lt;code&gt;seats_taken = 9&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, we create a student who is 20 years old (therefore too young to enroll). Finally, we try to enroll them. On running &lt;code&gt;mix gleam.test&lt;/code&gt;, you get the error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Running student_roll_test.main
F
Failures:

  1&lt;span class="o"&gt;)&lt;/span&gt; enrollment_test.enrolled_test: module &lt;span class="s1"&gt;'enrollment_test'&lt;/span&gt;
     &lt;span class="c"&gt;#{function =&amp;gt; &amp;lt;&amp;lt;"enrolled_test"&amp;gt;&amp;gt;,line =&amp;gt; 12,&lt;/span&gt;
       message &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &amp;lt;&amp;lt;&lt;span class="s2"&gt;"Assertion pattern match failed"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;,
       module &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="no"&gt;enrollment_test&lt;/span&gt;&lt;span class="sh"&gt;"&amp;gt;&amp;gt;,
       value =&amp;gt; {rejected,age_requirement_not_met},
       gleam_error =&amp;gt; let_assert}
     location: enrollment_test.enrolled_test:12
     stacktrace:
       enrollment_test.enrolled_test
     output:

Finished in 0.011 seconds
1 tests, 1 failures
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is good, but not great. Let's improve the output by using &lt;code&gt;gleeunit/should&lt;/code&gt;, a library that makes the unit testing experience better in Gleam:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import enrollment.{Course, Enrolled, Student}
import gleeunit
import gleeunit/should

pub fn main() {
  gleeunit.main()
}

pub fn enrolled_test() {
  let course = Course(id: 1, min_age: 21, seats: 10, seats_used: 9)
  let student = Student(id: 10, age: 20)
  enrollment.enroll(student, course)
  |&amp;gt; should.equal(Enrolled)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the output is much more readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Running student_roll_test.main
F
Failures:

  1&lt;span class="o"&gt;)&lt;/span&gt; student_roll_test.enrolled_test: module &lt;span class="s1"&gt;'student_roll_test'&lt;/span&gt;
     Values were not equal
     expected: Enrolled
          got: Rejected&lt;span class="o"&gt;(&lt;/span&gt;AgeRequirementNotMet&lt;span class="o"&gt;)&lt;/span&gt;
     output:

Finished &lt;span class="k"&gt;in &lt;/span&gt;0.011 seconds
1 tests, 1 failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's fix the test by calling it using a student who is old enough, and we will have a pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Running student_roll_test.main
&lt;span class="nb"&gt;.&lt;/span&gt;
Finished &lt;span class="k"&gt;in &lt;/span&gt;0.012 seconds
1 tests, 0 failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have the first test figured out, we should write some more tests to cover interesting branches in the code. But one important question still lingers: &lt;strong&gt;How do I call this from my Phoenix project?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling Gleam from Elixir
&lt;/h2&gt;

&lt;p&gt;Let's start working on the Phoenix side of our project. Go to &lt;code&gt;lib/student_roll/enrollment.ex&lt;/code&gt; and define two&lt;br&gt;
functions related to the enrollment process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;enrolled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;where:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;is_nil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitlisted_at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;enroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;student&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_student!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;course&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_course!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# some logic should go here&lt;/span&gt;

  &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;student_id:&lt;/span&gt; &lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;course_id:&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will write a test checking the enrollment logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"enrollment"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;StudentRoll&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;EnrollmentFixtures&lt;/span&gt;

  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"enroll a student"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;birthday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;student&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;student_fixture&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;date_of_birth:&lt;/span&gt; &lt;span class="n"&gt;birthday&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;course&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;course_fixture&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;min_age:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enrolled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it passes! But it should not. We created a student who is roughly 18 years old, but the course requires that the students be at least 22. This is because we did not plug our Gleam-written business logic into the mix. Let's fix this now.&lt;/p&gt;

&lt;p&gt;Calling compiled Gleam modules looks to Elixir the same way as Erlang modules. You invoke modules by prepending &lt;code&gt;:&lt;/code&gt; to the module name. In our case, this will be &lt;code&gt;:enrollment&lt;/code&gt;. We will also have to pass something that Gleam can interpret as its &lt;code&gt;Student&lt;/code&gt; and &lt;code&gt;Course&lt;/code&gt; types. This is the boilerplate I mentioned earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;enroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;student&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_student!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;course&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_course!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;student_age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_of_birth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;seats_taken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;where:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;is_nil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitlisted_at&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="ss"&gt;:count&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="ss"&gt;:enrollment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:student&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student_age&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:course&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_students&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seats_taken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="ss"&gt;:enrolled&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;student_id:&lt;/span&gt; &lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;course_id:&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:rejected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our &lt;code&gt;enroll&lt;/code&gt; function is slightly inflated, so let's walk through it.&lt;/p&gt;

&lt;p&gt;First, we need some more data. We calculate the student age in Elixir here and also fetch the number of already enrolled students. Second, we build a structure that Gleam will interpret as its type. These are just tuples, where the first element is the name of a type, followed by several fields required by the constructor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gleam: Student(id: Int, age: Int)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:student&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student_age&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Gleam: Course(id: Int, min_age: Int, seats: Int, seats_used: Int)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:course&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_students&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seats_taken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we call &lt;code&gt;:enrollment.enroll&lt;/code&gt; with these tuples. If we run the test now, we will get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;test &lt;/span&gt;enrollment enroll a student &lt;span class="o"&gt;(&lt;/span&gt;StudentRoll.EnrollmentTest&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;test&lt;/span&gt;/student_roll/enrollment_test.exs:131
  match &lt;span class="o"&gt;(=)&lt;/span&gt; failed
  code:  assert &lt;span class="o"&gt;{&lt;/span&gt;:ok, _&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; Enrollment.enroll&lt;span class="o"&gt;(&lt;/span&gt;course.id, student.id&lt;span class="o"&gt;)&lt;/span&gt;
  left:  &lt;span class="o"&gt;{&lt;/span&gt;:ok, _&lt;span class="o"&gt;}&lt;/span&gt;
  right: &lt;span class="o"&gt;{&lt;/span&gt;:error, &lt;span class="o"&gt;{&lt;/span&gt;:rejected, :age_requirement_not_met&lt;span class="o"&gt;}}&lt;/span&gt;
  stacktrace:
    &lt;span class="nb"&gt;test&lt;/span&gt;/student_roll/enrollment_test.exs:136: &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly what we wanted to have! The test does not pass because the student is too young.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub type RejectionReason {
  NoSeats
  AgeRequirementNotMet
}

pub type EnrollmentDecision {
  Enrolled
  Rejected(reason: RejectionReason)
}


# in Elixir:
# :enrolled | {:rejected, :age_requirement_not_met} | {:rejected | :no_seats}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've made the first step. We called the Gleam code from Elixir, got the results back, and interpreted them back into Elixir idioms (a result tuple of &lt;code&gt;{:ok, term()} | {:error, term()}&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;We should now write any remaining tests we want to run in Elixir. Remember that at this point, the enrollment process is thoroughly unit tested in Gleam by Gleeunit, so perhaps we don't need to duplicate all the cases — only the ones that have consequences in Elixir (but this is up to you and your testing strategy).&lt;/p&gt;

&lt;p&gt;After this, we can refactor the &lt;code&gt;enroll&lt;/code&gt; function to look nicer and more reader-friendly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;get_gleam_student!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;student&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_student!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;student_age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_of_birth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:student&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student_age&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;get_gleam_course!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;course&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_course!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;seats_taken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;where:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;is_nil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitlisted_at&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="ss"&gt;:count&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:course&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_students&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seats_taken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;enroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;course&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_gleam_course!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;student&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_gleam_student!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="ss"&gt;:enrollment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="ss"&gt;:enrolled&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;student_id:&lt;/span&gt; &lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;course_id:&lt;/span&gt; &lt;span class="n"&gt;course_id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:rejected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks better. You could even hide &lt;code&gt;get_gleam_student!/1&lt;/code&gt; and &lt;code&gt;get_gleam_course!/1&lt;/code&gt; in a module called, for example, &lt;code&gt;GleamTypes&lt;/code&gt;. This way, other contexts that potentially call these structures in Gleam will have them readily available.&lt;/p&gt;

&lt;p&gt;But that's not all. We still have some work to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Waitlist
&lt;/h2&gt;

&lt;p&gt;If you recall, our initial requirements also included a waitlist.&lt;/p&gt;

&lt;p&gt;This is missing in the implementation above; now is the time to fix it. This will allow you to see how changing the code in both Gleam and Elixir works. I strongly recommend you change the logic in Gleam first, unit-test it, and only then, write the Elixir proxy code (although if you fancy some strict TDD, you can start with some red Elixir tests too).&lt;/p&gt;

&lt;p&gt;But before diving into the code, let's ask ourselves one design question: How should we represent the waitlist? Should it be a number (&lt;code&gt;waitlist_size&lt;/code&gt;) or maybe a list of students? This is &lt;em&gt;the&lt;/em&gt; classic question in functional design and probably deserves a separate article to address it fully.&lt;/p&gt;

&lt;p&gt;In our project, for already-signed-in students, we just pass a number of them, not a list of them. This time, we will model the waitlist as an actual list to make things more interesting.&lt;/p&gt;

&lt;p&gt;First, we need to change our domain model by extending the &lt;code&gt;Course&lt;/code&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub type Course {
  Course(
    id: Int,
    min_age: Int,
    seats: Int,
    seats_used: Int,
    waitlist: List(Student),
    max_waitlist_size: Int,
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then the list of enrollment decisions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub type EnrollmentDecision {
  Enrolled
  Rejected(reason: RejectionReason)
  Waitlisted
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, we have to adjust our existing Gleam tests because now they fail with the following message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;error: Incorrect arity
   ┌─ /home/user/dev/student_roll/_build/dev/lib/student_roll/test/student_roll_test.gleam:10:16
   │
10 │   &lt;span class="nb"&gt;let &lt;/span&gt;course &lt;span class="o"&gt;=&lt;/span&gt; Course&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;: 1, min_age: 21, seats: 10, seats_used: 9&lt;span class="o"&gt;)&lt;/span&gt;
   │                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected 6 arguments, got 4

This call accepts these additional labelled arguments:

  - max_waitlist_size
  - waitlist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we also have to modify the Elixir glue code that calls Gleam:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;get_gleam_student!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;get_student!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;to_gleam_student&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;to_gleam_student&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;student_age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_of_birth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:student&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student_age&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;get_gleam_course!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;course&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_course!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;seats_taken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;where:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;is_nil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitlisted_at&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="ss"&gt;:count&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;waitlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;where:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;is_nil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitlisted_at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;to_gleam_student&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:course&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_students&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seats_taken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;waitlist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitlist_size&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# the enroll/2 function stays the same&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's write a failing test in Gleeunit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub fn waitlist_exceeded_test() {
  let course =
    Course(
      id: 2,
      min_age: 0,
      seats: 10,
      seats_used: 10,
      max_waitlist_size: 2,
      waitlist: [
        Student(id: 1, age: 22),
        Student(id: 2, age: 13),
        Student(id: 3, age: 54),
      ],
    )

  let student = Student(id: 4, age: 22)

  enrollment.enroll(student, course)
  |&amp;gt; should.equal(Rejected(NoSeats))
}

pub fn waitlisted_test() {
  let course =
    Course(
      id: 2,
      min_age: 0,
      seats: 10,
      seats_used: 10,
      max_waitlist_size: 2,
      waitlist: [],
    )

  let student = Student(id: 4, age: 22)

  enrollment.enroll(student, course)
  |&amp;gt; should.equal(Waitlisted)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of these tests will accidentally pass (because we reused the &lt;code&gt;NoSeats&lt;/code&gt; rejection reason, which might be a dubious choice). But the other one fails. Let's fix it now by actually implementing the waitlist. We need to import the &lt;code&gt;gleam/list&lt;/code&gt; package on top and change the branch which previously resulted in &lt;code&gt;NoSeats&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import gleam/list

# ...

pub fn enroll(student: Student, course: Course) -&amp;gt; EnrollmentDecision {
  case student.age &amp;gt;= course.min_age {
    False -&amp;gt; Rejected(AgeRequirementNotMet)
    True -&amp;gt;
      case course.seats &amp;gt; course.seats_used {
        False -&amp;gt;
          case list.length(course.waitlist) &amp;gt;= course.max_waitlist_size {
            True -&amp;gt; Rejected(NoSeats)
            False -&amp;gt; Waitlisted
          }
        True -&amp;gt; Enrolled
      }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final part will be done in Elixir. We have decided that a person should go to the waitlist, but now we have to persist it. Again, let's start with a test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"waitlist a student"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;student1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;student_fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;student2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;student_fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;course&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;course_fixture&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;max_students:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;waitlist_size:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitlisted?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need the &lt;code&gt;waitlisted?&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;waitlisted?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;where:&lt;/span&gt;
      &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;is_nil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitlisted_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we implement the infrastructure part, persisting the waitlist in the database. In the&lt;br&gt;
&lt;code&gt;enroll&lt;/code&gt; function's &lt;code&gt;case&lt;/code&gt; statement, we need to handle the &lt;code&gt;:waitlisted&lt;/code&gt; return type case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="ss"&gt;:waitlisted&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enrollment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;student_id:&lt;/span&gt; &lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;course_id:&lt;/span&gt; &lt;span class="n"&gt;course_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;waitlisted_at:&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the tests pass now. We have, therefore, successfully implemented a waitlist function in our Gleam/Elixir application!&lt;/p&gt;

&lt;p&gt;Looking at the requirements, we still have some way to go. We haven't touched on canceling an enrollment (when a user has successfully enrolled in the past but is no longer interested). Hint: this handles moving a person from a waitlist to a regular enrollment, so we at least need this type as a return value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub type CancellingEnrollmentDecision {
  Cancelled
  CancelledWithWaitlistProcessed(Student)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also a big topic that can be explored further but was not even covered in our requirements: how we should handle uniqueness. Everyone should be able to enroll in a given course only once, and they should not be able to cancel if they are not enrolled. There are at least three ways to model that behavior between Gleam and Elixir, but that's a topic for a separate article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Did We Do It Again?
&lt;/h2&gt;

&lt;p&gt;Everything we've done here could, of course, all have been written just in Elixir. The code would be shorter and in one language. In most cases, splitting the responsibilities between Elixir and Gleam probably would be a questionable choice. However, I personally think it's something worth considering if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You like to model your application with types and are missing this from Elixir.&lt;/li&gt;
&lt;li&gt;You expect your business logic to grow in complexity and want guarantees from a statically typed language to help you manage that complexity.&lt;/li&gt;
&lt;li&gt;You want a strong separation between pure business logic and stateful application code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these are true for you, consider this arcane-looking setup. In any case, it's good to be aware that it is possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;I have shown you how to call Gleam from an Elixir application and how to interpret the results. We wrote tests in both&lt;br&gt;
languages and some glue code.&lt;/p&gt;

&lt;p&gt;Happy coding, whether you choose to do it only in Elixir or you also make use of Gleam!&lt;/p&gt;

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

</description>
      <category>elixir</category>
      <category>gleam</category>
    </item>
    <item>
      <title>Checking Efx - testable effects for Elixir</title>
      <dc:creator>Paweł Świątkowski</dc:creator>
      <pubDate>Sat, 13 Jul 2024 11:32:17 +0000</pubDate>
      <link>https://dev.to/katafrakt/checking-efx-testable-effects-for-elixir-2d</link>
      <guid>https://dev.to/katafrakt/checking-efx-testable-effects-for-elixir-2d</guid>
      <description>&lt;p&gt;Mocking in Elixir is always a hot topic, mainly because of people coming from different other technologies having different expectation. The most "cannonical" solution, &lt;a href="https://hexdocs.pm/mox/Mox.html" rel="noopener noreferrer"&gt;Mox&lt;/a&gt;, gives a lot of security with regard to what the fake implementation returns, but requires a bit of a ceremony from the programmer. Other solutions like &lt;a href="https://github.com/jjh42/mock" rel="noopener noreferrer"&gt;Mock&lt;/a&gt;, &lt;a href="https://github.com/stephanos/rewire" rel="noopener noreferrer"&gt;Rewire&lt;/a&gt; or &lt;a href="https://hexdocs.pm/bypass/Bypass.html" rel="noopener noreferrer"&gt;Bypass&lt;/a&gt; offer more ad-hoc mocking, but t the price of the tests heving to be synchronous. Then there's &lt;a href="https://github.com/edgurgel/mimic" rel="noopener noreferrer"&gt;Mimic&lt;/a&gt;, which offers ad-hoc mocking and supports async&lt;br&gt;
tests.&lt;/p&gt;

&lt;p&gt;In this busy area, a new library appeared very recently. It's called &lt;a href="https://github.com/bravobike/efx" rel="noopener noreferrer"&gt;Efx&lt;/a&gt; and its description does not even mention mocking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A library to declaratively write testable effects &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It immediately cought m attention, because it seems to implement what I described as "&lt;a href="https://dev.to/2022/10/19/purity-injection-elixir/"&gt;purity injection&lt;/a&gt;" some time ago. As a programmer, you write a default impure implementation of the code, but you mark it as an "effect". Then in tests you replace a stateful implementation with something more predictable (you "inject purity") and you can enjoy nice, stable and&lt;br&gt;
fast tests.&lt;/p&gt;

&lt;p&gt;This post is a result of taking the library on a quick testing round. I decided to use it to the exact same problem as in the "purity injection" post. To remind, when you create a new order in your e-commerce application, you need to assign it a number. The number needs to be unique within a tenant and it should not contain ambiguous characters. The naive implementation, with which we started, looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;OrderNumber&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
      &lt;span class="ss"&gt;:crypto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strong_rand_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;padding:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;replace_ambiguous_characters&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
      &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="ss"&gt;where:&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's change this code to support effects in Efx's terminology. We will have two: one for generating a random string and another to check if the sequence has already been used.&lt;br&gt;
&lt;/p&gt;

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

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;generate_random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;replace_ambiguous_characters&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;check_existence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;true&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;false&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;generate_random&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;defeffect&lt;/span&gt; &lt;span class="n"&gt;generate_random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="ss"&gt;:crypto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strong_rand_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;padding:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;check_existence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="n"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;defeffect&lt;/span&gt; &lt;span class="n"&gt;check_existence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Query&lt;/span&gt;

    &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="no"&gt;Commerce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;where:&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Commerce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;replace_ambiguous_characters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;string&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"I"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"O"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;generate_random/0&lt;/code&gt; and &lt;code&gt;check_existence/2&lt;/code&gt; effects are compiled to a regular functions, which are called inside &lt;code&gt;generate/1&lt;/code&gt;. There's nothing too fancy in it, so let's see about testing it. In the first test we want to check if the ambiguous characters are replace, so we want our RNG to generate a sequence with such characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Commerce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;OrderNumberTest&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Commerce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;DataCase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;async:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;EfxCase&lt;/span&gt;

  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Commerce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;

  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"replace Os and Is"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generate_random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"A5O0I1"&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:check_existence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"A50011"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;bind/3&lt;/code&gt; function replaces the implementation of effects from a default one to a fake one. Now our &lt;code&gt;generate_random/0&lt;/code&gt; function returns &lt;code&gt;A5O0I1&lt;/code&gt; strings, which then replaces the ambiguous  characters. We also need to &lt;code&gt;bind&lt;/code&gt; the implementation of &lt;code&gt;check_existence&lt;/code&gt; here, even though we actually know that the database is empty so the check would pass. There are two reasons to do so:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We &lt;strong&gt;want&lt;/strong&gt; to do it - isolate all side-effects in tests, so tests are faster and less reliant on the infrastructure&lt;/li&gt;
&lt;li&gt;We &lt;strong&gt;have&lt;/strong&gt; to do it - because Efx forces us to&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If we don't &lt;code&gt;bind&lt;/code&gt; the &lt;code&gt;check_existence&lt;/code&gt;, we would get an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;** (RuntimeError) Mock for function check_existence/2 missing
    (efx 0.1.11) lib/efx_case/mock.ex:95: EfxCase.Mock.get_fun/3
    (efx 0.1.11) lib/efx_case/mock_state.ex:66: anonymous fn/5 in EfxCase.MockState.call/4
    (elixir 1.16.0) lib/agent/server.ex:16: Agent.Server.handle_call/3
    (stdlib 5.2) gen_server.erl:1131: :gen_server.try_handle_call/4
    (stdlib 5.2) gen_server.erl:1160: :gen_server.handle_msg/6
    (stdlib 5.2) proc_lib.erl:241: :proc_lib.init_p_do_apply/3
Last message (from #PID&amp;lt;0.405.0&amp;gt;): {:get_and_update, #Function&amp;lt;1.67167648/1 in EfxCase.MockState.call/4&amp;gt;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this is done, we can test more elaborate cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"use first candidate when existence check is false"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generate_random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"ABC"&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generate_random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"123"&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:check_existence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"ABC"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"use second candidate when first existence check is true"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generate_random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"ABC"&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;calls:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:check_existence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;calls:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generate_random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"123"&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:check_existence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"123"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we &lt;code&gt;bind&lt;/code&gt; the effects few times. The second test shows &lt;code&gt;calls: 1&lt;/code&gt; option passed, which makes sure that &lt;code&gt;bind&lt;/code&gt; only binds once. After that, the next bound implementation is used.&lt;/p&gt;

&lt;p&gt;Finally, just to be sure, we may want to write a test actually calling the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"check against actual database"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generate_random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"ABC"&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;calls:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:check_existence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generate_random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"123"&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Commerce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;tenant_id:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;number:&lt;/span&gt; &lt;span class="s2"&gt;"ABC"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Commerce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert!&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;OrderNumber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"123"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Binding with &lt;code&gt;{:default, arity}&lt;/code&gt; lets us call the original implementation. This has to be done fully intentionally, not cutting corners.&lt;/p&gt;

&lt;p&gt;We have successfully tested our order number generator in a much cleaner way, compared to my original post, without passing dependencies as an argument. Overall I think this is a very promising approach that, aside for improving using fake implementation in tests, nudges the developer in a nice way to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Think about side-effects when writing the code - you need to wrap them in &lt;code&gt;defeffect&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;Keep the number of impure functions in the module as low as possible - you have to always bind them all &lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;@spec&lt;/code&gt;s - &lt;code&gt;defeffect&lt;/code&gt; requires them; although there are no type checks whether a fake implementation adheres
to the spec, at least having to define it might help to visually identify problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think Efx is a great addition to Elixir ecosystem and I salute the author. Nice job!&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>testing</category>
      <category>functional</category>
    </item>
    <item>
      <title>Don't refactor the code</title>
      <dc:creator>Paweł Świątkowski</dc:creator>
      <pubDate>Thu, 13 Jun 2024 18:52:10 +0000</pubDate>
      <link>https://dev.to/katafrakt/dont-refactor-the-code-igk</link>
      <guid>https://dev.to/katafrakt/dont-refactor-the-code-igk</guid>
      <description>&lt;p&gt;This is a piece of advice someone gave me a long time ago. Unfortunately, I don't really remember who, so I cannot properly attribute (although chances are they heard it somewhere too). But I decided to re-share this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is refactoring?&lt;/strong&gt; I'm sure we can find multitude of definitions. But with modern software development process it often becomes synonymous with any kind of code changes that do not add, modify or remove features. In other words, a non-product work. In effect it often becomes a blurry term and cause of tension between product stakeholders and the dev team.&lt;/p&gt;

&lt;p&gt;Who among us did not hear that on a status meeting: "Yesterday I spent most time refactoring the code around X"? I know I did. No less, I probably said a phrase like that more than once. What does this mean? What did you really do? This is hidden behind "I refactored" term. "I did an important technical work you would not understand" is another way of framing that.&lt;/p&gt;

&lt;p&gt;And this is exactly the problem with "refactoring the code". In many cases it means doing a really important work, but it's indistinguishable from almost-slacking-off, like renaming variables for no apparent reason.&lt;/p&gt;

&lt;p&gt;And this is what I mean by "don't refactor the code": use different words when talking about things you did, are doing or plan to do. Don't "refactor". Instead try these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I made the code more performant (identified N+1, found inefficient processing of a large amount of data)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I made the code more open to change (mostly should be justified by prediction that we will be changing this area more often now)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I made the code more defensive (failing early and with a clear message if run with incorrect arguments - because other teams are using it incorrectly and it leads to a subtle bugs)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I added the tests for an untested area (good rationale: because it failed few times recently; bad rationale: to increase our arbitrary code coverage metrics)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I added more logging / instrumentation (so we can understand better what is going on)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I change the code to meet our new style guide (because we will change it often)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Communicating like this is not only makes it easier for others to understand what was changed, but also helps you decide if the change you plan to make really makes a difference. Not being able to hide behind an umbrella "refactoring" term also helps to keep the changes more focused and easier to review for your colleagues.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>communication</category>
      <category>softwaredevelopment</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Creating Custom Exceptions in Elixir</title>
      <dc:creator>Paweł Świątkowski</dc:creator>
      <pubDate>Tue, 05 Mar 2024 11:25:35 +0000</pubDate>
      <link>https://dev.to/appsignal/creating-custom-exceptions-in-elixir-208c</link>
      <guid>https://dev.to/appsignal/creating-custom-exceptions-in-elixir-208c</guid>
      <description>&lt;p&gt;Exceptions and exception handling are widely accepted concepts in most modern programming languages. Even though they're not as prevalent in Elixir as in object-oriented languages, it's still important to learn about them.&lt;/p&gt;

&lt;p&gt;In this article, we will closely examine exceptions in Elixir, learning how to define and use them.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Elixir and Exceptions
&lt;/h2&gt;

&lt;p&gt;There is no single golden rule that covers when to use exceptions, especially custom ones. Throughout this article, I will keep to a &lt;a href="https://stackoverflow.com/a/77361" rel="noopener noreferrer"&gt;definition of exceptions from StackOverflow&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An exception is thrown when a fundamental assumption of the current code block is found to be false.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, we can expect an exception when something truly &lt;em&gt;exceptional&lt;/em&gt; and hard to predict happens. Network failures, databases going down, or running out of memory — these are all good examples of when an exception should be thrown. However, if the form input you send to your server does not pass validation, there are better expressions to use, such as error tuples.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check out our &lt;a href="https://blog.appsignal.com/2023/09/26/an-introduction-to-exceptions-in-elixir.html" rel="noopener noreferrer"&gt;An Introduction to Exceptions in Elixir&lt;/a&gt; post for an overview of exceptions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You might wonder how Erlang's famous "let it crash" philosophy works with exceptions. I would say it works pretty well. Exceptions are, in fact, crashes, as long as you don't catch them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Anatomy of Elixir's Exceptions
&lt;/h3&gt;

&lt;p&gt;The most common exception you may have seen is probably &lt;code&gt;NoMatchError&lt;/code&gt;. And if you've used Ecto with PostgreSQL, you also must have seen &lt;code&gt;Postgrex.Error&lt;/code&gt; at least a few times. Among other popular exceptions, we have &lt;code&gt;CaseClauseError&lt;/code&gt;, &lt;code&gt;UndefinedFunctionError&lt;/code&gt;, or &lt;code&gt;ArithmeticError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's now take a look at what exceptions are under the hood. The easiest way to do that is to cause an exception, rescue it, and then inspect it. We will use the following code to dissect &lt;code&gt;NoMatchError&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="ss"&gt;:ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:not_ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;%MatchError&lt;span class="o"&gt;{&lt;/span&gt;term: :not_ok&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, this is just a struct with some additional data. Using similar code, we can check &lt;code&gt;CaseClauseError&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;%CaseClauseError&lt;span class="o"&gt;{&lt;/span&gt;term: :not_ok&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can do more using functions provided by the &lt;code&gt;Exception&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# note that this is deprecated in favour of Kernel.is_exception&lt;/span&gt;
&lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="s2"&gt;"no case clause matching: :not_ok"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="s2"&gt;"** (CaseClauseError) no case clause matching: :not_ok"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And can peek even deeper by using functions from the &lt;code&gt;Map&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:__exception__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:__struct__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:term&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__struct__&lt;/span&gt;
&lt;span class="no"&gt;CaseClauseError&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__exception__&lt;/span&gt;
&lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;
&lt;span class="ss"&gt;:not_ok&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Armed with that knowledge, we create a "fake" exception using just a map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;__struct__:&lt;/span&gt; &lt;span class="no"&gt;CaseClauseError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;__exception__:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;term:&lt;/span&gt; &lt;span class="ss"&gt;:not_ok&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="s2"&gt;"** (CaseClauseError) no case clause matching: :not_ok"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this is not how we will define our custom exception. And before we dive into custom exceptions, let's try to answer one important question: when should we use them?&lt;/p&gt;

&lt;h2&gt;
  
  
  When Should You Use Custom Exceptions?
&lt;/h2&gt;

&lt;p&gt;The most common use case for custom exceptions is when you are creating your own library. Take Postgrex, for example: you have &lt;code&gt;Postgrex.Error&lt;/code&gt;. In Tesla (an HTTP client), you have &lt;code&gt;Tesla.Error&lt;/code&gt;. They are useful because they immediately indicate where an error happens and how to determine its cause.&lt;/p&gt;

&lt;p&gt;Most of us, however, do not write libraries often. It's more likely that you work on a specific application that powers your company's business (or its customers). Even in your application's code, it can be useful to define some custom exceptions.&lt;/p&gt;

&lt;p&gt;For example, your application might send webhook notifications to a URL defined in an environment variable. Consider this very simplified code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;WebhookSender&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"WEBHOOK_ENDPOINT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;HttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can reasonably expect that a &lt;code&gt;WEBHOOK_ENDPOINT&lt;/code&gt; environment variable is set on a machine where your application is deployed. If it's not, that's a misconfiguration. To paraphrase the &lt;a href="https://stackoverflow.com/questions/77127/when-to-throw-an-exception/77361#77361" rel="noopener noreferrer"&gt;earlier definition of exceptions from StackOverflow&lt;/a&gt;, it's a "fundamental assumption of the current code being false".&lt;/p&gt;

&lt;p&gt;Of course, running that code when &lt;code&gt;System.get_env&lt;/code&gt; call evaluates to &lt;code&gt;nil&lt;/code&gt; &lt;strong&gt;will&lt;/strong&gt; result in an exception from &lt;code&gt;HttpClient&lt;/code&gt;. However, instead, you can be more defensive and perform a direct check in the &lt;code&gt;WebhookSender&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;Imagine you swap &lt;code&gt;HttpClient&lt;/code&gt; for &lt;code&gt;BetterHttpClient&lt;/code&gt; in the future, and all exceptions change. Now, throughout your application, you must fix all the places where you use a reported exception (for example, when providing an informative error message to the client). And this is because you changed a dependency, an implementation detail.&lt;/p&gt;



&lt;h2&gt;
  
  
  How to Define a Custom Exception in Elixir
&lt;/h2&gt;

&lt;p&gt;As we have seen, exceptions in Elixir are just "special" structs. They are special because they have an &lt;code&gt;__exception__&lt;/code&gt; field, which holds a value of &lt;code&gt;true&lt;/code&gt;. While we could just use a &lt;code&gt;Map&lt;/code&gt; of regular &lt;code&gt;defstruct&lt;/code&gt;, this exception would not work nicely with all the tooling around exceptions.&lt;/p&gt;

&lt;p&gt;To define a proper exception, we should use the &lt;code&gt;defexception&lt;/code&gt; macro. Let's do this for the webhook example we looked at earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;WebhookSender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ConfigurationError&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defexception&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is as simple as that. You can then improve the code of the &lt;code&gt;WebhookSender&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;WebhookSender&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"WEBHOOK_ENDPOINT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ConfigurationError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="s2"&gt;"WEBHOOK_ENDPOINT env var not defined"&lt;/span&gt;
      &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;HttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run this code (of course, assuming the variable is not set), it will show an error just like with a regular exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;**&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;WebhookSender.ConfigurationError&lt;span class="o"&gt;)&lt;/span&gt; WEBHOOK_ENDPOINT &lt;span class="nb"&gt;env &lt;/span&gt;var not defined
    exceptions_test.exs:24: &lt;span class="o"&gt;(&lt;/span&gt;file&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.0&lt;span class="o"&gt;)&lt;/span&gt; lib/code.ex:1245: Code.require_file/2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This exception clearly shows where the error originated from: a &lt;code&gt;WebhookSender&lt;/code&gt; module. Imagine that, instead, you see something like &lt;code&gt;HttpClient.CannotPerformRequest&lt;/code&gt; here. Chances are you are using &lt;code&gt;HttpClient&lt;/code&gt; in multiple places in the application. First, you must traverse the stack trace and find out which &lt;code&gt;HttpClient&lt;/code&gt; invocation is the culprit. Then, you still have to figure out the actual reason for the error.&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;:message&lt;/code&gt; is just an example of a field you can define on the exception. Although it's a nice default, it is not strictly needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;SpaceshipConstruction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;IncompatibleModules&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defexception&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:module_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:module_b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;SpaceshipConstruction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;IncompatibleModules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;module_a:&lt;/span&gt; &lt;span class="no"&gt;LithiumLoadingBay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;module_b:&lt;/span&gt; &lt;span class="no"&gt;OxygenTreatmentPlant&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this code runs, however, it will crash on attempting to format the exception message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;**&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;SpaceshipConstruction.IncompatibleModules&lt;span class="o"&gt;)&lt;/span&gt; got UndefinedFunctionError with message &lt;span class="s2"&gt;"function SpaceshipConstruction.IncompatibleModules.message/1 is undefined or private"&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;retrieving Exception.message/1 &lt;span class="k"&gt;for&lt;/span&gt; %SpaceshipConstruction.IncompatibleModules&lt;span class="o"&gt;{&lt;/span&gt;module_a: LithiumLoadingBay, module_b: OxygenTreatmentPlant&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; Stacktrace:
    SpaceshipConstruction.IncompatibleModules.message&lt;span class="o"&gt;(&lt;/span&gt;%SpaceshipConstruction.IncompatibleModules&lt;span class="o"&gt;{&lt;/span&gt;module_a: LithiumLoadingBay, module_b: OxygenTreatmentPlant&lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.0&lt;span class="o"&gt;)&lt;/span&gt; lib/exception.ex:66: Exception.message/1
    &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.0&lt;span class="o"&gt;)&lt;/span&gt; lib/exception.ex:117: Exception.format_banner/3
    &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.0&lt;span class="o"&gt;)&lt;/span&gt; lib/kernel/cli.ex:102: Kernel.CLI.format_error/3
    &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.0&lt;span class="o"&gt;)&lt;/span&gt; lib/kernel/cli.ex:183: Kernel.CLI.print_error/3
    &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.0&lt;span class="o"&gt;)&lt;/span&gt; lib/kernel/cli.ex:145: anonymous fn/3 &lt;span class="k"&gt;in &lt;/span&gt;Kernel.CLI.exec_fun/2

    space_exceptions.exs:5: &lt;span class="o"&gt;(&lt;/span&gt;file&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.0&lt;span class="o"&gt;)&lt;/span&gt; lib/code.ex:1245: Code.require_file/2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two ways to fix this without having to manually pass an error message every time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a &lt;code&gt;message/1&lt;/code&gt; function to an exception module.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;SpaceshipConstruction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;IncompatibleModules&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defexception&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:module_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:module_b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Given modules are not compatible"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, an argument to the function is an exception itself, so you can construct a more precise error message from it. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="s2"&gt;"Module &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;module_a&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;module_b&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; are not compatible"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Provide a default message.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;SpaceshipConstruction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;IncompatibleModules&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defexception&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:module_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:module_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="s2"&gt;"Given modules are not compatible"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Repackaging Exceptions
&lt;/h2&gt;

&lt;p&gt;One interesting use case for custom exceptions is when you want to "repackage" an existing exception to fit a specific condition. This can make the exception stand out more in your error tracker.&lt;/p&gt;

&lt;p&gt;For example, we did that with database deadlocks at the company I work for. Deadlocks are one of the hardest database-related errors to track, but they are just reported as &lt;code&gt;Postgrex.Error&lt;/code&gt;. We wanted clearer visibility over when these errors happen (compared to other &lt;code&gt;Postgrex.Error&lt;/code&gt;s) and on which GraphQL mutations.&lt;/p&gt;

&lt;p&gt;So, I added an &lt;a href="https://hexdocs.pm/absinthe/Absinthe.Middleware.html" rel="noopener noreferrer"&gt;Absinthe middleware&lt;/a&gt; checking for the exception. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Middlewares&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ExceptionHandler&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Absinthe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Resolution&lt;/span&gt;
  &lt;span class="nv"&gt;@behaviour&lt;/span&gt; &lt;span class="no"&gt;Absinthe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Middleware&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Resolution&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__STACKTRACE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;~r/ERROR 40P01/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;report_deadlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__STACKTRACE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="nv"&gt;@error_reporter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;report_exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;stacktrace:&lt;/span&gt; &lt;span class="n"&gt;__STACKTRACE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;resolution&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;report_deadlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stacktrace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;original_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mutation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_mutation_name_from_process_metadata&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;reraise&lt;/span&gt; &lt;span class="no"&gt;DeadlockDetected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="s2"&gt;"Deadlock in mutation &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mutation&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;original_message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;stacktrace&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt;
      &lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="nv"&gt;@error_reporter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;report_exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;stacktrace:&lt;/span&gt; &lt;span class="n"&gt;__STACKTRACE__&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This might seem like a lot of code. Let's break it down a little. When an exception is raised, we check if its message contains &lt;code&gt;ERROR 40P01&lt;/code&gt; (code for a deadlock).&lt;/p&gt;

&lt;p&gt;Then, we raise a custom &lt;code&gt;DeadlockDetected&lt;/code&gt; exception and immediately rescue it to send it to an &lt;a href="https://www.appsignal.com/tour/errors" rel="noopener noreferrer"&gt;error reporter, such as AppSignal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, instead of generic &lt;code&gt;Postgrex.Error&lt;/code&gt;s, often mixed with other database exceptions, we have a separate class of exceptions just dedicated to deadlocks. And custom exception messages allow us to quickly identify the mutation with deadlock-unsafe code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repackaging Exits as Exceptions
&lt;/h2&gt;

&lt;p&gt;Another case for custom exceptions is when you want to transform an exit into an exception. This might be because your error reporting software does not support exits, or you may just want a more specific message than the default.&lt;/p&gt;

&lt;p&gt;The most common case for exits is timeouts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ImportantTask&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;ImportantTask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, we spawn an async task that takes 200 milliseconds to complete, but we allow it to run for 100 ms. Here's the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;**&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; exited &lt;span class="k"&gt;in&lt;/span&gt;: Task.await&lt;span class="o"&gt;(&lt;/span&gt;%Task&lt;span class="o"&gt;{&lt;/span&gt;mfa: &lt;span class="o"&gt;{&lt;/span&gt;:erlang, :apply, 2&lt;span class="o"&gt;}&lt;/span&gt;, owner: &lt;span class="c"&gt;#PID&amp;lt;0.96.0&amp;gt;, pid: #PID&amp;lt;0.103.0&amp;gt;, ref: #Reference&amp;lt;0.131053822.3597205508.67962&amp;gt;}, 100)&lt;/span&gt;
    &lt;span class="k"&gt;**&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;EXIT&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;time &lt;/span&gt;out
    &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.0&lt;span class="o"&gt;)&lt;/span&gt; lib/task.ex:830: Task.await/2
    &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.0&lt;span class="o"&gt;)&lt;/span&gt; lib/code.ex:1245: Code.require_file/2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's pretty generic, isn't it? Let's make it a bit nicer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;ImportantTask&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;defexception&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;catch&lt;/span&gt;
  &lt;span class="ss"&gt;:exit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;ImportantTask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we define a custom &lt;code&gt;Timeout&lt;/code&gt; exception, then catch an exit and raise an exception instead. The result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;**&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ImportantTask.Timeout&lt;span class="o"&gt;)&lt;/span&gt; exited &lt;span class="k"&gt;in&lt;/span&gt;: Task.await&lt;span class="o"&gt;(&lt;/span&gt;%Task&lt;span class="o"&gt;{&lt;/span&gt;mfa: &lt;span class="o"&gt;{&lt;/span&gt;:erlang, :apply, 2&lt;span class="o"&gt;}&lt;/span&gt;, owner: &lt;span class="c"&gt;#PID&amp;lt;0.96.0&amp;gt;, pid: #PID&amp;lt;0.106.0&amp;gt;, ref: #Reference&amp;lt;0.342776.2255814665.42176&amp;gt;}, 100)&lt;/span&gt;
    &lt;span class="k"&gt;**&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;EXIT&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;time &lt;/span&gt;out
    exits_to_exceptions.exs:12: ImportantTask.run/0
    &lt;span class="o"&gt;(&lt;/span&gt;elixir 1.14.0&lt;span class="o"&gt;)&lt;/span&gt; lib/code.ex:1245: Code.require_file/2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While you may consider that this error message is still a bit cryptic, it adds two main quality-of-life improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;code&gt;ImportantTask.Timeout&lt;/code&gt; exception, which makes it easy to assign the error to a particular piece of functionality in your code.&lt;/li&gt;
&lt;li&gt;A line from our code in the stack trace (&lt;code&gt;exits_to_exceptions.exs:12: ImportantTask.run/0&lt;/code&gt;). Note that the default exit message does not include this, so it's much harder to find the offending place in the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this post, we learned how to define custom exceptions in Elixir. They are very useful when building a library, but they also have their place in your application code.&lt;/p&gt;

&lt;p&gt;By repackaging generic exceptions or trapping and re-raising exits, you can make your code much easier to debug if something goes wrong. Your future self (and your colleagues) will be grateful!&lt;/p&gt;

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

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

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Reconfiguring your application live with dRuby</title>
      <dc:creator>Paweł Świątkowski</dc:creator>
      <pubDate>Wed, 10 Jan 2024 21:47:08 +0000</pubDate>
      <link>https://dev.to/katafrakt/reconfiguring-your-application-live-with-druby-4fgo</link>
      <guid>https://dev.to/katafrakt/reconfiguring-your-application-live-with-druby-4fgo</guid>
      <description>&lt;p&gt;dRuby is a pretty old but relatively unknown part of Ruby standard distribution. I first wrote about it &lt;a href="https://katafrakt.me/2018/06/06/hidden-jewels-ruby-stdlib/" rel="noopener noreferrer"&gt;here&lt;/a&gt; in 2018 and I have to admit that to this day I haven really found a production use case for it. However, I still think it a gem worth knowing, even if only to impress you Ruby friends on a conference afterparty.&lt;/p&gt;

&lt;p&gt;To demonstrate what dRuby can do, we will write a simple application. It will periodically check Mastodon API of &lt;a href="https://ruby.social/" rel="noopener noreferrer"&gt;ruby.social&lt;/a&gt; server and check for new messages (called toots). To keep things as simple as possible, we'll just use &lt;code&gt;net/http&lt;/code&gt; as an HTTP client. Here's our first draft:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"net/http"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"json"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RubySocialChecker&lt;/span&gt;
  &lt;span class="no"&gt;ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://ruby.social/api/v1/timelines/public?local=true"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&amp;amp;limit=1"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@last_toot_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;run_loop&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_loop&lt;/span&gt;
    &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&amp;amp;min_id=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@last_toot_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;toot&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;toot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'uri'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="vi"&gt;@last_toot_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;RubySocialChecker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run it and you're lucky (i.e. someone posts something), you will see links to new toots being printed to stdout. As you see, the code is not particularly complicated. So let's complicate it with seemengly no good reason. In the next step we will extract a configuration to a separate class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:debug&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;interval: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;debug: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;
    &lt;span class="vi"&gt;@debug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RubySocialChecker&lt;/span&gt;
  &lt;span class="no"&gt;ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://ruby.social/api/v1/timelines/public?local=true"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&amp;amp;limit=1"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@last_toot_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;run_loop&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_loop&lt;/span&gt;
    &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&amp;amp;min_id=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@last_toot_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] Fetched &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; toots"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;toot&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;toot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'uri'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="vi"&gt;@last_toot_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hmm... This starts to look serious! We now have a config, which we pass to the checker. The config specifies how often we should check for new toots and also has a flag for a debug mode. In this mode we output a message with how many toot we just fetched, so you can at least see that something is happening.&lt;/p&gt;

&lt;p&gt;We can run our checker in a debug mode now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;debug: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;RubySocialChecker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, but why did we do that? Because now we want to add dRuby. This gem essentially allows you to "hook into" your running Ruby program from another process in a controlled manner. Let's add dRuby server to our program right before the code starting the checker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"drb/drb"&lt;/span&gt;
&lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"druby://localhost:8787"&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="no"&gt;DRb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;RubySocialChecker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, run the program (note that the debug mode is off) and now in a different terminal window fire up IRB. In the IRB session, do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):001&amp;gt; require "drb/drb"
=&amp;gt; true
irb(main):002&amp;gt; DRb.start_service
=&amp;gt; #&amp;lt;DRb::DRbServer:0x00007f60d2da7868 ...&amp;gt;
irb(main):003&amp;gt; config = DRbObject.new_with_uri("druby://localhost:8787")
=&amp;gt; #&amp;lt;DRb::DRbObject:0x00007f60d27d3d10 @ref=nil, @uri="druby://localhost:8...
irb(main):004&amp;gt; config.debug = true
=&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you now look at the terminal where your program is running... Magic! The debug messages started to show. Now let's spice the things up a bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):005&amp;gt; config.interval = 1
=&amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logs show up even faster. We haven't touched anything in the running program, it does not read from any database on each loop step, but we managed to alter its behaviour from the outside. It's also worth noting that the IRB process does not know anything about the checker or config. If you try to reference them, you'll see the uninitialized constant error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):009&amp;gt; RubySocialChecker
(irb):9:in `&amp;lt;main&amp;gt;': uninitialized constant RubySocialChecker (NameError)
    from /home/katafrakt/.asdf/...
irb(main):010&amp;gt; Config
(irb):10:in `&amp;lt;main&amp;gt;': uninitialized constant Config (NameError)
Did you mean?  RbConfig
    from /home/katafrakt/.asdf/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See this in action:&lt;/p&gt;

&lt;h2&gt;
  
  
  Ok, but why?
&lt;/h2&gt;

&lt;p&gt;Like I said, you probably won't benefit from it in your Rails application. Web applications are stateless by nature and here you need some in-memory state to hook into. However, there are some cases, mostly long-running processes, where this can be useful. The first time I've seen a magic like that, although it was not in Ruby, was an IRC bot, in which admin was able to turn some features on and off live, add people to denylist etc., all without restarting the application.&lt;/p&gt;

&lt;p&gt;It might also be an alternative to logs. If you have, for example, a scraper that scrapes thousands of pages, instead of log results every 100 of them, you can expose an interface over dRuby to ask how many pages you checked, how many had useful results and even return these results.&lt;/p&gt;

&lt;p&gt;But even if you don't do any of these things, it's good to know that doing things like that is possible and you don't even have to install any additional gem to do that.&lt;/p&gt;

&lt;p&gt;You can read more about dRuby &lt;a href="https://ruby-doc.org/3.3.0/stdlibs/drb/DRb.html" rel="noopener noreferrer"&gt;in the docs&lt;/a&gt;. Or you can even &lt;a href="https://www.amazon.com/dRuby-Book-Distributed-Parallel-Computing/dp/193435693X" rel="noopener noreferrer"&gt;buy a book&lt;/a&gt; about it.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>druby</category>
    </item>
    <item>
      <title>Functional domain modeling in Elixir</title>
      <dc:creator>Paweł Świątkowski</dc:creator>
      <pubDate>Wed, 25 Oct 2023 07:16:41 +0000</pubDate>
      <link>https://dev.to/katafrakt/functional-domain-modeling-in-elixir-ab8</link>
      <guid>https://dev.to/katafrakt/functional-domain-modeling-in-elixir-ab8</guid>
      <description>&lt;p&gt;In this blog post I want to explore how using techniques from functional modeling can improve the code written in Elixir. We will see if this way we can avoid some common pitfalls often met in Elixir codebases that lead to convoluted code and, especially, to overly complex (and slow) tests.&lt;/p&gt;

&lt;p&gt;For the purpose of this, we will be implementing the following piece from a Library (as in book borrowing) domain: borrowing a book. I am fully aware that libraries work differently in different places of the world, so I picked a handful of requirements. To borrow a book, the following conditions must be met:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A patron must have an active subscription in the library&lt;/li&gt;
&lt;li&gt;A patron can have at most 5 books borrowed at any time (so if this is a sixth book, it cannot be borrowed)&lt;/li&gt;
&lt;li&gt;A patron cannot have any unpaid fines from keeping the books for too long&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many of us will on instinct write a code similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;borrow_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;get_active_subscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;Finance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_unpaid_fines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;currently_borrowed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;get_currently_borrowed_books&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currently_borrowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Borrowing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:too_many_borrowed_books&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;[%&lt;/span&gt;&lt;span class="no"&gt;Fine&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:unpaid_fines&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:subscription_not_found&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:no_active_subscription&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;get_active_subscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Subscriptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:subscription_not_found&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This code is not inherently bad by any means. It can be followed quite easily to check what requirements have been implemented. It has reasonable separation of concerns (subscription checks in &lt;code&gt;Subscriptions&lt;/code&gt;  context etc.). However, at least for me, it does not have a great look'n'feel and also has some more objective issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It creates tight coupling between current module (supposedly &lt;code&gt;Borrowing&lt;/code&gt;) and other modules - &lt;code&gt;Subscriptions&lt;/code&gt; and &lt;code&gt;Finance&lt;/code&gt;. Some changes in those module might cause necessity to make changes in this module as well. This is especially visible in tests: if the test setup required for &lt;code&gt;get_active_subscription&lt;/code&gt; changes (because, for example, you check it in an external service now, instead of a local database), you have to adjust the setup for test testing &lt;code&gt;borrow_book&lt;/code&gt;; otherwise it won't work.&lt;/li&gt;
&lt;li&gt;It mixes "technical" concerns of saving into the database with domain logic.&lt;/li&gt;
&lt;li&gt;It accepts pretty meaningless IDs (numbers, uuids) as input. If you make a mistake and call it in the reverse order &lt;code&gt;borrow_book(some_book_id, some_patron_id)&lt;/code&gt;, it will be relatively hard to detect what went wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see if applying techniques of functional modeling can help us here.&lt;/p&gt;
&lt;h2&gt;
  
  
  Functional what?
&lt;/h2&gt;

&lt;p&gt;But first, we need to &lt;em&gt;finally&lt;/em&gt; get to the definition of functional modeling. I'm going to use the definition from &lt;a class="mentioned-user" href="https://dev.to/jakub_zalas"&gt;@jakub_zalas&lt;/a&gt; article: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/jakub_zalas" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F400372%2F0ac7f6a8-3ac9-4a21-83aa-4b2e148d95b1.jpg" alt="jakub_zalas"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/jakub_zalas/functional-domain-model-o3j" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Functional domain model&lt;/h2&gt;
      &lt;h3&gt;Jakub Zalas ・ Oct 13 '23&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#kotlin&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#softwareengineering&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#functional&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ddd&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;A functional domain model is made of pure functions and immutable types.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Immutable part is really easy in Elixir, because things are immutable by default, until you try really hard to make them mutable. But the other part is interesting and, in my opinion, often overlooked in Elixir, given it's usually advertised as functional language.&lt;/p&gt;

&lt;p&gt;Just to recap: a pure function means that for any given input it returns the same predictable output. In other words, it does not have side effects or rely on such - current time, database data, external API calls, I/O etc.&lt;/p&gt;

&lt;p&gt;Using pure functions in functional domain modeling allows us to concentrate on the essence of the domain - verifying domain requirements - instead of dabbling with everything around that. We will see what we can do with our little function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's model the domain functionally!
&lt;/h2&gt;

&lt;p&gt;But first, let's reiterate on the requirements:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A patron must have an &lt;u&gt;active subscription&lt;/u&gt; in the library. A patron can have at most 5 &lt;u&gt;books borrowed&lt;/u&gt; at any time. A patron cannot have any &lt;u&gt;unpaid fines&lt;/u&gt; from keeping the books for too long&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I made them a little more terse, but also underlined a couple of things. These are the information we need to run our business logic. We don't (for now) care how to obtain it. We just assume it is available to us. There are a few possibilities of how to pass it into our function, but we will use the simplest one, which is: positional arguments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;borrow_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Subscription&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Borrowing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Fine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;borrow_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;active_subscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currently_borrowed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unpaid_fines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or actually we could simplify it even further, if we feel like it, by using just primitive values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;borrow_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;borrow_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;has_active_subscription?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number_of_borrowed_books&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;has_unpaid_fines?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whether to choose the first option or second is a choice you need to make. First one is more verbose, makes it more clear what you should pass by just looking at the types. However, in a way, it does create some kind of loose coupling. If you change the name &lt;code&gt;Borrowing&lt;/code&gt; to &lt;code&gt;Lending&lt;/code&gt;, you will have to change the type signature of the function (although logic would remain untouched).&lt;/p&gt;

&lt;p&gt;The first definition is at the same time more open to extension in the future. Maybe if unpaid fines amount to less than $10 you still can borrow? Or perhaps you can if you only have one from last month? Since you have a list of unpaid fines at hand, you might change the check without changing the signature of the function. Using only the primitive values, you'd have to change boolean argument &lt;code&gt;has_unpaid_fines?&lt;/code&gt; to some other, like integer &lt;code&gt;sum_of_unpaid_fines_amount&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Leaving this quandary aside, let us note on important thing here: we no longer reference neither the book, nor the patron herself! It turns out that to &lt;strong&gt;make a decision&lt;/strong&gt; whether a book might be borrowed or not, in this case, these are not exactly needed. This is also very important observation: a pure domain function makes a decision whether something should happen or not and returns it. It doesn't necessarily need to execute that decision.&lt;/p&gt;

&lt;p&gt;After all this talk, let's now try to actually write the function's body (I picked the first signature, by the way):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;borrow_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;active_subscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currently_borrowed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unpaid_fines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;cond&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;is_nil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;active_subscription&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:no_active_subscription&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currently_borrowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:too_many_borrowed_books&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unpaid_fines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:unpaid_fines&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="no"&gt;true&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used &lt;code&gt;cond&lt;/code&gt; here instead of &lt;code&gt;with&lt;/code&gt;, because in my opinion it fits a bit better now. This might have something to do with my Ruby background though, where I'd use guard clauses with early return in the beginning of the function to check potential failures and only then execute the happy path. However, you could just as well still use &lt;code&gt;with&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It might seem that this function does not do a lot compared to our original function. That observation is 100% correct. It does much less. In fact it does &lt;strong&gt;precisely&lt;/strong&gt; what we wanted it to do: checks the domain requirement. Nothing more, nothing less. It does not rely on any mutable global state (a.k.a. the database), it does not write to the database - it simply &lt;strong&gt;makes a decision&lt;/strong&gt; whether borrowing can happen or not.&lt;/p&gt;

&lt;p&gt;As you can imagine, testing it is a bliss. No setup, no transactions and Ecto sandboxes. You just use &lt;code&gt;ExUnit.Case&lt;/code&gt;, you call the function and assert on the result.&lt;/p&gt;

&lt;p&gt;There is only one question lingering here...&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay, but what next?
&lt;/h2&gt;

&lt;p&gt;The domain logic of our application does not exist in vain. Let's be honest here: most web application are glorified wrappers over the database. Almost every request consists of three phases: fetch some data, process this data, persist the result in the database. We are going to build upon this observation. We will take out pure domain function and will make a "functional sandwich" (also sometimes called I/O sandwich or &lt;a href="https://blog.ploeh.dk/2020/03/02/impureim-sandwich/" rel="noopener noreferrer"&gt;impureim sandwich&lt;/a&gt;). This technique bases on isolating "impure" part of the application to the beginning and the end of the request handling, while the middle remains functionally pure. The whole sandwich builder is usually called (at least in DDD-ish circles) a service layer. We need such layer too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Borrowing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;BorrowingService&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;borrow_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# something&lt;/span&gt;
    &lt;span class="c1"&gt;# call our pure function&lt;/span&gt;
    &lt;span class="c1"&gt;# something&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks familiar, doesn't it? The implementation might look familiar too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;borrow_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Subscriptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;unpaid_fines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Finance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_unpaid_fines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;currently_borrowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_currently_borrowed_books&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;borrow_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currently_borrowed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unpaid_fines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;lt;- !!!&lt;/span&gt;
    &lt;span class="ss"&gt;:ok&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="no"&gt;Borrowing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patron_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; 
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One might say we did not win a lot, but actually we did. We have our core function called in a marked line. Everything before it is preparation, everything after is "post-processing". Our core function is well-tested, so we probably don't need to exhaustively test the service. Only 1 – 2 "plumbing tests" (the name comes from Filip Pacanowski), but this is not a domain logic testing. It just checks if we connected the building blocks correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  To retrospect
&lt;/h2&gt;

&lt;p&gt;We split our initial function into two. One of them is a pure function, which means that it's super-easy and super-fast to test. Incidentally, it also handles the most important part of the whole implementation - the actual domain logic. The other function adds "two slices of bread" on both sides of the function: to prepare the required data and to save the result in the database (if needed).&lt;/p&gt;

&lt;p&gt;With that we separated the concerns a little but. If the data source or database schema changes, only a service function should be affected. The domain logic stays the same and the function in the &lt;code&gt;Domain&lt;/code&gt; module is unaffected by the change. In the opposite direction, if the domain requirements change (for example with allowing to borrow even if there are some unpaid fines), only the domain function will change.&lt;/p&gt;

&lt;p&gt;It is, of course, possible that both functions will have to change at the same time: if the change in requirements causes the need to fetch more or different data. In our case, let's imagine that a book of "erotica" genre can be lent only to adult patrons. In that case not only you need to actually fetch a patron and a book, but you also need to pass additional data to the domain function. This might quickly lead to an explosion of the number of the argument. There are some ways to improve this situation, but this is outside of the scope of this article (perhaps I'll write another one).&lt;/p&gt;

&lt;p&gt;All in all, I think this kind of the design is not only more functional, but more testable and a bit nicer on the eyes (easier to scan). And I certainly would prefer writing more code like this than like our initial &lt;code&gt;borrow_book&lt;/code&gt; version.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>ddd</category>
      <category>functional</category>
    </item>
    <item>
      <title>On using Phlex</title>
      <dc:creator>Paweł Świątkowski</dc:creator>
      <pubDate>Wed, 04 Oct 2023 07:20:12 +0000</pubDate>
      <link>https://dev.to/katafrakt/on-using-phlex-b9e</link>
      <guid>https://dev.to/katafrakt/on-using-phlex-b9e</guid>
      <description>&lt;p&gt;Seb Wilgosz recently published an article &lt;a href="https://hanamimastery.com/episodes/48-phlex-in-hanami" rel="noopener noreferrer"&gt;Phlex with Hanami&lt;/a&gt; on Hanami Mastery. It made the latest &lt;a href="https://rubyweekly.com/issues/671" rel="noopener noreferrer"&gt;Ruby Weekly&lt;/a&gt; issue as the first item and generally was quite well received. I write this post as an addendum to Seb's article, giving my perspective on using Phlex in Ruby applications. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important note&lt;/strong&gt;: you should probably read Seb's article first, as I don't do an introduction to Phlex here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Establishing credentials
&lt;/h3&gt;

&lt;p&gt;Who am I to talk? I have been using Phlex in my toy project, writing a &lt;a href="https://github.com/katafrakt/palaver" rel="noopener noreferrer"&gt;forum engine in Hanami&lt;/a&gt;. I also added it to some pages in one of my oldest Rails projects, serving a small community in production for 10+ years (source code not available). It's not too much, but more than just playing around with hello worlds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phlex – a missing view layer we always needed
&lt;/h2&gt;

&lt;p&gt;The most important characteristic of Phlex in my opinion is that, at least for Rails, it's a view layer we never had but always needed. Rails, despite stating that it's MVC, does not really offer much on the "V" front. It feels much more like a model-template-controller, where templates are dumb and typical responsibilities of a view are shoved into a model, controller or a separate kind of object (like form objects). Oh, and there are helpers that do not fit anywhere in this model.&lt;/p&gt;

&lt;p&gt;Let's take a quick example. The requirement is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;User has an optional avatar attached. In the user profile view we display that avatar. If the avatar is not present, we use a placeholder image. However, if it's a VIP user, the placeholder image is different than the one for a regular user.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With "traditional Rails" I wouldn't be surprised if I saw it added as &lt;code&gt;avatar_url&lt;/code&gt; method of a &lt;code&gt;User&lt;/code&gt; model. The alternative is to put the logic directly in the ERB with &lt;code&gt;if&lt;/code&gt;s and &lt;code&gt;else&lt;/code&gt;s, or to use a helper method. The first issue here is that Rails does not really offer an answer on where to put that kind of logic. The second - that all these options are flawed in some way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In a model – this is a purely presentational thing, it simply does not belong in the model&lt;/li&gt;
&lt;li&gt;In an ERB file – it makes the template less readable, the logic is not reusable (unless you use partials) and perhaps more importantly: it's really hard to test, as Rails basically offers only testing views by executing the whole controller action&lt;/li&gt;
&lt;li&gt;In a helper – it is probably the right place, but many people will reject this as "not OOP enough"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How would that look in Phlex? (a proper view layer)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Phlex&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;template&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"user-profile"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;src: &lt;/span&gt;&lt;span class="n"&gt;avatar_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;avatar_url&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_avatar?&lt;/span&gt;
      &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;avatar_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:small&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vip?&lt;/span&gt;
      &lt;span class="s2"&gt;"/path/to/vip_avatar_placeholder.png"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="s2"&gt;"/path/to/avatar_placeholder.png"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. this is just a Ruby object with a state (user is available via an instance variable) where we can define all kinds of helper methods we want. It's all collocated together, you don't need to do file-jumping in search of a definition of &lt;code&gt;user_avatar_url&lt;/code&gt; helper method. It's also testable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phlex is Ruby - it's extremely testable
&lt;/h2&gt;

&lt;p&gt;To test the code above you don't need to instrument the whole request machinery. You don't even need a user persisted in the database, you also don't need to set up a session. It's a simple test, testing exactly what you need to test. It's also extremely fast to run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"phlex/testing/view_helper"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestHello&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Minitest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Phlex&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Testing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FragmentHelper&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_user_with_avatar&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;avatar: &lt;/span&gt;&lt;span class="s2"&gt;"some_file.png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="no"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/avatars/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/avatar.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"img"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_user_without_avatar_but_vip&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;avatar: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;vip: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="no"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/vip_avatar_placeholder.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"img"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_user_without_avatar&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;avatar: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;vip: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="no"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/avatar_placeholder.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"img"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Phlex is Ruby - you can do Ruby things with it
&lt;/h2&gt;

&lt;p&gt;Phlex views being pure Ruby classes mean that you can do everything what you could do with any other Ruby class. I, for example, use a mixin to include common typography helpers in a Phlex view.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticlePartial&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Phlex&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Ui&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Typography&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;template&lt;/span&gt;
    &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"article"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;heading1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;motto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example both &lt;code&gt;heading1&lt;/code&gt; and &lt;code&gt;subtitle&lt;/code&gt; are just methods in &lt;code&gt;Ui::Typography&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;But that's not it. If you really want it for some reason, you can do really fancy Ruby things, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Phlex&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;template&lt;/span&gt;
    &lt;span class="n"&gt;emph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:u&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;

    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="s2"&gt;"this text will have "&lt;/span&gt;
      &lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"emphasis"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="s2"&gt;" of some kind"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On each render, the word "emphasis" will randomly be underlined, bold or in italics. Because why not?&lt;/p&gt;

&lt;h2&gt;
  
  
  Phlex is Ruby - you can package it
&lt;/h2&gt;

&lt;p&gt;If you have a set of components, such as &lt;code&gt;Card&lt;/code&gt;, &lt;code&gt;DataTable&lt;/code&gt;, &lt;code&gt;PaginationControls&lt;/code&gt; etc., that just accept simple primitive values (strings, numbers, arrays, but not objects such as models), you can easily put them in a gem. And suddenly you have a distributable design system, which you could use across several applications - and have them look the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some final words
&lt;/h2&gt;

&lt;p&gt;This is my experience with working with Phlex so far. However, there might be some questions popping up in your head.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I rewrite everything in Phlex?
&lt;/h3&gt;

&lt;p&gt;Phlex has some downsides. The most important is that is abstracts out HTML as Ruby and it &lt;em&gt;might&lt;/em&gt; have some negative consequences on your workflow. If you are copying a lot of HTML from open source projects, StackOverflow or HTML file prepared by your designer, you will have to convert everything into Phlex Ruby code. This might be a huge slowdown.&lt;/p&gt;

&lt;p&gt;Another problem is if you expect a designer or a UX specialist to actually modify HTML templates in your project. While ERB views are almost-HTML, Phlex views are not. It might be difficult to comprehend how they work for people not knowing Ruby.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does it compare to ViewComponent?
&lt;/h3&gt;

&lt;p&gt;I have used both ViewComponent and Cells when ViewComponent has not yet been born. They both offer a bit different approach, in which you put a mini-controllers inside your templates. There controllers perform some logic and then render another template, defined in a separate file. For many people, this might feel more familiar or more in line with Rails philosophy.&lt;/p&gt;

&lt;p&gt;I personally think that the Phlex approach is closer to the original Rails approach to views. It does not introduce the whole new layer of those mini-controller-components rendering their own views (so you get model-view-template-component-componenttemplate), but rather replaces the dumb default view layer with something smarter, more Rubyish and testable.&lt;/p&gt;

&lt;p&gt;Of course, everyone's mileage can vary. These are just tools that are, in fact, very different and it's not like one is automatically better than another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://katafrakt.me/2023/10/03/on-using-phlex/" rel="noopener noreferrer"&gt;Original version&lt;/a&gt; of this blog post&lt;/li&gt;
&lt;li&gt;&lt;a href="https://phlex.fun" rel="noopener noreferrer"&gt;Phlex website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.namingthings.org/p/ruby-is-a-beautiful-language-for" rel="noopener noreferrer"&gt;Ruby is a beautiful language for writing views&lt;/a&gt; – a blog post by Joel Drapper, author of Phlex, where he explains some of his motivation for the approach&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://remoteruby.com/197" rel="noopener noreferrer"&gt;Phlexing with Joel Drapper&lt;/a&gt; – a Remote Ruby podcast episode about Phlex from September 2022&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.phlexing.fun/" rel="noopener noreferrer"&gt;Phlexing&lt;/a&gt; – an ERB to Phlex converter&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/serpapi/nokolexbor" rel="noopener noreferrer"&gt;Nokolexbor&lt;/a&gt; – a faster drop-in replacement for Nokogiri, which I actually use to test Phlex views&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>phlex</category>
    </item>
    <item>
      <title>Data migrations in regular migrations and why you (probably) should not do that</title>
      <dc:creator>Paweł Świątkowski</dc:creator>
      <pubDate>Mon, 18 Sep 2023 21:58:35 +0000</pubDate>
      <link>https://dev.to/katafrakt/data-migrations-in-regular-migrations-and-why-you-probably-should-not-do-that-2eem</link>
      <guid>https://dev.to/katafrakt/data-migrations-in-regular-migrations-and-why-you-probably-should-not-do-that-2eem</guid>
      <description>&lt;p&gt;Every once in a while I read Lucian Ghinda's &lt;a href="https://newsletter.shortruby.com/" rel="noopener noreferrer"&gt;Short Ruby&lt;/a&gt; newsletter and I see a "take" there which begs for some elaboration and at least slight disagreement. So I decided to try to write these short (but too long for a tweet) replies here.&lt;/p&gt;




&lt;p&gt;Today's topic comes from &lt;a href="https://newsletter.shortruby.com/p/edition-59" rel="noopener noreferrer"&gt;issue #59&lt;/a&gt;, where Matt Swanson touches the topic of data migrations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fniyt8f4q4lckdm6q1ec4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fniyt8f4q4lckdm6q1ec4.png" alt=" " width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(here is &lt;a href="https://nitter.net/_swanson/status/1701704048947396844" rel="noopener noreferrer"&gt;a link&lt;/a&gt; to the tweet)&lt;/p&gt;

&lt;p&gt;So what's wrong here? I think the first of all we need to divide what Matt conveniently put together, because "don't reference Rails models in migrations" and "don't run data migrations in schema migrations" are two separate rules coming from different backgrounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't reference models
&lt;/h2&gt;

&lt;p&gt;The rule of not referencing models relies on the fact that the code of the models change and it might change a lot. In fact, the assumption here is that the migration referencing a model will successfully run on Monday, but won't run on Thursday, because the model file changed and the assumptions made in migration code are no longer correct.&lt;/p&gt;

&lt;p&gt;I generally agree that the points in the tweet can greatly reduce the risk of that happening. Although at the same time these points are not realistic. Just how would you "run migrations in dev often" during your two-weeks vacations? Fortnight is definitely enough time in larger projects for model and migration to go out of sync, resulting in a migration not being able to run.&lt;/p&gt;

&lt;p&gt;However, I don't want to sweat on it, because that's up to your team if you want to take this risk. The second part is much more interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't run data migrations along schema migrations
&lt;/h2&gt;

&lt;p&gt;Let's say it out loud: the migrations were thought as a tool to consistently modify database &lt;strong&gt;schema&lt;/strong&gt; across the environments. If you don't believe me, just check how the table keeping track of the migrations already run is called.&lt;/p&gt;

&lt;p&gt;However, in time, people started to use it to run data migrations too. It have few obvious upsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You already have a tool for that&lt;/li&gt;
&lt;li&gt;Everyone in your organization is expected to run migrations in dev environment regularly, so they will have their data in sync automatically. You don't need to announce on Slack that "everyone please run &lt;code&gt;rake data:backfill_order_numbers&lt;/code&gt;".&lt;/li&gt;
&lt;li&gt;You already have a step of running it in your deployment pipeline and you don't have to add anything new&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my experience, these advantages start to fade when the project (and especially the database) grows. Here are some problem I encountered with this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data migrations can take a lot of time. If you put schema change with data migration in the same file, you might and up with a table locked exclusively for a prolonged amount of time, effectively bringing your app down.&lt;/li&gt;
&lt;li&gt;Data in production database is often far more exotic than in staging/test environments. The migration might pass on staging, but will fail on production, because of some weird data in the database. Now your pipeline is blocked and you either have to revert or submit a hotfix.&lt;/li&gt;
&lt;li&gt;It's not uncommon to have parameterized data migrations, where you first run it for only some tenants or just in some countries. When you check that everything works fine, you proceed with another batch.&lt;/li&gt;
&lt;li&gt;You have little control over when the data migration is run. In my current company, for example, you just put your PR in a merge queue and you don't really know when it will be merged (and deployed). Sometimes it's hours later. With data migrations, that might put significant stress on the database, it's better to have very strict control when it's run.&lt;/li&gt;
&lt;li&gt;Similarly, you might want to merge and deploy the code during working hours, but run the data migration in off-hours (or even during the weekend). But you have coupled one to the other, so you cannot really do that.&lt;/li&gt;
&lt;li&gt;Last but definitely not least: if you write your data migration as a rake task, you can actually write tests for it. Do you write tests for your migrations?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To me personally, these points highly outweigh the comfort of using schema migrations for data migrations. Sure, you lost the comfort of using the tool that's already there, but at the same time you put the stability of the app, the well-being of the whole team and your peace of mind at risk.&lt;/p&gt;

&lt;p&gt;So I guess my "counter-take" is:&lt;/p&gt;

&lt;p&gt;At least think of having the process of running data migrations in separation from your schema migrations, knowing what's at stake.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>database</category>
    </item>
  </channel>
</rss>
