<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Kevin Gathuku]]></title><description><![CDATA[Adventures in Software Development]]></description><link>https://kevgathuku.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 22:34:09 GMT</lastBuildDate><atom:link href="https://kevgathuku.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Mastering Clojure Iteration: A Practical Guide]]></title><description><![CDATA[You're working with a collection in Clojure and need to iterate over it. You reach for... wait, which one?

for

map

doseq

run!


In my experience, while the official documentation exists, it's often unclear when to use each construct. This guide b...]]></description><link>https://kevgathuku.dev/mastering-clojure-iteration-a-practical-guide</link><guid isPermaLink="true">https://kevgathuku.dev/mastering-clojure-iteration-a-practical-guide</guid><category><![CDATA[Clojure]]></category><category><![CDATA[iteration]]></category><category><![CDATA[list comprehension]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Tue, 30 Dec 2025 00:18:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/VEH_xb3IIqQ/upload/9ba322f7c6d9c37964abd7f7aa87d28b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You're working with a collection in Clojure and need to iterate over it. You reach for... wait, which one?</p>
<ul>
<li><p><code>for</code></p>
</li>
<li><p><code>map</code></p>
</li>
<li><p><code>doseq</code></p>
</li>
<li><p><code>run!</code></p>
</li>
</ul>
<p>In my experience, while the official documentation exists, it's often unclear <strong>when</strong> to use each construct. This guide breaks down the differences with practical examples, so you can make the right choice every time.</p>
<p><strong>TLDR:</strong> Always use <code>run!</code> and <code>doseq</code> for side-effects, and <code>map</code> and <code>for</code> for lazy data transformations</p>
<h3 id="heading-1-map-transform-collections"><strong>1.</strong> <code>map</code> - Transform Collections</h3>
<p><strong>Purpose:</strong> Transform each element of a collection into something else.</p>
<p><strong>Returns:</strong> A lazy sequence of transformed values.</p>
<p><strong>When to use:</strong></p>
<ul>
<li><p>You want to convert one collection into another</p>
</li>
<li><p>You're applying a transformation function</p>
</li>
<li><p>You <strong>need the result</strong> for further processing</p>
</li>
</ul>
<p><strong>Characteristics:</strong></p>
<ul>
<li><p><strong>Lazy</strong> - only computes values when needed</p>
</li>
<li><p><strong>Returns a sequence</strong> - the transformed collection</p>
</li>
<li><p><strong>Pure</strong> - no side effects expected</p>
</li>
</ul>
<pre><code class="lang-clojure"><span class="hljs-comment">;; Transform numbers to their squares</span>
(<span class="hljs-name"><span class="hljs-builtin-name">map</span></span> #(<span class="hljs-name"><span class="hljs-builtin-name">*</span></span> % %) [<span class="hljs-number">1</span> <span class="hljs-number">2</span> <span class="hljs-number">3</span> <span class="hljs-number">4</span>])
<span class="hljs-comment">;; =&gt; (1 4 9 16)</span>

<span class="hljs-comment">;; Extract user IDs</span>
(<span class="hljs-name"><span class="hljs-builtin-name">map</span></span> <span class="hljs-symbol">:user-id</span> users)
<span class="hljs-comment">;; =&gt; ("alice" "bob" "charlie")</span>

<span class="hljs-comment">;; Transform with a function</span>
(<span class="hljs-name"><span class="hljs-builtin-name">map</span></span> str/upper-case [<span class="hljs-string">"hello"</span> <span class="hljs-string">"world"</span>])
<span class="hljs-comment">;; =&gt; ("HELLO" "WORLD")</span>
</code></pre>
<p><strong>When NOT to use map:</strong></p>
<p>The key insight is that <code>map</code> is lazy, so side effects inside <code>map</code> can lead to surprising results.</p>
<p>If you're doing side effects, you probably want <code>doseq</code> or <code>run!</code> instead.</p>
<pre><code class="lang-clojure"><span class="hljs-comment">;; This creates a lazy sequence but doesn't execute</span>
(<span class="hljs-keyword">def</span> <span class="hljs-title">squares</span> (<span class="hljs-name"><span class="hljs-builtin-name">map</span></span> #(<span class="hljs-name"><span class="hljs-builtin-name">do</span></span> (<span class="hljs-name">println</span> <span class="hljs-string">"Computing"</span> %)
                       (<span class="hljs-name"><span class="hljs-builtin-name">*</span></span> % %))
                  [<span class="hljs-number">1</span> <span class="hljs-number">2</span> <span class="hljs-number">3</span>]))
<span class="hljs-comment">;; =&gt; No output yet!</span>

<span class="hljs-comment">;; Realizing the sequence triggers computation</span>
(<span class="hljs-name"><span class="hljs-builtin-name">doall</span></span> squares)
<span class="hljs-comment">;; Computing 1</span>
<span class="hljs-comment">;; Computing 2</span>
<span class="hljs-comment">;; Computing 3</span>
<span class="hljs-comment">;; =&gt; (1 4 9)</span>
</code></pre>
<h3 id="heading-2-for-list-comprehensions"><strong>2.</strong> <code>for</code> - List Comprehensions</h3>
<p><strong>Purpose:</strong> Build a collection with complex iteration logic (filters, multiple bindings, let bindings).</p>
<p><strong>Returns:</strong> A lazy sequence.</p>
<p><strong>When to use:</strong></p>
<ul>
<li><p>You need complex iteration with multiple sequences</p>
</li>
<li><p>You want to filter while iterating</p>
</li>
<li><p>You need <code>let</code> bindings inside the iteration</p>
</li>
<li><p>You're translating a mathematical set notation</p>
</li>
</ul>
<p><strong>Characteristics:</strong></p>
<ul>
<li><p><strong>Lazy</strong> - only computes when realized</p>
</li>
<li><p><strong>Returns a sequence</strong> - the generated collection</p>
</li>
<li><p><strong>Declarative</strong> - reads like "for each X in Y, produce Z"</p>
</li>
</ul>
<pre><code class="lang-clojure"><span class="hljs-comment">;; Cartesian product with filter</span>
(<span class="hljs-name"><span class="hljs-builtin-name">for</span></span> [x [<span class="hljs-number">1</span> <span class="hljs-number">2</span> <span class="hljs-number">3</span>]
      y [<span class="hljs-number">4</span> <span class="hljs-number">5</span> <span class="hljs-number">6</span>]
      <span class="hljs-symbol">:when</span> (<span class="hljs-name"><span class="hljs-builtin-name">even?</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">+</span></span> x y))]
  [x y])
<span class="hljs-comment">;; =&gt; ([1 5] [2 4] [2 6] [3 5])</span>

<span class="hljs-comment">;; Multiple bindings with let</span>
(<span class="hljs-name"><span class="hljs-builtin-name">for</span></span> [user users
      <span class="hljs-symbol">:let</span> [email (<span class="hljs-symbol">:email</span> user)
            domain (<span class="hljs-name"><span class="hljs-builtin-name">second</span></span> (<span class="hljs-name">str/split</span> email #<span class="hljs-string">"@"</span>))]
      <span class="hljs-symbol">:when</span> (<span class="hljs-name"><span class="hljs-builtin-name">=</span></span> domain <span class="hljs-string">"example.com"</span>)]
  (<span class="hljs-symbol">:name</span> user))
<span class="hljs-comment">;; =&gt; ("Alice" "Bob")</span>

<span class="hljs-comment">;; Nested iteration</span>
(<span class="hljs-name"><span class="hljs-builtin-name">for</span></span> [feed feeds
      item (<span class="hljs-symbol">:items</span> feed)]
  (<span class="hljs-name">process-item</span> feed item))

<span class="hljs-comment">;; Perfect for generating a deck of cards</span>
(<span class="hljs-keyword">def</span> <span class="hljs-title">suits</span> #{<span class="hljs-symbol">:hearts</span> <span class="hljs-symbol">:diamonds</span> <span class="hljs-symbol">:clubs</span> <span class="hljs-symbol">:spades</span>})
(<span class="hljs-keyword">def</span> <span class="hljs-title">ranks</span> [<span class="hljs-string">"2"</span> <span class="hljs-string">"3"</span> <span class="hljs-string">"4"</span> <span class="hljs-string">"5"</span> <span class="hljs-string">"6"</span> <span class="hljs-string">"7"</span> <span class="hljs-string">"8"</span> <span class="hljs-string">"9"</span> <span class="hljs-string">"10"</span> <span class="hljs-string">"J"</span> <span class="hljs-string">"Q"</span> <span class="hljs-string">"K"</span> <span class="hljs-string">"A"</span>])

(<span class="hljs-keyword">defn</span> <span class="hljs-title">make-deck</span>
  <span class="hljs-string">"Create a 52-card deck"</span>
  []
  (<span class="hljs-name">shuffle</span>
   (<span class="hljs-name"><span class="hljs-builtin-name">for</span></span> [suit suits
         rank ranks]
     {<span class="hljs-symbol">:suit</span> suit <span class="hljs-symbol">:rank</span> rank})))
</code></pre>
<p><strong>Rule of thumb:</strong> Use <code>for</code> when you need <code>:when</code>, <code>:let</code>, or multiple sequences. Use <code>map</code> for simple one-to-one transformations.</p>
<p>Generally, follow the same rules as <code>map</code> Don’t use this when you need to generate side effects.</p>
<pre><code class="lang-clojure"><span class="hljs-comment">;; Unnecessarily verbose</span>
(<span class="hljs-name"><span class="hljs-builtin-name">for</span></span> [user users]
  (<span class="hljs-symbol">:email</span> user))

<span class="hljs-comment">;; Better - Use map</span>
(<span class="hljs-name"><span class="hljs-builtin-name">map</span></span> <span class="hljs-symbol">:email</span> users)

<span class="hljs-comment">;; weird with side effects</span>
=&gt; (<span class="hljs-name"><span class="hljs-builtin-name">doall</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">map</span></span> println [<span class="hljs-number">1</span> <span class="hljs-number">2</span> <span class="hljs-number">3</span>]))
<span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>
(<span class="hljs-name">nil</span> <span class="hljs-literal">nil</span> <span class="hljs-literal">nil</span>)
</code></pre>
<h3 id="heading-3-doseq-imperative-iteration-for-side-effects"><strong>3.</strong> <code>doseq</code> - Imperative Iteration for Side Effects</h3>
<p><strong>Purpose:</strong> Execute side effects for each element in a collection.</p>
<p><strong>Returns:</strong> <code>nil</code> (you're not using the return value).</p>
<p><strong>When to use:</strong></p>
<ul>
<li><p>You're doing I/O (printing, file writes, network calls)</p>
</li>
<li><p>You're mutating state (atoms, databases)</p>
</li>
<li><p>You need multiple operations per item</p>
</li>
<li><p>You need destructuring or complex bindings</p>
</li>
</ul>
<p><strong>Characteristics:</strong></p>
<ul>
<li><p><strong>Eager</strong> - executes immediately</p>
</li>
<li><p><strong>Returns nil</strong> - signals "I'm doing side effects"</p>
</li>
<li><p><strong>Imperative</strong> - like a <code>for</code> loop in other languages</p>
</li>
</ul>
<pre><code class="lang-clojure"><span class="hljs-comment">;; doseq - Best for performing side effects for each result</span>

<span class="hljs-comment">;; Destructuring with complex logic with :keys</span>
(<span class="hljs-name"><span class="hljs-builtin-name">doseq</span></span> [{<span class="hljs-symbol">:keys</span> [name email role]} users]
  (<span class="hljs-name"><span class="hljs-builtin-name">when</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">=</span></span> role <span class="hljs-string">"admin"</span>)
    (<span class="hljs-name">send-admin-notification</span> name email)))

<span class="hljs-comment">;; Multiple operations per item</span>
(<span class="hljs-name"><span class="hljs-builtin-name">doseq</span></span> [order orders]
  (<span class="hljs-name">send-confirmation-email</span> order)
  (<span class="hljs-name">update-inventory</span> order)
  (<span class="hljs-name">log-transaction</span> order))

<span class="hljs-comment">;; Nested iteration</span>
(<span class="hljs-name"><span class="hljs-builtin-name">doseq</span></span> [feed feeds]
  (<span class="hljs-name">println</span> <span class="hljs-string">"Processing"</span> (<span class="hljs-symbol">:feed-id</span> feed))
  (<span class="hljs-name"><span class="hljs-builtin-name">doseq</span></span> [item (<span class="hljs-symbol">:items</span> feed)]
    (<span class="hljs-name">save-to-db</span> item)))
</code></pre>
<h3 id="heading-4-run-concise-side-effects"><strong>4.</strong> <code>run!</code> - Concise Side Effects</h3>
<p><strong>Purpose:</strong> Apply a <strong>single side-effecting function</strong> to each element.</p>
<p><strong>Returns:</strong> <code>nil</code>.</p>
<p><strong>When to use:</strong></p>
<ul>
<li><p>You're calling one side-effect producing function per item</p>
</li>
<li><p>You don't need destructuring</p>
</li>
<li><p>You want concise code</p>
</li>
</ul>
<p><strong>Characteristics:</strong></p>
<ul>
<li><p><strong>Eager</strong> - executes immediately</p>
</li>
<li><p><strong>Returns nil</strong> - signals side effects</p>
</li>
<li><p><strong>Concise</strong> - one-liner for simple cases</p>
</li>
</ul>
<p><code>run!</code> is more concise for when you're just calling a single function</p>
<pre><code class="lang-clojure"><span class="hljs-comment">;; doseq - verbose</span>
(<span class="hljs-name"><span class="hljs-builtin-name">doseq</span></span> [alert alerts]
  (<span class="hljs-name">println</span> alert))

<span class="hljs-comment">;; Better with run! - concise</span>
(<span class="hljs-name">run!</span> println alerts)

<span class="hljs-comment">;; Send email to each user</span>
(<span class="hljs-name">run!</span> send-email users)

<span class="hljs-comment">;; Save each document</span>
(<span class="hljs-name">run!</span> save-to-db documents)

<span class="hljs-comment">;; Close each connection</span>
(<span class="hljs-name">run!</span> #(<span class="hljs-name">.close</span> %) connections)
</code></pre>
<h4 id="heading-when-you-shouldnt-use-run"><strong>When You Shouldn’t Use</strong> <code>run!</code></h4>
<pre><code class="lang-clojure"><span class="hljs-comment">;; Multiple operations - better with doseq</span>
(<span class="hljs-name"><span class="hljs-builtin-name">doseq</span></span> [user users]
  (<span class="hljs-name">send-email</span> user)
  (<span class="hljs-name">log-activity</span> user)
  (<span class="hljs-name">update-db</span> user))

<span class="hljs-comment">;; Destructuring</span>
<span class="hljs-comment">;; With run! you need a wrapper</span>
(<span class="hljs-name">run!</span> (<span class="hljs-name"><span class="hljs-builtin-name">fn</span></span> [{<span class="hljs-symbol">:keys</span> [name email]}]
        (<span class="hljs-name">send-email</span> name email))
      users)

<span class="hljs-comment">;; Better with doseq!</span>
(<span class="hljs-name"><span class="hljs-builtin-name">doseq</span></span> [{<span class="hljs-symbol">:keys</span> [name email]} users]
  (<span class="hljs-name">send-email</span> name email))

<span class="hljs-comment">;; Conditional logic</span>
<span class="hljs-comment">;; With run! you need the logic in the function</span>
(<span class="hljs-name">run!</span> (<span class="hljs-name"><span class="hljs-builtin-name">fn</span></span> [user]
        (<span class="hljs-name"><span class="hljs-builtin-name">when</span></span> (<span class="hljs-name">active?</span> user)
          (<span class="hljs-name">send-notification</span> user)))
      users)

<span class="hljs-comment">;; doseq is clearer</span>
(<span class="hljs-name"><span class="hljs-builtin-name">doseq</span></span> [user users]
  (<span class="hljs-name"><span class="hljs-builtin-name">when</span></span> (<span class="hljs-name">active?</span> user)
    (<span class="hljs-name">send-notification</span> user)))
</code></pre>
<h3 id="heading-summary"><strong>Summary</strong></h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Construct</strong></td><td><strong>Returns</strong></td><td><strong>Eager/Lazy</strong></td><td><strong>Primary Use</strong></td><td><strong>Side Effects?</strong></td></tr>
</thead>
<tbody>
<tr>
<td><code>map</code></td><td>Transformed sequence</td><td>Lazy</td><td>Transform data</td><td>❌ No (pure)</td></tr>
<tr>
<td><code>for</code></td><td>Generated sequence</td><td>Lazy</td><td>Complex collection building</td><td>❌ No (pure)</td></tr>
<tr>
<td><code>doseq</code></td><td><code>nil</code></td><td>Eager</td><td>Multiple side effects per item</td><td>✅ Yes</td></tr>
<tr>
<td><code>run!</code></td><td><code>nil</code></td><td>Eager</td><td>Single function call per item</td><td>✅ Yes</td></tr>
</tbody>
</table>
</div><p>Understanding the different iteration constructs in Clojure is crucial for writing efficient and effective code.</p>
<p>Use <code>map</code> and <code>for</code> when transforming data with lazy evaluation and pure functions. For producing side effects, <code>doseq</code> and <code>run!</code> are better suited, providing eager execution and clear intent for side-effecting operations.</p>
<p>I hope this helps on your Clojure journey. It has definitely helped in mine.</p>
<p>Further Reading Resources:</p>
<ul>
<li><p><a target="_blank" href="https://clojure.org/guides/learn/flow#_clojures_for">Clojure’s for</a></p>
</li>
<li><p><a target="_blank" href="https://clojuredocs.org/clojure.core/map">Clojure map documentation</a></p>
</li>
<li><p><a target="_blank" href="https://clojuredocs.org/clojure.core/doseq">Clojure doseq documentation</a></p>
</li>
<li><p><a target="_blank" href="https://clojuredocs.org/clojure.core/doall">Clojure doall documentation</a></p>
</li>
<li><p><a target="_blank" href="https://clojure.org/guides/learn/flow#_iteration_for_side_effects">Iteration for Side Effects - Clojure Guides</a></p>
</li>
<li><p><a target="_blank" href="https://clojuredocs.org/clojure.core/run!">Clojure run! documentation</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[URL WTFs]]></title><description><![CDATA[While working on an application that would accept user-provided URLs, it would be nice to test a wide range of URLs to figure out which ones to accept and which ones not to accept. Lots of security vulnerabilities might be hiding behind such simple f...]]></description><link>https://kevgathuku.dev/url-wtfs</link><guid isPermaLink="true">https://kevgathuku.dev/url-wtfs</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Security]]></category><category><![CDATA[ssrf ]]></category><category><![CDATA[property-based testing]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Sun, 14 Dec 2025 21:14:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/iar-afB0QQw/upload/6258eaf55bee9b6f76f8db652db4aac6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While working on an application that would accept user-provided URLs, it would be nice to test a wide range of URLs to figure out which ones to accept and which ones not to accept. Lots of security vulnerabilities might be hiding behind such simple functionality as fetching a user-provided URL.</p>
<p>Validating URLs is one of those cases that is perfect for property-based testing. I happened to use fast-check and they have a <a target="_blank" href="https://fast-check.dev/docs/introduction/why-property-based/">pretty comprehensive page</a> on why you would need property based testing in the first place. Please read it.</p>
<p>The idea is that you just don’t want to just test on a few hardcoded values, get some basic tests passing and call it a day. You want to test your application logic against a wide range of possibly valid data and ensure the application behaves correctly when provided with a wide range of data, especially examples that you wouldn’t think to check. Property based tests are a pretty elegant approach to this problem, since libraries like fast-check can take care of generating valid values, in this case URLs, and then feeding them to the tests and checking that the code behaves as expected.</p>
<p>Property based tests using fast-check looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> fc <span class="hljs-keyword">from</span> <span class="hljs-string">'fast-check'</span>;
<span class="hljs-keyword">import</span> { validateUrl } <span class="hljs-keyword">from</span> <span class="hljs-string">'../server/services/FetcherUtils.res.mjs'</span>;

describe(<span class="hljs-string">"URL validation rejects invalid URLs"</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">"should accept valid http and https URLs with domain names"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> validUrl = fc.webUrl({
      <span class="hljs-attr">validSchemes</span>: [<span class="hljs-string">'http'</span>, <span class="hljs-string">'https'</span>],
      <span class="hljs-attr">withFragments</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">withQueryParameters</span>: <span class="hljs-literal">true</span>,
    });

    fc.assert(
      fc.property(validUrl, <span class="hljs-function">(<span class="hljs-params">url</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> result = validateUrl(url);
        <span class="hljs-keyword">return</span> result.valid === <span class="hljs-literal">true</span> &amp;&amp; result.error === <span class="hljs-literal">undefined</span>;
      }),
      { <span class="hljs-attr">numRuns</span>: <span class="hljs-number">20</span> }
    );
  });

  it(<span class="hljs-string">"should reject URLs with invalid protocols"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> invalidProtocolUrl = fc.webUrl({
        <span class="hljs-attr">validSchemes</span>: [<span class="hljs-string">'ftp'</span>, <span class="hljs-string">'file'</span>, <span class="hljs-string">'javascript'</span>, <span class="hljs-string">'data'</span>, <span class="hljs-string">'mailto'</span>, <span class="hljs-string">'ws'</span>, <span class="hljs-string">'wss'</span>],
        <span class="hljs-attr">withFragments</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">withQueryParameters</span>: <span class="hljs-literal">true</span>,
      });

    fc.assert(
      fc.property(invalidProtocolUrl, <span class="hljs-function">(<span class="hljs-params">url</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> result = validateUrl(url);
        <span class="hljs-keyword">return</span> (
          result.valid === <span class="hljs-literal">false</span> &amp;&amp;
          result.error === <span class="hljs-string">"URL must use http or https protocol"</span>
        );
      }),
      { <span class="hljs-attr">numRuns</span>: <span class="hljs-number">20</span> }
    );
  });
});
</code></pre>
<p>The idea should be clear - Generate different kinds of URLs, both valid and invalid, and ensure your code handles it appropriately.</p>
<p>The functions used to generate different kinds of data should be mostly self-explanatory. In this example we are using <a target="_blank" href="https://fast-check.dev/docs/core-blocks/arbitraries/fake-data/internet/#weburl">fc.webUrl</a> to generate different kinds of URLs specifying the schemes we want to allow</p>
<p>As an aside, the function being tested is imported from a compiled <a target="_blank" href="https://rescript-lang.org/">Rescript</a> file. This is one of the great advantages of Rescript. It gives you all the benefits of a type-safe language, but it compiles to plain JS, so there is zero lock-in. You can even delete it and keep the compiled JS files. Anyway for this case, the nice part about this example is that you can use all the amazing testing tools and libraries already available in JS-land to test functions written in Rescript 🤩</p>
<h2 id="heading-detour-what-is-a-valid-url">Detour: What is a valid URL?</h2>
<p>I came to this structure after finding out that my original code was not restrictive enough. The property tests were generating quite strange URLs that kept being accepted as valid. This right here is one of the strengths of using property-based tests. So what was going on?</p>
<p>This is the JavaScript compiled version of the code I started out with to accept and fetch URLs, basically deferring to the already existing <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/URL"><code>URL</code> interface</a> in JavaScript.</p>
<p>Looks like an innocent and simple function but don’t be fooled. There are a lot of dangers lurking underneath. Read on for all the issues I found from this simple function</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchUrl</span>(<span class="hljs-params">userInput</span>) </span>{
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(userInput);
  <span class="hljs-keyword">return</span> fetch(url);
}
</code></pre>
<p>For full transparency, I asked an AI agent to document why some of the weird URLs that were being generated from the property tests were being accepted. I thought it best to write it down somewhere easy to reference and so that I don’t need to re-discover this again in the future.</p>
<h3 id="heading-javascript-url-constructor-quirks">JavaScript URL Constructor Quirks</h3>
<p>The JavaScript <code>URL</code> constructor (based on the WHATWG URL Standard) accepts many URL formats that may be surprising or unexpected. This document catalogs these behaviors to help developers understand URL validation edge cases.</p>
<p><strong>Key Takeaway</strong>: The URL constructor is <strong>very permissive</strong> and accepts many strings that don't look like traditional URLs. Always validate the parsed components (protocol, hostname, etc.) after construction.</p>
<h2 id="heading-unexpected-formats-accepted"><strong>Unexpected Formats Accepted</strong></h2>
<h3 id="heading-1-single-letter-schemes"><strong>1. Single-Letter Schemes</strong></h3>
<p>The URL constructor accepts any single letter followed by a colon as a valid URL scheme.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'A: '</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// protocol: "a:"</span>
<span class="hljs-comment">// hostname: ""</span>
<span class="hljs-comment">// href: "a:"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'Z:'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// protocol: "z:"</span>
<span class="hljs-comment">// hostname: ""</span>
<span class="hljs-comment">// href: "z:"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'x:anything'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// protocol: "x:"</span>
<span class="hljs-comment">// hostname: ""</span>
<span class="hljs-comment">// pathname: "anything"</span>
</code></pre>
<p><strong>Why</strong>: Per the URL spec, any string matching the pattern <code>[a-zA-Z][a-zA-Z0-9+.-]*:</code> is a valid scheme.</p>
<p><strong>Implication</strong>: Must explicitly check for allowed protocols (http/https) after parsing.</p>
<h3 id="heading-2-schemes-without-authority"><strong>2. Schemes Without Authority</strong></h3>
<p>URLs don't require the <code>//</code> authority component.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'mailto:user@example.com'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// protocol: "mailto:"</span>
<span class="hljs-comment">// pathname: "user@example.com"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'data:text/plain,Hello'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// protocol: "data:"</span>
<span class="hljs-comment">// pathname: "text/plain,Hello"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'javascript:alert(1)'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// protocol: "javascript:"</span>
<span class="hljs-comment">// pathname: "alert(1)"</span>
</code></pre>
<p><strong>Why</strong>: Many URL schemes (<code>mailto</code>, <code>data</code>, <code>javascript</code>, etc.) don't use the <code>//</code> authority syntax.</p>
<p><strong>Implication</strong>: Dangerous for security - must validate protocol to prevent XSS and other attacks.</p>
<h3 id="heading-3-single-slash-after-protocol"><strong>3. Single Slash After Protocol</strong></h3>
<p>URLs with a single slash after the protocol are valid.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http:/example.com'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// protocol: "http:"</span>
<span class="hljs-comment">// hostname: "example.com"</span>
<span class="hljs-comment">// pathname: ""</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'https:/path/to/resource'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// protocol: "https:"</span>
<span class="hljs-comment">// hostname: "path"</span>
<span class="hljs-comment">// pathname: "/to/resource"</span>
</code></pre>
<p><strong>Why</strong>: The URL spec allows this - the authority component is optional.</p>
<p><strong>Implication</strong>: May parse differently than expected - hostname might not be what you think.</p>
<h3 id="heading-4-empty-authority"><strong>4. Empty Authority</strong></h3>
<p>URLs can have an empty authority (no hostname).</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://'</span>)
<span class="hljs-comment">// ❌ Throws TypeError: Invalid URL</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http:///'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// protocol: "http:"</span>
<span class="hljs-comment">// hostname: ""</span>
<span class="hljs-comment">// pathname: "/"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'file:///'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// protocol: "file:"</span>
<span class="hljs-comment">// hostname: ""</span>
<span class="hljs-comment">// pathname: "/"</span>
</code></pre>
<p><strong>Why</strong>: The spec allows empty hostnames for certain schemes.</p>
<p><strong>Implication</strong>: Must check that hostname is not empty for http/https URLs.</p>
<h3 id="heading-5-whitespace-handling"><strong>5. Whitespace Handling</strong></h3>
<p>Leading and trailing whitespace is trimmed, but internal whitespace causes errors.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'  http://example.com  '</span>)
<span class="hljs-comment">// ✅ Valid (whitespace trimmed)</span>
<span class="hljs-comment">// href: "http://example.com/"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://example .com'</span>)
<span class="hljs-comment">// ❌ Throws TypeError: Invalid URL</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://example.com/path with spaces'</span>)
<span class="hljs-comment">// ❌ Throws TypeError: Invalid URL</span>
</code></pre>
<p><strong>Why</strong>: The spec requires trimming ASCII whitespace from the input.</p>
<p><strong>Implication</strong>: Whitespace in the middle is invalid, but leading/trailing is silently removed.</p>
<h3 id="heading-6-case-insensitivity"><strong>6. Case Insensitivity</strong></h3>
<p>Schemes and hostnames are case-insensitive and normalized to lowercase.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'HTTP://EXAMPLE.COM'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// protocol: "http:"</span>
<span class="hljs-comment">// hostname: "example.com"</span>
<span class="hljs-comment">// href: "http://example.com/"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'HtTp://ExAmPlE.cOm'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// protocol: "http:"</span>
<span class="hljs-comment">// hostname: "example.com"</span>
</code></pre>
<p><strong>Why</strong>: DNS and URL schemes are case-insensitive per spec.</p>
<p><strong>Implication</strong>: Always compare protocols and hostnames in lowercase.</p>
<h3 id="heading-7-numeric-hostnames"><strong>7. Numeric Hostnames</strong></h3>
<p>Hostnames can be numeric (interpreted as IP addresses).</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://127.0.0.1'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// hostname: "127.0.0.1"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://2130706433'</span>)
<span class="hljs-comment">// ✅ Valid (decimal IP representation)</span>
<span class="hljs-comment">// hostname: "2130706433"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://0x7f000001'</span>)
<span class="hljs-comment">// ✅ Valid (hexadecimal IP)</span>
<span class="hljs-comment">// hostname: "0x7f000001"</span>
</code></pre>
<p><strong>Why</strong>: The spec allows various IP address formats.</p>
<p><strong>Implication</strong>: Must validate that hostname is not an IP address if you want to require domain names.</p>
<h3 id="heading-8-ipv6-addresses"><strong>8. IPv6 Addresses</strong></h3>
<p>IPv6 addresses must be enclosed in brackets.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://[::1]'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// hostname: "[::1]"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://[2001:db8::1]'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// hostname: "[2001:db8::1]"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://::1'</span>)
<span class="hljs-comment">// ❌ Throws TypeError: Invalid URL</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://2001:db8::1'</span>)
<span class="hljs-comment">// ❌ Throws TypeError: Invalid URL</span>
</code></pre>
<p><strong>Why</strong>: Brackets are required to disambiguate colons in IPv6 from port numbers.</p>
<p><strong>Implication</strong>: IPv6 addresses without brackets are invalid.</p>
<h3 id="heading-9-port-numbers"><strong>9. Port Numbers</strong></h3>
<p>Port numbers can be specified but are optional.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://example.com:8080'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// port: "8080"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://example.com:'</span>)
<span class="hljs-comment">// ✅ Valid (empty port)</span>
<span class="hljs-comment">// port: ""</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://example.com:abc'</span>)
<span class="hljs-comment">// ❌ Throws TypeError: Invalid URL</span>
</code></pre>
<p><strong>Why</strong>: Ports must be numeric or empty.</p>
<p><strong>Implication</strong>: Empty port is valid but non-numeric ports are not.</p>
<h3 id="heading-10-userinfo-in-urls"><strong>10. Userinfo in URLs</strong></h3>
<p>URLs can contain username and password (deprecated for security).</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://user:pass@example.com'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// username: "user"</span>
<span class="hljs-comment">// password: "pass"</span>
<span class="hljs-comment">// hostname: "example.com"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://user@example.com'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// username: "user"</span>
<span class="hljs-comment">// hostname: "example.com"</span>
</code></pre>
<p><strong>Why</strong>: The spec supports userinfo for backward compatibility.</p>
<p><strong>Implication</strong>: Security risk - passwords in URLs are visible in logs, history, etc.</p>
<h3 id="heading-11-fragment-and-query-handling"><strong>11. Fragment and Query Handling</strong></h3>
<p>Fragments (#) and queries (?) are always valid.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://example.com#fragment'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// hash: "#fragment"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://example.com?query=value'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// search: "?query=value"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'http://example.com?#'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// search: "?"</span>
<span class="hljs-comment">// hash: "#"</span>
</code></pre>
<p><strong>Why</strong>: Fragments and queries are standard URL components.</p>
<p><strong>Implication</strong>: Always present, even if empty. Ensure to parse the whole URL not just the domain.</p>
<h3 id="heading-12-relative-urls-require-base"><strong>12. Relative URLs Require Base</strong></h3>
<p>Relative URLs need a base URL to resolve against.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'/path/to/resource'</span>)
<span class="hljs-comment">// ❌ Throws TypeError: Invalid URL</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'/path/to/resource'</span>, <span class="hljs-string">'http://example.com'</span>)
<span class="hljs-comment">// ✅ Valid</span>
<span class="hljs-comment">// href: "http://example.com/path/to/resource"</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'//example.com/path'</span>)
<span class="hljs-comment">// ❌ Throws TypeError: Invalid URL</span>

<span class="hljs-keyword">new</span> URL(<span class="hljs-string">'//example.com/path'</span>, <span class="hljs-string">'http://base.com'</span>)
<span class="hljs-comment">// ✅ Valid (protocol-relative URL)</span>
<span class="hljs-comment">// href: "http://example.com/path"</span>
</code></pre>
<p><strong>Why</strong>: Relative URLs need context to be resolved.</p>
<p><strong>Implication</strong>: Must provide base URL for relative URLs. (This was absolutely new for me)</p>
<h2 id="heading-security-implications"><strong>Security Implications</strong></h2>
<h3 id="heading-xss-cross-site-scripting"><strong>XSS (Cross-Site Scripting)</strong></h3>
<p>JavaScript URLs can execute code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">new</span> URL(<span class="hljs-string">'javascript:alert(1)'</span>)
<span class="hljs-comment">// ✅ Valid but DANGEROUS</span>
<span class="hljs-comment">// protocol: "javascript:"</span>

<span class="hljs-comment">// ❌ DANGEROUS - Could execute JavaScript</span>
element.href = userInput;

<span class="hljs-comment">// ✅ SAFE - Validates protocol</span>
<span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(userInput);
<span class="hljs-keyword">if</span> (url.protocol !== <span class="hljs-string">'http:'</span> &amp;&amp; url.protocol !== <span class="hljs-string">'https:'</span>) {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Invalid protocol'</span>);
}
element.href = url.href;
</code></pre>
<h2 id="heading-resolutions"><strong>Resolutions</strong></h2>
<p>I ended up discovering a better way to parse and validate a URL before blindly fetching it, especially if it comes from external sources.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ❌ DANGEROUS - Accepts many unexpected formats</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchUrl</span>(<span class="hljs-params">userInput</span>) </span>{
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(userInput);
  <span class="hljs-keyword">return</span> fetch(url);
}

<span class="hljs-comment">// Basic SSRF protection</span>
<span class="hljs-comment">// Avoid redirect to unintended locations e.g. internal resources</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isValidDomainName</span>(<span class="hljs-params">hostname</span>) </span>{
  <span class="hljs-comment">// Check it's not an IP address</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/^\d+\.\d+\.\d+\.\d+$/</span>.test(hostname)) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }

  <span class="hljs-comment">// Check it's not IPv6 (in brackets)</span>
  <span class="hljs-keyword">if</span> (hostname.startsWith(<span class="hljs-string">'['</span>) &amp;&amp; hostname.endsWith(<span class="hljs-string">']'</span>)) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }

  <span class="hljs-comment">// Check it contains at least one dot (has TLD)</span>
  <span class="hljs-keyword">if</span> (!hostname.includes(<span class="hljs-string">'.'</span>)) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }

  <span class="hljs-comment">// Check it's not localhost</span>
  <span class="hljs-keyword">if</span> (hostname === <span class="hljs-string">'localhost'</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }

  <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

<span class="hljs-comment">// ✅ SAFE - Validates protocol and hostname</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchUrl</span>(<span class="hljs-params">userInput</span>) </span>{
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(userInput);

  <span class="hljs-comment">// ✅ Only allow known-safe protocols</span>
  <span class="hljs-keyword">if</span> (url.protocol !== <span class="hljs-string">'http:'</span> &amp;&amp; url.protocol !== <span class="hljs-string">'https:'</span>) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Invalid protocol'</span>);
  }

  <span class="hljs-comment">// Check hostname is not empty</span>
  <span class="hljs-keyword">if</span> (!url.hostname) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Hostname required'</span>);
  }

  <span class="hljs-comment">// Validate not IP / Loopback / IPV6</span>
  <span class="hljs-keyword">if</span> (isValidDomainName(url.hostname)) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'IP addresses not allowed'</span>);
  }

  <span class="hljs-keyword">return</span> fetch(url);
}
</code></pre>
<h3 id="heading-conclusion">Conclusion</h3>
<p>As a developer working on web applications, knowing all the kind of URLs that exist and are considered valid should have been common knowledge. Unfortunately a lot of the strange valid URLs were new to me and I was learning most of the security vulnerabilities hiding behind a simple URL for the first time. For example, I had no idea about <a target="_blank" href="https://portswigger.net/web-security/ssrf"><strong>Server-side request forgery (SSRF</strong></a><strong>)</strong> but fortunately now I am much wiser.</p>
<p>Adopting property-based testing helped me discover some huge security holes in my application and I hope this discovery and the examples can also help others avoid similar issues when developing their own web applications.</p>
]]></content:encoded></item><item><title><![CDATA[Reviving Legacy Code: Rescript Tips for Modernizing Your JavaScript Projects with AI]]></title><description><![CDATA[I have been on a journey to revive one of my oldest projects, partly to explore the capabilities of AI agents. This project in particular is around 10 years old, a full stack JavaScript app. The backend API is built with Express & MongoDB, and the fr...]]></description><link>https://kevgathuku.dev/reviving-legacy-code-rescript-tips-for-modernizing-your-javascript-projects-with-ai</link><guid isPermaLink="true">https://kevgathuku.dev/reviving-legacy-code-rescript-tips-for-modernizing-your-javascript-projects-with-ai</guid><category><![CDATA[AI]]></category><category><![CDATA[rescript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><category><![CDATA[Programming Tips]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Wed, 03 Dec 2025 19:49:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/-p-KCm6xB9I/upload/3270fbb5ab3f4f7fccaff22b0167a3f0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have been on a journey to revive one of my <a target="_blank" href="https://github.com/kevgathuku/docue-stack">oldest</a> projects, partly to explore the capabilities of AI agents. This project in particular is around 10 years old, a full stack JavaScript app. The backend API is built with Express &amp; MongoDB, and the frontend Single Page Application is powered by a mix of React and Elm. Turns out this mix of frontend technologies was not such a good idea, and here is where Rescript comes in. I chose to rewrite the Elm parts in Rescript.</p>
<p>Rescript offers an excellent way to work with existing JavaScript code and integrated especially well with React. Each Rescript file compiles into a single JS file, making it easy to use in any JS project or to gradually convert small bits to Rescript. This made it ideal for this experiment.</p>
<p>This leads me to some of the lessons I have had to learn the hard way. I don’t see a lot of content about Rescript out there and I had to discover some of this myself, so just writing it down for anyone else who might run into similar issues.</p>
<p>They are all about interoperating with JS so that’s an area that you need to be careful with in general. Also a lot of this code is AI-generated, so it might reflect the quality or age of the code it was trained with. Also Rescript is changing quite a lot with the <a target="_blank" href="https://rescript-lang.org/blog/release-12-0-0">recent v12 release</a> being quite aggressive with deprecating old stuff, so also watch out when using AI agents with Rescript.</p>
<ol>
<li><h3 id="heading-always-verify-the-ai-output">Always verify the AI output 🪄</h3>
</li>
</ol>
<p>I saw the AI agent sprinkling a few <code>Obj.magic</code> calls arond and was left wondering what this function is and what it does. There isn’t much information on this on the <a target="_blank" href="https://rescript-lang.org/docs/manual/api">official docs</a>, so it seems like an API that is not encouraged or deprecated.</p>
<p>Anyway I asked the Agent to explain:</p>
<blockquote>
<p><code>Obj.magic</code> is a ReScript function that performs an unsafe type cast - it tells the type system "trust me, I know what I'm doing" and bypasses type checking.</p>
</blockquote>
<p>Here’s an example</p>
<pre><code class="lang-rust"><span class="hljs-keyword">let</span> x: int = <span class="hljs-number">42</span> 

<span class="hljs-comment">// Obj.magic can convert ANY type to ANY other type </span>
<span class="hljs-comment">// Compiles, but DANGEROUS!</span>
<span class="hljs-keyword">let</span> y: string = Obj.magic(x)
</code></pre>
<p>Yikes! Looks dangerous, and looks exactly like the kind of thing that beats the whole point of using a strongly typed language in the first place.</p>
<p>This was the actual code in my case.</p>
<pre><code class="lang-rust"><span class="hljs-comment">// types are not known in advance</span>
<span class="hljs-keyword">let</span> validateBooleanQueryFromJS = (
  ~mustTerms: <span class="hljs-symbol">'a</span>,
  ~shouldTerms: <span class="hljs-symbol">'b</span>,
  ~mustNotTerms: <span class="hljs-symbol">'c</span>,
) =&gt; {
   <span class="hljs-comment">// Check if inputs are arrays</span>
  <span class="hljs-keyword">if</span> !Array.isArray(mustTerms) {
    {<span class="hljs-string">"valid"</span>: <span class="hljs-literal">false</span>, <span class="hljs-string">"error"</span>: <span class="hljs-literal">Some</span>(<span class="hljs-string">"mustTerms must be an array"</span>)}
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> !Array.isArray(shouldTerms) {
    {<span class="hljs-string">"valid"</span>: <span class="hljs-literal">false</span>, <span class="hljs-string">"error"</span>: <span class="hljs-literal">Some</span>(<span class="hljs-string">"shouldTerms must be an array"</span>)}
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> !Array.isArray(mustNotTerms) {
    {<span class="hljs-string">"valid"</span>: <span class="hljs-literal">false</span>, <span class="hljs-string">"error"</span>: <span class="hljs-literal">Some</span>(<span class="hljs-string">"mustNotTerms must be an array"</span>)}
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Cast to array after the checks</span>
    <span class="hljs-keyword">let</span> mustTermsArray: array&lt;string&gt; = Obj.magic(mustTerms)
    <span class="hljs-keyword">let</span> shouldTermsArray: array&lt;string&gt; = Obj.magic(shouldTerms)
    <span class="hljs-keyword">let</span> mustNotTermsArray: array&lt;string&gt; = Obj.magic(mustNotTerms)

    <span class="hljs-comment">// ...</span>
    <span class="hljs-comment">// Further processing of the arrays</span>
}
</code></pre>
<p>And this is how the function is called from the JS side</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> result = validateBooleanQueryFromJS(mustTerms || [], shouldTerms || [], mustNotTerms || []);
</code></pre>
<p>So I finally understood why the <code>Obj.magic</code> call was included. Since there are no explicit types being specified for <code>mustTerms</code>, <code>shouldTerms</code> and <code>mustNotTerms</code> in the <code>validateBooleanQueryFromJS</code> function, it is possible to call the function passing any data type.</p>
<p>We are validating the data through the explicit <code>Array.isArray</code> calls, then casting each of the arrays to an <code>array&lt;string&gt;</code> type afterwards when we have validated that indeed the data passed in is an array.</p>
<p>There are a couple of ways we can deal with this.</p>
<p>The first option is the more straightforward one: Do the same validation, but on the JS side, then using the concrete <code>array&lt;string&gt;</code> type for the function parameters.</p>
<p>This basically means moving the array checks to before calling the Rescript method <code>validateBooleanQueryFromJS</code> from the JavaScript side</p>
<p>The parameter types can now be written as follows:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">let</span> validateBooleanQuery = (
  ~mustTerms: array&lt;string&gt;,
  ~shouldTerms: array&lt;string&gt;,
  ~mustNotTerms: array&lt;string&gt;,
) =&gt; {
    <span class="hljs-comment">// No need to check if inputs are arrays</span>
    <span class="hljs-comment">// ...</span>
    <span class="hljs-comment">// Further processing of the arrays</span>
</code></pre>
<p>And called from the JS side after doing the validations there</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Option 1: Simple and straightforward validation</span>
<span class="hljs-comment">// Less clarity on which variable has the wrong type</span>
<span class="hljs-keyword">const</span> terms = [mustTerms, shouldTerms, mustNotTerms];
<span class="hljs-keyword">if</span> (!terms.every(<span class="hljs-built_in">Array</span>.isArray)) {
  <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Terms must be arrays'</span> });
}

<span class="hljs-keyword">const</span> result = validateBooleanQuery(mustTerms, shouldTerms, mustNotTerms);

<span class="hljs-comment">// Option 2: More verbose and more explicit</span>
<span class="hljs-keyword">if</span> (!<span class="hljs-built_in">Array</span>.isArray(mustTerms)) {
  <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'mustTerms must be an array'</span> });
}
<span class="hljs-keyword">if</span> (!<span class="hljs-built_in">Array</span>.isArray(shouldTerms)) {
  <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'shouldTerms must be an array'</span> });
}
<span class="hljs-keyword">if</span> (!<span class="hljs-built_in">Array</span>.isArray(mustNotTerms)) {
  <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'mustNotTerms must be an array'</span> });
}
</code></pre>
<p>This could be convenient, but if there is another place where the same function needs to be called and the same validations need to be done again, it can get hectic.</p>
<p>The second option is to keep the <strong>unsafe</strong> type conversions with <a target="_blank" href="https://rescript-lang.org/docs/manual/type/#type-escape-hatch"><code>%identity</code></a> which is the <em>supported syntax</em>. To be clear, this is essentially the same as <code>Obj.magic</code> and is generally not recommended to unsafely convert between types. Reserve this for when there’s no other option.</p>
<pre><code class="lang-rust"> <span class="hljs-comment">// Use %identity for unsafe conversion from one type to another type.</span>
external convertToArray: <span class="hljs-symbol">'a</span> =&gt; array&lt;string&gt; = <span class="hljs-string">"%identity"</span>

<span class="hljs-keyword">let</span> validateBooleanQueryFromJS = (
  ~mustTerms: <span class="hljs-symbol">'a</span>,
  ~shouldTerms: <span class="hljs-symbol">'b</span>,
  ~mustNotTerms: <span class="hljs-symbol">'c</span>,
) =&gt; {
   <span class="hljs-comment">// Check if inputs are arrays</span>
  <span class="hljs-keyword">if</span> !Array.isArray(mustTerms) {
    {<span class="hljs-string">"valid"</span>: <span class="hljs-literal">false</span>, <span class="hljs-string">"error"</span>: <span class="hljs-literal">Some</span>(<span class="hljs-string">"mustTerms must be an array"</span>)}
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> !Array.isArray(shouldTerms) {
    {<span class="hljs-string">"valid"</span>: <span class="hljs-literal">false</span>, <span class="hljs-string">"error"</span>: <span class="hljs-literal">Some</span>(<span class="hljs-string">"shouldTerms must be an array"</span>)}
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> !Array.isArray(mustNotTerms) {
    {<span class="hljs-string">"valid"</span>: <span class="hljs-literal">false</span>, <span class="hljs-string">"error"</span>: <span class="hljs-literal">Some</span>(<span class="hljs-string">"mustNotTerms must be an array"</span>)}
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Use convertToArray to safely cast after verifying types</span>
    <span class="hljs-keyword">let</span> mustTermsArray = convertToArray(mustTerms)
    <span class="hljs-keyword">let</span> shouldTermsArray = convertToArray(shouldTerms)
    <span class="hljs-keyword">let</span> mustNotTermsArray = convertToArray(mustNotTerms)

    <span class="hljs-comment">// ...</span>
    <span class="hljs-comment">// Further processing of the arrays</span>
}
</code></pre>
<p>So you can choose what works best for you. Both are valid options and it helps to know the tradeoffs you are making with each method.</p>
<p>So I’m watching the content from the AI agent more closely, and trying to learn from the agent rather than just accepting whatever code it provides.</p>
<p>Check out this excellent post that is required reading for anyone working with AI-generated code: <a target="_blank" href="https://addyo.substack.com/p/treat-ai-generated-code-as-a-draft"><strong>Treat AI-Generated code as a draft</strong></a></p>
<ol start="2">
<li><h3 id="heading-conversion-between-rescript-types-and-javascript">Conversion between Rescript types and JavaScript</h3>
</li>
</ol>
<p>Some parts of Rescript are very nice such as the <a target="_blank" href="https://rescript-lang.org/docs/manual/api/stdlib/result">Result type</a>, which encodes an operation that could succeed (<strong>Ok</strong>) or fail (<strong>Error</strong>). It’s particularly helpful for avoiding throwing exceptions and handling errors more gracefully with pattern matching.</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">type</span> <span class="hljs-title">result</span></span>&lt;<span class="hljs-symbol">'ok</span>, <span class="hljs-symbol">'error</span>&gt; = <span class="hljs-literal">Ok</span>(<span class="hljs-symbol">'ok</span>) | Error(<span class="hljs-symbol">'error</span>)

<span class="hljs-keyword">let</span> divide = (a, b) =&gt; {
   <span class="hljs-keyword">if</span> b == <span class="hljs-number">0</span> {
     Error(<span class="hljs-string">"Division by zero"</span>)
   } <span class="hljs-keyword">else</span> {
     <span class="hljs-literal">Ok</span>(a / b)
   }
 }

switch divide(<span class="hljs-number">10</span>, <span class="hljs-number">0</span>) {
| Error(err) =&gt; Console.log(<span class="hljs-string">"Nice try! Error! "</span> ++ err)
| <span class="hljs-literal">Ok</span>(result) =&gt; Console.log(<span class="hljs-string">"Success: "</span> ++ Int.toString(result))
}
</code></pre>
<p>However, trying to return the Result type from a function and using this directly from JS is a bit weird. For example this <code>divide</code> function compiles to this JavaScript code</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">divide</span>(<span class="hljs-params">a, b</span>) </span>{
  <span class="hljs-keyword">if</span> (b === <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">TAG</span>: <span class="hljs-string">"Error"</span>,
      <span class="hljs-attr">_0</span>: <span class="hljs-string">"Division by zero"</span>
    };
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">TAG</span>: <span class="hljs-string">"Ok"</span>,
      <span class="hljs-attr">_0</span>: Primitive_int.div(a, b)
    };
  }
}
</code></pre>
<p>And using this does seem a bit strange and inconvenient. Reaching into a field called <code>result._0</code> definitely seems like an API that was built not to be used directly</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { divide } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path/to/compiled_js'</span>);

<span class="hljs-keyword">const</span> result = divide(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>);

<span class="hljs-keyword">if</span> (result.TAG === <span class="hljs-string">"Ok"</span>) {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Success:"</span>, result._0);
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (result.TAG === <span class="hljs-string">"Error"</span>) {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Error:"</span>, result._0);
}
</code></pre>
<p>So in such cases, we could create and export a new function for use from JavaScript. This function can return a custom object, providing a more user-friendly experience when using the function from JS</p>
<pre><code class="lang-rust"><span class="hljs-comment">// Export a wrapper for JavaScript - return a plain object instead of result</span>
<span class="hljs-keyword">let</span> divideFromJS = (a, b) =&gt; {
  switch divide(a, b) {
  | <span class="hljs-literal">Ok</span>(value) =&gt; {
      <span class="hljs-string">"success"</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-string">"value"</span>: value,
    }
  | Error(msg) =&gt; {
      <span class="hljs-string">"success"</span>: <span class="hljs-literal">false</span>,
      <span class="hljs-string">"error"</span>: msg,
    }
  }
}
</code></pre>
<p>And this is much nicer to use from JS</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { divideFromJS } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path/to/compiled_js'</span>);

<span class="hljs-keyword">const</span> result = divideFromJS(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>);

<span class="hljs-keyword">if</span> (result.success) {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Result:'</span>, result.value);
} <span class="hljs-keyword">else</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Error:'</span>, result.error);
}
</code></pre>
<p>So what have I learned from all this?</p>
<ol>
<li><p>Rescript provides a nice experience for writing type safe JavaScript code with no runtime errors. However the interoperability with JavaScript sometimes can sometimes be hectic, and you can customize your functions and return types for a better experience.</p>
</li>
<li><p>Always review AI-generated code. Check for unsafe or deprecated APIs usage and ensure whatever it generates fits your needs</p>
</li>
<li><p>Languages like Rescript help to tame JavaScript. It’s hard to go back to writing plain JS after experiencing a compiler that gives you confidence when shipping</p>
</li>
</ol>
<p>Back to playing with some typed languages 🤖</p>
]]></content:encoded></item><item><title><![CDATA[The AI Reckoning]]></title><description><![CDATA[Do you believe your job can be done by AI?
Up until very recently I believed this was all hype. No way this could happen.
There have always been proclamations of new technologies like AI taking jobs. Most of the time dismissed as fantasy, most takes ...]]></description><link>https://kevgathuku.dev/the-ai-reckoning</link><guid isPermaLink="true">https://kevgathuku.dev/the-ai-reckoning</guid><category><![CDATA[AI]]></category><category><![CDATA[llm]]></category><category><![CDATA[Career]]></category><category><![CDATA[Career Growth]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Wed, 05 Nov 2025 20:09:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/fEgt5QRI-rA/upload/b38bd4eab4cda612035e4546f8d0b79b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Do you believe your job can be done by AI?</p>
<p>Up until very recently I believed this was all hype. No way this could happen.</p>
<p>There have always been proclamations of new technologies like AI taking jobs. Most of the time dismissed as fantasy, most takes filled with a hint of denial, not willing to accept reality.</p>
<blockquote>
<p>Maybe the frontend development jobs, or the junior developers jobs, or some other job that’s not mine.</p>
</blockquote>
<p>The time of reckoning is here.</p>
<p>All of the models are available for pretty much free or a <em>very low</em> cost and the future does not look good for software engineering careers.</p>
<p>AI can now do the job of several developers. With tools evolving at breakneck speed, we can now extract maximum performance from the best AI tools, providing just the needed context and making them perform even the most complicated tasks. It’s just a matter of time before the models start wrecking carnage on the job market.</p>
<p>So what can we do?</p>
<p><strong>Get better at the craft.</strong> AI makes mistakes. It is more valuable to be able to spot the LLM’s mistakes especially when AI is generating a huge volume of code. The only way to get better is from gaining real experience and diving deeper to be more familiar with the tools, languages and frameworks you use. AI can still help with a lot of the development but it helps to know where to check for the occasional errors.</p>
<p><strong>Gain complementary skills.</strong> Building working long-lived software is not just about conjuring pretty algorithms. Keeping a system running for years while handling evolving requirements from paying customers requires more than just ninja coding skills. Operating at scale with larger data sets requires skills like extracting insights from data, optimization to enable efficient use of resources, investigation and debugging when the occasional incident or unexpected errors occur, working in a team, and so on.</p>
<p><strong>Get closer to the business and product.</strong> Understand the real needs of customers. Get better at converting these into viable product specs and implementing them within the constraints that exist in your particular business. This is harder to outsource to AI than just implementing code.</p>
<p>Ultimately this is a double edged sword. What are some of the positives?</p>
<p><strong>Massive Productivity Unlock</strong> I have never been more productive especially with personal projects.</p>
<p>With AI tools, I am learning faster and developing features from start to finish faster than ever. A single person turbocharged with AI tools is now more capable than ever, and it would be a huge self-inflicted mistake not to take advantage of this huge productivity boost.</p>
<p>There are no more blockers, even on personal projects, where there are real constraints like not having experienced colleagues to bounce ideas off.</p>
<p><strong>No limit to learning.</strong><br />I have always been fascinated by some of the niche languages with good ideas, but which still remain unpopular, like Ocaml. Previously it was always a disadvantage working with such esoteric languages. There aren't as many resources to learn from, not many example projects, not the biggest communities, and lots of other issues that don’t face the more popular languages. Right now with the AI tools available today none of those matter. You can learn whatever you want with no limitations.</p>
<h4 id="heading-what-next">What next?</h4>
<p>This is just my impression of things as I see them. I have no idea how it ends up but the best strategy seems to be trying to keep up, always be learning and hope to survive whatever comes next ✌️</p>
]]></content:encoded></item><item><title><![CDATA[Building our own zsh_stats command line app]]></title><description><![CDATA[In the previous post we saw how zsh has a nice inbuilt function zsh_stats to get a summarized list of the most commonly used terminal commands.
This got me wondering, can we replicate this result ourselves? 🤔
Let’s find out.
Language of Choice
We wi...]]></description><link>https://kevgathuku.dev/building-our-own-zsh-stats-cli-app</link><guid isPermaLink="true">https://kevgathuku.dev/building-our-own-zsh-stats-cli-app</guid><category><![CDATA[zsh]]></category><category><![CDATA[#fsharp]]></category><category><![CDATA[F#]]></category><category><![CDATA[cli]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Tue, 17 Sep 2024 10:10:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/amKrqhuM56A/upload/dda5681fe0b936fb4cd40a0b269d9c6c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the <a target="_blank" href="https://kevgathuku.dev/til-getting-your-most-commonly-used-terminal-commands">previous post</a> we saw how zsh has a nice inbuilt function <code>zsh_stats</code> to get a summarized list of the most commonly used terminal commands.</p>
<p>This got me wondering, can we replicate this result ourselves? 🤔</p>
<p>Let’s find out.</p>
<h3 id="heading-language-of-choice">Language of Choice</h3>
<p>We will go with <a target="_blank" href="https://fsharp.org/">F#</a> to do this.</p>
<p>I’ve been checking it out recently and found it to be a really nice concise language (like most other functional languages), with a really helpful type system and an amazing development experience. I love the ability to interactively evaluate expressions right inside the editor. Reminds me a lot of Clojure.</p>
<p>This article: <a target="_blank" href="https://devblogs.microsoft.com/dotnet/why-is-fsharp-code-so-robust-and-reliable/?hide_banner=true">Why is F# code so robust and reliable?</a> does a great job of explaining its amazing features better than I could, so please go and check it out.</p>
<p>I like it so far and want to use it more, so I’m taking every chance I get to build more projects with it 👷🏾</p>
<h3 id="heading-understanding-the-history-file">Understanding the history file</h3>
<p>The first thing we need to do is to get where these historical commands are written and try to read the file ourselves.</p>
<p>In my case (using zsh and oh-my-zsh) I found that the history file is stored in the <code>$HISTFILE</code> environment variable. This is nice since we can just provide the same environment variable to our program.</p>
<p>Let’s see how it looks like with <code>cat $HISTFILE</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726074738998/c7cd51bb-b3b0-4904-b7f4-07e0133331ca.png" alt="Sample of commands from the shell history" class="image--center mx-auto" /></p>
<p>We can see in the small sample here that there are some blank lines mixed in with others that contain a command, along with some other content. Let’s figure it out.</p>
<p>It looks like the lines with the commands start with a colon <code>:</code> and a space, then a number that looks like a unix timestamp, and another colon then <code>0</code><br />Then we have a semicolon <code>;</code> and finally we have the actual command.</p>
<p>According to ChatGPT this the meaning of each part of the line</p>
<blockquote>
<ol>
<li><p><code>: &lt;epoch_timestamp&gt;</code>:</p>
<ul>
<li>This is a Unix epoch timestamp (the number of seconds since January 1, 1970). It represents when the command was executed.</li>
</ul>
</li>
<li><p><code>: &lt;elapsed_time_in_seconds&gt;</code>:</p>
<ul>
<li>This is the amount of time (in seconds) that the command took to execute. It's the difference between the time the command started and when it finished.</li>
</ul>
</li>
<li><p><code>;command</code>:</p>
<ul>
<li>After the semicolon (<code>;</code>), the actual command that was executed in the shell appears. This is the command as the user typed it.</li>
</ul>
</li>
</ol>
</blockquote>
<p>Now that we understand what we have to deal with, let’s setup the project.</p>
<h3 id="heading-project-setup">Project Setup</h3>
<p>If you want to follow along ensure you have <a target="_blank" href="https://dotnet.microsoft.com/en-us/">.NET</a> installed. As of the time of writing, the most current version is .NET8.</p>
<p>Once everything is setup you should have the <code>dotnet</code> command</p>
<pre><code class="lang-bash">❯ dotnet --version
8.0.104
</code></pre>
<p>Let’s use the <code>dotnet</code> CLI to create a new console app and call it <code>HistoryStats</code>. We also need to specify that we want to use <code>F#</code> as the language.</p>
<pre><code class="lang-bash">dotnet new console -lang <span class="hljs-string">"F#"</span> -o HistoryStats
</code></pre>
<p>It generates a console app with this directory structure</p>
<pre><code class="lang-bash">❯ tree
.
├── HistoryStats.fsproj
└── Program.fs

1 directory, 2 files
</code></pre>
<p>We will add all the subsequent code to the <code>Program.fs</code> file.</p>
<p>To make sure everything is working, run the app with <code>dotnet run</code> and you should see the following message printed from the default program</p>
<pre><code class="lang-bash"><span class="hljs-string">"Hello from F#"</span>
</code></pre>
<p>Now that we have the environment ready, let’s get started.</p>
<p>The eventual goal is to run <code>dotnet run $HISTFILE</code> and it should give us a ranked list of the most commonly used commands in our terminal.</p>
<h3 id="heading-reading-the-history-file">Reading the History File</h3>
<p>This is the first iteration of a program to read and print out each line from the history file, which is provided as a command line argument</p>
<pre><code class="lang-fsharp"><span class="hljs-keyword">open</span> System
<span class="hljs-keyword">open</span> System.IO

<span class="hljs-keyword">let</span> commandsByFrequency historyFile =
    File.ReadLines(historyFile)
    |&gt; Seq.iter (<span class="hljs-keyword">fun</span> line -&gt; printfn <span class="hljs-string">"%s"</span> line)

<span class="hljs-meta">[&lt;EntryPoint&gt;]</span>
<span class="hljs-keyword">let</span> main argv =
    <span class="hljs-keyword">let</span> args = Environment.GetCommandLineArgs()

    <span class="hljs-comment">// Optional: Print all the arguments</span>
    printfn <span class="hljs-string">"Command-line arguments: %A"</span> args

    <span class="hljs-keyword">match</span> args <span class="hljs-keyword">with</span>
    | [| _app; historyFile; |] -&gt;
        printfn <span class="hljs-string">"First argument: %s"</span> historyFile
        readFileLines historyFile

    | _ -&gt;
        printfn <span class="hljs-string">"Usage: dotnet run &lt;historyFile&gt;"</span>

    <span class="hljs-number">0</span>
</code></pre>
<p>The main part to focus on is the <code>commandsByFrequency</code> function, which reads the provided file line by line. We then use <code>Seq.iter</code> to iterate and print out each line in the file. We will add most of the functionality to this function.</p>
<p>In the <code>main</code> function, which will be the entrypoint, we also have some pattern matching to ensure we only try to process the file if the program has been run with the first argument, which should be the history file.</p>
<p>If the file path is not provided, we print a message showing that we require an argument to be provided and how to provide it.</p>
<p>Now we can run our application, providing <code>$HISTFILE</code> as the argument, and it should print out each line of the history file.</p>
<pre><code class="lang-fsharp">dotnet run $HISTFILE
</code></pre>
<h3 id="heading-parsing-the-history-file">Parsing the History File</h3>
<p>On my system, printing out all the lines in the history file is quite noisy, so let’s start processing those lines to get to the interesting part.</p>
<p>We will focus on the <code>commandsByFrequency</code> function to do all the processing we need.</p>
<p>First, we need to skip the blank lines before continuing with the processing</p>
<pre><code class="lang-fsharp"><span class="hljs-keyword">let</span> commandsByFrequency filePath =
    <span class="hljs-comment">// Returns an enumerable over the lines in the file</span>
    File.ReadLines(filePath)
    |&gt; Seq.choose (<span class="hljs-keyword">fun</span> line -&gt; <span class="hljs-keyword">if</span> String.IsNullOrWhiteSpace line <span class="hljs-keyword">then</span> None <span class="hljs-keyword">else</span> Some line)
    |&gt; Seq.iter (<span class="hljs-keyword">fun</span> line -&gt; printfn <span class="hljs-string">"%s"</span> line)
</code></pre>
<p>If you run the app now you should see that we no longer print out the blank lines in the file.</p>
<p>We will make heavy use of the <a target="_blank" href="https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-seqmodule.html">Seq module</a>, which is roughly similar to the <a target="_blank" href="https://ruby-doc.org/3.3.5/Enumerable.html">Enumerable</a> module in Ruby. It has a ton of useful functions for processing collections of data. It is lazy by default and only processes individual sequence elements as required, making it a nice tool for our needs.</p>
<p><a target="_blank" href="https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-seqmodule.html#choose"><code>Seq.choose</code></a> deserves a special mention since it makes it so easy to separate the lines we want to keep and the ones to discard. It takes a function that should return <code>None</code> if we want to skip the item or <code>Some x</code> if we want to keep the item <code>x</code></p>
<p>I found it to be quite elegant and we will use it again in the next section.</p>
<h4 id="heading-extracting-the-command-from-a-history-line">Extracting the Command from a History Line</h4>
<p>Now that we have established a pattern for iterating over the lines in the file, let’s start parsing them to extract the info we need.</p>
<p>Just a reminder that this is how a line in the history file looks:<br /><code>: 1726414019:0;brew info mongod</code></p>
<p>To extract the command from a non-blank line, let’s add a new function that should do 2 things:</p>
<ol>
<li><p>Get the command, which starts immediately after the first semicolon i.e. <code>;</code></p>
</li>
<li><p>Extract the first part of the command, without the arguments</p>
</li>
</ol>
<pre><code class="lang-fsharp"><span class="hljs-keyword">let</span> parseHistoryLine (line: string) =
    <span class="hljs-keyword">if</span> line.StartsWith(<span class="hljs-string">":"</span>) <span class="hljs-keyword">then</span>
        <span class="hljs-keyword">let</span> semiColonIndex = line.IndexOf(';')
        <span class="hljs-comment">// 1. Get the command i.e. everything after the semicolon</span>
        <span class="hljs-keyword">let</span> fullCommand = line.Substring(semiColonIndex + <span class="hljs-number">1</span>)
        <span class="hljs-comment">// 2. Split by space and get the first part of the command</span>
        <span class="hljs-keyword">let</span> command = fullCommand.Split([| ' ' |]) |&gt; Array.head
        Some command
    <span class="hljs-keyword">else</span>
        None
</code></pre>
<p>We will use the same pattern of using a function that returns an option, to determine which lines to keep or discard, then passing this function to <code>Seq.choose</code></p>
<p>We return <code>Some command</code> to indicate that want to keep the line and <code>None</code> to indicate that we want to discard the line.</p>
<p>By now we already know how to use this command in our processing pipeline:</p>
<pre><code class="lang-fsharp"><span class="hljs-keyword">let</span> commandsByFrequency historyFile =
    File.ReadLines(historyFile)
    <span class="hljs-comment">// Take only the non-blank lines</span>
    |&gt; Seq.choose (<span class="hljs-keyword">fun</span> line -&gt; <span class="hljs-keyword">if</span> String.IsNullOrWhiteSpace line <span class="hljs-keyword">then</span> None <span class="hljs-keyword">else</span> Some line)
    <span class="hljs-comment">// Extract the command from the line</span>
    |&gt; Seq.choose (<span class="hljs-keyword">fun</span> line -&gt; (parseHistoryLine line))
    <span class="hljs-comment">// Optional: Print out the commands we will process</span>
    |&gt; Seq.iter (<span class="hljs-keyword">fun</span> command -&gt; printfn <span class="hljs-string">"%s"</span> command)
</code></pre>
<p>Now that we have this pattern established, let’s take advantage of it to now finally get the most frequently used commands.</p>
<h4 id="heading-more-seq-magic">More Seq Magic</h4>
<p>All that’s let for us to do is to find out how many times each command occurs. Once we have that, we can sort the sequence of commands in descending order, then get the top commands based on the frequency of occurrence.</p>
<p>Let’s write out the whole function then go through the important parts</p>
<pre><code class="lang-fsharp"><span class="hljs-keyword">let</span> commandsByFrequency count historyFile =
    <span class="hljs-comment">// Returns an enumerable over the lines in the file</span>
    File.ReadLines(historyFile)
    <span class="hljs-comment">// Take only the non-blank lines</span>
    |&gt; Seq.choose (<span class="hljs-keyword">fun</span> line -&gt; <span class="hljs-keyword">if</span> String.IsNullOrWhiteSpace line <span class="hljs-keyword">then</span> None <span class="hljs-keyword">else</span> Some line)
    <span class="hljs-comment">// Extract the command from the line</span>
    |&gt; Seq.choose (<span class="hljs-keyword">fun</span> line -&gt; (parseHistoryLine line))
    <span class="hljs-comment">// Group by command -&gt; (command, seq of commands)</span>
    |&gt; Seq.groupBy id
    <span class="hljs-comment">// Count occurrences</span>
    |&gt; Seq.map (<span class="hljs-keyword">fun</span> (command, occurrences) -&gt; (command, Seq.length occurrences))
    <span class="hljs-comment">// Sort by the count in descending order</span>
    |&gt; Seq.sortByDescending snd
    <span class="hljs-comment">// Take the top `count` elements</span>
    |&gt; Seq.take count
</code></pre>
<p>The first change is the addition of a new <code>count</code> parameter, which represents the number of commands we want to return.</p>
<p>The first addition is <a target="_blank" href="https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-seqmodule.html#groupBy"><code>Seq.groupBy</code></a></p>
<blockquote>
<p>Applies a key-generating function to each element of a sequence and yields a sequence of unique keys. Each unique key contains a sequence of all elements that match to this key.</p>
</blockquote>
<p>The key-generating function we provide is the <a target="_blank" href="https://fsharp.github.io/fsharp-core-docs/reference/fsharp-core-operators.html#id"><code>id</code></a> function, which simply returns whatever is provided to it as an input.</p>
<pre><code class="lang-fsharp"> id <span class="hljs-number">12345</span>     <span class="hljs-comment">//  Evaluates to 12345</span>
 id <span class="hljs-string">"command"</span>  <span class="hljs-comment">//  Evaluates to "command"</span>
</code></pre>
<p>In our case, what it does is to group the commands by the command itself. It returns a <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/tuples">tuple</a> containing 2 items, where the first item is the command itself, and the second is each occurrence of the command in the provided sequence of commands.</p>
<p>This is the sample output we would get if we printed out the result at this point in the program</p>
<pre><code class="lang-fsharp">(<span class="hljs-string">"omz_history"</span>, seq [<span class="hljs-string">"omz_history"</span>])
(<span class="hljs-string">"gds"</span>, seq [<span class="hljs-string">"gds"</span>])
(<span class="hljs-string">"ggpush"</span>, seq [<span class="hljs-string">"ggpush"</span>])
(<span class="hljs-string">"zsh_stats"</span>, seq [<span class="hljs-string">"zsh_stats"</span>; <span class="hljs-string">"zsh_stats"</span>; <span class="hljs-string">"zsh_stats"</span>])
</code></pre>
<p>Now that we have the command and a sequence of all the occurrences, then we can count the occurrences of each command. This is what happens in the next step:</p>
<pre><code class="lang-fsharp"><span class="hljs-comment">// Count occurrences</span>
Seq.map (<span class="hljs-keyword">fun</span> (command, occurrences) -&gt; (command, Seq.length occurrences))
</code></pre>
<p>This takes in the tuple returned from the <code>groupBy</code> and transforms it into a new tuple containing the command and the frequency i.e. number of times it occurs.</p>
<p>So it would transform the previous input into something like this:</p>
<pre><code class="lang-fsharp">(<span class="hljs-string">"omz_history"</span>, <span class="hljs-number">1</span>)
(<span class="hljs-string">"gds"</span>, <span class="hljs-number">1</span>)
(<span class="hljs-string">"ggpush"</span>, <span class="hljs-number">1</span>)
(<span class="hljs-string">"zsh_stats"</span>, <span class="hljs-number">3</span>)
</code></pre>
<p>And now that we have the counts of each command, we can now sort the commands by the count in descending order, which is the second element of the tuple.</p>
<pre><code class="lang-fsharp"><span class="hljs-comment">// Sort by the count in descending order</span>
|&gt; Seq.sortByDescending snd
</code></pre>
<p><code>snd</code> is a function that returns the second element of a tuple, and we use it here to get the count and use it to sort the commands.</p>
<p>And now that we have the sorted commands, the final part is to return the top commands by the count.</p>
<pre><code class="lang-fsharp"><span class="hljs-comment">// Take the top `count` elements</span>
|&gt; Seq.take count
</code></pre>
<p>And just like that, we have our very own custom <code>zsh_stats</code> clone</p>
<h3 id="heading-wrapping-up">Wrapping up</h3>
<p>Now we can take a look at the whole program</p>
<pre><code class="lang-fsharp"><span class="hljs-keyword">open</span> System
<span class="hljs-keyword">open</span> System.IO

<span class="hljs-keyword">let</span> parseHistoryLine (line: string) =
    <span class="hljs-keyword">if</span> line.StartsWith(<span class="hljs-string">":"</span>) <span class="hljs-keyword">then</span>
        <span class="hljs-keyword">let</span> semiColonIndex = line.IndexOf(';')
        <span class="hljs-comment">// 1. Get the command i.e. everything after the semicolon</span>
        <span class="hljs-keyword">let</span> fullCommand = line.Substring(semiColonIndex + <span class="hljs-number">1</span>)
        <span class="hljs-comment">// 2. Split by space to get the first part of the command</span>
        <span class="hljs-keyword">let</span> command = fullCommand.Split([| ' ' |]) |&gt; Array.head
        Some command
    <span class="hljs-keyword">else</span>
        None

<span class="hljs-keyword">let</span> commandsByFrequency count historyFile =
    <span class="hljs-comment">// Returns an enumerable over the lines in the file</span>
    File.ReadLines(historyFile)
    <span class="hljs-comment">// Take only the non-blank lines</span>
    |&gt; Seq.choose (<span class="hljs-keyword">fun</span> line -&gt; <span class="hljs-keyword">if</span> String.IsNullOrWhiteSpace line <span class="hljs-keyword">then</span> None <span class="hljs-keyword">else</span> Some line)
    <span class="hljs-comment">// Extract the command from the line</span>
    |&gt; Seq.choose (<span class="hljs-keyword">fun</span> line -&gt; (parseHistoryLine line))
    <span class="hljs-comment">// Group by command -&gt; (command, seq of commands)</span>
    |&gt; Seq.groupBy id
    <span class="hljs-comment">// Count occurrences</span>
    |&gt; Seq.map (<span class="hljs-keyword">fun</span> (command, occurrences) -&gt; (command, Seq.length occurrences))
    <span class="hljs-comment">// Sort by the count in descending order</span>
    |&gt; Seq.sortByDescending snd
    <span class="hljs-comment">// Take the top `count` elements</span>
    |&gt; Seq.take count


<span class="hljs-meta">[&lt;EntryPoint&gt;]</span>
<span class="hljs-keyword">let</span> main argv =
    <span class="hljs-keyword">let</span> args = Environment.GetCommandLineArgs()

    <span class="hljs-keyword">match</span> args <span class="hljs-keyword">with</span>
    | [| _app; historyFile |] -&gt;
        printfn <span class="hljs-string">"History file: %s"</span> historyFile
        <span class="hljs-keyword">let</span> result = commandsByFrequency <span class="hljs-number">10</span> historyFile

        <span class="hljs-keyword">for</span> (cmd: string, count) <span class="hljs-keyword">in</span> result <span class="hljs-keyword">do</span>
            printfn <span class="hljs-string">"Command: %s \tCount: %d"</span> cmd count

    | _ -&gt; printfn <span class="hljs-string">"Usage: dotnet run &lt;historyFile&gt;"</span>

    <span class="hljs-number">0</span>
</code></pre>
<p>The only addition is that we now print out the results of the <code>commandsByFrequency</code> function. We will default to getting the top 10 results for now.</p>
<p>And now for the moment of truth, let’s compare our program’s output and and the <code>zsh_stats</code> command output</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726565565489/8c87f669-e194-4fb4-a251-966eb3264bb1.png" alt="zsh_stats command output" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726565579072/0eb524e3-3cba-4035-9512-bdc8af3d49cc.png" alt class="image--center mx-auto" /></p>
<p>There’s an off by one error with the <code>ls</code> result which returns 166 in our command vs 165 in <code>zsh_stats</code> but they are mostly similar and it gets the job done.</p>
<iframe src="https://giphy.com/embed/NmerZ36iBkmKk" width="480" height="322" class="giphy-embed"></iframe>

<p><a target="_blank" href="https://giphy.com/gifs/NmerZ36iBkmKk">via GIPHY</a></p>
<h4 id="heading-conclusion">Conclusion</h4>
<p>In summary, we have gone through replicating the zsh function <code>zsh_stats</code> by writing a custom program in F#.</p>
<p>It has proven to be capable enough to replicate the original function, and more importantly, has helped us learn more about operating on sequences of data in F#.</p>
<h5 id="heading-ps">PS:</h5>
<p>If you are interested in how the original <code>zsh_stats</code> function works, here you go</p>
<pre><code class="lang-bash">❯ <span class="hljs-built_in">which</span> zsh_stats
<span class="hljs-function"><span class="hljs-title">zsh_stats</span></span> () {
        <span class="hljs-built_in">fc</span> -l 1 | awk <span class="hljs-string">'{ CMD[$2]++; count++; } END { for (a in CMD) print CMD[a] " " CMD[a]*100/count "% " a }'</span> | grep -v <span class="hljs-string">"./"</span> | sort -nr | head -n 20 | column -c3 -s <span class="hljs-string">" "</span> -t | nl
}
</code></pre>
<p>You can access the full example in the <a target="_blank" href="https://github.com/kevgathuku/HistoryStats">GitHub repository</a></p>
]]></content:encoded></item><item><title><![CDATA[TIL: Getting Your Most Commonly Used Terminal Commands]]></title><description><![CDATA[Today I learned that my favorite terminal zsh combined with oh-my-zsh includes an inbuilt command that I can use to check my most used terminal commands.
To access it you just need to run zsh_stats and it will produce a ranked list of the top 20 comm...]]></description><link>https://kevgathuku.dev/til-getting-your-most-commonly-used-terminal-commands</link><guid isPermaLink="true">https://kevgathuku.dev/til-getting-your-most-commonly-used-terminal-commands</guid><category><![CDATA[zsh]]></category><category><![CDATA[TIL]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Mon, 19 Aug 2024 14:37:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/kt-wA0GDFq8/upload/d5060db54c331db11b58251ffa7d8ea4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today I learned that my favorite terminal <a target="_blank" href="https://www.zsh.org/">zsh</a> combined with <a target="_blank" href="https://ohmyz.sh/">oh-my-zsh</a> includes an inbuilt command that I can use to check my most used terminal commands.</p>
<p>To access it you just need to run <code>zsh_stats</code> and it will produce a ranked list of the top 20 commands and how many times they have been run.</p>
<p>This is the output I get from my terminal</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724077697619/f2815590-80fd-4a75-a053-55b8352cd254.png" alt class="image--center mx-auto" /></p>
<p>A lot of those are command aliases from oh-my-zsh plugins, most of them from the <a target="_blank" href="https://github.com/ohmyzsh/ohmyzsh/blob/master/plugins/git/README.md">git plugin</a>. The top command there is a shortcut for <code>git commit -m</code> which is just a lot to type out every time I need to create a commit message.</p>
<p>This <a target="_blank" href="https://github.com/ohmyzsh/ohmyzsh/wiki/Cheatsheet">cheatsheet</a> contains some more convenient shortcuts and the inbuilt <a target="_blank" href="https://github.com/ohmyzsh/ohmyzsh/wiki/Plugins">oh-my-zsh plugins</a> contain even more awesomeness! I always discover something new there.</p>
<p>Just thought this is a really cool feature. oh-my-zsh is pretty amazing 🤩</p>
]]></content:encoded></item><item><title><![CDATA[Get State from a GenServer process in Elixir]]></title><description><![CDATA[TIL that I have been doing this completely wrong.
I have been using this custom implementation:
defmodule MyModule do
 use GenServer

  def report(server) do
    GenServer.call(server, :report)
  end

  # More functions

  @impl true
  def handle_cal...]]></description><link>https://kevgathuku.dev/get-state-from-a-genserver-process-in-elixir</link><guid isPermaLink="true">https://kevgathuku.dev/get-state-from-a-genserver-process-in-elixir</guid><category><![CDATA[Elixir]]></category><category><![CDATA[genserver]]></category><category><![CDATA[Erlang]]></category><category><![CDATA[TIL]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Thu, 28 Dec 2023 16:22:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/EdSdhvPX36M/upload/9f7a3bff43f9c50d554b17d4f5a5b683.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>TIL that I have been doing this completely wrong.</p>
<p>I have been using this custom implementation:</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">MyModule</span></span> <span class="hljs-keyword">do</span>
 <span class="hljs-keyword">use</span> GenServer

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">report</span></span>(server) <span class="hljs-keyword">do</span>
    GenServer.call(server, <span class="hljs-symbol">:report</span>)
  <span class="hljs-keyword">end</span>

  <span class="hljs-comment"># More functions</span>

  <span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_call</span></span>(<span class="hljs-symbol">:report</span>, _from, state) <span class="hljs-keyword">do</span>
    <span class="hljs-comment"># No modifications. Return the current state</span>
    {<span class="hljs-symbol">:reply</span>, state, state}
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>It is all good and it works. However, it turns out this is all I needed to do</p>
<pre><code class="lang-elixir">{<span class="hljs-symbol">:ok</span>, pid} = MyModule.start_link([])
<span class="hljs-comment"># ... some more operations...</span>
<span class="hljs-symbol">:sys</span>.get_state(pid)
</code></pre>
<p>Well, it does say right there in the <a target="_blank" href="https://www.erlang.org/doc/man/sys.html#get_state-2">get_state docs</a> that the whole purpose is to help the users not to reimplement it.</p>
<p>Now I know and so do you 😀</p>
<p>More resources:</p>
<p><a target="_blank" href="https://hexdocs.pm/elixir/GenServer.html#module-debugging-with-the-sys-module">https://hexdocs.pm/elixir/GenServer.html#module-debugging-with-the-sys-module</a></p>
<p><a target="_blank" href="https://www.erlang.org/doc/man/sys.html#module">https://www.erlang.org/doc/man/sys.html#module</a></p>
]]></content:encoded></item><item><title><![CDATA[[Failing to] Install Ruby 2.2 on MacOS Ventura]]></title><description><![CDATA[If you find yourself in a situation where you might need to install an older Ruby version on a newer Mac (M1 and newer) you are likely to run into a few issues.
This is my (failed) attempt to install Ruby 2.2.10 natively on MacOS Ventura 13.4. It is ...]]></description><link>https://kevgathuku.dev/failing-to-install-ruby-22-on-macos-ventura</link><guid isPermaLink="true">https://kevgathuku.dev/failing-to-install-ruby-22-on-macos-ventura</guid><category><![CDATA[Ruby]]></category><category><![CDATA[macOS]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Fri, 25 Aug 2023 15:30:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/CB--C_XfoRQ/upload/f0cc59e06db9e6f1d9f3f86cb5c349c2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you find yourself in a situation where you might need to install an older Ruby version on a newer Mac (M1 and newer) you are likely to run into a few issues.</p>
<p>This is my (failed) attempt to install Ruby 2.2.10 natively on MacOS Ventura 13.4. It is very unlikely that anyone else out there needs this specific combination, but if you do, please go ahead and enjoy the content.</p>
<p>I'll be using <a target="_blank" href="https://github.com/rbenv/rbenv">rbenv</a> to install Ruby as it's my preferred tool for managing multiple Ruby versions at once.</p>
<blockquote>
<p>Using Docker would probably be the better way to go but Docker on Mac is way slower than Docker on Linux, so be prepared to wait longer for everything.</p>
<p>Following this guide may or may not work for other Ruby versions, or other MacOS versions. Use at your own risk.</p>
</blockquote>
<h3 id="heading-install-openssl-10">Install OpenSSL 1.0</h3>
<p>This is the first requirement when trying to install this Ruby version as it was the supported OpenSSL version at the time of the Ruby release.</p>
<p>Trying to install Ruby 2.2 will initially lead to this error message, which is the first issue we will try to resolve</p>
<pre><code class="lang-bash">rbenv install 2.2.10
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693070207130/235d811a-7086-4239-a169-06311941b58f.png" alt class="image--center mx-auto" /></p>
<p>To resolve this, we'll start by first installing the main dependencies - OpenSSL.</p>
<p>The current OpenSSL version at the time of writing is 3.1.2.1, but Ruby versions lower than 2.3 require OpenSSL 1.0. Since OpenSSL 1.0 is now deprecated, it has since been <a target="_blank" href="https://brew.sh/2019/11/27/homebrew-2.2.0/">removed from Homebrew</a>.</p>
<p>The best way I found to install it is through this <a target="_blank" href="https://github.com/nutcracker/homebrew-tap">repo</a> and the command to do so is:</p>
<pre><code class="lang-bash">brew install nutcracker/tap/openssl@1.0
</code></pre>
<p>However, I quickly ran into this issue when installing:</p>
<p><a target="_blank" href="https://asciinema.org/a/ty2o4dOMw0RhSyuuitI78hv0b"><img src="https://asciinema.org/a/ty2o4dOMw0RhSyuuitI78hv0b.svg" alt="asciicast" /></a></p>
<p>I finally found the solution in this <a target="_blank" href="https://github.com/nutcracker/homebrew-tap/issues/2#issuecomment-1555327201">GH issue</a> from <a target="_blank" href="https://github.com/rhanton">@rhanton</a>, which was to run the `brew install` command with the debug flag and ignore the error when it pops up and asks for user input</p>
<pre><code class="lang-bash">brew install -d nutcracker/tap/openssl@1.0
</code></pre>
<p><a target="_blank" href="https://asciinema.org/a/OlGGFIYksdNBVfk3YdqnXGHBB"><img src="https://asciinema.org/a/OlGGFIYksdNBVfk3YdqnXGHBB.svg" alt="asciicast" /></a></p>
<h3 id="heading-configure-ruby-install-dependencies">Configure Ruby Install Dependencies</h3>
<p>We now need the `rbenv install` command to find the OpenSSL 1.0 libraries when installing, so we'll need to configure a few things in the terminal</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> RUBY_CONFIGURE_OPTS=<span class="hljs-string">"--with-openssl-dir=<span class="hljs-subst">$(brew --prefix openssl@1.0)</span>"</span>
<span class="hljs-built_in">export</span> optflags=<span class="hljs-string">"-Wno-error=implicit-function-declaration"</span>
</code></pre>
<h3 id="heading-install-ruby-22">Install Ruby 2.2</h3>
<pre><code class="lang-bash">rbenv install 2.2.10
</code></pre>
<p>And we will run into one more error:</p>
<p><a target="_blank" href="https://asciinema.org/a/5zpJGaWnXABWeCmccFZEbJMgh"><img src="https://asciinema.org/a/5zpJGaWnXABWeCmccFZEbJMgh.svg" alt="asciicast" /></a></p>
<p>There is a wrong use of the `rm` command somewhere in the install command and we'll need some more manual intervention to fix it.</p>
<p>I found the solution in <a target="_blank" href="https://github.com/rbenv/ruby-build/issues/1742#issuecomment-835790760">this discussion</a> in the ruby-build repository, which involves removing the problematic command from the <code>config.status</code> file</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693493344939/ffc6957d-b46b-4a09-8c9d-91628498d187.png" alt class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://asciinema.org/a/5hqt0qnEGLFDE0LbTzUKAE5ls"><img src="https://asciinema.org/a/5hqt0qnEGLFDE0LbTzUKAE5ls.svg" alt="asciicast" /></a></p>
<p>With that, we should finally have Ruby 2.2.10 working 🎉 ... but the problems are not all gone yet</p>
<h3 id="heading-can-you-live-with-no-gems">Can you live with no gems?</h3>
<p>The main issue we now have to deal with is being unable to install gems. The first thing we need to install to be able to use any gem is <a target="_blank" href="https://bundler.io/">bundler</a></p>
<p>The latest version of bundler cannot be installed on Ruby versions before 2.3, so we will need <a target="_blank" href="https://github.com/rubygems/bundler/issues/6865#issuecomment-586060572">this workaround</a> to install an older version</p>
<pre><code class="lang-bash">gem install bundler -v <span class="hljs-string">'~&gt;1'</span>
</code></pre>
<p>Even with this latest workaround, there is still a final unresolved issue when trying to install any gems, which takes us back to the OpenSSL issue. Now we can't even install bundler since we can't connect to rubygems over SSL 🫠</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693493700478/e27b348d-ccee-4d28-bf16-f3bcf7256563.png" alt class="image--center mx-auto" /></p>
<p>I still haven't been able to resolve this but if you have any pointers please leave a comment!</p>
<p>And this is how our adventure ends...in disappointment. After all this effort we have a barely usable old version of Ruby installed. If there ever was a perfect use case for Docker this is it. It was still fun giving it a try 😀</p>
<p>Please try to use supported versions of the software you use whenever possible</p>
]]></content:encoded></item><item><title><![CDATA[3 mistakes to avoid in your rspec tests]]></title><description><![CDATA[rspec is an amazing testing tool for Ruby and Rails apps. I have used it in most of the projects I’ve worked on for a long time. Despite this, I am still prone to make mistakes, so I’ll keep a log of some of them here for future reference
Failing to ...]]></description><link>https://kevgathuku.dev/3-mistakes-to-avoid-in-your-rspec-tests</link><guid isPermaLink="true">https://kevgathuku.dev/3-mistakes-to-avoid-in-your-rspec-tests</guid><category><![CDATA[Rails]]></category><category><![CDATA[#rspec]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Tue, 20 Jun 2023 19:17:33 GMT</pubDate><content:encoded><![CDATA[<p><code>rspec</code> is an amazing testing tool for Ruby and Rails apps. I have used it in most of the projects I’ve worked on for a long time. Despite this, I am still prone to make mistakes, so I’ll keep a log of some of them here for future reference</p>
<h3 id="heading-failing-to-use-hooks-correctly">Failing to use hooks correctly</h3>
<p>Can you spot the error in the following code?</p>
<pre><code class="lang-ruby">context <span class="hljs-string">'some setup code here'</span> <span class="hljs-keyword">do</span>
    call_some_setup_function(x, y)

    it <span class="hljs-string">'validates some conditions'</span> <span class="hljs-keyword">do</span>
        expect(sum(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)).to eq <span class="hljs-number">3</span>
    <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>To help you along, this is the error message produced. Any luck?</p>
<pre><code class="lang-plaintext">NoMethodError:
  undefined method `call_some_setup_function' for #&lt;Class:0x000040229933d0&gt;
</code></pre>
<p>I don’t know about you but it took me quite some time to catch the source of the error. The mistake here is that the setup function should be called in a <code>before</code> block</p>
<pre><code class="lang-ruby">context <span class="hljs-string">'some setup code here'</span> <span class="hljs-keyword">do</span>
    before { call_some_setup_function(x, y) }

    it <span class="hljs-string">'validates some conditions'</span> <span class="hljs-keyword">do</span>
        expect(sum(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)).to eq <span class="hljs-number">3</span>
    <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<h3 id="heading-not-using-the-full-range-of-matchers">Not using the full range of matchers</h3>
<p>I wouldn’t be able to list all the possible matchers available by default in <code>rspec</code> here. For that, take a look at the <a target="_blank" href="https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/">Built-in matchers documentation</a></p>
<p>Here are some of them that I’ve found to be useful:</p>
<pre><code class="lang-ruby">expect(result).to be_success      <span class="hljs-comment"># passes if result.success?</span>
expect(result).to be_failure      <span class="hljs-comment"># passes if result.failure?</span>
expect(result).to be_processing   <span class="hljs-comment"># passes if result.processing?</span>

<span class="hljs-comment"># arrays</span>
expect(actual).to <span class="hljs-keyword">include</span>(expected)
expect(array).to match_array(expected_array) <span class="hljs-comment"># exact array match</span>

<span class="hljs-comment"># strings</span>
expect(<span class="hljs-string">'gato'</span>).not_to <span class="hljs-keyword">include</span>(<span class="hljs-string">'ef'</span>)
expect(<span class="hljs-string">'hola'</span>).to <span class="hljs-keyword">include</span>(<span class="hljs-string">'h'</span>)

<span class="hljs-comment"># changes to object count in DB from some external function</span>
<span class="hljs-comment"># e.g. HTTP Request</span>
expect { good_post_request }.to change { Account.count }.by(<span class="hljs-number">1</span>)
expect { bad_post_request }.not_to change { Account.count }
</code></pre>
<h3 id="heading-repeating-specs">Repeating specs</h3>
<p>If you have a set of specs that you seem to keep repeating in many different tests, put them in a <code>shared_examples</code> block</p>
<pre><code class="lang-ruby">shared_examples <span class="hljs-string">'account creation'</span> <span class="hljs-keyword">do</span>
  it <span class="hljs-string">'succeeds'</span> <span class="hljs-keyword">do</span>
    expect { post <span class="hljs-symbol">:create</span>, <span class="hljs-symbol">data:</span> data }.to change { Account.count }
    expect(response).to be_success
    <span class="hljs-comment"># ... and any other expectations for the new account</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-comment"># ...and to use it in other places without repeating the examples</span>
include_examples <span class="hljs-string">'account creation'</span>
</code></pre>
<p>You can also share and re-use common test setup steps with <code>shared_context</code> and <code>include_context</code></p>
<blockquote>
<p>Pro tip: Under the hood <code>shared_examples</code> , <code>shared_examples_for</code> and <code>shared_context</code> are all the same thing</p>
<p><a target="_blank" href="https://github.com/rspec/rspec-core/blob/8caecca0b9b299ccbaa5c7ea5dd885ab42cd57d3/lib/rspec/core/shared_example_group.rb#L100">rspec-core/lib/rspec/core/shared_example_group.rb at 8caecca0b9b299ccbaa5c7ea5dd885ab42cd57d3 · rspec/rspec-core · GitHub</a></p>
</blockquote>
<p>I still need to understand this better and you can find more information in the <a target="_blank" href="https://rspec.info/features/3-12/rspec-core/example-groups/shared-examples/">shared examples documentation</a></p>
<p>Happy testing!</p>
<hr />
<p>Cover Photo by <a target="_blank" href="https://burst.shopify.com/@sarahpflugphoto?utm_campaign=photo_credit&amp;utm_content=Free+Brewery+Beer+Tanks+Image%3A+Browse+1000s+of+Pics&amp;utm_medium=referral&amp;utm_source=credit">Sarah Pflug</a> from <a target="_blank" href="https://burst.shopify.com/beer?utm_campaign=photo_credit&amp;utm_content=Free+Brewery+Beer+Tanks+Image%3A+Browse+1000s+of+Pics&amp;utm_medium=referral&amp;utm_source=credit">Burst</a></p>
]]></content:encoded></item><item><title><![CDATA[Managing Multiple Rails Versions in Development]]></title><description><![CDATA[You can have multiple Rails versions installed at the same time.
To install the rails version you need:
gem install rails --version=5.2.8

How to specify the rails version to use
rails _5.2.8_ new old_school_rails

And inside the project, you will ha...]]></description><link>https://kevgathuku.dev/managing-multiple-rails-versions-in-development</link><guid isPermaLink="true">https://kevgathuku.dev/managing-multiple-rails-versions-in-development</guid><category><![CDATA[Rails]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Wed, 14 Jun 2023 21:14:34 GMT</pubDate><content:encoded><![CDATA[<p>You can have multiple Rails versions installed at the same time.</p>
<p>To install the rails version you need:</p>
<pre><code class="lang-bash">gem install rails --version=5.2.8
</code></pre>
<p>How to specify the rails version to use</p>
<pre><code class="lang-sh">rails _5.2.8_ new old_school_rails
</code></pre>
<p>And inside the project, you will have the correct rails version</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> old_school_rails
bundle <span class="hljs-built_in">exec</span> rails --version
Rails 5.2.8.1
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Getting Started with OCaml and Reason]]></title><description><![CDATA[Recently I've been exploring OCaml, an interesting statically typed functional language that's been around since 1996. I've tried OCaml in the past and always got frustrated by the tools and just gave up. This time round, there seems to have been an ...]]></description><link>https://kevgathuku.dev/getting-started-with-ocaml-and-reason</link><guid isPermaLink="true">https://kevgathuku.dev/getting-started-with-ocaml-and-reason</guid><category><![CDATA[reasonml]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Fri, 03 Sep 2021 15:19:34 GMT</pubDate><content:encoded><![CDATA[<p>Recently I've been exploring <a target="_blank" href="https://ocaml.org/">OCaml</a>, an interesting statically typed functional language that's been around since 1996. I've tried OCaml in the past and always got frustrated by the tools and just gave up. This time round, there seems to have been an effort to improve the developer experience and it shows. The tools are much easier to use, and mostly just work.</p>
<p>In this post, we will go through setting up an OCaml development environment on a Mac. I will also share a starter project, some of the essential packages you will need.</p>
<h3 id="heading-installing-ocaml">Installing Ocaml</h3>
<p>On a Mac, you should be able to install <code>ocaml</code> with homebrew:</p>
<pre><code class="lang-sh">brew install ocaml
</code></pre>
<p>Real World OCaml has the best <a target="_blank" href="https://dev.realworldocaml.org/install.html">installation guide</a> I have found so far, so follow that and you should be good.</p>
<p>At the end of that guide, you should have <code>ocaml</code> and <code>opam</code> installed:</p>
<pre><code>❯ <span class="hljs-selector-tag">ocaml</span> <span class="hljs-selector-tag">--version</span>
<span class="hljs-selector-tag">The</span> <span class="hljs-selector-tag">OCaml</span> <span class="hljs-selector-tag">toplevel</span>, <span class="hljs-selector-tag">version</span> 4<span class="hljs-selector-class">.12</span><span class="hljs-selector-class">.0</span>

❯ <span class="hljs-selector-tag">opam</span> <span class="hljs-selector-tag">--version</span>
2<span class="hljs-selector-class">.1</span><span class="hljs-selector-class">.0</span>
</code></pre><p>Opam is the default package management tool in Ocaml ecosystem.</p>
<p>In addition to following that guide, install  <a target="_blank" href="https://dune.build/">dune</a>, the ocaml build tool. We will need it later.</p>
<pre><code class="lang-sh">opam install dune
</code></pre>
<h3 id="heading-isolated-development-environments-per-project-with-opam">Isolated Development Environments per project with opam</h3>
<p>Opam can create isolated OCaml environments per project. Think of this as  <a target="_blank" href="https://virtualenv.pypa.io/en/latest/">virtualenv</a>, but for OCaml.
By default, when starting a new project, I run the following command in the project folder to create a brand new OCaml environment:</p>
<pre><code class="lang-sh">opam switch create . ocaml-base-compiler.4.12.0
</code></pre>
<p>This will create an isolated OCaml environment and with OCaml version 4.12.0 installed.</p>
<p>To activate the newly created environment in the current shell, run the following command:</p>
<pre><code class="lang-sh"><span class="hljs-built_in">eval</span> $(opam env)
</code></pre>
<p>To automate this process, I use <a target="_blank" href="https://direnv.net/">direnv</a> to activate the isolated OCaml environment in my shell every time I navigate to the folder. This is done through adding the command above to a <code>.envrc</code> file in the project folder.</p>
<h3 id="heading-editor-setup-vs-code">Editor setup: VS-Code</h3>
<p>For OCaml development on Visual Studio Code, the essential package you need is  <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=ocamllabs.ocaml-platform">VSCode OCaml Platform</a> </p>
<p>To make full use of the extension, I've found that I've needed to install the <code>ocamlformat</code> and <code>ocaml-lsp-server</code> packages individually in each of the specific project I'm developing.</p>
<h3 id="heading-starter-project-template-dune-package-manager">Starter Project Template - dune package manager</h3>
<p>To ease the process of getting started on a new project, please take a look at this  <a target="_blank" href="https://github.com/kevgathuku/hello-caml">project template </a> that I use whenever I need to start a new project.</p>
<h3 id="heading-reason-support">Reason Support</h3>
<p>In the starter project template, you should find a Reason file in the <code>lib/</code> directory.
Dune supports Reason files by default, so you don't need to do anything extra to write your code in the Reason variant. You can also mix and match Reason and Ocaml files in the project and it should work just fine.</p>
<p>I've found that to enable formatting Reason files in Visual Studio Code, I also have to install the <code>refmt</code> opam package locally in the project-specific environment.</p>
<p>That's all for now. </p>
<p>Enjoy the rest of your day!  </p>
]]></content:encoded></item><item><title><![CDATA[Automatically deploy a Gatsby site to Firebase Hosting]]></title><description><![CDATA[Firebase Hosting is a web content hosting platform by Google. Through this service, you can host your web apps on Google’s infrastructure. It enables easy one-step deployment and has other cool features such as fast hosting from CDNs and rollbacks.
G...]]></description><link>https://kevgathuku.dev/automatically-deploy-a-gatsby-site-to-firebase-hosting</link><guid isPermaLink="true">https://kevgathuku.dev/automatically-deploy-a-gatsby-site-to-firebase-hosting</guid><category><![CDATA[Gatsby]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Tue, 18 Jun 2019 18:07:31 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://firebase.google.com/products/hosting">Firebase Hosting</a> is a web content hosting platform by Google. Through this service, you can host your web apps on Google’s infrastructure. It enables easy one-step deployment and has other cool features such as fast hosting from CDNs and rollbacks.</p>
<p><a target="_blank" href="https://www.gatsbyjs.org/">Gatsby</a> is a tool that enables creating blazing fast React-based apps and websites. It enables building websites with data fetched from a wide variety of sources, including markdown files, APIs and even CMSs. It then combines this data with a React-based frontend architecture to build extremely fast interactive websites.</p>
<p>In this post, we will setup a simple Gatsby site, host the code on a GitHub repository and setup automatic deployment of our web application to Firebase Hosting using CircleCI.</p>
<p>Read the rest of the article <a target="_blank" href="https://circleci.com/blog/automatically-deploy-a-gatsby-site-to-firebase-hosting/">on the CircleCI blog</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Bootstrapping an Elm Project]]></title><description><![CDATA[Elm is a delightful language for building reliable web applications. It's a language that gets compiled to JavaScript and so is used to build applications that run in a web browser.
In this post, we're going to explore different ways of starting an E...]]></description><link>https://kevgathuku.dev/bootstrapping-an-elm-project</link><guid isPermaLink="true">https://kevgathuku.dev/bootstrapping-an-elm-project</guid><category><![CDATA[ELM]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Wed, 29 May 2019 17:52:14 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://elm-lang.org/">Elm</a> is a delightful language for building reliable web applications. It's a language that gets compiled to JavaScript and so is used to build applications that run in a web browser.</p>
<p>In this post, we're going to explore different ways of starting an Elm project, starting with the simplest and moving on to setups with more advanced features such as hot-reload in development.</p>
<p>Let's get started!</p>
<h3 id="prerequisites">Prerequisites</h3>
<p>Before we get started, please make sure to <a target="_blank" href="https://guide.elm-lang.org/install.html">Install Elm</a></p>
<p>To confirm if you have Elm installed, you can try running the interactive <code>repl</code> using the <code>elm repl</code> command. If you get a prompt such as the one shown in this image, you're good to go 👍</p>
<p><img src="https://kevgathuku.files.wordpress.com/2019/05/screen-shot-2019-05-29-at-10.15.12.png?w=1024" alt="Elm repl" /></p>
<h3 id="elm-cli">Elm CLI</h3>
<p>This is the officially supported way of creating a new Elm project.</p>
<ol>
<li>Create a new directory where your project will live</li>
</ol>
<p><code>mkdir my-awesome-elm-project</code></p>
<ol>
<li>Navigate to the newly created directory</li>
</ol>
<p><code>cd my-awesome-elm-project</code></p>
<ol>
<li>Run <code>elm init</code> inside this directory and you should get a prompt like the one below:</li>
</ol>
<p><img src="https://kevgathuku.files.wordpress.com/2019/05/screen-shot-2019-05-29-at-10.29.05.png" alt="Elm init prompt" /></p>
<ol>
<li>Press the <code>Enter</code> key and it should create an <code>elm.json</code> file in the current directory. A <code>src</code> directory will also be created.</li>
</ol>
<p><em>It is a good idea to read through the <a target="_blank" href="https://elm-lang.org/0.19.0/init">linked resource</a> that talks more about starting new Elm projects</em></p>
<ol>
<li>Let's start by creating a new <code>Main.elm</code> file in the <code>src</code> directory. Once we've created the file, let's add some Elm code that should show us the classic "Hello World!" message once we run it.</li>
</ol>
<pre><code class="lang-elm">module Main exposing (main)

import Browser
import Html exposing (h1, text)

main =
  h1 [] [ text "Hello World!" ]
</code></pre>
<ol>
<li><p>To run the code, let's run <code>elm reactor</code> in our directory and it should start a new local server at <a target="_blank" href="http://localhost:8000">http://localhost:8000</a>. <code>elm reactor</code> is the simplest way to get started running Elm code.</p>
</li>
<li><p>Once you navigate to <a target="_blank" href="http://localhost:8000">http://localhost:8000</a>, you should see an interface like the following.</p>
</li>
</ol>
<p><img src="https://kevgathuku.files.wordpress.com/2019/05/screen-shot-2019-05-29-at-10.48.52.png" alt="Elm Reactor" /></p>
<p>Click on the <code>src</code> link, then <code>Main.elm</code> and you should be greeted by our "Hello World!" message.</p>
<p>And that's it! We've successfully created a project the <code>elm init</code> command and run it using <code>elm reactor</code>.</p>
<h4 id="pros">Pros:</h4>
<ul>
<li>Easy to get started</li>
<li>No external dependencies apart from <code>elm</code> itself</li>
</ul>
<h4 id="cons">Cons:</h4>
<ul>
<li>Manual reloading once we make changes</li>
</ul>
<h3 id="elm-make">Elm Make</h3>
<p>This is an extension of <code>elm reactor</code> and includes the ability to compile our Elm code to static HTML.</p>
<p>Using the same project from our previous section, we can compile the project using the command:</p>
<p><code>elm make src/Main.elm</code></p>
<p>Once we run this command, an <code>index.html</code> file will be generated in the current working directory, and if you open it in a web browser, you should see the same "Hello World" message.</p>
<p>I don't have lots of experience with <code>elm make</code> so that's as far as I'll go with it.</p>
<h3 id="parcel">Parcel</h3>
<p><a target="_blank" href="https://parceljs.org/">Parcel</a> is a "Blazing fast, zero configuration web application bundler" and is my personal favourite to get started with an Elm application quickly. It handles compiling your Elm code to JavaScript and is super easy to get started with.</p>
<p>You can create an Elm application compiled by Parcel in a few simple steps:</p>
<ol>
<li>Install Parcel</li>
</ol>
<p><code>yarn global add parcel-bundler</code> or <code>npm install -g parcel-bundler</code></p>
<ol>
<li><p>Follow the instructions in the <a target="_blank" href="https://parceljs.org/elm.html">Elm section</a> of the Parcel website, which involves:</p>
</li>
<li><p>creating an <code>index.html</code> file with the following contents:</p>
</li>
</ol>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./index.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<ul>
<li>creating an <code>index.js</code> file with the following contents:</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Elm } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Main.elm"</span>

Elm.Main.init({
  <span class="hljs-attr">node</span>: <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"main"</span>),
})
</code></pre>
<ul>
<li>creating a <code>Main.elm</code> file with the following contents:</li>
</ul>
<pre><code class="lang-elm">module Main exposing (main)

import Browser
import Html exposing (h1, text)

main =
  h1 [] [ text "Hello, from Parcel!" ]
</code></pre>
<ol>
<li>To run the application, run the command:</li>
</ol>
<p><code>parcel index.html</code></p>
<p>I love this setup so much that I've created my own starter project based on Parcel. Feel free to check it out on <a target="_blank" href="https://github.com/kevgathuku/parcel-elm/">GitHub</a></p>
<h4 id="pros">Pros:</h4>
<ul>
<li>Easy to get started with</li>
<li>No manual configuration needed 💪</li>
<li>Hot reload included out of the box</li>
<li>Easy to get started with <a target="_blank" href="https://guide.elm-lang.org/interop/">JavaScript Interop</a></li>
</ul>
<h4 id="cons">Cons:</h4>
<p>🤷‍♀️</p>
<h3 id="notable-mentions">Notable Mentions:</h3>
<ul>
<li><a target="_blank" href="https://github.com/halfzebra/create-elm-app">create-elm-app</a> – Based on <a target="_blank" href="https://github.com/facebook/create-react-app">create-react-app</a> and creates a zero-config application based on Webpack</li>
<li><a target="_blank" href="https://github.com/wking-io/elm-live">elm-live</a> – A flexible dev server for Elm. Live reload included.</li>
</ul>
<p><em>This is not meant to be a comprehensive list, and any suggestions/additions are welcome in the comments section below.</em></p>
<p>I hope these options are useful for you when starting out your next Elm project 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Continuously testing React applications with Jest and Enzyme]]></title><description><![CDATA[React has taken the frontend world by storm, and for a good reason. It provides an intuitive model for building data-driven user interfaces. React enables us to write declarative components describing the user interface and provide the data needed to...]]></description><link>https://kevgathuku.dev/continuously-testing-react-applications-with-jest-and-enzyme</link><guid isPermaLink="true">https://kevgathuku.dev/continuously-testing-react-applications-with-jest-and-enzyme</guid><category><![CDATA[React]]></category><category><![CDATA[Jest]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Tue, 05 Feb 2019 18:05:50 GMT</pubDate><content:encoded><![CDATA[<p>React has taken the frontend world by storm, and for a good reason. It provides an intuitive model for building data-driven user interfaces. React enables us to write declarative components describing the user interface and provide the data needed to render these interfaces. It then efficiently updates the DOM when this data changes. Redux, on the other hand, enables managing the data that React needs to render interfaces. It offers a predictable way to structure and update the data in frontend applications and pairs quite nicely with React.</p>
<p>In this post, we will explore how to go about writing tests for an existing React and Redux application. We will then configure continuous integration with CircleCI, to automate the testing and ensure that any new code we add does not break the existing functionality.</p>
<p>Read the rest of the article <a target="_blank" href="https://circleci.com/blog/continuously-testing-react-applications-with-jest-and-enzyme/">on the Semaphore Blog</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Cleaner Clojure Functions with the :pre Special Form]]></title><description><![CDATA[In the course of programming, it's fairly common to face a scenario where you want to ensure that a function's arguments fulfil some condition(s), and if not, to throw an error.
For example, if our function should only accept positive numbers.
In thi...]]></description><link>https://kevgathuku.dev/cleaner-clojure-functions-with-the-pre-special-form</link><guid isPermaLink="true">https://kevgathuku.dev/cleaner-clojure-functions-with-the-pre-special-form</guid><category><![CDATA[Clojure]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Thu, 03 Jan 2019 17:48:39 GMT</pubDate><content:encoded><![CDATA[<p>In the course of programming, it's fairly common to face a scenario where you want to ensure that a function's arguments fulfil some condition(s), and if not, to throw an error.</p>
<p>For example, if our function should only accept positive numbers.</p>
<p>In this post, we're going to see how Clojure provides a handy special form that greatly simplifies this task.</p>
<p>One such scenario that I've come across recently is trying to come up with a function that would compute the <a target="_blank" href="https://en.wikipedia.org/wiki/Collatz_conjecture">3x + 1 problem (Collatz conjecture)</a>. The problem is as follows:</p>
<blockquote>
<p>Take any positive integer n.</p>
<p>If n is even, divide n by 2 to get n / 2.</p>
<p>If n is odd, multiply n by 3 and add 1 to get 3n + 1.</p>
<p>Repeat the process indefinitely.</p>
<p>The conjecture states that no matter which number you start with, you will always reach 1 eventually.</p>
</blockquote>
<p>The task at hand was to write a function that takes a number as an argument and returns the number of steps it takes for the number to eventually be 1.</p>
<p>You'll also notice that the first condition for the number to be passed in is that it has to be a <strong>positive integer</strong>. Therefore, given any value less than 1, the function should throw an error.</p>
<p>A typical solution in Clojure would look as follows:</p>
<pre><code class="lang-clojure">(defn compute-collatz [num steps]
  (cond
     (= num 1) steps
     (even? num) (compute-collatz (/ num 2) (inc steps))
     (odd? num)  (compute-collatz (+ 1 (* num 3)) (inc steps))))


(defn collatz [num]
  ; Check if the provided number is positive. If not throw an error
  (if
    (pos? num) (compute-collatz num 0)
    (throw (AssertionError. "Positive integer required."))))
</code></pre>
<p>While this definitely works, I found out that Clojure provides a much more elegant way to accomplish this, with the help of the <a target="_blank" href="https://clojure.org/reference/special_forms#_fn_name_param_condition_map_expr_2"><code>:pre</code> special form</a>.</p>
<p>This form enables specifying conditions about the arguments that should be met before the rest of the function body is evaluated. If any of the conditions is not met, an assertion error is thrown.</p>
<p>This is how we could rewrite the function making use of the <code>:pre</code> condition:</p>
<pre><code class="lang-clojure">(defn compute-collatz [num steps]
  (cond
     (= num 1) steps
     (even? num) (compute-collatz (/ num 2) (inc steps))
     (odd? num)  (compute-collatz (+ 1 (* num 3)) (inc steps))))


(defn collatz [num]
  {:pre [(pos? num)]}
  (compute-collatz num 0))
</code></pre>
<p>In this case, if the function is called with <code>0</code> or a negative number, an assertion error is automatically thrown.</p>
<p>I think you would agree that the second version looks much nicer 🙂</p>
]]></content:encoded></item><item><title><![CDATA[Auto-formatting your JavaScript Codebase with Prettier]]></title><description><![CDATA[While creating new JavaScript projects, I've found that I need to add Prettier to each one of them. Prettier is an awesome opinionated code formatter, with good enough defaults that I almost never need to configure it. It just works, and it looks goo...]]></description><link>https://kevgathuku.dev/auto-formatting-your-javascript-codebase-with-prettier</link><guid isPermaLink="true">https://kevgathuku.dev/auto-formatting-your-javascript-codebase-with-prettier</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Prettier]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Thu, 13 Dec 2018 18:03:50 GMT</pubDate><content:encoded><![CDATA[<p>While creating new JavaScript projects, I've found that I need to add <a target="_blank" href="https://prettier.io/">Prettier</a> to each one of them. Prettier is an awesome opinionated code formatter, with good enough defaults that I almost never need to configure it. It just works, and it looks good 🎨</p>
<p>I'm documenting this so that I can have a single place to refer to the next time. I'm also doing it because I honestly need to get back to producing content and I might as well start with something I find useful.</p>
<p>Also it's 2018 and you want to spend your time actually writing code not thinking about whether to use double or single quotes every single time 😅</p>
<p>After initialising your project and ensuring you have a <code>package.json</code> file, run the following command to install prettier:</p>
<p><img src="https://kevgathuku.files.wordpress.com/2018/12/carbon.png?w=700" alt="carbon" /></p>
<p>That's enough to use prettier. It comes with nice defaults that work pretty well for most cases. However, if you must make changes to the config, here's the different <a target="_blank" href="https://prettier.io/docs/en/options.html">Prettier config options</a>.</p>
<p>The next part is configuring Prettier to work with <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a>, my preferred editor.</p>
<p>In Visual Studio Code, go to the extensions pane and search for <code>Prettier - Code formatter</code>​ and install the first extension in the search results.</p>
<p>Now you can format the particular file by using the formatting key combination. Mine is <code>control + option + b</code> and this formats the file.</p>
<p>Prettier has support for lots of files, but I mostly use it for JavaScript, JSX, JSON, HTML and CSS as those are the ones I work with regularly.</p>
<p>And that ends today's post. Hope you get to try out Prettier too, trust me, it's awesome.</p>
]]></content:encoded></item><item><title><![CDATA[Getting Started Contributing to Nextcloud]]></title><description><![CDATA[In August 2018, I had the pleasure of speaking at the Nextcloud Conference held at the Technical University Berlin. It was my first time giving a talk at a conference and I gave a lightning talk (5 minutes) about getting started contributing to Nextc...]]></description><link>https://kevgathuku.dev/getting-started-contributing-to-nextcloud</link><guid isPermaLink="true">https://kevgathuku.dev/getting-started-contributing-to-nextcloud</guid><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Wed, 29 Aug 2018 18:00:53 GMT</pubDate><content:encoded><![CDATA[<p>In August 2018, I had the pleasure of speaking at the <a target="_blank" href="https://nextcloud.com/conf/">Nextcloud Conference</a> held at the Technical University Berlin. It was my first time giving a talk at a conference and I gave a lightning talk (5 minutes) about getting started contributing to <a target="_blank" href="https://github.com/nextcloud">Nextcloud</a>. This is a blog post version of the talk I gave and hopefully it motivates you to get started contributing too!</p>
<h2 id="what-is-nextcloud">What is Nextcloud?</h2>
<p>Nextcloud is a set of technologies that enables file sharing and collaboration. I like to think of it as a self-hosted version of Google Drive or Dropbox. It enables its users to self-host and manage their files on their own servers, and offers lots of collaboration options. It also has apps that add more functionality such as calendar and email management, chat and video conferencing. A great place to learn more about the company is the <a target="_blank" href="https://nextcloud.com/about/">Nextcloud website</a>.</p>
<p>An interesting thing is that the software that powers all of this is entirely open source. Anyone can contribute to it, and this is where I come in. Contributing to open source software has always been one of my best learning avenues, and so I wanted to help others to get started contributing as well. I started contributing code to Nextcloud around 2 months ago and found that it's a very welcoming community of people that would benefit from having even more contributors joining in.</p>
<p>Now that we know what Nextcloud is and why you would want to join as a contributor, let's get to the actual process!</p>
<h2 id="building-trust">Building Trust</h2>
<p>The core idea I follow when contributing to open source is building trust between myself as a developer and the maintainers of the software. Beyond just contributing code, I find that maintainers appreciate developers who help make their job easier. This involves things like working on the right stuff and proactive communication. This eventually leads to a build-up of trust over time and this process of earning this trust is what I will focus on in this post. I will go over it using practical examples from my experiences contributing to Nextcloud.</p>
<h3 id="first-steps">First Steps</h3>
<p>Nextcloud has a <a target="_blank" href="https://github.com/nextcloud/server/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22">good first issues</a> label on their issue tracker to identify issues that are best suited for first time contributors. In my experience, this is the best place to get started if you're contributing for the first time. At the time of writing this, there are 76 issues with this label so there's lots of opportunities to contribute 😀</p>
<p>As a contributor, working on good first issues has quite some advantages:</p>
<ul>
<li>You're able to set up and run the project on your dev environment</li>
<li>You get familiar with the contribution process. This info is usually contained in a <a target="_blank" href="https://github.com/nextcloud/server/blob/master/CONTRIBUTING.md">contributing doc</a> on the repository</li>
<li>You start to gain the trust of the maintainers, since they see that you care enough to want to contribute</li>
</ul>
<p>For the maintainers, a new contributor is able to help them in the following ways:</p>
<ul>
<li>An issue actually gets fixed. It might be a simple problem to fix, but it's still an impactful issue that makes the project better.</li>
<li>Identify potential friction points for new contributors e.g. if the contribution docs are unclear, if there's missing docs, etc.</li>
<li>Building community. A good first contribution experience helps new contributors grow into more active community members and become potential future maintainers.</li>
</ul>
<h3 id="use-the-software">Use the Software</h3>
<p>At this point, you're a new contributor who has worked on a good first issue and hopefully got your contribution merged. To continue finding new opportunities to contribute, you have to actively use the software to get a feel of an end-user's perspective. For example, once I got started using Nextcloud, I noticed it could have better support for keyboard navigation. I reported the issues I found on GitHub and eventually <a target="_blank" href="https://github.com/nextcloud/server/pulls?q=is%3Apr+is%3Aclosed+author%3Akevgathuku">contributed fixes</a> for them.</p>
<p>Once again, this has some benefits for you as the contributor, such as:</p>
<ul>
<li>Learning how to file a helpful bug report e.g. what info to include to help reproduce an issue.</li>
<li>It enables more interaction with the maintainers i.e. you ask questions when stuck and they give you feedback on your contributions.</li>
<li>It helps build trust. Working through issues together is one of the best avenues to build trust between contributors and maintainers.</li>
</ul>
<p>For the maintainers, when you use the software, report issues and fix them, it benefits them by:</p>
<ul>
<li>Making it easier for them to provide help and guidance when you're stuck</li>
<li>Helping set expectations on how to approach issues e.g. <a target="_blank" href="https://github.com/nextcloud/server/pull/9956#issuecomment-399245874">this comment</a> shows a practical example of this point</li>
<li>Getting a perspective on what matters to different users</li>
</ul>
<h3 id="explore-the-ecosystem">Explore the ecosystem</h3>
<p>Most open source software projects usually have an ecosystem around them. In our case, Nextcloud has the concept of <a target="_blank" href="https://apps.nextcloud.com/">apps</a> through which developers can add new functionalities beyond the main purpose of file-sharing and collaboration. Additionally, the officially supported Nextcloud apps are also open source too.</p>
<p>The apps offer a new place to build out a new set of skills by working on more diverse things. I chose to contribute to the <a target="_blank" href="https://apps.nextcloud.com/apps/mail">Mail app</a> which is an e-mail client built inside Nextcloud.</p>
<p>The advantages of working on the apps in the ecosystem are:</p>
<ul>
<li>Smaller surface area. The apps usually have a very specific focus and consequently you can have more of an impact</li>
<li>There's lots of apps and so there's lots of opportunities to share best practices with the other apps e.g. There's an effort to <a target="_blank" href="https://github.com/nextcloud/mail/projects/4">rewrite the mail app in Vue</a> and we've taken lots of cues from the <a target="_blank" href="https://github.com/nextcloud/contacts/tree/vue">contacts app</a> on the way to go about it.</li>
</ul>
<h2 id="lessons-learnt">Lessons Learnt</h2>
<p>Having gone through all these steps above, and applied them while contributing to Nextcloud, here's some of the things I've learnt:</p>
<ul>
<li>The Nextcloud team is extremely welcoming to new contributors and very supportive in helping them grow. You should join to see for yourself 😃</li>
<li>Working with a fully open source team like Nextcloud is a great experience and I guarantee that you'll learn a lot</li>
<li>Assume good intentions. Be nice when interacting with others online.</li>
<li>Take ownership of issues you pick up. Take the work seriously and take it to completion. Again, this helps build trust.</li>
<li>Things are not always perfect e.g. your contributions may take some time to be reviewed/merged, etc. Be patient and remember to be nice.</li>
</ul>
<p>If you've ever wanted to get started contributing to open source, and not sure on how to get started, I hope these pointers help. Everyone started somewhere.</p>
<p>Finally, I'd love to give a shout-out the Nextcloud team for their <a target="_blank" href="https://nextcloud.com/blog/nextcloud-conference-2018-contributor-travel-support-program/">Contributor Travel Support Program</a>, which enabled me to attend the conference this year 😀</p>
<p><strong>Update:</strong></p>
<p>The video of the talk is now available. You can watch it <a target="_blank" href="https://www.youtube.com/watch?v=U3hF_NjsW-E">on YouTube</a></p>
]]></content:encoded></item><item><title><![CDATA[A Practical Use Case for React’s componentDidUpdate]]></title><description><![CDATA[Let's take a scenario where we're building a blog app. The blog will be very minimal, with these features:

When a user visits the homepage, they should see a list of posts and should be able to click on any of the posts to be taken to the blog post'...]]></description><link>https://kevgathuku.dev/a-practical-use-case-for-reacts-componentdidupdate</link><guid isPermaLink="true">https://kevgathuku.dev/a-practical-use-case-for-reacts-componentdidupdate</guid><category><![CDATA[React]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Thu, 07 Jun 2018 17:59:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1771152080579/bcf7c29c-0076-4890-93f0-71a96db4dc8c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let's take a scenario where we're building a blog app. The blog will be very minimal, with these features:</p>
<ol>
<li><p>When a user visits the homepage, they should see a list of posts and should be able to click on any of the posts to be taken to the blog post's page.</p>
</li>
<li><p>When a user visits a blog post page, they should see the blog details i.e. <em>title</em> and <em>content</em>, and a list of recommended posts at the bottom of the page.</p>
</li>
<li><p>Clicking on a recommended blog post's link should take the user to that post's page.</p>
</li>
</ol>
<p>Now that we've figured out the requirements, let's proceed to build the app.</p>
<h3 id="heading-building-the-app"><strong>Building the App</strong></h3>
<p>We will need 3 components for our app: the home page, the blog post component and the app's root component, which decides which page to render depending on the route. For this example, we'll fetch the posts from a local file rather than making network requests.</p>
<p>Let's start off with the root component, which is where we'll mount the app to a specific DOM node.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom"</span>
<span class="hljs-keyword">import</span> { BrowserRouter <span class="hljs-keyword">as</span> Router, Route, Switch } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>
<span class="hljs-keyword">import</span> Home <span class="hljs-keyword">from</span> <span class="hljs-string">"./Home"</span>
<span class="hljs-keyword">import</span> PostDetails <span class="hljs-keyword">from</span> <span class="hljs-string">"./PostDetails"</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Router</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Switch</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">exact</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Home}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/posts/:postId"</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{PostDetails}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Switch</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Router</span>&gt;</span></span>
)

ReactDOM.render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>, <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"root"</span>))
</code></pre>
<p>We're using the ​<a target="_blank" href="https://www.npmjs.org/package/react-router-dom">​react-router-dom</a> package to render either the <code>Home</code> or <code>PostDetails</code> component depending on the route. A request with a URL such as <code>/posts/2</code> will be routed to the <code>PostDetails</code> component and the <code>postId</code> variable will take the value of 2.</p>
<p>Let's look at the <code>Home</code> component, which will act as our homepage:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>
<span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>
<span class="hljs-keyword">import</span> { posts } <span class="hljs-keyword">from</span> <span class="hljs-string">"./posts"</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">"./Home.css"</span>

<span class="hljs-keyword">const</span> Home = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App-header"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App-title"</span>&gt;</span>Blog Posts<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        {posts.map(post =&gt; (
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{post.id}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">{</span>`/<span class="hljs-attr">posts</span>/${<span class="hljs-attr">post.id</span>}`}&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">h4</span>&gt;</span>{post.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h4</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        ))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Home
</code></pre>
<p>In this file, we fetch posts locally from <code>posts.js</code> and use the <code>Home</code> component render a list of links to each of the posts. Each post has an <code>id</code> attribute which is used to construct the URL using the <a target="_blank" href="https://reacttraining.com/react-router/web/api/Link">Link</a> helper from <code>react-router-dom​</code></p>
<p>This is how our sample ​post data looks:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> posts = [
  {
    <span class="hljs-attr">id</span>: <span class="hljs-number">22</span>,
    <span class="hljs-attr">title</span>: <span class="hljs-string">"Welcome to React"</span>,
    <span class="hljs-attr">body</span>: <span class="hljs-string">`React makes it painless to create interactive UIs.
      Design simple views for each state in your application,
      and React will efficiently update and render just the
      right components when your data changes.
      Declarative views make your code more predictable and easier to debug.`</span>,
  },
  {
    <span class="hljs-attr">id</span>: <span class="hljs-number">23</span>,
    <span class="hljs-attr">title</span>: <span class="hljs-string">"An Introduction to React Router"</span>,
    <span class="hljs-attr">body</span>: <span class="hljs-string">`There are three types of components in React Router:
          router components, route matching components, and navigation components.
          All of the components that you use in a web application should be
          imported from react-router-dom.`</span>,
  },
  {
    <span class="hljs-attr">id</span>: <span class="hljs-number">24</span>,
    <span class="hljs-attr">title</span>: <span class="hljs-string">"Welcome to Redux"</span>,
    <span class="hljs-attr">body</span>: <span class="hljs-string">`It helps you write applications that behave consistently,
          run in different environments (client, server, and native), and are easy to test.
          On top of that, it provides a great developer experience, such as live code editing
          combined with a time traveling debugger.`</span>,
  },
]

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> recommendedPosts = [
  {
    <span class="hljs-attr">id</span>: <span class="hljs-number">30</span>,
    <span class="hljs-attr">title</span>: <span class="hljs-string">"Angular"</span>,
    <span class="hljs-attr">body</span>: <span class="hljs-string">`Learn one way to build applications with Angular and reuse your code
          and abilities to build apps for any deployment target.
          For web, mobile web, native mobile and native desktop.`</span>,
  },
  {
    <span class="hljs-attr">id</span>: <span class="hljs-number">31</span>,
    <span class="hljs-attr">title</span>: <span class="hljs-string">"Ember.js"</span>,
    <span class="hljs-attr">body</span>: <span class="hljs-string">`Ember.js is built for productivity. Designed with developer ergonomics in mind,
          its friendly APIs help you get your job done—fast.`</span>,
  },
  {
    <span class="hljs-attr">id</span>: <span class="hljs-number">32</span>,
    <span class="hljs-attr">title</span>: <span class="hljs-string">"Backbone.js"</span>,
    <span class="hljs-attr">body</span>: <span class="hljs-string">`Backbone.js gives structure to web applications by providing models with
          key-value binding and custom events, collections with a rich API of
          enumerable functions, views with declarative event handling, and connects
          it all to your existing API over a RESTful JSON interface.`</span>,
  },
]

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> allPosts = posts.concat(recommendedPosts)
</code></pre>
<p>The final piece of the puzzle is the ​<code>​PostDetails.jsx</code> component:</p>
<p><img src alt="carbon_2018-08-11_14-20-13" /></p>
<p><a target="_blank" href="https://kevgathuku.files.wordpress.com/2018/06/carbon_2018-08-11_14-20-13.png?w=700">3</a></p>
<p><a target="_blank" href="https://gist.github.com/kevgathuku/8bd81a6e94ee7db37df284bc912aa386">Link to PostDetails.jsx</a></p>
<p>In this component, we'll store the post's details in the component state, and all the values are initially empty. To fetch the post's data, we'll need to extract the <code>postId</code> parameter from the URL. <code>​​​​​​​​​​​​​​react-router-dom</code> stores the route params in the component's <code>props</code> and it enables us to access this parameter via <code>this.props.match.params.postId</code>. Once we have the <code>postId</code>, we then find the post with a matching ID in the posts array, and set it in the component state. This is handled in the<code>fetchPostData</code> function.</p>
<p>In the <code>​​render</code> method, we show the post's details and a <code>Recommeded Posts</code> section. When you click on one of the posts in this section, you should be taken to that post's URL and shown that post's details.</p>
<p>You can try out the app in it's current state at <a target="_blank" href="https://r1ywq3kkmn.codesandbox.io/">CodeSandbox</a> and also view the live code in the <a target="_blank" href="https://codesandbox.io/s/r1ywq3kkmn">Live Editor</a></p>
<p>However, we have a bug lurking somewhere in our code. Try clicking on one of the recommended posts and see what happens.</p>
<h3 id="heading-figuring-out-the-problem"><strong>Figuring out the Problem</strong></h3>
<p>If you haven't tried out the <a target="_blank" href="https://r1ywq3kkmn.codesandbox.io/">live app</a>, the issue we have is that when we're in a blog post's page, clicking on a recommended post doesn't seem to do anything. If you're keen though, you'll notice that the URL does change but the page content doesn't change 🤔</p>
<p>When you click on a recommended blog post link, React Router re-renders the <code>PostDetails</code> route component with a new <code>postId</code>parameter. This parameter is passed to the component as <code>props</code>. We can be sure the component does receive new props since the URL changes. But why does the component fail to display the updated data?</p>
<p>The root of the issue here is that we fetch data only on <code>componentDidMount</code> which is not called when a component re-renders.</p>
<p>What we need to do to solve the problem is to fetch new post data when we detect that the <code>postId</code> parameter has changed. This is where <code>componentDidUpdate</code> comes in.</p>
<h3 id="heading-solution"><strong>Solution</strong></h3>
<p>Here's a comment I came across in the react-router <a target="_blank" href="https://github.com/ReactTraining/react-router/issues/1982#issuecomment-172173634">issue tracker</a> that let me down the right path.</p>
<blockquote>
<p>You probably want to just respond to prop changes with componentDidUpdate to refetch data when the same route is active but with different parameters</p>
<p><a target="_blank" href="https://github.com/ReactTraining/react-router/issues/1982#issuecomment-172173634">Ryan Florence</a></p>
</blockquote>
<p>Therefore, to fix the issue, we have to add <code>componentDidUpdate</code>, where we'll check if the <code>postId</code> has been updated and then fetch the details of the new post. Once we have the new details and store them in the component state, the component re-renders and shows the updated content.</p>
<p>Here's the small addition we need to make to fix the bug:</p>
<pre><code class="lang-jsx">componentDidUpdate(prevProps) {
    <span class="hljs-keyword">const</span> {
      <span class="hljs-attr">match</span>: {
        <span class="hljs-attr">params</span>: { postId }
      }
    } = <span class="hljs-built_in">this</span>.props;
    <span class="hljs-keyword">const</span> prevPostId = prevProps.match.params.postId;
    <span class="hljs-keyword">if</span> (prevPostId !== postId) {
      <span class="hljs-built_in">this</span>.fetchPostData(postId);
    }
</code></pre>
<p>The <code>componentDidUpdate</code> lifecycle method takes in the previous props as the first argument. We extract the <code>postId</code> from the previous props, and compare that with the <code>postId</code> from the current props. If there's a change, we fetch new data using the current <code>postId</code> parameter. This helps us update the page content when the component re-renders with a new <code>postId</code> parameter, and this fixes our bug!</p>
<p>You can try out the new bug-free version on <a target="_blank" href="https://x729mr1qjw.codesandbox.io/">CodeSandbox</a> and check out the code on the <a target="_blank" href="https://codesandbox.io/s/x729mr1qjw">Live Editor</a>. If you're interested in the accompanying code samples, you can access them on <a target="_blank" href="https://github.com/kevgathuku/react-router-quirks">GitHub</a>.</p>
<p>And thus ends the story of how I learned how to use <code>componentDidUpdate</code> on a real practical problem. This was an interesting discovery for me and I hope you've learnt something new too!</p>
<p>Cover Image: “<a target="_blank" href="https://www.flickr.com/photos/malinbobeck/24292994416">Those Who Affected Me</a>” by <a target="_blank" href="https://www.flickr.com/photos/malinbobeck/">Malin Bobeck</a>, <a target="_blank" href="https://creativecommons.org/licenses/by/2.0/deed.en">CC BY 2.0</a></p>
]]></content:encoded></item><item><title><![CDATA[How to Build Chat into Django Applications with Twilio Programmable Chat]]></title><description><![CDATA[Looking to build a realtime chat app? Building this from scratch requires thinking about lots of concerns at once. How should you model your users? What about different channels and different access levels? How about showing which users are online an...]]></description><link>https://kevgathuku.dev/how-to-build-chat-into-django-applications-with-twilio-programmable-chat</link><guid isPermaLink="true">https://kevgathuku.dev/how-to-build-chat-into-django-applications-with-twilio-programmable-chat</guid><category><![CDATA[twilio]]></category><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Tue, 15 May 2018 17:57:49 GMT</pubDate><content:encoded><![CDATA[<p>Looking to build a realtime chat app? Building this from scratch requires thinking about lots of concerns at once. How should you model your users? What about different channels and different access levels? How about showing which users are online and when they start typing a message? There’s these questions and a lot more to answer when building a quality chat app.</p>
<p>That’s where <a target="_blank" href="https://www.twilio.com/docs/chat/api">Twilio Programmable Chat</a> comes in. This awesome service helps abstract away the groundwork involved in building realtime chat applications. Chat comes with support for basic chat features such as channels, users, roles and permissions. There are also many other advanced features that you can add incrementally to your app.</p>
<p>Read the rest of the article <a target="_blank" href="https://www.twilio.com/blog/2018/05/build-chat-python-django-applications-programmable-chat.html">on the Twilio blog</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Building and Testing Web Applications with Elm]]></title><description><![CDATA[Elm is a functional programming language meant for building reliable web applications. It compiles to JavaScript and is well-known for its helpful compiler that promises no runtime errors.
Elm brings all the best features of a compiled functional lan...]]></description><link>https://kevgathuku.dev/building-and-testing-web-applications-with-elm</link><guid isPermaLink="true">https://kevgathuku.dev/building-and-testing-web-applications-with-elm</guid><category><![CDATA[ELM]]></category><dc:creator><![CDATA[Kevin Gathuku]]></dc:creator><pubDate>Wed, 28 Jun 2017 17:44:24 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://elm-lang.org/">Elm</a> is a functional programming language meant for building reliable web applications. It compiles to JavaScript and is well-known for its helpful compiler that promises no runtime errors.</p>
<p>Elm brings all the best features of a compiled functional language such as immutable values, strong static types and a helpful compiler that guides you in writing code that will not crash in the hands of your users to the frontend.</p>
<p>This tutorial will introduce Elm and its ecosystem. We’ll also set up a simple but complete Elm project, along with tests and continuous integration with <a target="_blank" href="https://semaphoreci.com/">Semaphore</a>.</p>
<p>Read the rest of the article <a target="_blank" href="https://semaphoreci.com/community/tutorials/building-and-testing-web-applications-with-elm">on the Semaphore Blog</a>.</p>
]]></content:encoded></item></channel></rss>