<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tutorials | AkitaOnRails.com</title><link>https://akitaonrails.github.io/en/tags/tutorials/</link><description>Fabio Akita's blog — tech, career, and assorted geek topics. English edition.</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 19 Oct 2026 23:20:52 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/en/tags/tutorials/index.xml" rel="self" type="application/rss+xml"/><item><title>Why a Perfect Digital Election Still Wouldn't Be Viable?</title><link>https://akitaonrails.github.io/en/2026/08/07/why-a-perfect-digital-election-still-wouldnt-be-viable/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/08/07/why-a-perfect-digital-election-still-wouldnt-be-viable/</guid><pubDate>Fri, 07 Aug 2026 10:00:00 -0300</pubDate><description>&lt;p&gt;Every election in Brazil turns into the same soap opera: half the country doesn&amp;rsquo;t trust the result. And unlike most countries, there is nothing to recount. Brazil is one of the few countries in the world with a &lt;strong&gt;100% digital&lt;/strong&gt; election: no paper receipt, no physical ballot, no possibility of an independent recount. Your vote goes into an electronic voting machine, becomes a number inside closed software, and out comes a tally. Either you trust the TSE (the Superior Electoral Court), or there&amp;rsquo;s nothing you can do.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Every election in Brazil turns into the same soap opera: half the country doesn&rsquo;t trust the result. And unlike most countries, there is nothing to recount. Brazil is one of the few countries in the world with a <strong>100% digital</strong> election: no paper receipt, no physical ballot, no possibility of an independent recount. Your vote goes into an electronic voting machine, becomes a number inside closed software, and out comes a tally. Either you trust the TSE (the Superior Electoral Court), or there&rsquo;s nothing you can do.</p>
<p>Recently the TSE put on a little theater show: it <a href="https://www.tse.jus.br/comunicacao/noticias/2026/Junho/eleicoes-2026-tse-abre-urna-eletronica-para-tecnicos-da-sociedade-brasileira-de-computacao"target="_blank" rel="noopener">&ldquo;opened up the voting machine&rdquo;</a> so technicians from the Brazilian Computer Society could look at the components. That&rsquo;s obviously useless. Showing a motherboard, a processor, and some memory chips proves there&rsquo;s no malware the way looking at a car&rsquo;s engine proves the driver is sober. Hardware is just the stage; the play happens in the software. And auditing the software is precisely what&rsquo;s hard, restricted, full of ritual and short windows — which completely defeats the purpose of public transparency. An audit that only a handful of credentialed people can perform, under supervision, for a few days, is not transparency. It&rsquo;s a performance.</p>
<p>And for the record: I&rsquo;m not defending the current system, and I have zero expectations for it. As I summed up in <a href="https://x.com/AkitaOnRails/status/2084669984555331706"target="_blank" rel="noopener">this tweet</a>: the voting machine is just a box, an old PC. Even if the software were perfect, it wouldn&rsquo;t matter — the processes around it stay secret, done behind closed doors. The machine is a smokescreen. With or without an alternative, I don&rsquo;t care about it.</p>
<p>Many people conclude from this that the answer is digital elections, but with a different system. This article is a computer science exercise: what would a hypothetically perfect system look like? And, more importantly, the final conclusion: <strong>why even that perfect system would not be a viable option.</strong></p>
<p>One thing worth emphasizing before we start: the system I&rsquo;m about to describe would require <strong>no</strong> secrecy from the government. No secret components, no secret processes, no locked rooms with credentialed people standing guard. Everything — the code, the data, the whole structure — could be 100% open, accessible to anyone, no restrictions whatsoever. And it would still be possible to prove, mathematically, that fraud is impossible. It&rsquo;s the exact opposite of the current model, where trust is born out of secrecy.</p>
<blockquote>
  <p>No patience for code and math along the way? <a href="#why-this-would-never-work">Skip straight to the part where I explain why this wouldn&rsquo;t work</a>. And at the end of the article, three appendices: <a href="#appendix-1-so-is-the-current-system-auditable">is the current system auditable?</a>, <a href="#appendix-2-skin-in-the-game--rewarding-those-who-verify">how to reward voters who verify their vote</a>, and <a href="#appendix-3-why-zero-knowledge-is-so-hard-to-swallow">why &ldquo;zero knowledge&rdquo; is so hard to swallow</a>.</p>

</blockquote>
<h2>&ldquo;The machine is safe because it&rsquo;s not on the internet&rdquo;<span class="hx:absolute hx:-mt-20" id="the-machine-is-safe-because-its-not-on-the-internet"></span>
    <a href="#the-machine-is-safe-because-its-not-on-the-internet" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Sooner or later, every defender of the current system plays this card: the voting machine isn&rsquo;t connected to the internet, so it can&rsquo;t be hacked from the outside. Technically true. And completely beside the point.</p>
<p>First, because isolation says nothing about what the software does. An offline machine can flip votes just the same — you simply get no way to watch it happen. And malware doesn&rsquo;t need a network to get in: it can come from the factory, from an update, from the supply chain, from a technician with physical access. Stuxnet, the most famous worm in history, crossed the air gap into Iran&rsquo;s centrifuges on a USB stick. An air gap is an obstacle, not proof of honesty. And notice: the machine&rsquo;s data has to leave it somehow at the end of the day, on physical media carried around or through later transmission. &ldquo;Not on the internet&rdquo; is a logistical half-truth.</p>
<p>Second, and more important: that argument exposes the wrong mental model. The current system&rsquo;s security comes from <strong>physical custody</strong> — seals, locked rooms, accredited observers, rituals. In other words, once again, from trusting people and processes you cannot see.</p>
<p>In the system I&rsquo;m about to describe, the machine could be <strong>plugged straight into the internet</strong>, publishing every vote to the public tree in real time, and fraud would still be impossible. Not because the network is safe, but because nobody needs to trust the machine:</p>
<ul>
<li>What it publishes are opaque commitments: even broadcasting everything live, there is no vote to leak in the published data.</li>
<li>Every commitment carries a mathematical validity proof: the machine cannot mint an invalid vote.</li>
<li>The tree is public and replicated by independent observers: nothing published can be altered later without breaking the hashes.</li>
<li>And if the machine flips your vote at the booth, the Benaloh challenge catches it (that&rsquo;s Building block 4, further down): it cannot know whether you&rsquo;ll cast or audit.</li>
</ul>
<p>Being online or not stops being the question. Security doesn&rsquo;t live in the absence of a network; it lives in public verification. &ldquo;Trust us, the machine is locked in a room&rdquo; becomes &ldquo;don&rsquo;t trust anything, check the math.&rdquo; That inversion is the one thing the current model cannot offer.</p>
<h2>The tweet that inspired this post<span class="hx:absolute hx:-mt-20" id="the-tweet-that-inspired-this-post"></span>
    <a href="#the-tweet-that-inspired-this-post" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I recently posted <a href="https://x.com/AkitaOnRails/status/2085550756837335483"target="_blank" rel="noopener">this tweet</a> about end-to-end verifiability (E2E-V). The gist of the idea:</p>
<ol>
<li>Every voter gets a paper receipt with a unique identifier — a hash — that <strong>identifies neither the person nor the vote</strong>.</li>
<li>Each precinct&rsquo;s votes go into a public structure you can only append to, never delete from (<em>append-only</em>): a Merkle tree (the same principle as a blockchain).</li>
<li>That tree is published in full. Any citizen downloads and verifies it.</li>
<li><strong>Individual verifiability:</strong> each voter checks that their hash is there, no middleman.</li>
<li><strong>Universal verifiability:</strong> anyone recomputes the tree and checks that it produces the announced total.</li>
</ol>
<p>I made it explicit in the tweet: this is <strong>not</strong> a solution, it&rsquo;s a napkin sketch. The concept exists, it&rsquo;s mathematically solid, and it&rsquo;s old news to anyone in computer science. But the reaction was interesting.</p>
<h2>The voto de cabresto problem<span class="hx:absolute hx:-mt-20" id="the-voto-de-cabresto-problem"></span>
    <a href="#the-voto-de-cabresto-problem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A lot of the comments got stuck on the same point: the <em>voto de cabresto</em> — the &ldquo;halter vote&rdquo;. It&rsquo;s a historical practice in Brazil: the local political boss buys a poor voter&rsquo;s vote and demands proof they voted &ldquo;the right way&rdquo;. Back in the day it was the pre-marked ballot; today it would be a photo of the voting machine&rsquo;s screen (which is why phones are banned inside the booth).</p>
<p>And that raises an apparent contradiction:</p>
<ul>
<li>If the receipt the voter takes home <strong>reveals</strong> who they voted for, the vote can be coerced or sold.</li>
<li>If the receipt <strong>doesn&rsquo;t reveal</strong> who they voted for, how does the voter check that their vote went to the right candidate?</li>
</ul>
<p>I recently posted the answer, albeit only in passing: <strong>zero-knowledge proofs</strong> (ZK). The same principle behind truly anonymous cryptocurrencies like Monero and Zcash.</p>
<p>The flow would go like this: the voter picks the candidate on the screen; the machine generates a secret random number, computes <code>ciphertext = Enc(election_public_key, vote; random)</code>, produces a ZK proof that this ciphertext contains a valid vote, and publishes everything to a public Merkle tree. The voter takes home only a serial number. The serial <strong>does not contain the vote</strong>, but it lets you prove the vote exists in the tree and was not tampered with.</p>
<p>Here&rsquo;s what this looks like for programmers, step by step, with real values. And at the end I&rsquo;ll explain why, even working perfectly, this would fix nothing.</p>
<h2>Building block 1: hash as commitment<span class="hx:absolute hx:-mt-20" id="building-block-1-hash-as-commitment"></span>
    <a href="#building-block-1-hash-as-commitment" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The foundation of everything is the hash function. A function like SHA-256 takes any input and produces 32 seemingly random bytes. Three properties matter here:</p>
<ul>
<li><strong>Deterministic:</strong> same input, same output. Always.</li>
<li><strong>One-way:</strong> given the hash, there&rsquo;s no going back to the input.</li>
<li><strong>Avalanche:</strong> flip one bit of the input and the whole hash changes.</li>
</ul>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">hashlib</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">commit</span><span class="p">(</span><span class="n">voto</span><span class="p">,</span> <span class="n">nonce</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">voto</span><span class="si">}</span><span class="s2">:</span><span class="si">{</span><span class="n">nonce</span><span class="si">}</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">encode</span><span class="p">())</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">()</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">commit(&#39;Candidato A&#39;, 987654321) = 337051f1dbc6a8ef412ecc14067c263d6a0dc83dada6939b51d74b6651727b69
</span></span><span class="line"><span class="cl">commit(&#39;Candidato A&#39;, 123456789) = 55512bcd60924abf68d162f1a130023635089974db08ea9cff691e1f209898ab
</span></span><span class="line"><span class="cl">commit(&#39;Candidato B&#39;, 987654321) = faf54e90eb84ba4c0446701c3779a382e48d02847bd2301f3836f673c54f9693</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Look closely. The first and second hashes hide <strong>the same vote</strong> — what changes is the <code>nonce</code>, a random number that acts as &ldquo;wrapping&rdquo;. The first and third share the same nonce, but different votes. The three hashes have no visible resemblance to one another.</p>
<p>This is a <strong>commitment</strong>: I publish the hash today, and tomorrow I can reveal <code>(vote, nonce)</code> and anyone can check the hash matches. I can&rsquo;t change my vote after publishing (the <em>binding</em> property), and nobody can figure out the vote before the reveal (the <em>hiding</em> property).</p>
<p>Except there&rsquo;s a problem for our election: if the voter takes home both the vote and the nonce, they can <strong>show both to the coercer</strong>, who verifies the hash and confirms the vote. The halter is back. We need something better.</p>
<h2>Building block 2: Pedersen commitments — hiding for real<span class="hx:absolute hx:-mt-20" id="building-block-2-pedersen-commitments--hiding-for-real"></span>
    <a href="#building-block-2-pedersen-commitments--hiding-for-real" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The Pedersen commitment solves this with modular arithmetic. I&rsquo;ll use toy numbers so you can check the math by hand; real systems use 2048-bit primes or elliptic curves, but the math is identical.</p>
<p>Take a prime <code>p = 23</code> and two generators <code>g = 4</code> and <code>h = 8</code> (both generate a subgroup of order 11 modulo 23 — check it: <code>4^11 mod 23 = 1</code> and <code>8^11 mod 23 = 1</code>). The commitment to a vote <code>v</code> with nonce <code>n</code> is:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">C = g^v * h^n  (mod p)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">p</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">h</span> <span class="o">=</span> <span class="mi">23</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">8</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pedersen</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="nb">pow</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">v</span><span class="p">,</span> <span class="n">p</span><span class="p">)</span> <span class="o">*</span> <span class="nb">pow</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">p</span><span class="p">))</span> <span class="o">%</span> <span class="n">p</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">C(voto=1, n=3)  = 1
</span></span><span class="line"><span class="cl">C(voto=1, n=9)  = 13
</span></span><span class="line"><span class="cl">C(voto=0, n=3)  = 6</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Same vote, different nonces, completely different commitments. Now the part that matters. Pedersen has a property called <strong>perfect hiding</strong>: for any commitment <code>C</code>, there <strong>exists</strong> a nonce that opens <code>C</code> as vote 0, and there <strong>exists</strong> a nonce that opens <code>C</code> as vote 1. With our toy prime we can prove it by brute force:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">C</span> <span class="o">=</span> <span class="mi">1</span>  <span class="c1"># the commitment from above, C(vote=1, n=3)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">11</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">pedersen</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span> <span class="o">==</span> <span class="n">C</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;C abre como voto=0 com nonce </span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">pedersen</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span> <span class="o">==</span> <span class="n">C</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;C abre como voto=1 com nonce </span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">C=1 abre como voto=0 com nonce 0
</span></span><span class="line"><span class="cl">C=1 abre como voto=1 com nonce 3</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Read that again, because this is the heart of the article. The commitment <code>C = 1</code> is consistent with <strong>both stories</strong>. Anyone who sees only <code>C</code> has no way to know which one is true — not by brute force, not with a quantum computer, because both openings exist mathematically. The value <code>C</code> simply does not contain the vote&rsquo;s information.</p>
<p>&ldquo;Hold on,&rdquo; you say, &ldquo;so the voter can change their vote afterwards?&rdquo; No, and that&rsquo;s the other half of the property: the commitment is <strong>computationally binding</strong>. Whoever generated the commitment with <code>(vote=1, n=3)</code> can only reveal the other opening (<code>vote=0, n=0</code>) if they can compute a discrete logarithm — that is, find <code>x</code> such that <code>g^x = h mod p</code>. With <code>p = 23</code> that&rsquo;s trivial; with 2048 bits, it&rsquo;s computationally impossible. To sum up:</p>
<ul>
<li><strong>Outsiders</strong> can&rsquo;t discover the vote (hiding).</li>
<li><strong>Whoever committed</strong> can&rsquo;t change the vote afterwards (binding).</li>
</ul>
<p>And the final detail that kills the halter vote: <strong>the machine generates the nonce, not the voter.</strong> The voter sees their choice on the screen, the machine makes the commitment internally, discards the nonce, and prints only the <code>C</code> — the serial. The voter leaves the booth with no way to open their own commitment, even if they want to.</p>
<p>(One implementation detail I&rsquo;ll simplify from here on: Pedersen hides so well that <strong>nobody</strong> can decrypt it — not even the authorities at tally time. In practice, the machine also publishes an ElGamal ciphertext of the same vote: just as opaque to any outsider, but decryptable by the authorities at the tally, with a ZK proof that both carry the same vote. Our toy Pedersen plays both roles in this article, to keep the math on small numbers.)</p>
<h2>Building block 3: the Merkle tree — a ballot box anyone can audit<span class="hx:absolute hx:-mt-20" id="building-block-3-the-merkle-tree--a-ballot-box-anyone-can-audit"></span>
    <a href="#building-block-3-the-merkle-tree--a-ballot-box-anyone-can-audit" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Where do these commitments live? In a public structure anyone can download and verify: a Merkle tree. If you watched my video on <a href="/2023/11/10/akitando-147-criptografia-na-pratica-certificados-bittorrent-git-bitcoin/">cryptography in practice — certificates, BitTorrent, Git, Bitcoin</a>, you&rsquo;ve seen this structure in action: it&rsquo;s the same one that scales BitTorrent, organizes Git commits, and packs the transactions of a Bitcoin block.</p>
<p>The construction is simple: each leaf is the hash of a vote (one voter&rsquo;s commitment <code>C</code>), and each internal node is the hash of the concatenation of its two children, until a single hash remains at the top: the <strong>root</strong>.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">H</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">x</span><span class="o">.</span><span class="n">encode</span><span class="p">())</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">eleitores</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">7</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">10</span><span class="p">),</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">8</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">leaves</span> <span class="o">=</span> <span class="p">[</span><span class="n">H</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">:</span><span class="si">{</span><span class="n">pedersen</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">eleitores</span><span class="p">)]</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>With the 8 example votes, the leaves look like this (hashes abbreviated):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">eleitor 0: voto=1 nonce=3  -&gt; C=1  -&gt; folha=ef134f2a180ba05d...
</span></span><span class="line"><span class="cl">eleitor 1: voto=0 nonce=7  -&gt; C=12 -&gt; folha=ce356d2f943ea5af...
</span></span><span class="line"><span class="cl">eleitor 2: voto=1 nonce=2  -&gt; C=3  -&gt; folha=8e0375adfc1f4563...
</span></span><span class="line"><span class="cl">eleitor 3: voto=1 nonce=10 -&gt; C=12 -&gt; folha=df284a49f837c454...
</span></span><span class="line"><span class="cl">eleitor 4: voto=0 nonce=5  -&gt; C=16 -&gt; folha=fd6df9e3530cb74f...
</span></span><span class="line"><span class="cl">eleitor 5: voto=1 nonce=1  -&gt; C=9  -&gt; folha=e0e9d38f9ccb7a41...
</span></span><span class="line"><span class="cl">eleitor 6: voto=0 nonce=8  -&gt; C=4  -&gt; folha=e719f7fde83fafda...
</span></span><span class="line"><span class="cl">eleitor 7: voto=1 nonce=4  -&gt; C=8  -&gt; folha=1393ac80e69a8991...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">RAIZ PUBLICA: 48826b6481b574e37156f85e34d877105bc55073fb5a5b981239572a8e7c4b61</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>(The votes and nonces in the table are shown in the clear only so you can check the math; the public tree holds only the leaves — opaque hashes.)</p>
<p>The root is the &ldquo;summary&rdquo; of the whole election: 32 bytes representing every vote. If <strong>a single bit</strong> of a single vote changes, the root changes completely. The root and the entire tree get published. Any citizen, at home, with open-source code, rebuilds the tree and checks whether the published root matches. That&rsquo;s <strong>universal verifiability</strong>.</p>
<p>Now the individual part. Suppose you&rsquo;re voter 4. Your receipt is the serial — the leaf <code>fd6df9e3530cb74f1f0795b751a43454cab281a431d0558b413e33bba83a4100</code>, which is the hash of your commitment <code>C = 16</code> at position 4 in the tree. To prove it&rsquo;s in the tree, you don&rsquo;t need to download and check all 8 votes — you need only <code>log2(8) = 3</code> hashes, the path of &ldquo;siblings&rdquo; up to the root:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">merkle_verify</span><span class="p">(</span><span class="n">leaf</span><span class="p">,</span> <span class="n">proof</span><span class="p">,</span> <span class="n">root</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">cur</span> <span class="o">=</span> <span class="n">leaf</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">h_</span><span class="p">,</span> <span class="n">side</span> <span class="ow">in</span> <span class="n">proof</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">cur</span> <span class="o">=</span> <span class="n">H</span><span class="p">(</span><span class="n">h_</span> <span class="o">+</span> <span class="n">cur</span><span class="p">)</span> <span class="k">if</span> <span class="n">side</span> <span class="o">==</span> <span class="s2">&#34;esq&#34;</span> <span class="k">else</span> <span class="n">H</span><span class="p">(</span><span class="n">cur</span> <span class="o">+</span> <span class="n">h_</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">cur</span> <span class="o">==</span> <span class="n">root</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">prova do eleitor 4:
</span></span><span class="line"><span class="cl">  (dir) e0e9d38f9ccb7a41...  (irmão: folha do eleitor 5)
</span></span><span class="line"><span class="cl">  (dir) 3149c3bf17d98fbc...  (irmão: nó dos eleitores 6-7)
</span></span><span class="line"><span class="cl">  (esq) 3b902c849a40619b...  (irmão: nó dos eleitores 0-3)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">verificação local: True
</span></span><span class="line"><span class="cl">tentando folha adulterada: False</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>You take your serial, concatenate it with the 3 proof hashes in the right order, hash three times, and check whether you land on the public root. If you do, it&rsquo;s <strong>mathematically impossible</strong> for your vote not to be in the tree — because producing a fake proof would require finding a SHA-256 collision, and nobody on the planet knows how to do that. If someone swaps your vote afterwards, your proof stops working and you hold evidence of the fraud in your hand. In a real election with 150 million votes, the proof would be about 28 hashes — fits on a paper receipt or a QR code.</p>
<p>Notice what just happened: you proved that <strong>a piece of information is in the public tree</strong> and that <strong>nobody messed with it</strong>, carrying home only a 32-byte number that, by itself, says absolutely nothing about the content. That&rsquo;s what people mean by &ldquo;zero knowledge&rdquo; in this context: the verification happens without the knowledge (the vote) ever having to travel.</p>
<h2>Building block 4: the Benaloh challenge — checking the machine on the spot<span class="hx:absolute hx:-mt-20" id="building-block-4-the-benaloh-challenge--checking-the-machine-on-the-spot"></span>
    <a href="#building-block-4-the-benaloh-challenge--checking-the-machine-on-the-spot" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>There&rsquo;s one blind spot left. The machine shows &ldquo;vote recorded&rdquo; on the screen and prints your serial <code>C</code>. At home, you confirm that <code>C</code> sits in the tree. All good? Not quite. What if the machine <strong>lied</strong> and committed a different vote? The screen shows the candidate you picked, but under the hood it computed the commitment for another one. You would never notice, because the commitment is opaque by design. That&rsquo;s the hiding property working against you.</p>
<p>The classic fix comes from cryptographer Josh Benaloh, and it goes by <strong>Benaloh challenge</strong> (or <em>cast-or-challenge</em>). The idea: after the machine shows the commitment <code>C</code> on screen, but <strong>before</strong> you confirm, you get two options:</p>
<ul>
<li><strong>Cast</strong>: the vote counts, enters the tree, and the nonce is destroyed forever.</li>
<li><strong>Challenge</strong>: you declare that ballot a <strong>test vote</strong>. The machine must reveal the nonce and the vote it put inside the commitment, and you redo the math on the spot — in an independent app, on your own phone, not on the machine&rsquo;s software:</li>
</ul>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># the machine showed on screen: C = 1</span>
</span></span><span class="line"><span class="cl"><span class="c1"># you challenged; the machine reveals: vote=1, nonce=3</span>
</span></span><span class="line"><span class="cl"><span class="n">pedersen</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>   <span class="c1"># True -&gt; the machine committed exactly what you picked</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If it matches, the machine was honest <strong>on that ballot</strong>. The test ballot is spoiled and never enters the tally — the revealed nonce would make it readable — and you vote again, for real this time.</p>
<p>Now suppose a rigged machine that flips a fraction of the votes. You pick <code>vote=1</code>; it internally records <code>vote=0</code> and shows <code>C = 12</code> on screen (<code>pedersen(0, 7) = 12</code>). If you <strong>cast</strong>, the fraud sails through. But if you <strong>challenge</strong>, the machine is cornered: it has to reveal a <code>(vote, nonce)</code> pair that opens <code>C = 12</code>. The only one it knows is <code>(0, 7)</code> — and revealing that exposes the swap right in front of you: &ldquo;I voted 1!&rdquo;. Opening it as <code>vote=1</code> would require finding a nonce <code>n</code> with <code>pedersen(1, n) = 12</code>, which is the discrete log problem all over again. With our toy prime a brute force search finds one (<code>n = 10</code> exists), but with 2048-bit primes a cheating machine flat out cannot produce the answer.</p>
<p>And what springs the trap: the machine <strong>cannot know in advance</strong> whether you&rsquo;ll cast or challenge. The decision is yours, made after the commitment is already on screen. If a slice of the electorate tests a few ballots before voting for real, a machine that flips votes at scale gets caught with overwhelming probability. Real verifiable voting systems like Helios and ElectionGuard use exactly this mechanism.</p>
<p>And notice this breaks nobody&rsquo;s secrecy: the revealed nonce belongs to a <strong>spoiled</strong> ballot that doesn&rsquo;t count. The vote that counts keeps its nonce destroyed and its commitment impenetrable.</p>
<h2>Building block 5: a real zero-knowledge proof — Schnorr<span class="hx:absolute hx:-mt-20" id="building-block-5-a-real-zero-knowledge-proof--schnorr"></span>
    <a href="#building-block-5-a-real-zero-knowledge-proof--schnorr" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>One more piece is missing. What guarantees that each commitment in the tree contains a <strong>valid vote</strong> — and not, say, <code>vote = 500</code>, which would inflate the result? The machine must prove the commitment opens to a legitimate value <strong>without opening the commitment</strong>. That&rsquo;s a zero-knowledge proof in the strict sense.</p>
<p>The canonical example, and one you can demonstrate with small numbers, is the <strong>Schnorr</strong> protocol: proving you know a secret <code>s</code> such that <code>y = g^s mod p</code>, without revealing <code>s</code>. The intuition before the math: it&rsquo;s Ali Baba&rsquo;s cave. The cave has two passages that meet at a locked door. You prove you have the key by walking in one side and coming out whichever side the verifier calls — without ever showing the key. Without the key, you&rsquo;d only guess the call right by luck, 50% of the time; after 20 rounds, the odds of fooling anyone are below one in a million.</p>
<p>The mathematical version, with our toy numbers (<code>p = 23</code>, <code>g = 4</code>, order <code>q = 11</code>):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">segredo</span> <span class="o">=</span> <span class="mi">7</span>
</span></span><span class="line"><span class="cl"><span class="n">y</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">segredo</span><span class="p">,</span> <span class="n">p</span><span class="p">)</span>   <span class="c1"># y = 4^7 mod 23 = 8  (public value)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 1. commitment: the prover picks a random r and sends t</span>
</span></span><span class="line"><span class="cl"><span class="n">r</span> <span class="o">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="n">t</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">r</span><span class="p">,</span> <span class="n">p</span><span class="p">)</span>          <span class="c1"># t = 4^3 mod 23 = 18</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 2. challenge: the verifier picks a random c</span>
</span></span><span class="line"><span class="cl"><span class="n">c</span> <span class="o">=</span> <span class="mi">5</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 3. response: the prover computes z</span>
</span></span><span class="line"><span class="cl"><span class="n">z</span> <span class="o">=</span> <span class="p">(</span><span class="n">r</span> <span class="o">+</span> <span class="n">c</span> <span class="o">*</span> <span class="n">segredo</span><span class="p">)</span> <span class="o">%</span> <span class="n">q</span>  <span class="c1"># z = (3 + 5*7) mod 11 = 5</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># verification: g^z == t * y^c (mod p)</span>
</span></span><span class="line"><span class="cl"><span class="n">esq</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">z</span><span class="p">,</span> <span class="n">p</span><span class="p">)</span>                 <span class="c1"># 4^5 mod 23 = 12</span>
</span></span><span class="line"><span class="cl"><span class="nb">dir</span> <span class="o">=</span> <span class="p">(</span><span class="n">t</span> <span class="o">*</span> <span class="nb">pow</span><span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">p</span><span class="p">))</span> <span class="o">%</span> <span class="n">p</span>       <span class="c1"># 18 * 8^5 mod 23 = 12</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">esq</span> <span class="o">==</span> <span class="nb">dir</span><span class="p">)</span>                  <span class="c1"># True</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It works because <code>g^z = g^(r + c·s) = g^r · (g^s)^c = t · y^c</code>. The algebra checks out. Now observe what the verifier saw: the numbers <code>t</code>, <code>c</code>, <code>z</code>, and the final check. At no point did the secret <code>s = 7</code> show up. And an impostor who doesn&rsquo;t know <code>s</code> can&rsquo;t answer an arbitrary challenge: if they guess <code>z = 2</code>, the verifier computes <code>g^2 = 16 ≠ 12</code> and the fraud is exposed.</p>
<p>And why does the verifier learn <strong>nothing</strong> about <code>s</code>, rather than just &ldquo;not much&rdquo;? Because the whole conversation could have been fabricated by someone who <strong>doesn&rsquo;t know the secret</strong>, in this inverted order:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># simulator: pick c and z FIRST, then compute the t that closes the equation</span>
</span></span><span class="line"><span class="cl"><span class="n">c_sim</span><span class="p">,</span> <span class="n">z_sim</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">9</span>
</span></span><span class="line"><span class="cl"><span class="n">t_sim</span> <span class="o">=</span> <span class="p">(</span><span class="nb">pow</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">z_sim</span><span class="p">,</span> <span class="n">p</span><span class="p">)</span> <span class="o">*</span> <span class="nb">pow</span><span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="o">-</span><span class="n">c_sim</span> <span class="o">%</span> <span class="n">q</span><span class="p">,</span> <span class="n">p</span><span class="p">))</span> <span class="o">%</span> <span class="n">p</span>  <span class="c1"># t = 8</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># checking the forged transcript:</span>
</span></span><span class="line"><span class="cl"><span class="nb">pow</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">z_sim</span><span class="p">,</span> <span class="n">p</span><span class="p">)</span>              <span class="c1"># 4^9 mod 23 = 13</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">t_sim</span> <span class="o">*</span> <span class="nb">pow</span><span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">c_sim</span><span class="p">,</span> <span class="n">p</span><span class="p">))</span> <span class="o">%</span> <span class="n">p</span>  <span class="c1"># 8 * 8^5 mod 23 = 13  -&gt; it matches!</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The forged transcript <code>(t=8, c=5, z=9)</code> passes verification and is <strong>indistinguishable</strong> from a real one. If anyone can fabricate a valid conversation without knowing the secret, then the real conversation cannot contain any information about the secret. That&rsquo;s what &ldquo;zero knowledge&rdquo; formally means. (For use outside a lab, the challenge <code>c</code> is derived by hashing the commitment — the Fiat-Shamir transform — and the proof becomes a single, non-interactive object that anyone verifies offline.)</p>
<p>In our hypothetical election, the machine publishes, alongside each vote, a proof of this kind — in practice, an OR-variant (&ldquo;the vote is 0 <strong>or</strong> it is 1&rdquo;, without saying which) — and any auditor verifies that every vote in the tree is valid, without ever seeing a single one.</p>
<h2>Building block 6: what stops phantom votes?<span class="hx:absolute hx:-mt-20" id="building-block-6-what-stops-phantom-votes"></span>
    <a href="#building-block-6-what-stops-phantom-votes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>One hole is left, and it&rsquo;s a favorite among digital-election skeptics: what stops the authority from <strong>stuffing the tree with invented votes</strong>? Every commitment in the Merkle tree carries a ZK proof that it contains a valid vote — but the proof only guarantees the value is 0 or 1. A thousand phantom votes would be a thousand perfectly valid commitments with perfectly correct proofs. The tree is public, anyone can count the leaves&hellip; but who says there should be 8 and not 8,000?</p>
<p>The first answer is replication. The tree follows the transparency-log model — the same Certificate Transparency that protects the HTTPS certificates you use all day: one publisher writes, and dozens of independent observers (parties, the bar association, universities, the press, you) copy the tree, sign the root, and cross-check each other. Rewriting history would mean convincing all of them to accept a root different from the one they already signed.</p>
<p>That protects what&rsquo;s already published. But <strong>creating</strong> phantom votes? That&rsquo;s where the most elegant piece of all comes in, from the same David Chaum who invented digital cash and mix-nets: the <strong>blind signature</strong> (1982).</p>
<p>The flow: you authenticate at the registration desk like today — ID, biometrics. In the booth, the machine computes your commitment (Building block 2), but before publishing it needs a credential from the desk. The machine <strong>blinds</strong> the commitment with a random factor and sends the desk only the blinded value — and here being online is no problem at all, as we saw in the air-gap section. The desk, which already registered your authentication, signs exactly once, without seeing the content. The machine unblinds the signature and publishes the pair <code>(commitment, signature)</code>. In the end, the desk has no idea which commitment it signed — so it cannot link your identity to your vote. With toy RSA, you can watch the magic happen:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># the desk&#39;s toy RSA: n=3233, e=17 (public), d=2753 (private)</span>
</span></span><span class="line"><span class="cl"><span class="n">n</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="n">d</span> <span class="o">=</span> <span class="mi">3233</span><span class="p">,</span> <span class="mi">17</span><span class="p">,</span> <span class="mi">2753</span>
</span></span><span class="line"><span class="cl"><span class="n">C</span> <span class="o">=</span> <span class="mi">16</span>        <span class="c1"># your Pedersen commitment, the same one from Building block 3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 1. the machine blinds the commitment with a random factor r</span>
</span></span><span class="line"><span class="cl"><span class="n">r</span> <span class="o">=</span> <span class="mi">7</span>
</span></span><span class="line"><span class="cl"><span class="n">cego</span> <span class="o">=</span> <span class="p">(</span><span class="n">C</span> <span class="o">*</span> <span class="nb">pow</span><span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="n">n</span><span class="p">))</span> <span class="o">%</span> <span class="n">n</span>       <span class="c1"># 2341 -&gt; this is all the desk sees</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 2. the desk, which already logged your authentication, signs the BLIND value</span>
</span></span><span class="line"><span class="cl"><span class="n">ass_cego</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">cego</span><span class="p">,</span> <span class="n">d</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span> <span class="o">%</span> <span class="n">n</span>      <span class="c1"># 216</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 3. the machine removes the blind factor and publishes (C, s) to the tree</span>
</span></span><span class="line"><span class="cl"><span class="n">s</span> <span class="o">=</span> <span class="p">(</span><span class="n">ass_cego</span> <span class="o">*</span> <span class="nb">pow</span><span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span><span class="p">))</span> <span class="o">%</span> <span class="n">n</span>  <span class="c1"># 2802</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># any auditor checks: s^e mod n == C ?</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="nb">pow</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="n">n</span><span class="p">))</span>   <span class="c1"># 16 -&gt; the commitment, with a valid signature!</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Notice: the desk only saw the number 2341, which to them is noise. They signed 216 without knowing they were signing your <code>C = 16</code>. Yet the pair <code>(C, s)</code> passes any auditor&rsquo;s verification. It&rsquo;s a regular digital signature — <code>s</code> is exactly <code>C^d mod n</code> — just produced without the signer ever seeing the document. It looks like a magic trick, but it&rsquo;s plain RSA algebra: blinding multiplies by <code>r^e</code>; signing raises everything to <code>d</code>, and since <code>e·d ≡ 1</code>, what&rsquo;s left is <code>C^d · r</code>; unblinding divides by <code>r</code>, leaving <code>C^d</code>.</p>
<p>Now assemble the full picture: <strong>a leaf only enters the tree carrying two things — the ZK validity proof (Building block 5) and a credential signed by the desk (Building block 6).</strong> The desk issues one credential per authentication, and authentication is a public event: there&rsquo;s a line at the precinct, poll workers, party watchers, the biometric log, the per-precinct attendance count published every election. At the end of the day, the equation anyone can check is:</p>
<p><strong>number of leaves in the tree == number of authenticated voters.</strong></p>
<p>A phantom vote is a leaf without a credential (doesn&rsquo;t get in) or a credential without a voter (inflates the count and shows). Removing a legitimate vote breaks the receipt&rsquo;s inclusion proof (Building block 3). Altering a published vote breaks the hashes on the observers&rsquo; copies. Stealing credentials would mean fooling the biometrics in front of watchers. Every fraud path hits a different wall — and every wall is checkable by anyone, from home.</p>
<h2>The weakest link: who proves you are you?<span class="hx:absolute hx:-mt-20" id="the-weakest-link-who-proves-you-are-you"></span>
    <a href="#the-weakest-link-who-proves-you-are-you" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Everything before this section is math. But there&rsquo;s one door no math can lock: authentication. No algorithm proves the human standing in front of you is who they claim to be. That is, honestly, the weakest link in the entire system — and it&rsquo;s worth facing head-on.</p>
<p>And here Brazil simplifies the problem, because it already paid the expensive part: the voter biometric registry exists and covers over 80% of the electorate — more than 124 million people, <a href="https://www.tse.jus.br/comunicacao/noticias/2024/Fevereiro/eleitores-sem-cadastro-biometrico-podem-votar-normalmente"target="_blank" rel="noopener">per the electoral court</a>. National deduplication already ran: one finger exists only once in the roll, no matter how many forged documents back it up. And fingerprints already release the vote at the precinct — in 2026, in <a href="https://www.tre-mg.jus.br/comunicacao/noticias/2026/Julho/manual-do-eleitor-entenda-como-vai-funcionar-a-biometria-nas-eleicoes-2026"target="_blank" rel="noopener">every precinct in the country</a>. For this article&rsquo;s hypothetical system, biometric authentication is free: reuse what&rsquo;s already standing.</p>
<p>Of course, that registry existing is itself the privacy bet: biometrics are irrevocable data — you can change a password, not your fingerprints. Aadhaar, India&rsquo;s biometric registry, had incidents exposing data on over a billion people; the OPM, the US government&rsquo;s HR office, lost 5.6 million fingerprints in a single 2015 breach. A national biometric database is a single point of catastrophic, permanent failure — and Brazil already made that bet. The hypothetical system doesn&rsquo;t make it worse; it just inherits it.</p>
<p>For anyone outside the biometric registry, the precinct falls back to the photo ID — and cards get forged. They probably get forged today. But notice what the architecture does to that problem: one forged authentication mints <strong>one</strong> credential, which yields <strong>one</strong> leaf in the tree. To fraud at scale, the criminal needs flesh-and-blood people showing up in person, in front of poll workers and watchers, once per fake vote. That&rsquo;s retail fraud: expensive, visible, slow. A thousand fake votes require a thousand bodies — and then it&rsquo;s not silent digital fraud anymore, it&rsquo;s chartered-bus logistics everyone sees rolling by. The system doesn&rsquo;t prevent forgery; it <strong>caps the damage per forgery at exactly one vote</strong> and pushes the cost into the physical world.</p>
<p>And anomalies surface: the equation from Building block 6 — leaves == authentications — can be checked precinct by precinct. A precinct with 400 registered voters and 420 authentications raises eyebrows. Regional turnout above 100% raises eyebrows. And if paranoia runs high, the desk&rsquo;s signing key can be fragmented like the election key, requiring a quorum to issue credentials.</p>
<p>The honest summary: the link stays physical and human — a human proving, once, in public, that they&rsquo;re on the roll — and no system, however perfect, eliminates it. The best possible design does two things with it: shrinks the trust surface down to that single event and makes its failures <strong>visible</strong> instead of silent. With biometrics valid at every precinct in 2026, identity forgery becomes the most expensive, least scalable path of all — which is exactly where a well-designed system wants it. There&rsquo;s no free lunch, and pretending there is one is the kind of pitch this whole article refuses to make.</p>
<h2>Putting it all together: the full protocol<span class="hx:absolute hx:-mt-20" id="putting-it-all-together-the-full-protocol"></span>
    <a href="#putting-it-all-together-the-full-protocol" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The hypothetically perfect election would go like this:</p>
<ol>
<li><strong>Setup.</strong> A group of independent authorities (the electoral court, the bar association, parties, civil society) jointly generates the election&rsquo;s public key. The corresponding private key stays fragmented: no single authority can decrypt anything; only a majority acting together can.</li>
<li><strong>Authentication.</strong> The voter identifies at the registration desk — ID, biometrics, like today — and the desk logs the authentication. This authorizes the issuance of <strong>one</strong> credential: the blind signature on a commitment, without the desk seeing the content. No link between identity and vote.</li>
<li><strong>Voting.</strong> The voter picks the candidate on the screen. The machine generates a random nonce, computes the Pedersen commitment and the ElGamal ciphertext of the same vote (the commitment becomes the receipt&rsquo;s serial; the ciphertext is what gets tallied), produces the ZK validity proof, and shows the commitment on screen. The voter then decides: cast, and the nonce is destroyed — or challenge, and the machine reveals the nonce for an on-the-spot check, the ballot is spoiled, and they vote again. Once cast, the machine blinds the commitment, the desk signs it sight unseen, and the machine unblinds the signature.</li>
<li><strong>Publication.</strong> The commitment goes into a public Merkle tree, together with the ZK proof and the credential — replicated and signed by multiple independent observers. No credential, no entry.</li>
<li><strong>Receipt.</strong> The voter takes home a slip of paper with the serial. They <strong>cannot</strong> prove to anyone who they voted for — even if they want to, because they don&rsquo;t have the nonce.</li>
<li><strong>Individual verification.</strong> At home, the voter downloads the tree (or uses any independent website) and checks their serial is there, with the inclusion proof. If it isn&rsquo;t, they hold material proof of fraud.</li>
<li><strong>Universal verification.</strong> Any citizen, university, or party rebuilds the entire tree, checks the root, validates every ZK proof and credential, and confirms the leaf count matches the published per-precinct authentication totals.</li>
<li><strong>Tallying.</strong> In the end, the encrypted votes go through a mix-net (they get re-shuffled and re-encrypted, severing the link to their original positions) and the authorities decrypt jointly, proving each step. The total matches the public root, or the fraud is evident.</li>
</ol>
<p>Notice what changed relative to the current system: <strong>you no longer need to trust the electoral court, the voting machine, or any auditor.</strong> Every property is individually verifiable by anyone with a computer. It&rsquo;s the same principle that lets Bitcoin work without a central bank: don&rsquo;t trust, verify.</p>
<p>Before anyone gets too excited: this is a simplification. A real system still has to solve anonymous credentials more robust than our toy blind signature, tree availability, and a pile of operational details. The point here is not the complete design; it&rsquo;s the core mechanism.</p>
<h2>Why this would never work<span class="hx:absolute hx:-mt-20" id="why-this-would-never-work"></span>
    <a href="#why-this-would-never-work" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Now for the part almost nobody proposing these systems wants to hear.</p>
<p>Scroll back to the beginning of this article and notice what I had to explain to get here: hash functions, commitments, modular arithmetic, discrete logarithms, Merkle trees, the Benaloh challenge, blind signatures, zero-knowledge proofs, simulators. With code, with numbers, with step-by-step examples. And even so, I&rsquo;d bet a good share of readers — programmers included — made it here without full certainty that they truly understand why the scheme is secure.</p>
<p>And that&rsquo;s not for lack of intelligence. It&rsquo;s because trusting this system requires understanding math that the vast majority of the population will never understand. The only human being who can be <strong>100% certain</strong> this system is correct is the one who can verify the mathematical proofs on their own. Everyone else — 99.9% of the population — wouldn&rsquo;t be <em>verifying</em> anything. They&rsquo;d be <strong>believing</strong> the mathematician who says it works.</p>
<p>And here we hit the fatal contradiction: if an electoral system requires the ordinary citizen to blindly believe a specialist they can&rsquo;t audit, it is <strong>exactly as opaque as the TSE&rsquo;s current system</strong>. The opacity just moved address: instead of trusting the court bureaucrat, you trust the cryptographer. For dona Maria, who sells pastel at the street market, it&rsquo;s all the same — both are &ldquo;a bunch of mumbo-jumbo I don&rsquo;t understand&rdquo;. And a system the population cannot understand is a system whose legitimacy it will never accept. Today&rsquo;s skeptic saying &ldquo;I don&rsquo;t trust the voting machine&rdquo; would become tomorrow&rsquo;s skeptic saying &ldquo;I don&rsquo;t trust this algebra&rdquo;.</p>
<p>An election is not only an engineering problem; it&rsquo;s a <strong>social trust</strong> problem. The gold standard of transparency is the system <strong>anyone can watch with their own eyes</strong>: paper ballot, glass ballot box, public counting at the precinct, the tally posted on the door. Anyone understands paper being counted in public. Nobody has to believe anybody.</p>
<p>That&rsquo;s why countries far richer and more technological than Brazil — Germany, the Netherlands, France, most of the US — stick with paper or demand an auditable paper trail. That&rsquo;s not backwardness. It&rsquo;s the recognition that verifiability only specialists understand is not public verifiability.</p>
<h2>Sidebar: what cryptocurrencies prove every day<span class="hx:absolute hx:-mt-20" id="sidebar-what-cryptocurrencies-prove-every-day"></span>
    <a href="#sidebar-what-cryptocurrencies-prove-every-day" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Before wrapping up, a detour to answer the question that always comes up: &ldquo;but does this actually work, or is it napkin theory?&rdquo; It works. And the proof has been running for almost two decades, moving billions of dollars, under constant attack: blockchains.</p>
<p>First, clearing up a misunderstanding: <strong>blockchain is not synonymous with cryptocurrency.</strong> As the name says, it&rsquo;s just a chain of blocks. Each block carries a Merkle tree of records and the previous block&rsquo;s hash. In Bitcoin, the block header holds the transaction tree&rsquo;s root, the previous block&rsquo;s hash, a timestamp, and a nonce — here, a mining counter — that miners vary until the hash of the entire block falls below the difficulty target: the famous proof of work. It&rsquo;s Building block 3 of this article, extended through time: not one tree, but a chain of trees, each sealing the one before.</p>
<p>The practical result is the guarantee that matters here: tamper with <strong>one transaction</strong> in an old block and the tree&rsquo;s root changes, the block&rsquo;s hash changes, the link to the next block breaks, and to hide that you&rsquo;d need to redo the proof of work of every block up to today — while thousands of honest nodes keep extending the true chain. Once everything is signed and sealed by hash, rewriting the past is computationally impossible. That&rsquo;s exactly why Bitcoin can be 100% public: the exposure itself is the protection. Everyone has a copy of everything, so nobody rewrites history.</p>
<p><strong>But public does not mean anonymous.</strong> That&rsquo;s the part most people get wrong. In Bitcoin, every transaction stays visible forever: which addresses fed it, which received, how much moved. Addresses are pseudonyms — pen names, not anonymity. And there&rsquo;s an entire chain-analysis industry (Chainalysis, Elliptic, TRM) making a living gluing identities to those pen names:</p>
<ul>
<li><strong>Common-input heuristic:</strong> if a transaction spends coins from several addresses, they almost certainly belong to the same wallet. Group them into a cluster.</li>
<li><strong>Change detection:</strong> the output that returns to a fresh address usually belongs to the payer.</li>
<li><strong>Bridge to the real world:</strong> when a cluster&rsquo;s address touches a KYC exchange, a name and a tax ID attach to the whole cluster — and to its complete history, retroactively.</li>
</ul>
<p>That&rsquo;s how the FBI recovered part of the Colonial Pipeline ransom, that&rsquo;s how the Silk Road coins were traced years later, and that&rsquo;s how the ~1,128 BTC stolen from ColdCards are still sitting in known addresses half the world is watching — I detailed it in the <a href="/en/2026/08/01/exploiting-coinkites-rng-egregious-problem/">article about Coinkite&rsquo;s RNG</a>. If the thief moves a single satoshi to a KYC exchange, they identify themselves. The chain guarantees integrity, not secrecy.</p>
<p>For real anonymity, the chain alone doesn&rsquo;t cut it: you need zero-knowledge proofs. Monero, for instance, combines three techniques:</p>
<ol>
<li><strong>Ring signatures:</strong> each spend is signed on behalf of a group of possible keys. The signature proves <strong>one of them</strong> authorized it, without revealing which.</li>
<li><strong>Stealth addresses:</strong> the recipient&rsquo;s address never appears on the chain; each payment creates a disposable address derived from a shared secret.</li>
<li><strong>RingCT</strong> (<em>confidential transactions</em>): amounts stay hidden inside <strong>Pedersen commitments</strong> — yes, the exact same Building block 2 from this article — and a ZK range proof guarantees nobody minted coins out of thin air, without revealing any amount.</li>
</ol>
<p>Zcash follows the same philosophy with zk-SNARKs: you prove &ldquo;I own a valid, unspent note&rdquo; without revealing which note. The network verifies the proof, accepts the transaction, and learns neither sender, recipient, nor value.</p>
<p>And here the circle closes. Look at what this article&rsquo;s electoral scheme uses: a public, immutable structure anyone can verify (the Merkle tree, the blockchain&rsquo;s simpler cousin), Pedersen commitments to hide content (the same primitive as Monero), and ZK proofs to guarantee validity without revelation (the same family as Monero and Zcash). None of this is lab conjecture: these are production systems, audited, attacked daily, protecting real money. An election is, if anything, a simpler problem — short window, a single publisher, offline verification.</p>
<p>That&rsquo;s why I said at the beginning that the idea is mathematically solid: it invents nothing, it just composes pieces that already proved they can survive the real world. We know an end-to-end verifiable digital election <strong>could</strong> be built, because every one of its components is already built and running. Which brings us back to the problem that isn&rsquo;t technical.</p>
<h2>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Let me make it clear once more, as I did in the tweets: <strong>this is not a proposal, it&rsquo;s an exercise.</strong> I don&rsquo;t know the solution to Brazil&rsquo;s electoral trust problem. If I did, I&rsquo;d be publishing a paper, not a blog post.</p>
<p>But the exercise is worth it for two reasons. First, because it shows the technology for individual and universal verifiability <strong>exists</strong> — anyone who says &ldquo;digital elections are inherently unauditable&rdquo; is wrong. Second, because it shows the real limit: the frontier isn&rsquo;t technical, it&rsquo;s epistemological. A perfect system nobody understands fails at the same point as an imperfect system nobody can audit.</p>
<p>If you understood every line of this article, congratulations: you&rsquo;re part of a minority far too small to carry a democracy on its back.</p>
<h2>Appendix 1: so is the current system auditable?<span class="hx:absolute hx:-mt-20" id="appendix-1-so-is-the-current-system-auditable"></span>
    <a href="#appendix-1-so-is-the-current-system-auditable" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A fair question hangs in the air: so the current system has no auditing at all? It does, and it&rsquo;s worth being honest about that. What doesn&rsquo;t exist is a specific kind of audit: one that doesn&rsquo;t depend on software. Let&rsquo;s take it piece by piece.</p>
<p>The current arsenal, summarized and sourced:</p>
<ul>
<li><strong>Source code inspection.</strong> Parties, the bar association, the federal audit court, the Federal Police, the prosecutor&rsquo;s office, and universities can examine the systems&rsquo; code at the electoral court&rsquo;s headquarters, months before the election — the so-called <a href="https://www.tse.jus.br/comunicacao/noticias/2025/Novembro/codigos-fonte-do-ciclo-de-transparencia-ao-teste-publico-da-urna"target="_blank" rel="noopener">transparency cycle</a>.</li>
<li><strong>Public Security Test (TPS).</strong> Outside researchers genuinely try to attack the system. And it works: in 2012, professor Diego Aranha&rsquo;s team unscrambled the RDV and showed it was possible to learn who each voter had voted for; in 2017, they managed to tamper with the voting software before installation (the story is in <a href="https://revistapesquisa.fapesp.br/a-urna-eletronica-na-maturidade/"target="_blank" rel="noopener">Revista Pesquisa FAPESP</a> and in this <a href="https://www.agencialupa.org/jornalismo/2023/11/27/entenda-como-funciona-o-teste-de-seguranca-das-urnas-eletronicas-feito-pelo-tse/"target="_blank" rel="noopener">Lupa summary</a>). The flaws found were fixed afterwards.</li>
<li><strong>Zero report, BU, and the Integrity Test.</strong> Before voting starts, the machine prints a zeroed-out report. At the end of the day, it prints the Boletim de Urna, posted at the precinct and published digitally with a QR code — anyone can download the BUs and add them up independently. And a random sample of machines is diverted to a filmed parallel vote, comparing the paper record against the digital one.</li>
<li><strong>RDV — the &ldquo;digital recount&rdquo;.</strong> The machine records each individual vote in a shuffled, anonymized file, published afterwards (<a href="https://www.tse.jus.br/legislacao/compilada/res/2021/resolucao-no-23-673-14-de-dezembro-de-2021"target="_blank" rel="noopener">definition in TSE Resolution 23.673/2021</a>). Parties and authorized entities can re-sum the votes and cross-check against the BUs and the official tally — the official mechanism presented as the substitute for a paper recount (<a href="https://www.tse.jus.br/eleicoes/historia/processo-eleitoral-brasileiro/votacao/votacao-segura"target="_blank" rel="noopener">the electoral court&rsquo;s explanation</a>).</li>
<li><strong>External audits.</strong> In 2022, the federal audit court (TCU) cross-checked 9 million records from 4,577 precincts against the tallying database and found zero discrepancies — probability of fraud &ldquo;approaching 0%&rdquo; (<a href="https://www.cnnbrasil.com.br/politica/auditoria-do-tcu-diz-que-possibilidade-de-fraude-nas-eleicoes-de-2022-e-proxima-de-0/"target="_blank" rel="noopener">CNN Brasil</a>). The Carter Center observed the 2022 elections, registered no evidence of fraud, and acknowledged the audits&rsquo; expansion (<a href="https://www.cartercenter.org/news/pr/2022/brazil-100522-portuguese.pdf"target="_blank" rel="noopener">mission statement</a>).</li>
</ul>
<p>Now the &ldquo;but&rdquo; this whole article builds toward: <strong>every layer on that list depends on software or on a ceremony run by the electoral court itself.</strong> The BU × RDV comparison proves internal consistency — that the published numbers match the recorded numbers. It does not prove the recorded numbers reflect what voters pressed on the screen, because a tampered machine would produce a tampered BU and RDV in perfect mutual agreement. As Aranha himself puts it: every audit available today depends on the software; if the program is tampered with, every check falls together (<a href="https://www1.folha.uol.com.br/poder/2021/06/so-brasil-bangladesh-e-butao-usam-urna-eletronica-sem-comprovante-do-voto-impresso.shtml"target="_blank" rel="noopener">Folha de S.Paulo</a>). And the Integrity Test, which covers a small sample of machines, is a poor cousin of the Benaloh challenge from Building block 4: instead of each voter auditing their own ballot on the spot, the auditing is done by a sample drawn and conducted by the system itself.</p>
<p>There&rsquo;s also the legal chapter: the attempt to create a voter-verifiable paper record, the &ldquo;printed vote,&rdquo; was ruled unconstitutional by the Supreme Court in ADI 4543 over ballot-secrecy risks, and the attempt to revive it was struck down again in 2020 (<a href="https://conjur.com.br/2020-set-16/supremo-confirma-liminar-impede-volta-voto-impresso/"target="_blank" rel="noopener">Conjur</a>). The irony is that this article&rsquo;s scheme resolves exactly the conflict that killed the printed vote: a verifiable receipt <strong>without</strong> breaking secrecy, via commitments and ZK proofs. The math unlocks what paper couldn&rsquo;t. Everything else stays locked, as the previous sections explain.</p>
<p>So the honest verdict: the Brazilian system is auditable in real digital and procedural ways, and no fraud altering an outcome has ever been proven in nearly 30 years of electronic voting. But there is no software-independent recount, because there is no voter-verified physical record. In the language of election-security research, a true <em>risk-limiting audit</em> is not possible. Trust stays deposited in the software, the pre-election inspections, and the custody of digital records — exactly what this article&rsquo;s hypothetical system tries to eliminate.</p>
<h2>Appendix 2: skin in the game — rewarding those who verify<span class="hx:absolute hx:-mt-20" id="appendix-2-skin-in-the-game--rewarding-those-who-verify"></span>
    <a href="#appendix-2-skin-in-the-game--rewarding-those-who-verify" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>One question hangs in the air after assembling the whole scheme: cryptocurrencies work because someone has something to lose. In Bitcoin, the miner burns real energy; in proof-of-stake, the validator deposits capital that gets confiscated if they cheat. And the voter? Voting costs nothing, and checking the receipt at home is work you do for free. The entire system depends on people verifying — and nobody has an incentive to verify. Can you put <em>skin in the game</em> on the voter?</p>
<p>The first idea that comes to mind is a deposit: vote, deposit some money, get it back later through the income tax system. As mechanism design, it has logic — it prices fake identities: a million phantom votes would cost a hundred million reais. In practice, it dies in three places. It&rsquo;s a disguised poll tax, exclusionary and unconstitutional — the refund fixes the accounting, not the cash flow of someone who doesn&rsquo;t have the money to front. The &ldquo;income tax&rdquo; channel doesn&rsquo;t reach most poor Brazilians, who don&rsquo;t even file — so it excludes exactly who the system most needs to include. And it would build, at the tax authority, a database linking identity, bank account, and electoral participation — a surveillance regression bigger than the problem it solves. All that before the plutocratic detail: security priced in money is security buyable by those who have money. Elections are designed to be wealth-blind — one vote per person. Putting a price on the security layer invites the rich to be more equal than the others.</p>
<p>The version that survives this scrutiny flips the sign: instead of charging to vote, <strong>reward those who verify</strong>. The scheme&rsquo;s real soft spot is the individual verification rate — if nobody checks their serial at home, the inclusion proof from Building block 3 becomes decoration. So: every verified receipt enters a public, auditable lottery, with the tree&rsquo;s own root serving as the randomness source (one more use for it). Checked your vote in the tree? You&rsquo;re in the drawing for a prize. The incentive points exactly at the behavior the system needs — millions of eyes checking the tree — without excluding anyone, charging anything, or building a new database, because the lottery runs on the anonymous serial, not on identity.</p>
<p>It&rsquo;s Bitcoin&rsquo;s logic on the right side: there, the block reward pays thousands of machines to keep the ledger honest; here, the prize would pay millions of voters to keep the election honest. It&rsquo;s almost a <em>proof of verification</em>: the block reward pays for the hashing work that keeps Bitcoin going, and the prize would pay for the checking work that would keep the election going. It&rsquo;s not a proposal — like nothing in this article — but as an incentive-design exercise, it&rsquo;s the version I&rsquo;d put on paper.</p>
<h2>Appendix 3: why &ldquo;zero knowledge&rdquo; is so hard to swallow<span class="hx:absolute hx:-mt-20" id="appendix-3-why-zero-knowledge-is-so-hard-to-swallow"></span>
    <a href="#appendix-3-why-zero-knowledge-is-so-hard-to-swallow" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>If you made it here feeling like you understood each piece individually but wouldn&rsquo;t swear you understood the whole thing, relax: you&rsquo;re in excellent company. When Goldwasser, Micali, and Rackoff formalized zero-knowledge proofs in the 1980s, the idea sounded paradoxical even to career cryptographers. Every proof we&rsquo;d ever known worked by <em>showing</em> something. How can a proof transfer conviction while transferring no information at all?</p>
<p>The math from Building blocks 2 through 5 is the complete answer, but intuition lags behind. So here are two classic analogies involving no equations at all — each one lights up half of the concept.</p>
<p><strong>Where&rsquo;s Waldo?</strong> I claim I found Waldo on the page, and I want to prove it without revealing where he is — otherwise you win the game for free. The solution: I take a huge sheet of cardboard, much bigger than the book, cut a small hole in the middle, and place it over the page so only Waldo shows through. You see Waldo. But since the cardboard covers everything around him, you have no idea which corner of the page he&rsquo;s in. I proved I know the location; you learned zero about it. And notice what holds the proof up: if I didn&rsquo;t know where he was, I couldn&rsquo;t have positioned the cardboard — the odds of landing the hole on Waldo by luck are tiny. It&rsquo;s Ali Baba&rsquo;s cave all over again, this time with cardboard.</p>
<p><strong>The color-blind friend&rsquo;s two balls.</strong> Your friend is red-green color-blind. You have two balls identical in size, weight, and texture — one red, one green — and you claim: &ldquo;these balls are different colors.&rdquo; To him, they&rsquo;re indistinguishable. How do you prove it without revealing which is which? He shows you the balls, one in each hand. He puts his hands behind his back and decides, on his own, whether to swap them or not. He shows them again and asks: &ldquo;did I swap?&rdquo; You, who see the colors, answer correctly every time. He, who doesn&rsquo;t, couldn&rsquo;t do better than 50%. After 20 correct answers in a row, he&rsquo;s convinced the balls differ — the odds of you bluffing 20 rounds are one in a million. And what did he learn about which ball is red? <strong>Nothing.</strong> Every answer you gave (&ldquo;swapped&rdquo;, &ldquo;didn&rsquo;t swap&rdquo;) was something he already knew — after all, he was the one who decided whether to swap. The round&rsquo;s information was already his; the only new thing is the conviction that you can see the difference.</p>
<p>The formal version of that intuition is the simulator from Building block 5: if anyone, knowing no secret at all, can fabricate a &ldquo;recording&rdquo; of a proof session indistinguishable from a real one, then the real session cannot carry any information about the secret. The convincing power comes from the order of operations — commitment first, challenge after. The zero knowledge comes from the recording, on its own, being forgeable. Both things are true at the same time, for different reasons. That&rsquo;s why the concept feels slippery: you&rsquo;re holding two facts that seem to contradict each other until you notice each one rests on something different.</p>
<p>And if the concept still feels slippery after all of this, good: you&rsquo;ve just experienced the article&rsquo;s central argument firsthand.</p>
]]></content:encoded><category>politics</category><category>security</category><category>tutorials</category></item><item><title>Omarchy 2.0 - LazyVim Basics</title><link>https://akitaonrails.github.io/en/2025/09/12/omarchy-2-0-lazyvim-basics/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/09/12/omarchy-2-0-lazyvim-basics/</guid><pubDate>Fri, 12 Sep 2025 15:00:00 -0300</pubDate><description>&lt;p&gt;Damn it, I said the previous post would be the last one in the &lt;a href="https://akitaonrails.github.io/en/tags/omarchy/"&gt;Omarchy&lt;/a&gt; series, but I figured this was a good chance to evangelize a bit about Vim too. I&amp;rsquo;ve already recommended following &lt;a href="https://www.youtube.com/@EustaquioRangel"target="_blank" rel="noopener"&gt;@Taq&amp;rsquo;s channel&lt;/a&gt;, where he explains in much more depth how NeoVim works. There&amp;rsquo;s also &lt;a href="https://github.com/ThePrimeagen/vim-be-good"target="_blank" rel="noopener"&gt;Primeagen&amp;rsquo;s &amp;ldquo;vim-be-good&amp;rdquo; tutorial&lt;/a&gt;. That second one in particular is an interactive tutorial inside NeoVim itself, to practice navigation - which many people find hard at first.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Damn it, I said the previous post would be the last one in the <a href="/en/tags/omarchy/">Omarchy</a> series, but I figured this was a good chance to evangelize a bit about Vim too. I&rsquo;ve already recommended following <a href="https://www.youtube.com/@EustaquioRangel"target="_blank" rel="noopener">@Taq&rsquo;s channel</a>, where he explains in much more depth how NeoVim works. There&rsquo;s also <a href="https://github.com/ThePrimeagen/vim-be-good"target="_blank" rel="noopener">Primeagen&rsquo;s &ldquo;vim-be-good&rdquo; tutorial</a>. That second one in particular is an interactive tutorial inside NeoVim itself, to practice navigation - which many people find hard at first.</p>
<p>I&rsquo;ll do only this one article about it and I don&rsquo;t intend to cover more than the tip of the iceberg. The idea is to serve whoever is coming from other editors like Sublime Text or VS Code and can&rsquo;t even start navigating or even quit Vim lol 🤣.</p>
<h3>Initial Commands<span class="hx:absolute hx:-mt-20" id="initial-commands"></span>
    <a href="#initial-commands" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>As I said in the <a href="https://akitaonrails.com/en/2025/09/07/omarchy-2-0-lazyvim-lazyextras/"target="_blank" rel="noopener">previous post about LazyVim</a>, I&rsquo;ll assume you already understand at least how to install support for your language with <strong>LazyExtras</strong> and already learned to version things using <a href="https://akitaonrails.com/en/2025/09/07/omarchy-2-0-mise-for-organizing-dev-environments/"target="_blank" rel="noopener"><strong>Mise</strong></a>.</p>
<p>Without even getting into LazyVim, the first thing to know is that it installs all the relevant configurations in the <code>~/.config/nvim</code> directory. Normally you don&rsquo;t need to touch anything there. Leave files like <code>lazyvim.json</code> alone. Notice that it does the same thing as any NPM out there: there&rsquo;s a <code>lazy-lock.json</code> file that declares the version/commit of each installed plugin. LazyVim does something similar to an <code>npm install</code>. Just a curiosity so you know how it organizes itself.</p>
<p>If you need to customize something, it&rsquo;s usually going to be in the <code>~/.config/nvim/lua/config</code> directory. That&rsquo;s where I added Mise support, for example:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">~/.config/nvim
</span></span><span class="line"><span class="cl">❯ cat lua/config/mise.lua
</span></span><span class="line"><span class="cl">-- Prepend mise shims to PATH
</span></span><span class="line"><span class="cl">vim.env.PATH <span class="o">=</span> vim.env.HOME .. <span class="s2">&#34;/.local/share/mise/shims:&#34;</span> .. vim.env.PATH</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>They&rsquo;ll always be scripts written in Lua. And you don&rsquo;t even need to know Lua deeply. At a glance it&rsquo;s a very simple language, not very different from Python or Javascript and any programmer should be able to read any snippet without any problem. Besides, in most cases you&rsquo;ll be copying configurations you got from the documentation, wiki or ChatGPT itself, so you just need to know that things go in this location.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912110425_screenshot-2025-09-12_11-04-07.png" alt="primeiros passos"  loading="lazy" /></p>
<p>Understand that NeoVim has the concept of the &ldquo;<strong>Leader</strong>&rdquo; key, a key that precedes hotkeys for other commands (there&rsquo;s also <strong>localleader</strong> which is backslash <strong>\</strong>). This is configurable but in LazyVim the Leader is the <strong>&ldquo;Space&rdquo;</strong> key. In a lot of documentation we&rsquo;ll see people saying <em>&ldquo;press Leader-w&rdquo;</em>, which means <strong>Space+w</strong> and now you know what that means.</p>
<p>To open a file there are at least 3 different ways, 2 of them shown in the image above:</p>
<blockquote>
  <p><strong>Space+E</strong> opens <strong>NeoTree</strong>, which is that sidebar on the left that looks like Windows Explorer. You can navigate with the keyboard or even with the mouse. Find your file and press &ldquo;Enter&rdquo; to open it, then &ldquo;Space+E&rdquo; again to hide the sidebar. If you want to know how to create a new file, a new subdirectory, rename things and so on, type <strong>?</strong> (question mark) to see this help:</p>

</blockquote>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912142438_neotree.gif" alt="NeoTree"  loading="lazy" /></p>
<p>The third way is manually with the <strong>:e</strong> command followed by the file name in the local directory. It has autocomplete too, this is the fastest way, if you know what you&rsquo;re looking for.</p>
<p>Whenever you want to cancel anything or close &ldquo;floating windows&rdquo; (pop-ups, dialog boxes). Just press &ldquo;Esc&rdquo;. Esc is fundamental in NeoVim.</p>
<p>Once a file is open, it sits in the <strong>Buffer</strong>. Think of Buffer as &ldquo;Open Tabs&rdquo; in your VS Code. To navigate through Buffers (like alt+tab between your already-opened files) it&rsquo;s simple:</p>
<ul>
<li><strong>]+b</strong> for next &ldquo;tab&rdquo; (next Buffer)</li>
<li><strong>[+b</strong> previous Buffer</li>
<li><strong>Space+bd</strong> to &ldquo;close the tab&rdquo; (close the Buffer)</li>
<li><strong>Space+bo</strong> to stay where you are and close the other buffers</li>
</ul>
<p>Open as many files as you want. If one of them is one of those huge logs you opened by accident, <code>Space+bd</code> to close it and done.</p>
<p>Speaking of closing, <strong>how do you exit NeoVim?</strong> There are several ways:</p>
<ul>
<li><strong>Space+q</strong></li>
<li><strong>:q</strong></li>
<li><strong>:q!</strong></li>
</ul>
<p>The difference: the first is the custom way in LazyVim. The second is the standard way using the &ldquo;commands&rdquo; feature that always starts with &ldquo;colon&rdquo;. And the third is if you modified something in the file but want to cancel, exit without saving anything, hence the exclamation mark.</p>
<p>Normally I use <strong>:wq</strong> which means &ldquo;write&rdquo; and &ldquo;quit&rdquo;. So it first saves the changes to the file and then quits the program. It&rsquo;s useful when I&rsquo;m just quickly editing a config file.</p>
<h3>Basic Navigation<span class="hx:absolute hx:-mt-20" id="basic-navigation"></span>
    <a href="#basic-navigation" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Now I have a file open. How do I navigate through it? First, how do I go to the end of the file and then back to the beginning? This is something I use a lot, just type <strong>Shift+g</strong> to go to the end of the file and then <strong>gg</strong> to go back to the beginning. Simple as that.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912143147_home-end.gif" alt="home-end"  loading="lazy" /></p>
<p>Now let&rsquo;s learn several commands at once:</p>
<ul>
<li>Type <strong>v</strong>. This starts the <strong>&ldquo;visual mode&rdquo;</strong> which, in practice, means starting to select from wherever your cursor is.</li>
<li>Type <strong>j</strong>. Which is the equivalent of &ldquo;arrow down&rdquo; to go to the line below. You&rsquo;ve probably heard that in Vim you navigate with &ldquo;hjkl&rdquo; which are, respectively, left, down, up, right.</li>
<li>Type <strong>x</strong>. Which is the equivalent of &ldquo;cut&rdquo; and putting the content in the clipboard. Having done that, typing <strong>p</strong> gives you &ldquo;paste&rdquo;.</li>
<li>Type <strong>u</strong>. Which is the most-used command: <strong>Undo</strong> or undoing the last modification.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912143741_marcar-apagar.gif" alt="marcar-apagar"  loading="lazy" /></p>
<p>All of this works in what&rsquo;s called <strong>&ldquo;command mode&rdquo;</strong>, which is the default mode Vim opens every file in. In this mode you can&rsquo;t type any text. As you can see, each letter by itself is a command, like &ldquo;x&rdquo; for cut or &ldquo;p&rdquo; for paste.</p>
<p>To type, you first need to press <strong>i</strong> (just the lowercase &ldquo;i&rdquo; letter), which then switches from command mode to <strong>&ldquo;edit mode&rdquo;</strong>. Now just type your text normally, like in any editor. When you want some command, just press <strong>ESC</strong> to go back to command mode. We&rsquo;ll be doing this all the time, which is why a lot of people remap the &ldquo;caps lock&rdquo; key on the keyboard to be ESC so it&rsquo;s easy to just tap with your left pinky.</p>
<h3>Combining Commands<span class="hx:absolute hx:-mt-20" id="combining-commands"></span>
    <a href="#combining-commands" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p>Still in command mode, how do we navigate through the file more easily?</p>

</blockquote>
<p>Beyond the &ldquo;hjkl&rdquo; keys, we can navigate more easily using <strong>b</strong> for &ldquo;previous word&rdquo; or <strong>w</strong> for next word:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912144226_words.gif" alt="words"  loading="lazy" /></p>
<p>Command mode lets you combine commands, so we can do:</p>
<ul>
<li><strong>dw</strong> to delete the next word</li>
<li><strong>db</strong> to delete the previous word</li>
<li><strong>d4w</strong> to delete the next 4 words</li>
<li><strong>d4j</strong> to delete the next 4 lines down</li>
</ul>
<p><a href="https://github.com/ThePrimeagen/vim-be-good"target="_blank" rel="noopener">Primeagen&rsquo;s tutorial</a> has several exercises for this kind of navigation, I recommend practicing. In VS Code everyone is used to first grabbing the mouse, marking the block they want to delete visually, then pressing delete or backspace. In Vim, we look at where the cursor is, press ESC to be in command mode, and type the operation and the location. Test all the combinations you can imagine.</p>
<p>This is the <strong>paradigm shift</strong> that bothers the lazy. But once you&rsquo;ve learned this, you&rsquo;ll find it annoying to use an editor that forces you to use the mouse for basic things.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912144801_change.gif" alt="change in place"  loading="lazy" /></p>
<p>Now it gets more interesting: <em>what if instead of deleting a word, I want to replace the word where my cursor is with another word?</em></p>
<p>In a normal editor, you&rsquo;d mark it with the mouse (or use some combination like &ldquo;ctrl+shift+arrow&rdquo;), and type over it or delete the word.</p>
<p>In LazyVim there are plugins configured for more interesting things. In the video above see how I&rsquo;m replacing words <em>&ldquo;in-place&rdquo;</em>. You do this with the <strong>ciw</strong> command.</p>
<ul>
<li><strong>ciw</strong> deletes the entire word under the cursor and opens edit mode for you to type the replacement</li>
<li><strong>ce</strong> deletes from the cursor to the end of the word (preserves the beginning of the word) and also opens for you to type in its place</li>
<li><strong>Shift-c</strong> (capital C) deletes everything from your cursor to the end of the entire line and opens for you to type.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912145439_bloco.gif" alt="Mudar Bloco Vertical"  loading="lazy" /></p>
<p>But that still doesn&rsquo;t look thaaaat cool. What if you want to change only the first letter of several lines at the same time, for example, in the text of the video above, I want to change only the hyphen (&quot;-&quot;) of each tag line?</p>
<p>I said before that pressing <strong>v</strong> in command mode starts visual mode and you can select from there. But there&rsquo;s <strong>Ctrl+v</strong> which opens vertical block selection mode.</p>
<p>In this mode we use the &ldquo;hjkl&rdquo; keys normally, like <strong>j</strong> to go down, but instead of marking the next entire line, it marks only the character right below your cursor. Now I can delete with <strong>d</strong> and type <strong>Shift-i</strong> (capital &ldquo;I&rdquo;) to enter edit mode at the beginning of the block and replace with whatever I want.</p>
<p>LazyVim has plugins for comments, but this is another alternative if you want to add &ldquo;//&rdquo; or &ldquo;#&rdquo; at the beginning of several lines to comment out, for example. In this case, read about the <a href="https://github.com/folke/ts-comments.nvim"target="_blank" rel="noopener"><strong>ts-comments</strong></a> plugin for better options for commenting. To start a new comment block, type <strong>gco</strong> to add a comment below.</p>
<p><em>&ldquo;Block mode&rdquo;</em> as I showed above, is one of the things I use the most in code.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912150112_search-replace.gif" alt="Search and Replace"  loading="lazy" /></p>
<p>But only that is very little. What if you want to replace <strong>ALL IDENTICAL WORDS</strong> in the text at once. In the example video above, I want to replace &ldquo;domínio&rdquo; with &ldquo;DOMAIN&rdquo; and &ldquo;Cloudflare&rdquo; with &ldquo;AWS Amazon&rdquo;, how do I do that? This is &ldquo;Search and Replace&rdquo; that exists in every editor and in Vim it&rsquo;s very easy:</p>
<p>We type <strong>:%s/domínio/DOMAIN/</strong>.</p>
<p>If you&rsquo;re a programmer you&rsquo;ve probably seen this syntax before. It&rsquo;s the <strong>REGEX MATCH</strong> syntax. Yes, you have to know Regex (and no, it&rsquo;s not PCRE nor Unicode, it&rsquo;s NeoVim&rsquo;s own Regex dialect, but it&rsquo;s similar).</p>
<p>As I said before, &ldquo;colon&rdquo; opens the command-typing box, <strong>&quot;%s&quot;</strong> starts &ldquo;search with regex&rdquo; and between the slashes &ldquo;//&rdquo; first goes what you&rsquo;re looking for and then what you want to replace it with.</p>
<p>But that&rsquo;s the way in pure NeoVim; in LazyVim, there&rsquo;s the <a href=""><strong>grug-far</strong></a> plugin and you activate it with the <strong>SPACE+sr</strong> command:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912150648_screenshot-2025-09-12_15-06-32.png" alt="grug-far"  loading="lazy" /></p>
<p>Now yes, more like VS Code, right? Use whichever you find easier.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>As I said, this is only the tip of the tip of the iceberg, the bare minimum of the bare minimum to at least start editing simple config files without opening Nano or VS Code just for that.</p>
<p>Read the LazyVim documentation. Check out the <a href="https://www.lazyvim.org/keymaps"target="_blank" rel="noopener">keymaps</a> to discover other commands. Pay attention to every letter you press in command mode, LazyVim shows the available options. For example, let&rsquo;s say I pressed <strong>g</strong> (goto), it will show a help in the bottom-right corner:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912151849_screenshot-2025-09-12_15-18-38.png" alt="ajuda"  loading="lazy" /></p>
<p>This way you can always discover new commands or remember commands you rarely use. That&rsquo;s why I recommend LazyVim, which comes well configured to help you. The plugin that provides this feature is <a href="https://github.com/folke/which-key.nvim"target="_blank" rel="noopener"><strong>which-key</strong></a>, in case you want the same thing in a NeoVim you configured from scratch.</p>
<p>There are several <a href="https://vim.rtorr.com/"target="_blank" rel="noopener"><strong>Cheatsheets</strong></a> out there with tables of important command combinations. Worth exploring.</p>
<p>And there&rsquo;s more: LazyVim comes with dozens of <a href="https://www.lazyvim.org/plugins"target="_blank" rel="noopener">plugins</a> for formatting, linting, integrated LSP, integrated GIT, and much more. There&rsquo;s no way I can document each one here. Each of them has its own documentation on the LazyVim.org plugins page.</p>
<p>Again, don&rsquo;t forget to call <strong>:LazyExtras</strong> and install the optional plugins for your languages.</p>
]]></content:encoded><category>omarchy</category><category>developer-tools</category><category>tutorials</category></item><item><title>Omarchy 2.0 - Recommended for Beginners?</title><link>https://akitaonrails.github.io/en/2025/09/12/omarchy-2-0-recommended-for-beginners/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/09/12/omarchy-2-0-recommended-for-beginners/</guid><pubDate>Fri, 12 Sep 2025 12:00:00 -0300</pubDate><description>&lt;p&gt;The most important video on my &lt;a href="https://www.youtube.com/@Akitando"target="_blank" rel="noopener"&gt;YouTube channel&lt;/a&gt; is &amp;ldquo;Don&amp;rsquo;t Outsource Your Decisions&amp;rdquo;:&lt;/p&gt;
&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/D3L8IOncLkg"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;The questions I get the most, literally every single day, come in this format: &lt;em&gt;&amp;ldquo;Akita, should I go to college?&amp;rdquo;&lt;/em&gt;, &lt;em&gt;&amp;ldquo;Akita, what do you think I should study?&amp;rdquo;&lt;/em&gt; and variations of that.&lt;/p&gt;
&lt;p&gt;Dude, I don&amp;rsquo;t know you, I will never know nor will I ever be interested in knowing who you are, what you aim for, your aspirations, ambitions, tastes, preferences. Nothing. Neither me nor &lt;strong&gt;ANYONE ELSE&lt;/strong&gt;. Only you know what&amp;rsquo;s best for you. If you don&amp;rsquo;t know, go find out. That&amp;rsquo;s what &lt;strong&gt;LIVING&lt;/strong&gt; means.&lt;/p&gt;</description><content:encoded><![CDATA[<p>The most important video on my <a href="https://www.youtube.com/@Akitando"target="_blank" rel="noopener">YouTube channel</a> is &ldquo;Don&rsquo;t Outsource Your Decisions&rdquo;:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/D3L8IOncLkg"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>The questions I get the most, literally every single day, come in this format: <em>&ldquo;Akita, should I go to college?&rdquo;</em>, <em>&ldquo;Akita, what do you think I should study?&rdquo;</em> and variations of that.</p>
<p>Dude, I don&rsquo;t know you, I will never know nor will I ever be interested in knowing who you are, what you aim for, your aspirations, ambitions, tastes, preferences. Nothing. Neither me nor <strong>ANYONE ELSE</strong>. Only you know what&rsquo;s best for you. If you don&rsquo;t know, go find out. That&rsquo;s what <strong>LIVING</strong> means.</p>
<p>What you won&rsquo;t admit is that you want someone you consider more important or relevant to approve your choice and tell you that you got it right.</p>
<p>Never expect that from me, I will never validate you.</p>
<p>I will always say: <strong>FIGURE IT OUT</strong>. That&rsquo;s how I became who I am. I never followed anyone, I never asked anyone what I should do. I just went there and did it. Fell on my face, got up, tried again. That&rsquo;s life.</p>
<p>Let me show you what actually <strong>BOTHERS</strong> me:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912124608_screenshot-2025-09-12_12-45-47.png" alt="pergunta idiota"  loading="lazy" /></p>
<p>Let&rsquo;s check the <a href="https://learn.omacom.io/2/the-omarchy-manual/93/security"target="_blank" rel="noopener">manual</a>?</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912124657_screenshot-2025-09-12_12-46-45.png" alt="Manual"  loading="lazy" /></p>
<p>DHH took the trouble to write a manual. It has a section called &ldquo;Security&rdquo;. A quick Google search would have found it. Or this other gem:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912124836_screenshot-2025-09-12_12-48-24.png" alt="devops"  loading="lazy" /></p>
<p>How do you want to become a &ldquo;DEVOPS&rdquo; (who has to know EVERYTHING about Linux) starting like this? Already off to a bad start. Park your butt in the chair, roll up your sleeves, and test them one by one, and learn.</p>
<h3>Linux for Beginners?<span class="hx:absolute hx:-mt-20" id="linux-for-beginners"></span>
    <a href="#linux-for-beginners" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>That said, watch my video. But since I&rsquo;ve been talking a lot about <a href="/en/tags/omarchy/">Omarchy</a> already, I&rsquo;ll say that yes, this is what I use in my day to day and it&rsquo;s the only one I&rsquo;ll recommend for the near future (I may change my mind later). Having used distros ever since Linux was invented in the early 90s, I know there are dozens of distros and even I don&rsquo;t have the patience to try them all.</p>
<p><em>Why Omarchy?</em></p>
<p>First, because it made the right choice of base distro: <strong>Arch Linux</strong>. I consider it the best distro out there today for two reasons: it has very little &ldquo;bloat&rdquo; (it doesn&rsquo;t install a bunch of stuff you&rsquo;ll never use, like Ubuntu, Fedora, Mint, Deepin, or any of those other &ldquo;beginner-friendly&rdquo; distros).</p>
<p>Second, Arch Linux has the AUR, which is a repository of user-made packages that has basically everything. You don&rsquo;t need to go hunting for third-party repositories or install things manually. With a single command, <code>yay</code>, you install everything. There&rsquo;s none of that <code>apt-repository add</code> nonsense or having to <code>make install</code> or anything like that. The AUR manages all of it for you.</p>
<p>Third, it installs Hyprland (which I was already using and liked), with very good configs, and a very good package selection. Everything I would install manually myself. So it&rsquo;s a thousand times easier to get what would take me much more work to do on my own. Check my <a href="/en/tags/omarchy/">series of posts about Omarchy</a> to see those options.</p>
<h3>Linux for Beginners?<span class="hx:absolute hx:-mt-20" id="linux-for-beginners-1"></span>
    <a href="#linux-for-beginners-1" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><em>Beginner at what?</em></p>
<p>I hate this question. If you&rsquo;re really an amateur user, not a programmer, maybe you&rsquo;re an accountant, maybe you&rsquo;re just a teenage gamer. The first question I ask is: <em>why do you want to use Linux?</em></p>
<p>Let&rsquo;s say it&rsquo;s because you heard it&rsquo;s more secure (and it actually is). Then it doesn&rsquo;t matter, any distro will do. Install Ubuntu, install Manjaro, install Garuda. It makes no difference, because you&rsquo;ll never deal with anything under the hood. You&rsquo;ll open Chrome, open Steam, and that&rsquo;s it. The rest is going to be web apps.</p>
<p>Let&rsquo;s say it&rsquo;s just for gaming, then install <a href="https://bazzite.gg/"target="_blank" rel="noopener">Bazzite</a> on your mini-PC or handheld. Or just install your games on a separate Windows box just for gaming like I did. Whatever.</p>
<p>Or just stick with Windows or macOS. If you&rsquo;re a total amateur, you&rsquo;re probably going to use the same weak password for everything, and click on WhatsApp links without checking who they&rsquo;re from. It literally makes no difference which OS you use. No security from any OS will protect you from your bad habits and ignorance.</p>
<p>If you&rsquo;re a beginner at programming or tech, then I have no mercy: learn Linux properly. You want to be a professional, be a professional, learn the right way: learn to have 100% control over your own machine. There&rsquo;s no step-by-step or tutorial for that. It&rsquo;s something you&rsquo;re going to do continuously for the rest of your life. I&rsquo;ve been in this field for over 30 years and I still learn new things every day.</p>
<h3>Hardware<span class="hx:absolute hx:-mt-20" id="hardware"></span>
    <a href="#hardware" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The <strong>BIGGEST</strong> problem anyone is going to face, with ANY distro, is going to be hardware support. And that&rsquo;s why you shouldn&rsquo;t just follow what someone recommends to you:</p>
<p><strong>Nobody has the same PC as you.</strong></p>
<p>What&rsquo;s your notebook? A Lenovo? A Dell? A Positivo? From what year? Which model?</p>
<p>Everything varies: which Wi-Fi and Bluetooth device? Which graphics card? Which firmware is running? With which power profile support? Do you need FIDO2 devices for things like a fingerprint reader? Hardware keys? Does your machine have TPU? Will you need Secure Boot?</p>
<p>I don&rsquo;t know. You are the one who has to know.</p>
<p>A practical example, I have an <strong>Asus Zephyrus G14</strong> notebook that I use with Windows because it only lives in my workshop to run Bambu Studio to control my X1C 3D Printer. I haven&rsquo;t gotten around to installing Omarchy on it, but let&rsquo;s say I wanted to. Would it work?</p>
<p>Fortunately there&rsquo;s <a href="https://wiki.archlinux.org/title/ASUS_ROG_Zephyrus_G16_%282023%29_GU603"target="_blank" rel="noopener">a page on the ArchWiki</a> dedicated to this model. It looks like everything works &ldquo;out-of-the-box&rdquo;. But if it were the <a href="https://wiki.archlinux.org/title/ASUS_ROG_Zephyrus_G14_%282022%29_GA402"target="_blank" rel="noopener">previous year&rsquo;s version</a> I&rsquo;d have to check extra items. There are Wi-Fi issues, for example.</p>
<p>Each different machine will have different problems.</p>
<h3>Live Env and Ventoy<span class="hx:absolute hx:-mt-20" id="live-env-and-ventoy"></span>
    <a href="#live-env-and-ventoy" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><em>&ldquo;So what do I do, you [censored]?&rdquo;</em></p>
<p>That&rsquo;s why pretty much every good distro offers an installation ISO that comes with a live environment. I&rsquo;ve seen a lot of people on Windows who have trouble knowing what to do with an ISO. It&rsquo;s very simple.</p>
<p><img src="https://etcher.balena.io/images/Etcher_steps.gif" alt="Balena Etcher"  loading="lazy" /></p>
<p>The easiest program to use is <a href="https://etcher.balena.io/"target="_blank" rel="noopener">Balena Etcher</a>. You download the file with the <code>.iso</code> extension, whether from Ubuntu, Mint, Elementary, etc (ALWAYS FROM THE OFFICIAL SITE!!!!). Open Balena and there are only 2 steps: select the image (the ISO file), select the drive (your pen drive, careful not to pick your HD!), and done.</p>
<p>Now just boot your PC and go into the BIOS to select the pen drive to boot from, or use a key like F12 (varies from BIOS to BIOS) to have UEFI select your pen drive.</p>
<p>That will boot the Live Environment straight from the pen drive.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912120806_try-ubuntu-install-ubuntu-800x530.jpeg" alt="Try Ubuntu"  loading="lazy" /></p>
<p>Most distros start by opening a &ldquo;Welcome&rdquo; screen similar to the one above. This installer app is called <a href="https://calamares.io/"target="_blank" rel="noopener"><strong>Calamares</strong></a> and everyone with a graphical installer uses it. That&rsquo;s why it looks similar across all of them. It&rsquo;s the same step-by-step. If you&rsquo;ve installed one distro, you&rsquo;ve installed them all.</p>
<p>But you don&rsquo;t have to follow Calamares. You can close it. And now you&rsquo;re already in the graphical environment of the distro you chose to install. <strong>USE IT.</strong> Try to configure your Wi-Fi, try to pair your Bluetooth headphones or mouse, try to turn on the webcam, put in a micro-SD card and see if it shows up. Try to browse the web and feel the performance.</p>
<p>If at this point you can do all the basic things, it means the chances that everything will work are high. If at this point you already start having problems, like not being able to connect to your Wi-Fi, that distro is probably going to give you a headache. Start by going to Google and see if somebody has already solved the problem for your notebook or PC model, <strong>before</strong> continuing with Calamares.</p>
<p>Let&rsquo;s say you decided to take the day to test several distros to see which one runs best on your machine. Great call, but you&rsquo;ve probably figured out that it&rsquo;s going to be a huge pain to have several pen drives, one for each distro. Or having to keep going back to Windows and using Balena to overwrite a new distro on your only pen drive.</p>
<p>You don&rsquo;t need to do that.</p>
<p>You just have to first download and install <a href="https://www.ventoy.net/en/index.html"target="_blank" rel="noopener"><strong>Ventoy</strong></a> on your pen drive. Check the <a href="https://www.ventoy.net/en/doc_start.html"target="_blank" rel="noopener">documentation</a> on their site, but it&rsquo;s ridiculously simple.</p>
<p><img src="https://www.ventoy.net/static/img/screen/screen_uefi_en.png?v=4" alt="Ventoy Menu"  loading="lazy" /></p>
<p>After installing it on your pen drive, now just copy <strong>ALL THE ISOS</strong> you want at once onto the pen drive. When you boot from it, you&rsquo;ll enter the Ventoy menu, which lets you pick any of the ISOs you copied there. If you didn&rsquo;t like the current distro, just reboot into Ventoy again and pick another ISO. It&rsquo;s that simple.</p>
<p><img src="https://www.ventoy.net/static/img/ventoy2disk_en.png" alt="Ventoy"  loading="lazy" /></p>
<h3>Distrobox<span class="hx:absolute hx:-mt-20" id="distrobox"></span>
    <a href="#distrobox" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Let&rsquo;s say you&rsquo;re a programmer, or a devops, or any tech professional. Your concern isn&rsquo;t so much the graphical interface (which you can test with Ventoy), but whether the OS infrastructure is going to work with your development requirements.</p>
<p>Let&rsquo;s say you use Fedora. I wonder, if I go to Mint, will it have the K8s packages the way I need them? Does it support the Java version I use? If I build a <code>.deb</code> package on my Fedora, will it install properly on Ubuntu?</p>
<p>Having to dual boot or install a VM feels too heavy, too much work, just to test 2 command lines, for example.</p>
<p><img src="https://user-images.githubusercontent.com/598882/144294862-f6684334-ccf4-4e5e-85f8-1d66210a0fff.png" alt="Distrobox"  loading="lazy" /></p>
<p>That&rsquo;s what <a href="https://distrobox.it/"target="_blank" rel="noopener"><strong>Distrobox</strong></a> is for. It needs Docker or Podman already installed on your machine. But after that you just do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">distrobox create -n debian-test --init --image debian:latest --additional-packages <span class="s2">&#34;build-essential&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Once the box is created you just enter it:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">distrobox enter debian-test</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Done, you&rsquo;re inside Debian, super fast. Since it&rsquo;s a Docker container, you can map directories on your local machine into the container, so everything stays integrated. And the most important thing: it loads super fast, because it&rsquo;s just a Docker container.</p>
<p>The <a href="https://distrobox.it/#quick-start"target="_blank" rel="noopener">Quick Start</a> guide explains all of that and has this video with a tour to demonstrate how it works:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/Q2PrISAOtbY"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Seriously. With Distrobox there&rsquo;s no excuse not to test another distro in 2 minutes. Stop wasting time sending DMs to &ldquo;influencers&rdquo;. See for yourself! If you&rsquo;re not capable of doing that, maybe you&rsquo;re in the wrong profession.</p>
<p>Every &ldquo;dev&rdquo; or tech professional, whether devops, cybersec, data analyst, blah blah blah, whatever the trendy title is these days, <strong>MUST</strong> have control over their own machine. <strong>MUST</strong> know how to deal with security (that&rsquo;s why I wrote posts about Vaultwarden, Cloudflare, SSH, etc).</p>
<p><strong>IT&rsquo;S YOUR RESPONSIBILITY!</strong></p>
<p>Don&rsquo;t outsource your responsibility to others. Don&rsquo;t look for validation. Be <strong>promiscuous</strong> and test whichever distros you want, and do whatever you want on those distros: they are yours! There&rsquo;s no such thing as a &ldquo;distro for beginners&rdquo;, &ldquo;distro for devs&rdquo;, &ldquo;distro for devops&rdquo;. A distro is a distro. Linux is Linux. It&rsquo;s all the same, what varies is <strong>your skill</strong>. If you don&rsquo;t have the skill: earn it! Learn! Break your head over it! Pull all-nighters tuning configs! Read the official documentation! Don&rsquo;t waste time thinking you&rsquo;re going to learn on social media. You won&rsquo;t.</p>
<p>I&rsquo;ve been using Linux since the early 90s and I don&rsquo;t stick with Slackware just because it was the first one I learned and I feel a need to validate my first choice. I use whichever ones I want. Right now, I had my own Hyprland setup, and I decided I wanted to learn and use Omarchy. I spent the week banging my head against it, and wrote half a dozen blog posts. And now I&rsquo;ve learned. I know Omarchy: one more for the collection.</p>
]]></content:encoded><category>omarchy</category><category>linux</category><category>tutorials</category></item><item><title>Omarchy 2.0 - Install with the Omarchy ISO</title><link>https://akitaonrails.github.io/en/2025/09/12/omarchy-2-0-install-with-the-omarchy-iso/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/09/12/omarchy-2-0-install-with-the-omarchy-iso/</guid><pubDate>Fri, 12 Sep 2025 10:00:00 -0300</pubDate><description>&lt;p&gt;To wrap up the &lt;a href="https://akitaonrails.github.io/en/tags/omarchy/"&gt;Omarchy&lt;/a&gt; series for now, I think I still owe a quick word on installing via the official Omarchy ISO.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912093827_screenshot-2025-09-12_09-38-05.png" alt="ISO" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Just head to the &lt;a href="https://omarchy.org/"target="_blank" rel="noopener"&gt;official site&lt;/a&gt; and click &amp;ldquo;ISO&amp;rdquo; to download. Use Rufus or Balena Etcher on Windows (or &lt;a href="https://github.com/ifd3f/caligula"target="_blank" rel="noopener"&gt;Caligula&lt;/a&gt; on Linux) to burn it to a USB stick and boot from it. From there, follow the step-by-step and in less than 5 minutes you&amp;rsquo;ll have everything installed.&lt;/p&gt;</description><content:encoded><![CDATA[<p>To wrap up the <a href="/en/tags/omarchy/">Omarchy</a> series for now, I think I still owe a quick word on installing via the official Omarchy ISO.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912093827_screenshot-2025-09-12_09-38-05.png" alt="ISO"  loading="lazy" /></p>
<p>Just head to the <a href="https://omarchy.org/"target="_blank" rel="noopener">official site</a> and click &ldquo;ISO&rdquo; to download. Use Rufus or Balena Etcher on Windows (or <a href="https://github.com/ifd3f/caligula"target="_blank" rel="noopener">Caligula</a> on Linux) to burn it to a USB stick and boot from it. From there, follow the step-by-step and in less than 5 minutes you&rsquo;ll have everything installed.</p>
<p><img src="https://learn.omacom.io/u/configurator-iGH96F.png" alt="Omarchy install"  loading="lazy" /></p>
<p>As I explained in the <a href="https://akitaonrails.com/en/2025/08/29/new-omarchy-2-0-install/"target="_blank" rel="noopener">first article</a>, I did it differently: I installed pure <a href="https://archlinux.org/download/"target="_blank" rel="noopener">Arch Linux</a>, Desktop style, with Hyprland + SDDM. Then I installed Omarchy using the manual command line:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -fsSL https://omarchy.org/install <span class="p">|</span> bash</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>You can do it either way. The second gives you more control, but after gaining more experience and reading the install scripts, I recommend downloading the Omarchy ISO itself. It really is easier.</p>
<h3>Limine vs Grub<span class="hx:absolute hx:-mt-20" id="limine-vs-grub"></span>
    <a href="#limine-vs-grub" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The reason I went the manual route was because I wasn&rsquo;t sure if Omarchy supported BTRFS rollback via snapshots. I had heard that the <a href="https://codeberg.org/Limine/Limine"target="_blank" rel="noopener">Limine</a> bootloader didn&rsquo;t support that. But I was wrong: Omarchy installs Limine with <strong>Snapper</strong> support (which is similar to Timeshift, the one I usually use). Read <a href="https://wiki.archlinux.org/title/Limine"target="_blank" rel="noopener">ArchWiki section 6.2</a>. So this should be a superior option to my GRUB+Timeshift setup that I talked about in the first article.</p>
<p>Since I already installed it manually, I&rsquo;ll keep things as they are because in practice it&rsquo;s the same thing. But if I were installing again, I&rsquo;d go with Limine+Snapper just to try it out.</p>
<p>This means there&rsquo;s support for snapshotting your entire filesystem with BTRFS before a package update, and if something goes wrong, you can reboot and roll back to a previous snapshot. That gives you peace of mind so you don&rsquo;t lose a whole day trying to fix something if an important system package ships buggy or corrupted. That&rsquo;s the most important feature.</p>
<h3>SDDM vs UWSM<span class="hx:absolute hx:-mt-20" id="sddm-vs-uwsm"></span>
    <a href="#sddm-vs-uwsm" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Another thing I noticed was the login manager. On pure Arch, it installs Hyprland by default with <a href="https://wiki.archlinux.org/title/SDDM"target="_blank" rel="noopener">SDDM</a>, the same one KDE uses.</p>
<p>Omarchy uses UWSM, which auto-logs you in with your user at boot. So there&rsquo;s no login screen, it just opens Hyprland automatically.</p>
<p><em>&ldquo;But isn&rsquo;t that insecure??&rdquo;</em></p>
<p>No, because the Omarchy ISO pre-configures your SSD with LUKS, below the filesystem.</p>
<p>For those who don&rsquo;t know, <a href="https://wiki.archlinux.org/title/Dm-crypt/Encrypting_an_entire_system"target="_blank" rel="noopener">LUKS</a> stands for <strong>Linux Unified Key Setup</strong>, which provides full-disk encryption. Its advantage over Windows BitLocker is that it&rsquo;s <strong>filesystem-agnostic</strong>. In other words, it works with BTRFS or EXT4 or any other, because in the boot sequence, after the bootloader (whether Limine or Grub), it loads <strong>before</strong> the filesystem. It&rsquo;s a block-level encryptor.</p>
<p><img src="https://learn.omacom.io/u/arch-encryption-urjrDm.png" alt="install luks"  loading="lazy" /></p>
<p>So when you boot your system, it first asks for the passphrase to decrypt your disk. Then the filesystem opens and the Linux kernel can continue the normal boot with the disk unlocked. So you are <strong>always</strong> forced to type your strong user passphrase at boot to decrypt the disk.</p>
<p>That&rsquo;s why typing a password again at a login manager would be redundant and unnecessary, and I imagine that&rsquo;s why they chose UWSM instead of SDDM.</p>
<p>Especially on a laptop, or in situations where other people live with you, it&rsquo;s <strong>essential</strong> to make sure your disk is encrypted, no matter the operating system. Say you go out to work or travel and your laptop gets stolen. Without encryption, it&rsquo;s game over, regardless of whether you have a login manager. It doesn&rsquo;t protect your data. Anyone can just pull the SSD from your laptop, plug it into another computer, and boom, they can copy everything by booting as root.</p>
<p>With encryption, without the passphrase to decrypt it, accessing your data is impossible. Truly impossible, because on any modern computer from the last 15 years, the CPU ships with hardware-accelerated instructions for AES 256 encryption.</p>
<p>Getting a bit more nerdy: the decryption key <strong>is not your passphrase</strong>. During the install process, a new stronger and more secure key is generated using a <strong>key derivation</strong> process (KDF - Key Derivation Function), nowadays using Argon2id, previously PBKDF2. I explain this in this video:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/HCHqtpipwu4"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>So when you type your passphrase, what actually happens is it decrypts that secure key, and it&rsquo;s with that secure key that your disk is encrypted. That&rsquo;s why you can&rsquo;t brute-force decrypt your data.</p>
<p>Plus, AES 256 is one of those encryption algorithms that&rsquo;s <strong>quantum resistant</strong>. Even if a quantum computer were possible (it isn&rsquo;t), not even that could decrypt your disk. That&rsquo;s the level of security.</p>
<p><em>&ldquo;But won&rsquo;t it be slower?&rdquo;</em></p>
<p>No. It&rsquo;s practically imperceptible, because AES, as I said, is hardware-accelerated by your CPU and doesn&rsquo;t depend on operating system software. In day-to-day use, you&rsquo;ll barely notice. Performance is practically the same. There&rsquo;s no reason not to enable encryption, especially on a laptop.</p>
<p>In my personal case, where I installed it on a desktop PC that I control myself on a secure local network, because I know how to implement network security, it&rsquo;s not an issue. My most important data lives on my NAS (which is encrypted), in backups (which are encrypted). I don&rsquo;t have sensitive data on this PC, and the little I do have sits in files encrypted by <a href="https://veracrypt.io/en/Downloads.html"target="_blank" rel="noopener">Veracrypt</a> on a separate sub-volume.</p>
<p>That&rsquo;s why I didn&rsquo;t enable LUKS on my PC. And I kept the SDDM that pure Arch installed. Except it&rsquo;s <strong>REALLY</strong> ugly by default, so here&rsquo;s a small tip: install either the <a href="https://github.com/MarianArlt/sddm-sugar-dark"target="_blank" rel="noopener">sddm-sugar-dark</a> theme or <a href="https://github.com/simonesestito/dark-arch-sddm"target="_blank" rel="noopener">dark-arch-sddm</a>. I prefer the second one because it&rsquo;s more minimalist and looks like this:</p>
<p><img src="https://github.com/simonesestito/dark-arch-sddm/raw/master/base/screenshot.png" alt="dark arch sddm"  loading="lazy" /></p>
<p>I customized it and swapped the Arch logo for the Omarchy one, and that was it.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I&rsquo;ll say it again: only after installing manually did I see how Omarchy installs things, and today I prefer the Omarchy way. If I were installing from scratch, I&rsquo;d use the Omarchy ISO and keep it with Limine+Snapper and Hyprland+UWSM, with snapshot rollback support, full disk encryption, and automatic login into Hyprland.</p>
<p>You can tell the people behind Omarchy have experience and have already picked components that make sense to work together in the most convenient and secure way possible. I was worried that, being a new distro, maybe they hadn&rsquo;t given this much thought, but I&rsquo;m happy to say I was wrong.</p>
<p>Use the official Omarchy ISO.</p>
]]></content:encoded><category>omarchy</category><category>linux</category><category>tutorials</category></item><item><title>Omarchy 2.0 - LazyVim - LazyExtras</title><link>https://akitaonrails.github.io/en/2025/09/07/omarchy-2-0-lazyvim-lazyextras/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/09/07/omarchy-2-0-lazyvim-lazyextras/</guid><pubDate>Sun, 07 Sep 2025 16:00:00 -0300</pubDate><description>&lt;p&gt;Continuing my series of tips about &lt;a href="https://akitaonrails.com/en/tags/omarchy/"target="_blank" rel="noopener"&gt;Omarchy&lt;/a&gt;, I decided to write about &lt;a href="https://www.lazyvim.org/"target="_blank" rel="noopener"&gt;LazyVim&lt;/a&gt;, which comes pre-installed.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/292349/213447056-92290767-ea16-430c-8727-ce994c93e9cc.png" alt="LazyVim" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Even if you don&amp;rsquo;t use Omarchy, I recommend &lt;a href="https://www.lazyvim.org/installation"target="_blank" rel="noopener"&gt;installing&lt;/a&gt; LazyVim. Just do this:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;# required
mv ~/.config/nvim{,.bak}
# optional but recommended
mv ~/.local/share/nvim{,.bak}
mv ~/.local/state/nvim{,.bak}
mv ~/.cache/nvim{,.bak}
git clone https://github.com/LazyVim/starter ~/.config/nvim
rm -rf ~/.config/nvim/.git
nvim&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;That&amp;rsquo;s it. For those who don&amp;rsquo;t know, NeoVim is a more modern fork of the old Vim (hence the command is &lt;code&gt;nvim&lt;/code&gt;, leaving &lt;code&gt;vim&lt;/code&gt; reserved for the old one). It replaces the old VimScript with Lua scripts and has many optimizations and better quality-of-life features. These days, there&amp;rsquo;s no reason to keep using old Vim.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Continuing my series of tips about <a href="https://akitaonrails.com/en/tags/omarchy/"target="_blank" rel="noopener">Omarchy</a>, I decided to write about <a href="https://www.lazyvim.org/"target="_blank" rel="noopener">LazyVim</a>, which comes pre-installed.</p>
<p><img src="https://user-images.githubusercontent.com/292349/213447056-92290767-ea16-430c-8727-ce994c93e9cc.png" alt="LazyVim"  loading="lazy" /></p>
<p>Even if you don&rsquo;t use Omarchy, I recommend <a href="https://www.lazyvim.org/installation"target="_blank" rel="noopener">installing</a> LazyVim. Just do this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># required
mv ~/.config/nvim{,.bak}

# optional but recommended
mv ~/.local/share/nvim{,.bak}
mv ~/.local/state/nvim{,.bak}
mv ~/.cache/nvim{,.bak}

git clone https://github.com/LazyVim/starter ~/.config/nvim

rm -rf ~/.config/nvim/.git

nvim</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>That&rsquo;s it. For those who don&rsquo;t know, NeoVim is a more modern fork of the old Vim (hence the command is <code>nvim</code>, leaving <code>vim</code> reserved for the old one). It replaces the old VimScript with Lua scripts and has many optimizations and better quality-of-life features. These days, there&rsquo;s no reason to keep using old Vim.</p>
<p>Using Lua, you can configure Nvim any way you want. But configuring everything from scratch is a lot of work. It&rsquo;s a good exercise, but practically speaking, it&rsquo;s not worth it day to day. Better to install a pre-configured setup like the old <a href="https://www.lunarvim.org/"target="_blank" rel="noopener">LunarVim</a>, which I used before and even recommended in some Linux videos on my channel, or <a href="https://astronvim.com/"target="_blank" rel="noopener">AstroNvim</a>, or <strong>LazyVim</strong>, which is currently my favorite. Any of them is a good start and it comes down to personal preference.</p>
<p>Each plugin they install has many features and its own key combinations (hotkeys) that you need to read in the documentation to know what does what. I recommend going to the <a href="https://www.lazyvim.org/plugins"target="_blank" rel="noopener">plugins</a> section of the official site and reading them one by one. There&rsquo;s a lot of stuff you wouldn&rsquo;t even expect, and all of it is very useful.</p>
<p>LazyVim on its own already helps a lot because it opens pop-ups documenting some of the hotkeys. For example, what&rsquo;s called the <strong>Leader</strong> command in LazyVim is the &ldquo;space bar&rdquo;. When you press it, this context menu appears:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-07_15-44-17.png" alt="Leader context menu"  loading="lazy" /></p>
<p>From here, if you type, say, &ldquo;b&rdquo;, another context menu shows up:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-07_15-46-48.png" alt="Buffer context menu"  loading="lazy" /></p>
<p>And if you type &ldquo;b&rdquo; again, it switches to the next file already open in the Buffer. <strong>Buffer</strong> is the Vim name for where already-opened files live. Deleting from the Buffer is the equivalent of &ldquo;closing the file&rdquo;.</p>
<p>Next time, you won&rsquo;t need to look at the menus. Just type quickly in one go: &ldquo;Space+b+b&rdquo;. That&rsquo;s how you memorize the commands you use the most.</p>
<p>Some commands worth learning right away: <strong>Space+Space</strong> (twice), which opens the menu for opening files. Type a few letters of the file name and it filters dynamically:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-07_15-49-35.png" alt="find files"  loading="lazy" /></p>
<p>Or &ldquo;Space+e&rdquo; to open the NeoTree Explorer. Type &ldquo;?&rdquo; to open a popup with the commands you can use to create new subdirectories or files within the subdirectory:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-07_15-51-08.png" alt="NeoTree"  loading="lazy" /></p>
<p>Once you learn the other commands and plugins, you&rsquo;ll notice you don&rsquo;t even need to open NeoTree. It&rsquo;s more for when you want to explore visually. For day-to-day work, commands like &ldquo;:e&rdquo; should be enough. By the way, &ldquo;:&rdquo; (colon) is the command for executing NeoVim functions. For example &ldquo;:q&rdquo; is quit, &ldquo;:w&rdquo; is write, and &ldquo;:wq&rdquo; is write and quit at the same time.</p>
<p>If you want to learn NeoVim more deeply, I recommend the YouTube channel of my friend <a href="https://www.youtube.com/@EustaquioRangel"target="_blank" rel="noopener">Eustáquio Rangel</a>, a Linux, Rails, and Vim veteran who teaches NeoVim from scratch (without LazyVim).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-07_16-09-53.png" alt="TaQ"  loading="lazy" /></p>
<p>Now, let&rsquo;s say you&rsquo;ve already learned the basics of NeoVim and LazyVim, and now you want to start working on your PHP, Java, C#, Ruby, Python, or whatever projects. You&rsquo;ll notice that LazyVim doesn&rsquo;t have any functionality for any of them.</p>
<p>What do you do now? Install VS Code?? <strong>NO!!</strong></p>
<p>That&rsquo;s because you haven&rsquo;t run <a href="https://www.lazyvim.org/extras"target="_blank" rel="noopener">:LazyExtras</a> yet.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-07_15-35-36.png" alt="LazyExtras command"  loading="lazy" /></p>
<p>Type it exactly like that and watch this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-07_15-36-01.png" alt="Enabled Plugins"  loading="lazy" /></p>
<p>This window looks complicated but it&rsquo;s just a list of plugins split into sections. At the top you see the plugins already installed and enabled. If you don&rsquo;t want any of them, just use the arrows to move the cursor down to one of them and press &ldquo;x&rdquo; to unmark it. But leave that section as is. Let&rsquo;s scroll down to the &ldquo;Languages&rdquo; section:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-07_15-36-43.png" alt="Languages section"  loading="lazy" /></p>
<p>This is the section where all the languages you can enable support for live. Just scroll down with the arrows and mark with &ldquo;x&rdquo; which ones you want to install. And there&rsquo;s everything: Angular, Vue, Erlang, Elixir, Rust, Zig, Haskell, and much more.</p>
<p>Next to each language is the group of individual plugins it will install. To know what each one does, type their names into Google and find the documentation page, where you&rsquo;ll find hotkeys and extra configurations you can set.</p>
<p>With this you gain things like Linters, syntax highlighting, LSP, snippets, and much more.</p>
<p>And if you want to install additional plugins that aren&rsquo;t listed, you can add them manually. Read the <a href="https://www.lazyvim.org/configuration/plugins#-adding-plugins"target="_blank" rel="noopener">Adding Plugins</a> section in the documentation.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-07_16-05-15.png" alt="Linter"  loading="lazy" /></p>
<p>LazyVim + LazyExtras covers 90% of everything you need for day-to-day software development. It&rsquo;s lighter and faster than VS Code, uses way less memory, and will be much better especially on older machines. Don&rsquo;t be lazy about learning Vim. A real programmer shouldn&rsquo;t stick with VS Code just because they&rsquo;re too lazy to learn a few extra hotkeys.</p>
]]></content:encoded><category>omarchy</category><category>developer-tools</category><category>tutorials</category></item><item><title>How to Contribute to the AkitaOnRails Blog Using Docker</title><link>https://akitaonrails.github.io/en/2025/08/29/how-to-contribute-to-the-akitaonrails-blog-using-docker/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/08/29/how-to-contribute-to-the-akitaonrails-blog-using-docker/</guid><pubDate>Fri, 29 Aug 2025 22:37:16 -0300</pubDate><description>&lt;p&gt;Contributing to open source projects can be a pain when you have to set up a complex development environment. On the AkitaOnRails blog, that used to mean installing Hugo Extended, Go, Ruby, and all their dependencies. Now we have a much simpler solution: &lt;strong&gt;Docker&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;The Problem&lt;span class="hx:absolute hx:-mt-20" id="the-problem"&gt;&lt;/span&gt;
&lt;a href="#the-problem" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Before, to contribute to the blog you had to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install Hugo Extended&lt;/li&gt;
&lt;li&gt;Install Go&lt;/li&gt;
&lt;li&gt;Install Ruby&lt;/li&gt;
&lt;li&gt;Set up all the dependencies&lt;/li&gt;
&lt;li&gt;Generate the post index manually&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This created barriers for new contributors and could cause conflicts with other installations on the system. A nightmare.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Contributing to open source projects can be a pain when you have to set up a complex development environment. On the AkitaOnRails blog, that used to mean installing Hugo Extended, Go, Ruby, and all their dependencies. Now we have a much simpler solution: <strong>Docker</strong>.</p>
<h2>The Problem<span class="hx:absolute hx:-mt-20" id="the-problem"></span>
    <a href="#the-problem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Before, to contribute to the blog you had to:</p>
<ol>
<li>Install Hugo Extended</li>
<li>Install Go</li>
<li>Install Ruby</li>
<li>Set up all the dependencies</li>
<li>Generate the post index manually</li>
</ol>
<p>This created barriers for new contributors and could cause conflicts with other installations on the system. A nightmare.</p>
<h2>The Solution: Docker<span class="hx:absolute hx:-mt-20" id="the-solution-docker"></span>
    <a href="#the-solution-docker" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Now all you need is Docker and Docker Compose. Everything else runs inside isolated containers.</p>
<h3>Quick Setup<span class="hx:absolute hx:-mt-20" id="quick-setup"></span>
    <a href="#quick-setup" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Clone the repository</span>
</span></span><span class="line"><span class="cl">git clone https://github.com/akitaonrails/akitaonrails.github.io.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> akitaonrails.github.io
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start the environment</span>
</span></span><span class="line"><span class="cl">./scripts/dev.sh start
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Open the blog</span>
</span></span><span class="line"><span class="cl">open http://localhost:1313</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Done! The blog is running and you can start contributing.</p>
<h2>Useful Commands<span class="hx:absolute hx:-mt-20" id="useful-commands"></span>
    <a href="#useful-commands" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The project includes scripts that make development easier:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># See all available commands</span>
</span></span><span class="line"><span class="cl">./scripts/dev.sh <span class="nb">help</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create a new post</span>
</span></span><span class="line"><span class="cl">./scripts/dev.sh new-post <span class="s2">&#34;Título do Meu Post&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># View server logs</span>
</span></span><span class="line"><span class="cl">./scripts/dev.sh logs
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Generate the post index</span>
</span></span><span class="line"><span class="cl">./scripts/dev.sh generate-index
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Stop the environment</span>
</span></span><span class="line"><span class="cl">./scripts/dev.sh stop</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h2>Creating a Post<span class="hx:absolute hx:-mt-20" id="creating-a-post"></span>
    <a href="#creating-a-post" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Creating a new post is dead simple:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">./scripts/dev.sh new-post <span class="s2">&#34;Como Contribuir no Blog do AkitaOnRails&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The script automatically:</p>
<ul>
<li>Creates the directory structure with the current date</li>
<li>Generates an <code>index.md</code> file with basic front matter</li>
<li>The Hugo server reloads automatically</li>
</ul>
<h2>Structure of a Post<span class="hx:absolute hx:-mt-20" id="structure-of-a-post"></span>
    <a href="#structure-of-a-post" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The created file will have this structure:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: &#34;Título do Post&#34;
</span></span><span class="line"><span class="cl">date: &#34;2025-08-29&#34;
</span></span><span class="line"><span class="cl">draft: false
</span></span><span class="line"><span class="cl">description: &#34;Descrição do post&#34;
</span></span><span class="line"><span class="cl">tags: [tag1, tag2]
</span></span><span class="line"><span class="cl">categories: [categoria]
</span></span><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Conteúdo do post aqui...</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h2>Advantages of Docker<span class="hx:absolute hx:-mt-20" id="advantages-of-docker"></span>
    <a href="#advantages-of-docker" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>For Contributors<span class="hx:absolute hx:-mt-20" id="for-contributors"></span>
    <a href="#for-contributors" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Fast setup</strong>: Docker is the only requirement</li>
<li><strong>Isolated environment</strong>: Stays out of the way of your local system</li>
<li><strong>Hot reload</strong>: Changes show up automatically</li>
<li><strong>Reproducible</strong>: Works the same way for everyone</li>
</ul>
<h3>For the Project<span class="hx:absolute hx:-mt-20" id="for-the-project"></span>
    <a href="#for-the-project" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Fewer issues</strong>: Consistent environment</li>
<li><strong>More contributors</strong>: Lower barrier to entry</li>
<li><strong>Maintenance</strong>: Easy to update dependencies</li>
</ul>
<h2>How It Works<span class="hx:absolute hx:-mt-20" id="how-it-works"></span>
    <a href="#how-it-works" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The Docker environment includes:</p>
<ul>
<li><strong>Hugo Extended</strong>: Static site generator</li>
<li><strong>Go</strong>: For Hugo modules</li>
<li><strong>Ruby</strong>: For the index generation script</li>
<li><strong>Volumes</strong>: For hot reload of files</li>
</ul>
<p>The <code>docker-compose.yml</code> maps the local directories into the container, so when you edit a file, Hugo detects the change and reloads automatically.</p>
<h2>Local Development (Alternative)<span class="hx:absolute hx:-mt-20" id="local-development-alternative"></span>
    <a href="#local-development-alternative" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>If you prefer to skip Docker, you can still install everything locally:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Generate the index</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> content <span class="o">&amp;&amp;</span> ruby generate_index.rb
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run the server</span>
</span></span><span class="line"><span class="cl">hugo server --logLevel debug --disableFastRender -p <span class="m">1313</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>But Docker is way more practical!</p>
<h2>Next Steps<span class="hx:absolute hx:-mt-20" id="next-steps"></span>
    <a href="#next-steps" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ol>
<li><strong>Test the environment</strong>: Run <code>./scripts/dev.sh start</code></li>
<li><strong>Create a post</strong>: Use <code>./scripts/dev.sh new-post</code></li>
<li><strong>Explore the code</strong>: See how to customize layouts</li>
<li><strong>Contribute</strong>: Open a Pull Request</li>
</ol>
<hr>
<p><strong>Tip</strong>: The Docker environment automatically saves your changes and reloads the server. Just edit the files and watch the changes in real time! 🚀</p>
]]></content:encoded><category>open-source</category><category>containers</category><category>tutorials</category></item><item><title>Dissecting an Ollama Modelfile - Tuning Qwen3 for Code</title><link>https://akitaonrails.github.io/en/2025/04/29/dissecting-an-ollama-modelfile-tuning-qwen3-for-code/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/04/29/dissecting-an-ollama-modelfile-tuning-qwen3-for-code/</guid><pubDate>Tue, 29 Apr 2025 02:30:00 -0300</pubDate><description>&lt;p&gt;The hot news of the day is the release of the new Qwen3 model. I already &lt;a href="https://www.akitaonrails.com/en/2025/04/28/testando-o-recem-lancado-llm-open-source-qwen3-com-aider-e-ollama"target="_blank" rel="noopener"&gt;posted about it today&lt;/a&gt; and was quite impressed. I thought I would leave it at that, but then I saw this tweet:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/1jgq3ar5c5l7wfjt9wjfui8l598z" alt="ivan qwen tweaks" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;See the &lt;a href="https://x.com/ivanfioravanti/status/1916934241281061156"target="_blank" rel="noopener"&gt;full text at this link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;He gives tips on how to &amp;ldquo;tune&amp;rdquo; this new model, mostly for math-related questions, and I started wondering whether we can tune it for software development. Before more advanced researchers yell at me: this is my first time messing with the parameters of an LLM, so if you want to add context in the comments, you&amp;rsquo;re welcome.&lt;/p&gt;</description><content:encoded><![CDATA[<p>The hot news of the day is the release of the new Qwen3 model. I already <a href="https://www.akitaonrails.com/en/2025/04/28/testando-o-recem-lancado-llm-open-source-qwen3-com-aider-e-ollama"target="_blank" rel="noopener">posted about it today</a> and was quite impressed. I thought I would leave it at that, but then I saw this tweet:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/1jgq3ar5c5l7wfjt9wjfui8l598z" alt="ivan qwen tweaks"  loading="lazy" /></p>
<p>See the <a href="https://x.com/ivanfioravanti/status/1916934241281061156"target="_blank" rel="noopener">full text at this link</a></p>
<p>He gives tips on how to &ldquo;tune&rdquo; this new model, mostly for math-related questions, and I started wondering whether we can tune it for software development. Before more advanced researchers yell at me: this is my first time messing with the parameters of an LLM, so if you want to add context in the comments, you&rsquo;re welcome.</p>
<p>Let&rsquo;s go. First, what are these TopP, TopK and everything else Ivan mentions in his tweet? They are <strong>KEY SAMPLING</strong> parameters. It&rsquo;s a group of parameters that control <em>HOW</em> the model selects the <em>NEXT TOKEN</em> and goes on completing the text (giving the &ldquo;answer&rdquo;), based on its vocabulary and the probability distribution it calculates. The most common ones are exactly <code>temperature</code>, <code>top_p</code>, <code>top_k</code> and sometimes <code>min_p</code> or <code>repeat_penalty</code>.</p>
<p>I always say: an LLM is a next-word generator, a text completer based on probabilities. When the LLM processes your input (your context, chat history and the next question), it needs to calculate a probability &ldquo;score&rdquo; for every word or sub-word (token) across its entire vocabulary. Imagine a gigantic model, it needs to recalculate everything all the time, for every next word.</p>
<p>Sampling parameters tell the model &ldquo;how to choose the next token&rdquo; from the list of probabilities. Without these parameters the model would use a &ldquo;greedy&rdquo; decoder (too ambitious). That means it would just pick the token with the highest probability every time. And that leads to a repetitive, unnatural-sounding result. Sampling adds a kind of &ldquo;controlled randomness&rdquo; (remember? I always say there is randomness baked in, the answer is never &ldquo;right&rdquo;), and that makes the response more &ldquo;interesting&rdquo; to a human being (who is easily fooled). Here are some of the parameters:</p>
<h3>Temperature<span class="hx:absolute hx:-mt-20" id="temperature"></span>
    <a href="#temperature" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Controls randomness, which many people call &ldquo;creativity&rdquo;. It essentially makes the distribution hotter (more spread out and flatter) or colder (with more peaks around the same tokens). The higher the temperature (up to 1.0), the more it increases the probability of picking tokens that had lower calculated probabilities. The lower the temperature (down to 0.0), the smaller the chances for tokens with lower probabilities, and the more &ldquo;greedy&rdquo; the result gets (less &ldquo;human&rdquo;).</p>
<p>For code, crank it too high and you start getting &ldquo;hallucinations&rdquo; (which are basically increased randomness) and code that doesn&rsquo;t make sense. If it&rsquo;s zero, it gets very &ldquo;boilerplate&rdquo;, repetitive, using very common patterns and can potentially end up in an infinite loop with no answer (because it can&rsquo;t pull anything different).</p>
<p>The tweet suggests 0.6, which is moderate. But in my tests, a temperature that high already started giving me some weird code. Out of nowhere it would change a variable name. Out of nowhere a Chinese character would appear in the middle of the code (literally). I think 0 is too low, but 0.1 is enough, maybe 0.2.</p>
<p>You can change this in the <code>~/.aider.model.settings.yaml</code> file before loading the model (these parameters can be readjusted at startup):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ollama_chat/qwen3-dev:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extra_params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">temperature</span><span class="p">:</span><span class="w"> </span><span class="m">0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">num_ctx</span><span class="p">:</span><span class="w"> </span><span class="m">40960</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>top_p (Nucleus Sampling)<span class="hx:absolute hx:-mt-20" id="top_p-nucleus-sampling"></span>
    <a href="#top_p-nucleus-sampling" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Here it starts to get complicated. This <code>top_p</code> is used to select tokens from the smallest set of tokens whose cumulative probability exceeds this &ldquo;p&rdquo; limit. The model then samples only from this reduced set.</p>
<p>The model sorts all the tokens by their probabilities in descending order (from highest to lowest). Then it starts adding up their probabilities until the sum is greater than or equal to <code>top_p</code>. All the tokens included in the sum form the so-called &ldquo;nucleus&rdquo;, and from that set it samples the next token. So it&rsquo;s a draw among the tokens with the sum(x) highest probabilities, more or less. &ldquo;Pick one of the winners, but not necessarily THE winner (which would be greedy)&rdquo;.</p>
<h3>top_k<span class="hx:absolute hx:-mt-20" id="top_k"></span>
    <a href="#top_k" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Consider only the &ldquo;k&rdquo; most likely tokens. The model samples only from this reduced set of the top K highest probability tokens. That really means &ldquo;pick one out of the Top 10 best&rdquo;. The higher the K, the greater the diversity, the lower it goes, the more greedy.</p>
<p>And this is relative, top_k and top_p work together. This configuration helps anchor the selection to the &ldquo;most likely&rdquo; tokens while still allowing top_p to filter out the lower probability ones. It prioritizes plausibility (top_p) while restricting the total number of sampled tokens (top_k). It helps keep the result focused on the most likely &ldquo;correct&rdquo; answers (but again, it&rsquo;s a draw/sampling).</p>
<h3>min_p<span class="hx:absolute hx:-mt-20" id="min_p"></span>
    <a href="#min_p" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Filters out tokens whose probability is less than &ldquo;min_p&rdquo; times the probability of the most likely token. Before sampling, the model identifies the token with the highest probability (P_max), then removes any token with a probability (P_token) that is lower than <code>P_max * min_p</code>. Sampling then happens over the remaining list (which will be further filtered by top_p and top_k).</p>
<p>This prevents the model from choosing tokens that are &ldquo;extremely unlikely&rdquo; relative to the most obvious choice.</p>
<h3>repeat_penalty<span class="hx:absolute hx:-mt-20" id="repeat_penalty"></span>
    <a href="#repeat_penalty" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>This discourages the model from repeating the same tokens that have already appeared recently in the conversation history or generated text.</p>
<p>Before sampling, the probability score of tokens that have been seen recently is reduced, dividing by this <code>repeat_penalty</code> (with values greater than 1, since dividing by 1 is itself, so it keeps the same probability).</p>
<p>If you leave it at 1.0, it can start repeating the same answers or the same code, even the wrong ones. If it&rsquo;s too high, well above 1.0, the response can sound unnatural, since sometimes it really needs to repeat something to re-explain. A moderate value somewhere around 1.1 up to just under 1.2 tends to be more appropriate so it doesn&rsquo;t punish too much what&rsquo;s already in the history.</p>
<h3>num_predict (max tokens to generate)<span class="hx:absolute hx:-mt-20" id="num_predict-max-tokens-to-generate"></span>
    <a href="#num_predict-max-tokens-to-generate" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The absolute maximum number of tokens the model will generate as a response. If it goes over, it truncates. If it&rsquo;s too small, especially for a code response - which can be long - it can truncate too much and leave the answer incomplete. If it&rsquo;s too big it will greatly increase processing and demand way more machine resources and more time, and it can be wasteful.</p>
<p>A value of around 32 thousand tokens leaves plenty of room for tokens. You have to test, and it varies case by case for the type of response you expect and what each model supports.</p>
<h3>Recommended<span class="hx:absolute hx:-mt-20" id="recommended"></span>
    <a href="#recommended" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Knowing all this now you can also read Ivan&rsquo;s tweet and understand it. He recommends Temp 0.6, TopP 0.95, TopK 20, MinP 0.</p>
<p>I fiddled with it a bit more. And I think that for code, Temp has to be in the 0.1 or 0.2 range. I also think a <code>repeat_penalty</code> of 1.1 is worth it to avoid unnecessary repetitions, especially of code. Again, you have to test. These are empirical numbers, not exact ones.</p>
<p>I created a <a href="https://github.com/akitaonrails/qwen3-dev"target="_blank" rel="noopener">project on my GitHub</a> with a complete model file that not only implements these parameters but also pre-configures a system prompt to bias the model towards being a code assistant first. Here&rsquo;s my prompt:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>SYSTEM &#34;&#34;&#34;
You are a highly functional AI assistant specializing in software development tasks.
Respond to queries about coding, algorithms, debugging, system design,
and programming languages/frameworks.
Provide clear, accurate, step-by-step reasoning and functional code examples.
Always format code snippets using markdown triple backticks (```language ... ```).
Aim for comprehensive and correct technical assistance.
Never suggest changing code unrelated to the question topic at hand.
Restrict yourself to the minimum amount of changes that do resolve the problem at hand.
Avoid renaming functions or variables unless absolutely necessary, 
and in doing so make sure if there&#39;s anyone calling the old names, 
for them to be renamed to the new names. Never leave the code in a broken state. 
Pay close attention to correctness, not just answering the quickiest and dirtiest code to solve the problem.
&#34;&#34;&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>To make this file, first I pulled the original Qwen3 Modelfile with this command:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ollama show qwen3 --modelfile &gt; Modelfile</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It&rsquo;s important to do this because, from what I understand, modelfiles don&rsquo;t inherit from a previous one. They don&rsquo;t work like a &ldquo;FROM&rdquo; in a Dockerfile, where you continue a new image from a previous base.</p>
<p>After editing it with my adjustments - which is already on my GitHub, just create a new model:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ollama create qwen3-dev -f qwen3-dev.modelfile</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And it will show up in your local ollama. Check with <code>ollama list</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ ollama list
NAME                                            ID              SIZE      MODIFIED
qwen3-dev:latest                                fe03dfae5484    9.3 GB    About an hour ago
qwen3:32b                                       e1c9f234c6eb    20 GB     2 hours ago
qwen3:14b                                       7d7da67570e2    9.3 GB    8 hours ago
qwen3:latest                                    e4b5fd7f8af0    5.2 GB    8 hours ago
qwen2.5-coder:7b-instruct                       2b0496514337    4.7 GB    25 hours ago
....</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>See <code>qwen3-dev:latest</code>? That&rsquo;s our new model with parameter adjustments. In aider just use it directly:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>aider --watch-files --model ollama_chat/qwen3-dev:latest</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Remember to adjust the <code>.aider.model.settings.yml</code> as I showed above.</p>
<p>I haven&rsquo;t done long tests yet, but I decided to play with the 14b model as the base along with these adjustments. The base model already impressed me and at least my adjustments didn&rsquo;t break anything. I kept doing the same refactorings and unit tests on my little educational project and pushed another <a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pull/6"target="_blank" rel="noopener">pull request</a></p>
<p>I think Gemini 2.5 Pro Exp is a little better, but again, not by a very large margin. And in some cases (like refactoring), I think I prefer the code generated by Qwen3. Compare it with the code I got from Gemini in this <a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pull/3"target="_blank" rel="noopener">other pull request</a></p>
<p>With Gemini I had to explain less and it gave fewer errors, but both managed to do what I asked all the way through without breaking the main program. Qwen3 looks good enough for me to want to use it. With the advantage that even being the medium model, with 14 billion parameters (which runs on my 24GB of VRAM with room to spare), it still delivers very good and clean code results. And better: it runs MUCH, but MUCH faster than Qwen2.5 32b.</p>
<p>For those who have GPUs with less VRAM, say, a 3070 or 4070 with 16GB, you can use the even smaller 7b model. I tested qwen2.5-coder:7b and had excellent results, so I imagine the Qwen3 7b should be even better and much faster. Speed that is comparable to Claude or Gemini, which are much larger and run on much beefier machines than an RTX 4070.</p>
<p>Running my qwen3-dev, in my monitoring my CPU stayed idle most of the time and the GPU was above 90% the whole time, consuming almost 400W constantly, holding a temperature of 73 degrees (which is good).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jv8ub1bc6m70u1agm1nyiooav2mk" alt="btop"  loading="lazy" /></p>
<p>I even had a small incident where my PC shut down out of nowhere, because the temporary extension cord I was using couldn&rsquo;t take it and died. Combined with the CPU and my other devices plugged into the same outlet, it was consistently pulling 1000W from the wall for hours. Playing with A.I. uses <strong>A LOT</strong> of energy, with the air conditioner on because just to dissipate all of this, my apartment gets 1 to 2 degrees (my rough guess) above normal. You can actually feel it a bit warmer lol</p>
<p>But that&rsquo;s it, I hope you got a sense of how things actually work under the hood. There&rsquo;s no magic: it&rsquo;s next-word generation, based on a semi-random draw from probabilities.</p>
<p>Now you know.</p>
]]></content:encoded><category>llms</category><category>local-models</category><category>tutorials</category></item><item><title>Understanding the Basics of ComfyUI to Generate AI Images</title><link>https://akitaonrails.github.io/en/2025/04/20/understanding-the-basics-of-comfyui-to-generate-ai-images/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/04/20/understanding-the-basics-of-comfyui-to-generate-ai-images/</guid><pubDate>Sun, 20 Apr 2025 19:30:00 -0300</pubDate><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; this post is now obsolete. I replaced the manual setup with a cleaner containerized project: &lt;a href="https://github.com/akitaonrails/aitrepreneur-docker"target="_blank" rel="noopener"&gt;&lt;strong&gt;akitaonrails/aitrepreneur-docker&lt;/strong&gt;&lt;/a&gt;. It runs Aitrepreneur&amp;rsquo;s stacks locally in Docker, with pinned versions, persistent models, and pre-loaded workflows. Check the README for details.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the &lt;a href="https://www.akitaonrails.com/en/2025/04/20/generating-ai-images-even-ghibli-style-with-docker-and-cuda/"target="_blank" rel="noopener"&gt;previous post&lt;/a&gt; I explained my little project to spin up ComfyUI pre-configured and pre-loaded automatically using Docker Compose. Assuming you already have everything up and running, now it&amp;rsquo;s time to actually understand this beast called ComfyUI a little.&lt;/p&gt;</description><content:encoded><![CDATA[<blockquote>
  <p><strong>Update:</strong> this post is now obsolete. I replaced the manual setup with a cleaner containerized project: <a href="https://github.com/akitaonrails/aitrepreneur-docker"target="_blank" rel="noopener"><strong>akitaonrails/aitrepreneur-docker</strong></a>. It runs Aitrepreneur&rsquo;s stacks locally in Docker, with pinned versions, persistent models, and pre-loaded workflows. Check the README for details.</p>

</blockquote>
<p>In the <a href="https://www.akitaonrails.com/en/2025/04/20/generating-ai-images-even-ghibli-style-with-docker-and-cuda/"target="_blank" rel="noopener">previous post</a> I explained my little project to spin up ComfyUI pre-configured and pre-loaded automatically using Docker Compose. Assuming you already have everything up and running, now it&rsquo;s time to actually understand this beast called ComfyUI a little.</p>
<p>I&rsquo;m no expert either, but I thought it would be cool to explain a few concepts that many people overlook.</p>
<h3>Pre-Trained Models<span class="hx:absolute hx:-mt-20" id="pre-trained-models"></span>
    <a href="#pre-trained-models" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Every GPT out there, or Stable Diffusion or others, loads a vector database into the GPU&rsquo;s memory, into VRAM, in order to process. They are HUGE models, called 7B, 70B, 100B, etc. The &ldquo;B&rdquo; stands for &ldquo;Billions of Parameters&rdquo;. Parameters have no usable definition outside of math, they are simply numbers. Those numbers represent &ldquo;something&rdquo; inside millions of dimensions inside that giant file.</p>
<p>In a VERY ROUGH way, I think of them as compression artifacts. You take PETABYTES of books, images, texts, posts, etc, break it all into tokens and find &ldquo;relationships&rdquo; between one token and many other tokens, and you keep &ldquo;positioning&rdquo; them in space, but not in 3 dimensions, in millions of dimensions. AI models are &ldquo;Vector Space Models&rdquo; or VSMs, more or less like the index of an Elasticsearch if you&rsquo;ve ever played with relevance-based text search, or any &ldquo;Full Text Search&rdquo; plugin in Postgres or MS-SQL. They&rsquo;re all VSMs.</p>
<p>Here&rsquo;s how I think about it: it&rsquo;s as if the training process - the SUPER EXPENSIVE part, which takes MONTHS - takes all the information that&rsquo;s fed in and compiles a new &ldquo;dictionary&rdquo; for a new &ldquo;language&rdquo;. Now, when you &ldquo;talk&rdquo; to a GPT, first it needs to transform your text from your language into this internal &ldquo;language&rdquo; of its own, which is what we call an <strong>&ldquo;Embedding&rdquo;</strong>.</p>
<p>Any data can be transformed. Audio can be converted into text and from text into embeddings. Videos can be broken down into series of images with temporal information. Images can be deconstructed into elements like luminance, chrominance, etc. In the end, everything can be deconstructed into data structures, there&rsquo;s no magic.</p>
<p>Image generation models are trained differently. There are several focuses, focus on facial sharpness, focus on pose accuracy, focus on different styles like cartoon or painting, focus on categories like animals or objects. That&rsquo;s why there are dozens of models out there and lots of &ldquo;loras&rdquo;.</p>
<p>To generate an image from a prompt, first you need to decode your prompt into an embedding, and that&rsquo;s what things like a <strong>TEXT ENCODER</strong> in ComfyUI are for.</p>
<p>In the specific case of AI for images, the way I understand it is that they use something called a &ldquo;DIFFUSION&rdquo; model both to deconstruct the original image and to reconstruct a new image. It&rsquo;s not intuitive, but the reconstruction doesn&rsquo;t start on a blank canvas, the way it would for a human illustrator. It starts with an image filled with random &ldquo;noise&rdquo; like this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/rcvhb8m0boe3jetpy72kww2wmucw" alt="noise"  loading="lazy" /></p>
<p>The model starts from this noise and through a process called DIFFUSION it begins to redraw the image backwards, until it emerges from the chaos and arrives at an image.</p>
<p>What I &ldquo;THINK&rdquo;, what I &ldquo;GUESS&rdquo; is happening - without having read any paper, pure laziness - is that we try to influence this initial noise. For example, by extracting features from the image like a depth map, which looks like this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qj0ba39q5xbs7xyei52h4qmmpr2d" alt="Depth Map"  loading="lazy" /></p>
<p>The original photo looked like this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/6zionu0khn01gpudd9z0ws2mm8zx" alt="Original"  loading="lazy" />.jpg)</p>
<p>Extracting a Depth Map is an <strong>ALGORITHM</strong>, it&rsquo;s not &ldquo;AI&rdquo;. Any Photoshop or Premiere out there can do this with one hand tied behind its back. But the point is to explain that there&rsquo;s much more information in an image than what you - untrained as you are - can see is there.</p>
<p>Another well-known type of ALGORITHM is <strong>Canny Edge Detection</strong>, which produces an edge map, making it easier to know where something starts and ends in the image:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/std6jaroftohqjlc22mii9m7oso6" alt="Canny"  loading="lazy" /></p>
<p>So it goes more or less like this:</p>
<ul>
<li>just as a text is &ldquo;converted&rdquo; into an <strong>Embedding</strong>, vectors that capture the &ldquo;essence&rdquo; of the meaning of the text, dependent on the VSM of the pre-trained model, an image is &ldquo;converted&rdquo; internally into a <strong>LATENT</strong>, which because it&rsquo;s an image is a &ldquo;space&rdquo; (that&rsquo;s why people talk about Latent Space). We can&rsquo;t &ldquo;visualize&rdquo; Latents, because it&rsquo;s an internal representation that depends on the U-NET, the pre-trained model, the neural network that learns to &ldquo;denoise&rdquo; the Latent.</li>
<li>An image U-NET is roughly similar to a text model like GPT or LLaMA, the pre-trained, read-only model that will be used to produce the final result.</li>
<li>A GPT response is a continuation of the context of the previous conversation. It&rsquo;s not &ldquo;answering&rdquo; you directly, it&rsquo;s just &ldquo;continuing&rdquo; the text of the conversation and, by chance, due to training, the result &ldquo;looks like&rdquo; a response. In the case of images, we start with a randomly noisy image and through a process of removing and adding noise, it gradually &ldquo;rearranges&rdquo; the Latent into an image we can recognize.</li>
<li>There is a &ldquo;Scheduler&rdquo; that decides how much noise is added or removed at each time step. And there is the concept of a <strong>DIFFUSION MODEL</strong>, which is more or less the processing &ldquo;pipeline&rdquo;.</li>
<li>We can control the process by adding a text prompt, which is decoded into an &ldquo;embedding&rdquo; using a <strong>Text Decoder</strong> that usually has names like &ldquo;CLIP&rdquo; or properly a Transformer (such as LLava). These embeddings go into the U-NET via cross-attention layers, steering the &ldquo;denoising&rdquo; toward the meaning of the prompt.</li>
<li>On top of that we have <strong>CONTROLNET</strong>, which is a second parallel network that adapts the process to extra conditions. It&rsquo;s a copy of the U-NET architecture (with frozen weights) that receives extra inputs, like Canny Edges, or Depth Maps, pose skeletons (via OpenPose), etc, and learns small adjustments to <strong>INJECT</strong> into the main maps of the U-NET.</li>
<li>During the denoising process, ControlNet activations are merged with those of the U-NET, forcing the process to respect the conditions we passed in.</li>
</ul>
<p>So the U-NET is the &ldquo;generator&rdquo;, the Diffusion Scheduler orchestrates the process, text encoder and controlnet both influence the U-NET, and a VAE is used at the end to convert a latent space into an actual image that we humans can actually see.</p>
<p>VERY, VERY ROUGHLY. That&rsquo;s more or less how I understand the process. And the way I see it, ComfyUI exposes several of these steps in the form of &ldquo;PROGRAMMABLE NODES&rdquo; that we can parameterize and tweak to influence how we want the final image to come out.</p>
<p>Beyond numerical parameters, these Nodes also let us choose which pre-trained model files we want to use for each stage. It&rsquo;s all binary VSMs, but for different uses, which is why many of them have the same extension as I explained, but they live in different sub-directories. So each Node can find only what matters to it.</p>
<p>Taking my photo as an example, this isn&rsquo;t exactly it but just to illustrate, it helps me think that those maps I showed influence the &ldquo;noisy&rdquo; image in a non-random direction, like this one I grabbed from an intermediate stage of the workflow I&rsquo;m running:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/yy0yrlrcel015bucfr3z60izt42p" alt="noise"  loading="lazy" /></p>
<p>If you strain your eyes, you can see the influence of the previous images, and the Diffusion model keeps doing the &ldquo;denoising&rdquo;, being &ldquo;interrupted&rdquo; by the ControlNet. It&rsquo;s as if you were drawing and had a person next to you who every now and then erases what you did, or draws on top, and you keep adapting. So in the end it becomes a &ldquo;collaborative&rdquo; job between the two. If it were only the U-NET, it would generate a brand new image quite different from the original, but the ControlNet is the &ldquo;client&rdquo; that keeps nagging in your ear &ldquo;that&rsquo;s not how I want it, change it&rdquo;, until it arrives at an acceptable result.</p>
<p>On top of that, a U-NET isn&rsquo;t great at generating very high resolution images. The ideal is to generate small images, 512 pixels, 1024 pixels, but nothing bigger. But that would be useless for professional use. That&rsquo;s what <strong>UPSCALE MODELS</strong> are for. If you read my article from yesterday about <a href="https://www.akitaonrails.com/en/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a"target="_blank" rel="noopener">Video2K</a> I explain that I pick a model there that&rsquo;s good for temporal coherence in video. But there were other models that are better for &ldquo;STATIC IMAGES&rdquo;, or photos. And we can use them in ComfyUI.</p>
<p>After the new small image is generated by the U-NET, we can then use it as input to a second &ldquo;UPSCALE&rdquo; pass using a model like RealERSGAN, and go from a 1024 pixel image to 2160 pixels, essentially 4K. It&rsquo;s different from enlarging the image in Photoshop, where everything ends up &ldquo;soft&rdquo; and blurred. Since it&rsquo;s also another AI model, it&rsquo;s trained to &ldquo;redraw&rdquo; at higher resolution. And we can add this to the ComfyUI workflow as well.</p>
<p>More than that, ComfyUI also supports Nodes with models like Tencent&rsquo;s HunyuanVideo, which I introduced in another article from yesterday, about <a href="https://www.akitaonrails.com/en/2025/04/19/gerando-videos-de-ate-2-min-a-partir-de-uma-foto-com-i-a"target="_blank" rel="noopener">FramePack</a>. In fact, we can do the same thing FramePack does but from a ComfyUI workflow: generate a video from an image, using the same model. FramePack is infinitely easier to use, but ComfyUI is the Swiss Army knife of AI: it does <strong>EVERYTHING</strong>, which is also why it&rsquo;s infinitely more complicated and you need to put in serious effort to learn how to use it.</p>
<p>With what we&rsquo;ve covered so far, take a look at the workflow I used (it&rsquo;s zoomed out, you can dynamically zoom in):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/rrtdjr0sq5vsl3xnm08w3sz459w8" alt="workflow"  loading="lazy" /></p>
<p>You can see these intermediate steps using <strong>PREVIEW</strong> Nodes, so this is more or less how we can use ComfyUI to do a lot of advanced stuff. As I said, I myself haven&rsquo;t even started scratching the surface.</p>
<p>Let&rsquo;s now summarize some of the concepts used in ComfyUI:</p>
<h3><strong>1. Checkpoints (Base Models)</strong><span class="hx:absolute hx:-mt-20" id="1-checkpoints-base-models"></span>
    <a href="#1-checkpoints-base-models" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>What they are</strong>:<br>
These are the main Stable Diffusion models (e.g.: SD 1.5, SDXL, Flux). They contain the entire model architecture:</p>
<ul>
<li>
<p>Text Encoder (CLIP)</p>
</li>
<li>
<p>Diffusion Model (U-Net)</p>
</li>
<li>
<p>VAE (Image Decoder)</p>
</li>
</ul>
<p><strong>What they&rsquo;re for</strong>:<br>
Generating images from scratch. Each checkpoint has a different &ldquo;style&rdquo; (realistic, anime, etc).</p>
<p><strong>Examples</strong>:<br>
<code>flux.safetensors</code>, <code>realisticVisionV60.safetensors</code>, <code>anythingV5.safetensors</code></p>
<h3><strong>2. VAE (Variational Autoencoders)</strong><span class="hx:absolute hx:-mt-20" id="2-vae-variational-autoencoders"></span>
    <a href="#2-vae-variational-autoencoders" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>What they are</strong>:<br>
Component that decodes images from the latent space (the format the model understands) into visible pixels.</p>
<p><strong>What they&rsquo;re for</strong>:</p>
<ul>
<li>
<p>Improving details and colors</p>
</li>
<li>
<p>Some checkpoints require specific VAEs</p>
</li>
</ul>
<p><strong>Examples</strong>:<br>
<code>vae-ft-mse-840000.safetensors</code>, <code>animevae.pt</code></p>
<h3><strong>3. Text Encoders</strong><span class="hx:absolute hx:-mt-20" id="3-text-encoders"></span>
    <a href="#3-text-encoders" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>What they are</strong>:<br>
Models that convert your text prompt into numerical representations the AI understands.</p>
<p><strong>Common types</strong>:</p>
<ul>
<li>
<p><strong>CLIP</strong>: Standard for most models</p>
</li>
<li>
<p><strong>OpenCLIP</strong>: Alternative version used in SDXL</p>
</li>
<li>
<p><strong>T5</strong>: Larger models for complex prompts</p>
</li>
</ul>
<p><strong>Examples</strong>:<br>
<code>clip_vision.safetensors</code>, <code>openclip.safetensors</code></p>
<h3><strong>4. CLIP Vision</strong><span class="hx:absolute hx:-mt-20" id="4-clip-vision"></span>
    <a href="#4-clip-vision" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>What they are</strong>:<br>
Models specialized in visual analysis (as opposed to normal CLIP, which is text→image).</p>
<p><strong>What they&rsquo;re for</strong>:</p>
<ul>
<li>
<p>Creating descriptions of existing images</p>
</li>
<li>
<p>Tools like &ldquo;Image Prompt&rdquo; (which use the image as input)</p>
</li>
</ul>
<p><strong>Example</strong>:<br>
<code>clip_vision_g.safetensors</code></p>
<h3><strong>5. Diffusion Models</strong><span class="hx:absolute hx:-mt-20" id="5-diffusion-models"></span>
    <a href="#5-diffusion-models" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>What they are</strong>:<br>
A specific part of the architecture (U-Net) responsible for the diffusion process (adding/removing &ldquo;noise&rdquo;).</p>
<p><strong>What they&rsquo;re for</strong>:</p>
<ul>
<li>
<p>Training LoRAs</p>
</li>
<li>
<p>Advanced reuse workflows</p>
</li>
</ul>
<p><strong>Example</strong>:<br>
<code>unet.safetensors</code></p>
<h3><strong>6. LoRAs (Lightweight Adapters)</strong><span class="hx:absolute hx:-mt-20" id="6-loras-lightweight-adapters"></span>
    <a href="#6-loras-lightweight-adapters" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>What they are</strong>:<br>
Small files that modify the behavior of the main checkpoint without replacing it.</p>
<p><strong>What they&rsquo;re for</strong>:</p>
<ul>
<li>
<p>Adding specific styles (e.g.: anime)</p>
</li>
<li>
<p>Creating consistent characters</p>
</li>
<li>
<p>Adjusting body proportions</p>
</li>
</ul>
<p><strong>Example</strong>:<br>
<code>epiNoiseOffset.safetensors</code>, <code>add_detail.safetensors</code></p>
<h3><strong>7. ControlNet</strong><span class="hx:absolute hx:-mt-20" id="7-controlnet"></span>
    <a href="#7-controlnet" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>What they are</strong>:<br>
Models that impose control over the composition of the image (poses, depth, edges).</p>
<p><strong>Common types</strong>:</p>
<ul>
<li>
<p>OpenPose (human poses)</p>
</li>
<li>
<p>Canny (edges)</p>
</li>
<li>
<p>Depth (depth)</p>
</li>
</ul>
<p><strong>Example</strong>:<br>
<code>control_v11p_sd15_openpose.safetensors</code></p>
<h3><strong>8. Upscale Models</strong><span class="hx:absolute hx:-mt-20" id="8-upscale-models"></span>
    <a href="#8-upscale-models" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>What they are</strong>:<br>
Models for increasing the resolution and detail of generated images.</p>
<p><strong>Types</strong>:</p>
<ul>
<li>
<p><strong>ESRGAN</strong>: For realistic details</p>
</li>
<li>
<p><strong>Anime6B</strong>: Specialized in anime art</p>
</li>
</ul>
<p><strong>Example</strong>:<br>
<code>4x_NMKD-Superscale-SP_178000_G.pth</code></p>
<h3><strong>9. Embeddings (Textual Inversions)</strong><span class="hx:absolute hx:-mt-20" id="9-embeddings-textual-inversions"></span>
    <a href="#9-embeddings-textual-inversions" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>What they are</strong>:<br>
Small files that add new concepts to the model&rsquo;s vocabulary.</p>
<p><strong>What they&rsquo;re for</strong>:</p>
<ul>
<li>
<p>Specific artistic styles</p>
</li>
<li>
<p>Custom objects</p>
</li>
<li>
<p>Artifact corrections</p>
</li>
</ul>
<p><strong>Example</strong>:<br>
<code>bad_prompt.pt</code>, <code>easynegative.safetensors</code></p>
<h3><strong>10. Style Models</strong><span class="hx:absolute hx:-mt-20" id="10-style-models"></span>
    <a href="#10-style-models" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>What they are:</strong><br>
Models specialized in applying visual styles or art &ldquo;filters&rdquo; on top of the generated image, without actually altering the basic structure of the content.</p>
<p><strong>What they&rsquo;re for:</strong></p>
<ul>
<li>
<p><strong>Thematic colorization</strong> (e.g.: watercolor palette, sepia, chroma-key)</p>
</li>
<li>
<p><strong>Simulating artistic techniques</strong> (oil, gouache, manga linework)</p>
</li>
<li>
<p><strong>Unifying aesthetics</strong> across series of images (same color and light &ldquo;mood&rdquo;)</p>
</li>
</ul>
<p><strong>Example files:</strong></p>
<ul>
<li>
<p><code>flux1-redux-dev.safetensors</code> (&ldquo;Flux Redux&rdquo; style)</p>
</li>
<li>
<p><code>watercolor_v2.safetensors</code> (watercolor brushwork)</p>
</li>
<li>
<p><code>film_grain_vintage.safetensors</code></p>
</li>
</ul>
<h3><strong>11. SAMs (Segment Anything Models)</strong><span class="hx:absolute hx:-mt-20" id="11-sams-segment-anything-models"></span>
    <a href="#11-sams-segment-anything-models" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>What they are:</strong><br>
General segmentation networks capable of isolating objects or regions of interest in an image, producing binary or weighted masks.</p>
<p><strong>What they&rsquo;re for:</strong></p>
<ul>
<li>
<p><strong>Creating masks of objects/people</strong> for selective application of filters or compositions</p>
</li>
<li>
<p><strong>Guiding ControlNets</strong> (e.g.: applying diffusion only inside or outside a region)</p>
</li>
<li>
<p><strong>Pre-processing references</strong> (cropping background, separating layers)</p>
</li>
</ul>
<p><strong>Example file:</strong></p>
<ul>
<li><code>sam_vit_b_01ec64.pth</code> (SAM ViT-B, base segmentation model)</li>
</ul>
<h3>Visual Summary<span class="hx:absolute hx:-mt-20" id="visual-summary"></span>
    <a href="#visual-summary" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>models/
├── checkpoints/        ➔ Main models (Stable Diffusion)
│     └── *.safetensors, *.ckpt
├── diffusion_models/   ➔ Alternative diffusion models
│     └── *.safetensors, *.gguf
├── loras/              ➔ Style adapters (LoRAs)
│     └── *.safetensors
├── controlnet/         ➔ Composition controls (ControlNets)
│     └── *.pth, *.safetensors
├── vae/                ➔ VAE decoders / encoders
│     └── *.pt, *.safetensors
├── clip/               ➔ CLIP text encoders
│     └── *.safetensors, *.pt
├── clip_vision/        ➔ CLIP vision encoders
│     └── *.safetensors
├── text_encoders/      ➔ Advanced text encoders
│     └── *.safetensors, *.bin
├── upscale_models/     ➔ Super-resolution models
│     └── *.pth
├── sams/               ➔ Segment Anything (SAM) models
│     └── *.pth, *.onnx
├── style_models/       ➔ Visual style models
│     └── *.safetensors
└── embeddings/         ➔ Embeddings / custom concepts
      └── *.pt, *.safetensors</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>You have to download the right things into the right places. And to make matters worse, as I already said, the files usually have the same extension. So it&rsquo;s very easy to get confused and drop a text encoder into the clip directory or vice versa. Once again, that&rsquo;s why I encourage you to update the <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/models.conf"target="_blank" rel="noopener">&ldquo;models.conf&rdquo;</a> file and put the right URLs into the right lists, that way it stays much more organized and my Docker setup will pull things into the right places at startup.</p>
<p>Even so, you may still have a few other doubts that I had at the beginning too. For example <strong>&ldquo;Why is the U-NET called a Checkpoint?&rdquo;</strong></p>
<h3><strong>Checkpoints ≈ A Complete Operating System</strong><span class="hx:absolute hx:-mt-20" id="checkpoints--a-complete-operating-system"></span>
    <a href="#checkpoints--a-complete-operating-system" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>
<p><strong>Similarity</strong>: Just as a commit in Git captures a specific state of the code, a checkpoint is a complete &ldquo;snapshot&rdquo; of a model trained at a specific stage.</p>
</li>
<li>
<p><strong>Difference</strong>: Checkpoints are <strong>self-contained</strong> (they aren&rsquo;t &ldquo;diffs&rdquo; like commits), each one is a complete model that can work independently.</p>
</li>
<li>
<p><strong>Better Metaphor</strong>: Think of checkpoints as different versions of Windows (XP, 7, 10) - each one is a complete operating system, not incremental.</p>
</li>
</ul>
<p>So checkpoints kind of resemble, conceptually, a &ldquo;git commit&rdquo; - if you&rsquo;re a developer. Or BTRFS or ZFS snapshots - if you&rsquo;re an infra/devops person.</p>
<h3><strong>LoRAs ≈ Plugins/Extensions</strong><span class="hx:absolute hx:-mt-20" id="loras--pluginsextensions"></span>
    <a href="#loras--pluginsextensions" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>
<p><strong>Similarity</strong>: Just as a PR adds functionality to a base codebase, a LoRA modifies the behavior of the base model.</p>
</li>
<li>
<p><strong>Difference</strong>: LoRAs aren&rsquo;t &ldquo;merged&rdquo; into the original checkpoint, they are <strong>applied dynamically</strong> during inference.</p>
</li>
<li>
<p><strong>Better Metaphor</strong>: They&rsquo;re like Chrome extensions (e.g.: AdBlock) - they add specific behaviors without modifying the browser itself.</p>
</li>
</ul>
<p>Think of it this way: a model has Checkpoints, like commits, so you can &ldquo;continue&rdquo; training on top if you need to. At the same time, we don&rsquo;t have to mess with the main model, we can create separate layers, the so-called LoRas. For example, let&rsquo;s say a model is having trouble generating your face, no matter what prompt you use or what Nodes you configure.</p>
<p>Instead of that we can use the &ldquo;dictionary&rdquo; of the pre-trained model, like Flux or SDXL, and do training with our personal photos and produce a LoRa specific to me. Then we can add a LoRa Node into the workflow and that will result in much better images with your particular face.</p>
<p>There are entire online communities dedicated to this. One example. On the <a href="https://civitai.com/"target="_blank" rel="noopener">Civit AI</a> site we&rsquo;ll find lots of models based on existing models, like this <a href="https://civitai.com/models/24149/mistoonanime"target="_blank" rel="noopener">Mistoon</a> which is based on SDXL.</p>
<p><a href="https://civitai.com/models/24149/mistoonanime"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a4s6zs7oq6rtwto79lqk5itgd4or" alt="Mistoon Civit.ai"  loading="lazy" /></a></p>
<p>It&rsquo;s a checkpoint. We can sign up on the site and download the file <code>mistoonAnime_v10Noobai.safetensor</code> and put it in the <code>models/checkpoints</code> directory.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/17z9fbwbzkg5kwoxzrcivpwwd74c" alt="Mistoon Checkpoint Node"  loading="lazy" /></p>
<p>Now, in any workflow that has the &ldquo;Load Checkpoint&rdquo; Node we have the option to choose this model. But let&rsquo;s say this model isn&rsquo;t able to generate a character with a school uniform like in many anime. Browsing the site, we find this <strong>LoRa</strong>:</p>
<p><a href="https://civitai.com/models/115968/mistoonanime-school-uniform"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xlvhozw5u90xx9130a1kin5dl0da" alt="Mistoon LoRa"  loading="lazy" /></a>
The Mistoon model is large, around 6.6GB, but this LoRa - called <code>Mistoon_Anime\ school\ uniform.safetensor</code> which we should move to <code>models/loras</code> - is less than 290 MB, much smaller because it&rsquo;s a specific training only on images of school uniforms.</p>
<p>Now we can wire the &ldquo;Load Checkpoint&rdquo; Node to this other &ldquo;Load LoRa&rdquo; Node and choose this LoRa that we know is compatible:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/8ahr2bgdxe5jg3xg4y2wcie3qzvs" alt="LoRa Node"  loading="lazy" /></p>
<p>LoRas have to be picked to be used with the models they were based on. As I said before, think of the model as a &ldquo;language&rdquo;, if we make a LoRa in German, there&rsquo;s no point mixing it with a Chinese model, roughly speaking. But this is one way to do fine-tuning so the model you like delivers results no other one can.</p>
<p>One problem I have with the Civit AI site is that you can only download things while logged in. That&rsquo;s why I didn&rsquo;t include any of their models or loras in my automatic install script. Unfortunately, in their case you have to manually download the files and move them to the right directories, write it down somewhere, so on another machine you can download and move everything all over again. It&rsquo;s a pain that I haven&rsquo;t sat down to fix yet. If anyone wants to contribute a solution for this (without exposing anyone&rsquo;s login cookies, send a Pull Request).</p>
<h3>A Real Example<span class="hx:absolute hx:-mt-20" id="a-real-example"></span>
    <a href="#a-real-example" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Your day-to-day with Comfy, being a newbie like me, will go more or less like this: you start by going to Google and searching for &ldquo;best workflow image to anime ComfyUI&rdquo;. The first links are usually some Reddit thread:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/zhnvm94r6ze0279obqq9k0wd6t9c" alt="Reddit"  loading="lazy" /></p>
<p>Sub-Reddits like <a href="https://www.reddit.com/r/comfyui"target="_blank" rel="noopener">r/comfyui</a> or <a href="https://www.reddit.com/r/StableDiffusion"target="_blank" rel="noopener">r/StableDiffusion</a> usually have news and workflows like this one. Then we go straight to the first comments:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/n3y2qd1vxw4uldxlrs5bjsewt9vn" alt="Reddit Comment Links"  loading="lazy" /></p>
<p>Either that, or we open the workflow directly in ComfyUI and inspect the Nodes, one by one:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/b6hu6k4ygjpjdcq0chzv6mt1kx9w" alt="Nodes Check"  loading="lazy" /></p>
<p>We may already have a problem: the Node came pre-configured to load a LoRa called &ldquo;SDXL_aidma-niji_jini.safetensors&rdquo; but the link in the Reddit comment downloads an &ldquo;SDXL_Niji_V6_DLC_LoRa_V4.safetensors&rdquo;. Searching specifically for the previous one on Google, I can&rsquo;t find it. But it seems &ldquo;safe&rdquo; to use this other one, at least the names are very similar, both are derived from SDXL so theoretically they&rsquo;re at least compatible. This is the page of that model on Civit AI, so you have to download manually and send it to the <code>models/loras</code> directory, same thing for the AniToon Checkpoint in the Node above. Download manually and move it to <code>models/checkpoints</code>.</p>
<p><a href="https://civitai.com/models/541460/sdxlnijiv6dlclora"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/m8u6e9hc0vyrd41pdru0c26akolr" alt="LoRa Niji"  loading="lazy" /></a></p>
<p>Note that on this page it explains that this LoRa was made to work with the SDXL_Niji_V6 model and not with AniToon, which is what the Workflow I downloaded suggests in its Load Checkpoint Node. It&rsquo;s worth testing with both. Either way, since we&rsquo;re already on the site, let&rsquo;s also download the suggested model:</p>
<p><a href="https://civitai.com/models/120765"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/zrkk8wr9v1z24s5f5fgyv01uui4g" alt="SDXL Niji"  loading="lazy" /></a></p>
<p>File <code>sdxlNijiSeven_sdxlNijiSeven.safetensors</code>, 6.5GB, move it to <code>models/checkpoints</code>, are you all getting the process? Download, move it to the right place, reload the workflow in the browser and now it shows up, we select it:</p>
<p>By the way, I think it&rsquo;s intuitive but for those who haven&rsquo;t figured it out after opening the ComfyUI interface:</p>
<ul>
<li>left mouse button selects Nodes</li>
<li>middle mouse button drags the canvas</li>
<li>scroll wheel zooms the interface</li>
</ul>
<p>Now then, let&rsquo;s pick these two to test:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/m7bzo57hfez23vazar5ct4j5kndq" alt="Nodes Niji"  loading="lazy" /></p>
<p>Taking advantage of this same image, notice in the left corner a Florence2 Node, which is a Microsoft LLM (and my Docker script already installs it for you). I can&rsquo;t show all the related Nodes but these are the main ones explaining what it&rsquo;s for:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/8pjx9pr8x0jsmz7bhouz5zcft1zt" alt="Florence"  loading="lazy" /></p>
<p>The ideal in image generation is to write the longest and most detailed prompt about the original image that you can, but most people just write some garbage like &ldquo;person smiling standing up&rdquo; and think that&rsquo;s enough. The Florence2 model is built to read an image and describe it in text.</p>
<p>Now there&rsquo;s another detail: many checkpoints or Loras are trained with some activation keyword. So they only kick in if you type those words into your prompt. So this workflow doesn&rsquo;t need you to type a prompt manually, Florence2 will do that, and every time its text starts with &ldquo;The image &hellip; bla bla bla&rdquo;, so we have a &ldquo;Text Find and Replace&rdquo; Node that swaps that &ldquo;The Image&rdquo; for the keywords of Anitoon or Niji: &ldquo;aidma-niji, niji&rdquo;.</p>
<p>Are these the right words? <strong>I HAVE NO IDEA</strong> lol</p>
<p>The workflow already brought these words, but on the Civit AI pages - where it should be documented, it&rsquo;s not!! So we&rsquo;re really <strong>GUESSING</strong> that it should be &ldquo;niji&rdquo; since that&rsquo;s the name of the model and the lora. Pay attention to the keyword, it&rsquo;s important, otherwise you add the lora and nothing changes and that&rsquo;s because you forgot it in the prompt!!</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4i1n0fc3uvvy85nr17x6imee33bh" alt="ControlNet"  loading="lazy" /></p>
<p>There&rsquo;s more, you have to check that the Nodes for things like VAE, ControlNet, Canny and everything else are populated with files that we have. Usually you use the same half-dozen and I&rsquo;ve already pre-installed the main ones in Docker. In the image, see that the ControlNet is <code>contronet-union-sdxl-1.0-promax.safetensors</code>, compatible with SDXL, and we&rsquo;re using Niji which is built on top of SDXL. We just keep guessing like that.</p>
<p>Also notice that it uses Zoe Depth Map to extract the Depth Map and Canny to extract the Edge Map as in the other workflow. You can configure it, in this workflow notice that the Edge Map picked up some weird artifacts in the background, you can adjust it to remove that. That&rsquo;s why in the previous example the final image also had a window appearing, the model thought it was a window! But for now let&rsquo;s leave it as is.</p>
<p>Now we can run it:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/29nzuo5t1nr6mn4wss0t77at94no" alt="Run Comfy"  loading="lazy" /></p>
<p>There&rsquo;s a huge &ldquo;RUN&rdquo; button in blue down at the bottom. If everything is configured correctly, you&rsquo;ll see that a job has been added to the queue. The interface is smart enough to let you work on other workflows and just keep queueing jobs while the GPU is processing. You don&rsquo;t have to sit waiting, just leave several queued up and go to sleep.</p>
<p>When it runs, in the interface the Nodes that are processing at any given moment will get a green border, which is good so you know what stage of the process it&rsquo;s in. And if any Node has an error - because a file is missing, for example - it&rsquo;ll get a red border, so you have to fix it and run again.</p>
<p>And in the end, here&rsquo;s one of the results:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/7mbzamhj8c5sc2fesp8kkz6cvjhu" alt="Niji Result"  loading="lazy" /></p>
<p>Notice how the controlnet maps guaranteed the correct pose. The model interpreted those background artifacts from the edge map as lighting from some window, but in the original photo we can see it&rsquo;s like the concrete brick of the wall lol but that&rsquo;s how it is, without knowing, it guesses. And if you run it several times, it&rsquo;ll always give you a quite different result. Look at another one:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xbxu131ivlqu4endtdf6j5ayxzxt" alt="Niji 2"  loading="lazy" /></p>
<p>Way off lol But we can swap the SDXL-Niji model for Anitoon. Let&rsquo;s see one of the results:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/w7fsjrui29pbp8e98ka4ubuvtl6f" alt="Anitoon"  loading="lazy" /></p>
<p>Switching to the waiNSFIllustrious model:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2un0xtv7g210xrqc74nbceiwg7t4" alt="waiNSFIllustrious"  loading="lazy" /></p>
<p>This one came out even more off. Something in the maps is making the model assume that I have curly hair, no idea why. But that&rsquo;s it: you have to tweak the parameters. The result of the workflow I showed at the beginning of the article gave a better result, using the same models.</p>
<p>Of all the Nodes, one of the most important is the &ldquo;engine&rdquo; of the process, the <strong>KSampler</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/n6875915bhqq39vxw0kfripyyj3d" alt="KSampler"  loading="lazy" /></p>
<p>This is the guy who runs the show. We give it the model, the prompt, control-net outputs, and it&rsquo;s the one that iteratively (steps) does the &ldquo;denoise&rdquo; of the Latent. Notice that there&rsquo;s always a random factor (&ldquo;seed&rdquo;), a number of steps (25 to 50 is the average), &ldquo;cfg&rdquo; (classifier-free guidance) or &ldquo;guidance_scale&rdquo;, where higher values pull the image more toward your positive prompt. Denoise is how much noise the scheduler applies (1.0 is full noise, below 1.0 may produce more &ldquo;artistic&rdquo; results, you have to test).</p>
<p>Sampler Name is annoying because you&rsquo;d need to understand the math behind it, but they are the step algorithms, for example <code>euler</code>, <code>ddim</code>, <code>plms</code> and each one has a trade-off between speed and fidelity. In practice: test them one by one and see the results. Scheduler has <code>normal</code>, <code>klms</code>, <code>dpmsolver</code>. Same thing: test them one by one.</p>
<p>As you can see, there are PLENTY of parameters you can play with. But the main flow is more or less this. From there you have to keep learning Node by Node what each one can do and download lots of pre-made workflows to see how people have been solving various problems, which models and loras are the most popular for each kind of application. Anyway, Reddit is your friend. I hope this gave you some sense of direction, I&rsquo;m also still learning.</p>
]]></content:encoded><category>generative-media</category><category>machine-learning</category><category>tutorials</category></item><item><title>Generating AI Images - even Ghibli style 😂 - with Docker and CUDA</title><link>https://akitaonrails.github.io/en/2025/04/20/generating-ai-images-even-ghibli-style-with-docker-and-cuda/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/04/20/generating-ai-images-even-ghibli-style-with-docker-and-cuda/</guid><pubDate>Sun, 20 Apr 2025 17:30:00 -0300</pubDate><description>&lt;p&gt;This topic will be split into two posts. This one is purely technical, about how to get it running. In the other one I&amp;rsquo;ll explain what the heck ComfyUI is and roughly how to use it.&lt;/p&gt;
&lt;p&gt;More than a year ago, when I made my last videos about A.I. and on the podcasts, I always said I ran everything on my own machine. But I never detailed how. So today I fixed that with several blog posts and little projects on GitHub with Dockerfiles so you can run it on your own machine, without configuring anything difficult.&lt;/p&gt;</description><content:encoded><![CDATA[<p>This topic will be split into two posts. This one is purely technical, about how to get it running. In the other one I&rsquo;ll explain what the heck ComfyUI is and roughly how to use it.</p>
<p>More than a year ago, when I made my last videos about A.I. and on the podcasts, I always said I ran everything on my own machine. But I never detailed how. So today I fixed that with several blog posts and little projects on GitHub with Dockerfiles so you can run it on your own machine, without configuring anything difficult.</p>
<p>I finally made a <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"target="_blank" rel="noopener">Dockerfile</a> for ComfyUI, which is the best graphical interface for editing image generation workflows. If you&rsquo;ve ever worked with Davinci Resolve Fusion or Blender Geometry Nodes and the like, workflows are basically node configurations to process your images, audio, video and pipe them through processing steps, not just A.I. ones. It&rsquo;s like programming visually. That&rsquo;s why it&rsquo;s a much more complicated tool than the FramePack I showed in the previous post. You don&rsquo;t just click and go.</p>
<p>Recently everyone went hysterical with the ChatGPT updates and how it <em>&ldquo;OH MY GOD&rdquo;</em> can generate Ghibli anime style images, and they kept spamming the world with idiotic anime-style memes. Online hysteria is really annoying. But as I said, as impressive as it is, that&rsquo;s all it is: a meme generator. You can&rsquo;t use it professionally.</p>
<p>I&rsquo;m tired of trying to upload my own photos, or even generic photos, fictional characters, sometimes drawings of mine, and randomly ChatGPT just refuses to generate the image. Claiming privacy, security, copyright and whatever else it can invent as an excuse. You can&rsquo;t rely on this for work. Anyone who&rsquo;s a professional should have full control over the process, and that&rsquo;s what I&rsquo;m going to teach today.</p>
<p>To start, clone my <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"target="_blank" rel="noopener">GitHub repository</a> and follow the README instructions or do this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git clone https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded.git
cd ComfyUI-Docker-CUDA-preloaded
docker compose build</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Since it&rsquo;s more complex, this time I had to put together a Docker Compose. So to bring it up, after the build finishes, just do this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>docker compose up</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now it&rsquo;s going to take a while, because by default it will download almost <strong>500GB</strong> of pre-trained models so it can do almost anything image-related. Yes, to play with A.I. locally you need infrastructure. Otherwise, rent a beefy machine with lots of storage on a RunPod or similar and run it remotely. You can download fewer models, I&rsquo;ll explain that next.</p>
<p>On Docker Compose. I recommend avoiding the &ldquo;-d&rdquo; option to bring it up as a daemon, because it&rsquo;s good to reserve a terminal to keep watching the outputs (you can run <code>docker compose logs --follow</code> if you want, but it&rsquo;s easier directly).</p>
<p>ComfyUI is a more complex program than average, much more so than something like FramePack. It supports installing &ldquo;extensions&rdquo; (plugins, addons, call them what you want). And honestly on its own it doesn&rsquo;t do much, you need to install several extensions for it to start being really useful. Every now and then one of these extensions doesn&rsquo;t handle errors properly and blows up in the console, something like this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/n7w5oxun51elrsv2fra46f684xre" alt="Console Error"  loading="lazy" /></p>
<p>It&rsquo;s always useful to keep an eye on the console to find out if it tried to load a file that doesn&rsquo;t exist. Then you can grab the file name and search Google, download it and put it in the right place. Or if it&rsquo;s a really buggy extension that&rsquo;s better off removed from the &ldquo;custom_nodes&rdquo; directory, and so on. Without watching the console, it doesn&rsquo;t show up in the web interface and you&rsquo;ll be left in the dark.</p>
<p>A lot of the models and extensions I based on the <strong>Ultimate ComfyUI version 3</strong> configuration recommended by <strong>Aitrepreneur</strong>, again. Watch <a href="https://youtu.be/q5kpr84uyzc"target="_blank" rel="noopener">this video of his</a> to get all the details and everything that&rsquo;s possible to do. He has the scripts for Linux, Windows, and the workflows in his paid Patreon version and, of course, I won&rsquo;t share the workflows because they&rsquo;re not mine. In theory, my config should be able to do what he teaches.</p>
<p>Aitrepreneur&rsquo;s V3 workflow is really badass. But I can&rsquo;t share it because it&rsquo;s closed material on his Patreon, but here&rsquo;s a screenshot of my ComfyUI with all of his stuff loaded:</p>
<p><a href="https://www.patreon.com/posts/ultimate-all-in-121355089"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/1p3xfs0ecfimbw4yg55qz17buti6" alt="Ultimate V3"  loading="lazy" /></a></p>
<p>But the config itself, I redid it entirely to work in this Docker. You can load any other open workflow you find on a Reddit or wherever.</p>
<p>Here&rsquo;s what I did:</p>
<h2>Models<span class="hx:absolute hx:-mt-20" id="models"></span>
    <a href="#models" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>In the project there has to be a &ldquo;models&rdquo; directory, if there isn&rsquo;t one, create it, because docker compose will map it inside the container. When it starts, it&rsquo;ll run the file <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/init_models.sh"target="_blank" rel="noopener">init_models.sh</a>. This script will load a configuration file you can edit later: <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/models.conf"target="_blank" rel="noopener">models.conf</a>. It&rsquo;s huge and a piece of it looks like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
[CONTROLNET]
https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-canny-controlnet-v3.safetensors
https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-depth-controlnet-v3.safetensors
https://huggingface.co/brad-twinkl/controlnet-union-sdxl-1.0-promax/resolve/main/diffusion_pytorch_model.safetensors|controlnet-union-sdxl-1.0-promax.safetensors
https://huggingface.co/lllyasviel/ControlNet-v1-1/resolve/main/control_v11p_sd15_canny.pth
https://huggingface.co/lllyasviel/ControlNet-v1-1/resolve/main/control_v11f1p_sd15_depth.pth
https://huggingface.co/lllyasviel/ControlNet-v1-1/resolve/main/control_v11p_sd15_openpose.pth
https://huggingface.co/Aitrepreneur/FLX/resolve/main/Shakker-LabsFLUX1-dev-ControlNet-Union-Pro.safetensors

[STYLE_MODELS]
https://huggingface.co/Aitrepreneur/FLX/resolve/main/flux1-redux-dev.safetensors

[SAMS]
https://huggingface.co/Aitrepreneur/FLX/resolve/main/sam_vit_b_01ec64.pth</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Each section maps to a sub-directory, so &ldquo;[SAMS]&rdquo; will map to &ldquo;models/sams&rdquo; and download the binaries to the right places. There are some special ones in the &ldquo;[GIT_REPOS]&rdquo; section, for those that can be cloned via git, or &ldquo;[CUSTOM]&rdquo; for files you need to download later inside those cloned projects, for example. I don&rsquo;t yet know if it can be simplified, but I picked this up from Aitrepreneur&rsquo;s config.</p>
<p>The ideal is to always keep this list organized with models you find later. If you want to contribute, that would also be cool. The reason is that everything is scattered across all kinds of sites out there, you download manually, then move to the directory inside &ldquo;models&rdquo;. Tomorrow, if you reinstall your machine, how are you supposed to remember all the sites? There are dozens of available models.</p>
<p>In the next post I&rsquo;ll try to summarize what the hell &ldquo;checkpoints&rdquo;, &ldquo;diffusion models&rdquo;, &ldquo;loras&rdquo;, &ldquo;controlnet&rdquo; and all the rest are. But for now, the file extension doesn&rsquo;t say much. &ldquo;.pt&rdquo; for example only means it&rsquo;s a file PyTorch can open, but it doesn&rsquo;t tell you whether it&rsquo;s a clip or a text encoder, for example. Same thing with &ldquo;.safetensors&rdquo;, it can be anything, and ComfyUI needs each file to be in the correct sub-directory in order to use it, you can&rsquo;t just go around moving files randomly.</p>
<p>The &ldquo;init_models.sh&rdquo; script runs every time Docker is restarted and is smart enough to only download URLs that aren&rsquo;t already downloaded in the &ldquo;models&rdquo; directory. That&rsquo;s why it always runs when the container comes up, in the entrypoint. That&rsquo;s one of the advantages of my config: it has automatic maintenance.</p>
<h3>Extensions and Dependencies<span class="hx:absolute hx:-mt-20" id="extensions-and-dependencies"></span>
    <a href="#extensions-and-dependencies" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I made a second script called <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/init_extensions.sh"target="_blank" rel="noopener">init_extensions.sh</a>, which also runs at the container&rsquo;s entrypoint and which you shouldn&rsquo;t touch, but it loads another configuration file called <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/extensions.conf"target="_blank" rel="noopener">extensions.conf</a> and that one, yes, you can mess with and add new extensions later. A piece of it looks like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[EXTENSIONS]
https://github.com/ltdrdata/ComfyUI-Manager.git
https://codeberg.org/Gourieff/comfyui-reactor-node.git
https://github.com/ssitu/ComfyUI_UltimateSDUpscale.git
https://github.com/kijai/ComfyUI-LivePortraitKJ.git
https://github.com/PowerHouseMan/ComfyUI-AdvancedLivePortrait.git
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Here I declare all the extensions that add new functionality on top of ComfyUI, in particular new <strong>NODES</strong>, which is why they live in the &ldquo;custom_nodes&rdquo; directory, which is a volume external to Docker and therefore persistent, meaning every new extension survives container restarts.</p>
<p>But there&rsquo;s still a BIG catch: <strong>Python Dependencies</strong>. A nightmare.</p>
<p>The Docker image build does a bunch of <code>pip install</code> and since I&rsquo;m using Virtual ENV, everything gets installed in <code>/venv</code> inside the container, as it should.</p>
<p>However, the extensions that get installed outside the build - every time the container restarts - tend to be Python code and also have more dependencies. Extensions are GitHub projects and may be changing right this minute. If they change and you do a <code>git pull</code> to update, you need to run <code>pip install -r requirements.txt</code>. But if you have dozens of extensions, running <code>pip install</code> for each one on every Docker restart takes a long time to do almost nothing. As I said, a nightmare.</p>
<p>My solution - to brag a bit - fixes a good chunk of this maintenance. When the <code>init_extensions.sh</code> script runs, if an extension from the list is already installed, it&rsquo;ll skip it. Then it&rsquo;ll write to the <code>./custom_nodes/.last_commit/*.commit</code> directory the latest commit of each extension. When Docker restarts, it&rsquo;ll do a <code>git fetch</code> and check whether the latest commit hash has changed.</p>
<p>Only if it has changed will it run <code>pip install</code> for that extension that got an update, and skip all the others that haven&rsquo;t changed since the last restart, saving A LOT of time and avoiding having to manually update things. Since I&rsquo;m using VENV, everything will stay persisted in the &ldquo;/venv&rdquo; volume. Yes, it&rsquo;s a VOLUME, because it changes after the build with each extension update.</p>
<p>This way, everything in the image gets copied into that volume, and everything the extensions install on the side also goes into that volume. Every time you restart, it all comes back the way it was. That was the solution I found and so far it seems to work just fine.</p>
<p>But it&rsquo;s only a matter of time until some poorly made extension installs dependencies that break other dependencies, or an actual case I went through: there was an extension that crashes with no error message if you&rsquo;re using Python 3.11, the Zoe Depth Map from the <code>comfyui_controlnet_aux</code> extension, one of the packages that seems to be popular.</p>
<p>I hadn&rsquo;t realized I&rsquo;d started using a Docker base image of ubuntu22 instead of ubuntu24, I was afraid of using something too new and breaking things, but I ended up using something too old and it broke just the same. Managing versioning is a pain. Anyway, I changed the Dockerfile to ubuntu24.</p>
<p>But in the external VENV volume now there were a bunch of dependencies pinned to Python 3.11. No problem, just delete the volume:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>docker compose down # make sure it&#39;s stopped
docker volume rm comfyui-venv
rm ./custom_nodes/.last_commits/*.commit</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And done, the VENV will start over empty, fill up with the correct Python 3.12 dependencies from the image, and when the container starts my <code>init_extensions.sh</code> script won&rsquo;t find &ldquo;.last_commits&rdquo; and will force a <code>pip install -r requirements.txt</code> of all the extensions, now with Python 3.12. Super clean and reliable.</p>
<p>If even so you get an error, then it might be a bug in a specific extension, just go to its directory in &ldquo;./custom_nodes&rdquo; and delete it manually before restarting the container.</p>
<p>I think that&rsquo;s the least complicated thing you can do in this case. This docker wasn&rsquo;t built to run on its own on a server, it&rsquo;s meant to run on your own local machine, where you&rsquo;re monitoring the terminal output anyway.</p>
<p>Oh yes, if a new version of ComfyUI comes out then you have to rebuild the image and delete the VENV volume. The image is static, don&rsquo;t forget. Inside, the Dockerfile does a new git clone and grabs the newest commit. Then it&rsquo;ll install newer python packages, so you need to wipe the VENV volume and have the extensions reinstall their dependencies afterwards too. It&rsquo;s a huge pain, but that&rsquo;s just how it works.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>With all that done, with a simple <code>docker compose up</code> I can have a stable ComfyUI environment isolated from the rest of my system, without dirtying anything with thousands of dubious dependencies. When I&rsquo;m done I just do <code>docker compose down</code> or &ldquo;ctrl-c&rdquo; in its console and that&rsquo;s it, my system stays clean.</p>
<p>On top of that, directories like &ldquo;models&rdquo; and their 500GB of models stay separate, you can back them up elsewhere if you need to. And if you want to bring it up on another machine, just take only those directories and bring up Docker Compose there and it has to come up exactly the same way, zero-setup. That was my goal in Dockerizing ComfyUI, FramePack, Video2K: to have everything portable, isolated and clean to bring up on any machine later without having to run sketchy installation scripts directly on my machine.</p>
<p><a href="https://x.com/AkitaOnRails/status/1914030815962411367"target="_blank" rel="noopener">On my X</a> I already showed my ComfyUI working. I loaded some random workflow I found on the Web and look at this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jhx6v5va7qac4vocp17ku5o6ha51" alt="ComfyUI"  loading="lazy" /></p>
<p>In this workflow I upload a reference image and I can write a prompt for how I want to transform it:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20xq72nojtvb887mebeksr6q32cw" alt="Prompt"  loading="lazy" /></p>
<p>With ComfyUI I have control over every step of the process, for example, this workflow can separate things like Depth Map and Edge Contour from the original image, to use as context before making the final image, ensuring a much more precise result:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4r1en3a557znzmy0zojk8ei9tflu" alt="Depth Map"  loading="lazy" /></p>
<p>And here&rsquo;s a result I already got using the &ldquo;waiNSFIllustrious&rdquo; model that this workflow suggests, which is a model trained to generate anime-style images (there are dozens, for various different styles).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/m7amy8gmp5y4snztatjjlwlr0wye" alt="anime"  loading="lazy" /></p>
<p>Switching the model to <code>Mistoon</code>, based on Stable Diffusion (SDXL), here&rsquo;s another result, with the same prompt, in the same workflow:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/lkj86wdlvh5fy8h78a8ml5io8t6g" alt="anime 2"  loading="lazy" /></p>
<p>And I can keep tuning dozens of parameters inside this workflow, swap vaes, text encoders, controlnets and much more until I get exactly the result I want. That&rsquo;s the power of ComfyUI and its Custom Nodes and Workflows. That&rsquo;s what I&rsquo;ll try to explain the basics of in the next post.</p>
]]></content:encoded><category>generative-media</category><category>containers</category><category>tutorials</category></item><item><title>Colorizing Black and White Images with A.I.</title><link>https://akitaonrails.github.io/en/2025/04/19/colorizing-black-and-white-images-with-ai/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/04/19/colorizing-black-and-white-images-with-ai/</guid><pubDate>Sat, 19 Apr 2025 01:40:00 -0300</pubDate><description>&lt;p&gt;My girlfriend gave me a challenge today: she had old black and white photos and wanted to know if I could colorize them. If you go searching the Web you bump into a few sites for this, like &lt;a href="https://palette.fm/"target="_blank" rel="noopener"&gt;Palette&lt;/a&gt;. But it is paid, it is not cheap, and I do not think it can do something she wanted: use another colored image as a reference to pull the colors instead of letting the model guess.&lt;/p&gt;</description><content:encoded><![CDATA[<p>My girlfriend gave me a challenge today: she had old black and white photos and wanted to know if I could colorize them. If you go searching the Web you bump into a few sites for this, like <a href="https://palette.fm/"target="_blank" rel="noopener">Palette</a>. But it is paid, it is not cheap, and I do not think it can do something she wanted: use another colored image as a reference to pull the colors instead of letting the model guess.</p>
<p>I went poking around GitHub and there is an <a href="https://github.com/MarkMoHR/Awesome-Image-Colorization"target="_blank" rel="noopener">Awesome Image Colorization</a> page with a bunch of research papers. Very useful for researchers but totally useless for me, since I am not going to build one from scratch lol. There were links to projects like ChromaGAN, but that one has been discontinued for about 5 years. And several other projects I saw were also discontinued ages ago. I do not know why there is so little open source still actively maintained.</p>
<p>Luckily I bumped into a promising one: <a href="https://github.com/piddnad/DDColor"target="_blank" rel="noopener">DDColor</a>.</p>
<p>Let&rsquo;s clone the repo. And to keep going, I prefer to do everything inside Docker. Downloading Python dependencies always messes up my system and leaves a bunch of garbage behind, so the best thing is to isolate it all. Another thing, you need to download the binaries of the pre-trained models. Just for that I will use a bit of Python because of the &ldquo;modelscope&rdquo; lib that handles it:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/piddnad/DDColor.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> DDColor
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">mkdir modelscope
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># create a new venv inside the DDColor project</span>
</span></span><span class="line"><span class="cl">python -m venv venv
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">venv/bin/pip3 install modelscope
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">venv/bin/python3 -c <span class="s2">&#34;from modelscope.hub.snapshot_download import snapshot_download; snapshot_download(&#39;damo/cv_ddcolor_image-colorization&#39;, cache_dir=&#39;./modelscope&#39;)&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Good Python practice is to do everything inside a VENV, right? Anyway, this should download the model binary, which weighs almost 900MB. Not huge.</p>
<p>Now we need a Dockerfile:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Use NVIDIA CUDA base image with cuDNN 8 and Python support
FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04

# Install Python and system dependencies
RUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends \
    python3 python3-pip git libgl1 libglib2.0-0 &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app

# Copy DDColor source code into the image
COPY . /app

# Install PyTorch (CUDA 11.8 compatible) and matching torchvision/torchaudio
RUN pip3 install torch==2.2.0 torchvision==0.17.0 torchaudio==2.2.0 \
    --index-url https://download.pytorch.org/whl/cu118

# Install Python dependencies, skipping dlib (not needed for inference)
RUN sed &#39;/dlib/d&#39; requirements.txt &gt; temp-req.txt &amp;&amp; pip3 install -r temp-req.txt &amp;&amp; rm temp-req.txt

# Optional: install modelscope if you want to run snapshot_download manually inside the container
RUN pip3 install modelscope</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Too lazy to open a pull request for this, if anyone wants to, send it over there.</p>
<p>Now we need to put a black and white image somewhere:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mkdir input
mkdir results
mv ~/Downloads/bw.jpg input/</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Since my girlfriend&rsquo;s photos are family and private, obviously I am not going to share them, so I grabbed a random one from Google Images:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qgt3lynkp1f5uzkbysyvio2kmq04" alt="B&W Photo"  loading="lazy" /></p>
<p>Now we drop it in &ldquo;./input&rdquo; and run Docker with this command:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>docker run --rm --gpus all \
  -v &#34;$PWD/input&#34;:/workspace/input_images:ro \
  -v &#34;$PWD/results&#34;:/workspace/results:rw \
  -v &#34;$PWD/modelscope&#34;:/app/modelscope:ro \
  ddcolor:latest \
  python3 infer.py \
    --model_path /app/modelscope/damo/cv_ddcolor_image-colorization/pytorch_model.pt \
    --input /workspace/input_images \
    --output /workspace/results</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pay attention to the directory mapping. And note the &ldquo;&ndash;gpus&rdquo; option, which only works in my case because I have an RTX 4090 installed here. I have no idea if it is the same with AMD, but since the image is based on cuDNN/CUDA, I would say no. Ask ChatGPT.</p>
<p>If everything is right and it runs successfully, it will take just 1 second and produce this output in the terminal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>==========
== CUDA ==
==========

CUDA Version 11.8.0

Container image Copyright (c) 2016-2023, NVIDIA CORPORATION &amp; AFFILIATES. All rights reserved.

This container image and its contents are governed by the NVIDIA Deep Learning Container License.
By pulling and using the container, you accept the terms and conditions of this license:
https://developer.nvidia.com/ngc/nvidia-deep-learning-container-license

A copy of this license is made available in this container at /NGC-DL-CONTAINER-LICENSE for your convenience.

Output path: /workspace/results
100%|██████████| 1/1 [00:00&lt;00:00,  4.09it/s]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And here is the result that shows up in the <code>./results</code> directory:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/o2r085pzzpsavbyr39btg1vq0hwp" alt="Re-colorized photo"  loading="lazy" /></p>
<p>Yes, I was impressed. Even with the image being from the Web, with watermark and all, it did everything properly and worked!!</p>
<p>The only problem left is being able to use a reference image to try to pull the colors. That is a harder problem and I asked ChatGPT, which answered me this:</p>
<p><strong>No native support for a reference image:</strong></p>
<p>The DDColor model, both in the official version hosted on ModelScope and in the <code>piddnad/DDColor</code> GitHub repo, was designed only for <strong>automatic colorization of black and white images</strong>, with no option to accept a reference image as input. The official description itself makes it clear that the model takes <strong>a single B&amp;W image</strong> and returns a colored image.</p>
<p>The model&rsquo;s <code>forward</code> method expects <strong>only one tensor</strong>, the B&amp;W image converted into artificial RGB (actually the luminance expanded into 3 channels). In other words, <strong>there is no place in the model to pass a second image as a color reference</strong>.</p>
<p>This is also evident in the original <code>predict.py</code> from the Cog library, which only accepts the <code>image</code> parameter as input — with no support for <code>reference</code>, <code>ref_image</code> or anything of the kind.</p>
<h3>How to solve the problem: two possible approaches<span class="hx:absolute hx:-mt-20" id="how-to-solve-the-problem-two-possible-approaches"></span>
    <a href="#how-to-solve-the-problem-two-possible-approaches" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Since DDColor does not natively support a reference image to guide the colors, we have two alternatives:</p>
<h3>🔁 1. Use a model specialized in reference-based colorization<span class="hx:absolute hx:-mt-20" id="-1-use-a-model-specialized-in-reference-based-colorization"></span>
    <a href="#-1-use-a-model-specialized-in-reference-based-colorization" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>There are models developed specifically for this, such as:</p>
<ul>
<li>
<p><strong>Deep Exemplar-based Colorization</strong> (Zhang et al., CVPR 2018)</p>
</li>
<li>
<p><strong>ChromaGAN</strong> (2019)</p>
</li>
<li>
<p><strong>Exemplar-Transformer Colorization</strong> (Yin et al., ACM MM 2021)</p>
</li>
<li>
<p><strong>UniColor</strong> (Huang et al., 2022)</p>
</li>
</ul>
<p>These models accept <strong>two images</strong>:</p>
<ul>
<li>
<p>The B&amp;W image (main input)</p>
</li>
<li>
<p>A colored image as a <strong>color reference</strong></p>
</li>
</ul>
<p>These models map semantic similarities (e.g., &ldquo;skin here should have the same skin color as over there&rdquo;) and do a transfer <strong>based on the image content</strong>, getting much more detailed and specific results. However, this would require:</p>
<ul>
<li>
<p>Downloading different weights</p>
</li>
<li>
<p>Completely changing the architecture used</p>
</li>
<li>
<p>Adapting the pipeline and inference</p>
</li>
</ul>
<h3>🎯 2. Use global color transfer after the DDColor output (our current approach)<span class="hx:absolute hx:-mt-20" id="-2-use-global-color-transfer-after-the-ddcolor-output-our-current-approach"></span>
    <a href="#-2-use-global-color-transfer-after-the-ddcolor-output-our-current-approach" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The practical and lightweight solution we used was:
➡️ Run DDColor normally to generate the colored image automatically
➡️ Then <strong>globally</strong> adjust the colors of the result to look more like the reference image.</p>
<p>This adjustment is done with the <strong>Reinhard et al. (2001)</strong> algorithm:</p>
<ol>
<li>
<p>We convert both the output image and the reference to the Lab color space.</p>
</li>
<li>
<p>We compute the <strong>mean and standard deviation</strong> of the <code>a</code> and <code>b</code> channels (the ones that represent color).</p>
</li>
<li>
<p>We adjust the result to have <strong>the same statistical distribution</strong> of color as the reference image.</p>
</li>
<li>
<p>This tends to leave the image with the same overall &ldquo;mood&rdquo; or tone (warmer, cooler, more vibrant).</p>
</li>
</ol>
<p>This method is fast, lightweight, and works <strong>without altering the DDColor model</strong>.</p>
<h2>Continuing<span class="hx:absolute hx:-mt-20" id="continuing"></span>
    <a href="#continuing" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I used the modification it suggested to the predict.py file:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">cv2</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">torch</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">torch.nn.functional</span> <span class="k">as</span> <span class="nn">F</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">cog</span> <span class="kn">import</span> <span class="n">BasePredictor</span><span class="p">,</span> <span class="n">Input</span><span class="p">,</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">basicsr.archs.ddcolor_arch</span> <span class="kn">import</span> <span class="n">DDColor</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">color_transfer</span><span class="p">(</span><span class="n">reference_bgr</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">ndarray</span><span class="p">,</span> <span class="n">target_bgr</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">ndarray</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">np</span><span class="o">.</span><span class="n">ndarray</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Transfer color palette of reference image to target image using Lab mean/std.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Convert BGR images to Lab color space (float32 for precision)</span>
</span></span><span class="line"><span class="cl">    <span class="n">ref_lab</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">reference_bgr</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2LAB</span><span class="p">)</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;float32&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">tgt_lab</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">target_bgr</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2LAB</span><span class="p">)</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;float32&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Split channels</span>
</span></span><span class="line"><span class="cl">    <span class="n">L_ref</span><span class="p">,</span> <span class="n">a_ref</span><span class="p">,</span> <span class="n">b_ref</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">ref_lab</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">L_tgt</span><span class="p">,</span> <span class="n">a_tgt</span><span class="p">,</span> <span class="n">b_tgt</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">tgt_lab</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Compute mean and std for reference and target (a and b channels)</span>
</span></span><span class="line"><span class="cl">    <span class="n">a_ref_mean</span><span class="p">,</span> <span class="n">a_ref_std</span> <span class="o">=</span> <span class="n">a_ref</span><span class="o">.</span><span class="n">mean</span><span class="p">(),</span> <span class="n">a_ref</span><span class="o">.</span><span class="n">std</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">b_ref_mean</span><span class="p">,</span> <span class="n">b_ref_std</span> <span class="o">=</span> <span class="n">b_ref</span><span class="o">.</span><span class="n">mean</span><span class="p">(),</span> <span class="n">b_ref</span><span class="o">.</span><span class="n">std</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">a_tgt_mean</span><span class="p">,</span> <span class="n">a_tgt_std</span> <span class="o">=</span> <span class="n">a_tgt</span><span class="o">.</span><span class="n">mean</span><span class="p">(),</span> <span class="n">a_tgt</span><span class="o">.</span><span class="n">std</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">b_tgt_mean</span><span class="p">,</span> <span class="n">b_tgt_std</span> <span class="o">=</span> <span class="n">b_tgt</span><span class="o">.</span><span class="n">mean</span><span class="p">(),</span> <span class="n">b_tgt</span><span class="o">.</span><span class="n">std</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Subtract target means, scale by reference/target std, add reference means</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># (Avoid division by zero in case of zero std)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">a_tgt_std</span> <span class="o">&gt;</span> <span class="mf">1e-6</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">a_tgt</span> <span class="o">=</span> <span class="p">((</span><span class="n">a_tgt</span> <span class="o">-</span> <span class="n">a_tgt_mean</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">a_ref_std</span> <span class="o">/</span> <span class="n">a_tgt_std</span><span class="p">))</span> <span class="o">+</span> <span class="n">a_ref_mean</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">a_tgt</span> <span class="o">=</span> <span class="n">a_tgt</span> <span class="o">-</span> <span class="n">a_tgt_mean</span> <span class="o">+</span> <span class="n">a_ref_mean</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">b_tgt_std</span> <span class="o">&gt;</span> <span class="mf">1e-6</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">b_tgt</span> <span class="o">=</span> <span class="p">((</span><span class="n">b_tgt</span> <span class="o">-</span> <span class="n">b_tgt_mean</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">b_ref_std</span> <span class="o">/</span> <span class="n">b_tgt_std</span><span class="p">))</span> <span class="o">+</span> <span class="n">b_ref_mean</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">b_tgt</span> <span class="o">=</span> <span class="n">b_tgt</span> <span class="o">-</span> <span class="n">b_tgt_mean</span> <span class="o">+</span> <span class="n">b_ref_mean</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Clip values to valid Lab range [0,255] after transfer</span>
</span></span><span class="line"><span class="cl">    <span class="n">a_tgt</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">clip</span><span class="p">(</span><span class="n">a_tgt</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">b_tgt</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">clip</span><span class="p">(</span><span class="n">b_tgt</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Merge channels back (use original L from target to preserve brightness)</span>
</span></span><span class="line"><span class="cl">    <span class="n">merged_lab</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">merge</span><span class="p">([</span><span class="n">L_tgt</span><span class="p">,</span> <span class="n">a_tgt</span><span class="p">,</span> <span class="n">b_tgt</span><span class="p">])</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;uint8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Convert back to BGR color space</span>
</span></span><span class="line"><span class="cl">    <span class="n">result_bgr</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">merged_lab</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_LAB2BGR</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">result_bgr</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ImageColorizationPipeline</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Helper pipeline to load DDColor model and process images.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">model_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">input_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">256</span><span class="p">,</span> <span class="n">model_size</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;large&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">device</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">device</span><span class="p">(</span><span class="s2">&#34;cuda&#34;</span> <span class="k">if</span> <span class="n">torch</span><span class="o">.</span><span class="n">cuda</span><span class="o">.</span><span class="n">is_available</span><span class="p">()</span> <span class="k">else</span> <span class="s2">&#34;cpu&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Choose encoder backbone based on model size</span>
</span></span><span class="line"><span class="cl">        <span class="n">encoder_name</span> <span class="o">=</span> <span class="s2">&#34;convnext-t&#34;</span> <span class="k">if</span> <span class="n">model_size</span> <span class="o">==</span> <span class="s2">&#34;tiny&#34;</span> <span class="k">else</span> <span class="s2">&#34;convnext-l&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Initialize DDColor model</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">model</span> <span class="o">=</span> <span class="n">DDColor</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">encoder_name</span><span class="o">=</span><span class="n">encoder_name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">decoder_name</span><span class="o">=</span><span class="s2">&#34;MultiScaleColorDecoder&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">input_size</span><span class="o">=</span><span class="p">[</span><span class="n">input_size</span><span class="p">,</span> <span class="n">input_size</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="n">num_output_channels</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span>    <span class="c1"># model predicts ab channels</span>
</span></span><span class="line"><span class="cl">            <span class="n">last_norm</span><span class="o">=</span><span class="s2">&#34;Spectral&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">do_normalize</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">num_queries</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">num_scales</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">dec_layers</span><span class="o">=</span><span class="mi">9</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span><span class="o">.</span><span class="n">to</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">device</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Load weights</span>
</span></span><span class="line"><span class="cl">        <span class="n">state</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">model_path</span><span class="p">,</span> <span class="n">map_location</span><span class="o">=</span><span class="s2">&#34;cpu&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Some checkpoints store weights under &#39;params&#39; key</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="o">.</span><span class="n">load_state_dict</span><span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;params&#34;</span><span class="p">,</span> <span class="n">state</span><span class="p">),</span> <span class="n">strict</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="o">.</span><span class="n">eval</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">input_size</span> <span class="o">=</span> <span class="n">input_size</span>  <span class="c1"># store for processing</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@torch.no_grad</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">img_bgr</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">ndarray</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">np</span><span class="o">.</span><span class="n">ndarray</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Colorize a BGR image (numpy array) using the loaded DDColor model.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Preserve original resolution L channel</span>
</span></span><span class="line"><span class="cl">        <span class="n">orig_h</span><span class="p">,</span> <span class="n">orig_w</span> <span class="o">=</span> <span class="n">img_bgr</span><span class="o">.</span><span class="n">shape</span><span class="p">[:</span><span class="mi">2</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Convert to Lab and extract L channel at original size</span>
</span></span><span class="line"><span class="cl">        <span class="n">img_lab</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">img_bgr</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2LAB</span><span class="p">)</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;float32&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">orig_L</span> <span class="o">=</span> <span class="n">img_lab</span><span class="p">[:,</span> <span class="p">:,</span> <span class="mi">0</span><span class="p">:</span><span class="mi">1</span><span class="p">]</span>  <span class="c1"># shape (H, W, 1)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Prepare grayscale input at model resolution</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Resize input to model expected size</span>
</span></span><span class="line"><span class="cl">        <span class="n">inp</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">resize</span><span class="p">(</span><span class="n">img_bgr</span><span class="p">,</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">input_size</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">input_size</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">inp_lab</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">inp</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2LAB</span><span class="p">)</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;float32&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">L</span> <span class="o">=</span> <span class="n">inp_lab</span><span class="p">[:,</span> <span class="p">:,</span> <span class="mi">0</span><span class="p">:</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Create grayscale Lab image by zeroing AB channels</span>
</span></span><span class="line"><span class="cl">        <span class="n">gray_lab</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">concatenate</span><span class="p">([</span><span class="n">L</span><span class="p">,</span> <span class="n">np</span><span class="o">.</span><span class="n">zeros_like</span><span class="p">(</span><span class="n">L</span><span class="p">),</span> <span class="n">np</span><span class="o">.</span><span class="n">zeros_like</span><span class="p">(</span><span class="n">L</span><span class="p">)],</span> <span class="n">axis</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Convert back to RGB (now it&#39;s a gray RGB image of size input_size)</span>
</span></span><span class="line"><span class="cl">        <span class="n">gray_rgb</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">gray_lab</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;uint8&#34;</span><span class="p">),</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_LAB2RGB</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Prepare tensor and run model</span>
</span></span><span class="line"><span class="cl">        <span class="n">tensor</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">from_numpy</span><span class="p">(</span><span class="n">gray_rgb</span><span class="o">.</span><span class="n">transpose</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span><span class="o">.</span><span class="n">float</span><span class="p">()</span><span class="o">.</span><span class="n">unsqueeze</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">.</span><span class="n">to</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">device</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_ab</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="p">(</span><span class="n">tensor</span><span class="p">)</span>  <span class="c1"># output is (1,2,H_out,W_out) in Lab AB</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_ab</span> <span class="o">=</span> <span class="n">out_ab</span><span class="o">.</span><span class="n">cpu</span><span class="p">()</span><span class="o">.</span><span class="n">float</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Resize output AB to original image size</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_ab_resized</span> <span class="o">=</span> <span class="n">F</span><span class="o">.</span><span class="n">interpolate</span><span class="p">(</span><span class="n">out_ab</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="p">(</span><span class="n">orig_h</span><span class="p">,</span> <span class="n">orig_w</span><span class="p">),</span> <span class="n">mode</span><span class="o">=</span><span class="s2">&#34;bilinear&#34;</span><span class="p">,</span> <span class="n">align_corners</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_ab_resized</span> <span class="o">=</span> <span class="n">out_ab_resized</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">numpy</span><span class="p">()</span><span class="o">.</span><span class="n">transpose</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>  <span class="c1"># (H, W, 2)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Combine original L and predicted AB, then convert to BGR</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_lab</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">concatenate</span><span class="p">([</span><span class="n">orig_L</span><span class="p">,</span> <span class="n">out_ab_resized</span><span class="p">],</span> <span class="n">axis</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;uint8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_bgr</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">out_lab</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_LAB2BGR</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">out_bgr</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Predictor</span><span class="p">(</span><span class="n">BasePredictor</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">setup</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Load models into memory for efficient multiple predictions.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Determine paths for large and tiny model weights</span>
</span></span><span class="line"><span class="cl">        <span class="n">large_model_path</span> <span class="o">=</span> <span class="s2">&#34;checkpoints/ddcolor_modelscope.pth&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">tiny_model_path</span>  <span class="o">=</span> <span class="s2">&#34;checkpoints/ddcolor_paper_tiny.pth&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># (Ensure the above files exist. If not, download from ModelScope or HuggingFace as described.)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Initialize pipelines for large and tiny models</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">colorizer_large</span> <span class="o">=</span> <span class="n">ImageColorizationPipeline</span><span class="p">(</span><span class="n">model_path</span><span class="o">=</span><span class="n">large_model_path</span><span class="p">,</span> <span class="n">input_size</span><span class="o">=</span><span class="mi">512</span><span class="p">,</span> <span class="n">model_size</span><span class="o">=</span><span class="s2">&#34;large&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">colorizer_tiny</span>  <span class="o">=</span> <span class="n">ImageColorizationPipeline</span><span class="p">(</span><span class="n">model_path</span><span class="o">=</span><span class="n">tiny_model_path</span><span class="p">,</span>  <span class="n">input_size</span><span class="o">=</span><span class="mi">512</span><span class="p">,</span> <span class="n">model_size</span><span class="o">=</span><span class="s2">&#34;tiny&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">predict</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">image</span><span class="p">:</span> <span class="n">Path</span> <span class="o">=</span> <span class="n">Input</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&#34;Grayscale input image.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">reference</span><span class="p">:</span> <span class="n">Path</span> <span class="o">=</span> <span class="n">Input</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&#34;Optional reference image to guide colors&#34;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">model_size</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Input</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&#34;Model size to use (large = highest quality, tiny = faster).&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;large&#34;</span><span class="p">,</span> <span class="s2">&#34;tiny&#34;</span><span class="p">],</span> <span class="n">default</span><span class="o">=</span><span class="s2">&#34;large&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Run a single prediction. Colorizes the image, optionally using a reference image for color bias.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Read input image</span>
</span></span><span class="line"><span class="cl">        <span class="n">img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">image</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">img</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;Failed to load input image&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Select model pipeline</span>
</span></span><span class="line"><span class="cl">        <span class="n">pipeline</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">colorizer_tiny</span> <span class="k">if</span> <span class="n">model_size</span> <span class="o">==</span> <span class="s2">&#34;tiny&#34;</span> <span class="k">else</span> <span class="bp">self</span><span class="o">.</span><span class="n">colorizer_large</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Colorize the image using DDColor</span>
</span></span><span class="line"><span class="cl">        <span class="n">output_bgr</span> <span class="o">=</span> <span class="n">pipeline</span><span class="o">.</span><span class="n">process</span><span class="p">(</span><span class="n">img</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># If a reference image is provided, transfer its color style to the output</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">reference</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">ref_img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">reference</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">ref_img</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;Failed to load reference image&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Only apply transfer if reference is a color image</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">ref_img</span><span class="o">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">output_bgr</span> <span class="o">=</span> <span class="n">color_transfer</span><span class="p">(</span><span class="n">ref_img</span><span class="p">,</span> <span class="n">output_bgr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Save result</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_path</span> <span class="o">=</span> <span class="s2">&#34;/tmp/out.png&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">cv2</span><span class="o">.</span><span class="n">imwrite</span><span class="p">(</span><span class="n">out_path</span><span class="p">,</span> <span class="n">output_bgr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Path</span><span class="p">(</span><span class="n">out_path</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>With this modification I can run with a new parameter, note the &ldquo;&ndash;ref&rdquo; option that exists now and place your image in the right spot:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ docker run --rm --gpus all \
  -v &#34;$PWD/input&#34;:/workspace/input_images:ro \
  -v &#34;$PWD/results&#34;:/workspace/results:rw \
  -v &#34;$PWD/ref_images&#34;:/workspace/ref_images:ro \
  -v &#34;$PWD/modelscope&#34;:/app/modelscope:ro \
  -v &#34;$PWD/infer.py&#34;:/app/infer.py:ro \
  ddcolor:latest \
  python3 /app/infer.py \
    --model_path /app/modelscope/damo/cv_ddcolor_image-colorization/pytorch_model.pt \
    --input /workspace/input_images \
    --ref /workspace/ref_images/images.jpg \
    --output /workspace/results</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>I passed in another colored reference photo but saw no difference in the result. Once again, I asked ChatGPT:</p>
<h3>Why didn&rsquo;t you notice a difference in the result?<span class="hx:absolute hx:-mt-20" id="why-didnt-you-notice-a-difference-in-the-result"></span>
    <a href="#why-didnt-you-notice-a-difference-in-the-result" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The <strong>global color transfer</strong> only changes the &ldquo;overall palette&rdquo; — and not the colors of each object.</p>
<p>If the reference image has soft tones similar to what DDColor would already apply automatically, the final effect <strong>will look identical</strong>.</p>
<p>But if you use as reference a heavily saturated image (for example, an orange sky, or a deep blue environment), you will see DDColor&rsquo;s output <strong>visually pulled</strong> toward that style.</p>
<h2>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I followed the suggestion and downloaded another totally unrelated image, colored, super saturated with vibrant colors to see if it made a difference. This is the reference image I passed to Docker:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ryptql9u8mk04o3r7ah01fk1qz5q" alt="Reference Image"  loading="lazy" /></p>
<p>And here is the new result applying it to the previous image in post-processing (this step is no longer A.I., it really is just image post-processing).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/cxcwwnj2ncszvmv4whj4qesafgnm" alt="Post-processed photo"  loading="lazy" /></p>
<p>Compare it with the other re-colorized photo, I will put it down here again so you can compare:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/o2r085pzzpsavbyr39btg1vq0hwp" alt="Repeating the first re-colorization"  loading="lazy" /></p>
<p>Got it? Now you have to keep testing with different references to see how they influence the final result, but indeed, it manages to apply the Reinhard algorithm and do a global color transfer. As the name says, it is &ldquo;global&rdquo;, not &ldquo;per object&rdquo;, so it is hard to control only parts of the image — it acts on the whole image. But in theory I think you could do a manual colorization in Photoshop with saturated colors, near where you want to influence, to try to nudge it, but I have not tried that yet. Later, if anyone does, drop it in the comments.</p>
<p>But that is it. Now at least I can have fun grabbing old family photos and re-colorizing them.</p>
]]></content:encoded><category>generative-media</category><category>containers</category><category>tutorials</category></item><item><title>[Beginner] Long live PhantomJS, let's use Chrome Headless now</title><link>https://akitaonrails.github.io/en/2017/10/31/beginner-long-live-phantomjs-let-s-use-chrome-headless-now/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2017/10/31/beginner-long-live-phantomjs-let-s-use-chrome-headless-now/</guid><pubDate>Tue, 31 Oct 2017 19:48:00 -0200</pubDate><description>&lt;p&gt;If you do Feature Specs, the act of loading up a real app server and then a real headless browser to do real user feature testing, then you know &lt;a href="https://github.com/teamcapybara/capybara/issues/1860"target="_blank" rel="noopener"&gt;Capybara&lt;/a&gt; and one of its most well-known drivers, &lt;a href="https://github.com/teampoltergeist/poltergeist/issues/882"target="_blank" rel="noopener"&gt;Poltergeist&lt;/a&gt;. Poltergeist wraps up PhantomJS, which is a well known WebKit-based headless browser.&lt;/p&gt;
&lt;p&gt;But WebKit is known for being &lt;strong&gt;very&lt;/strong&gt; complicated to deal with. So I can only imagine the nightmare to maintain PhantomJS, which is akin to maintaining a full-blown web browser like Chrome or Safari.&lt;/p&gt;</description><content:encoded><![CDATA[<p>If you do Feature Specs, the act of loading up a real app server and then a real headless browser to do real user feature testing, then you know <a href="https://github.com/teamcapybara/capybara/issues/1860"target="_blank" rel="noopener">Capybara</a> and one of its most well-known drivers, <a href="https://github.com/teampoltergeist/poltergeist/issues/882"target="_blank" rel="noopener">Poltergeist</a>. Poltergeist wraps up PhantomJS, which is a well known WebKit-based headless browser.</p>
<p>But WebKit is known for being <strong>very</strong> complicated to deal with. So I can only imagine the nightmare to maintain PhantomJS, which is akin to maintaining a full-blown web browser like Chrome or Safari.</p>
<p>So it&rsquo;s no wonder that when the Chrome team announced the availability of the <a href="https://developers.google.com/web/updates/2017/04/headless-chrome"target="_blank" rel="noopener">Chrome Driver</a>, then the maintainer of PhantomJS <a href="https://github.com/teampoltergeist/poltergeist/issues/882"target="_blank" rel="noopener">decided to step down</a>.</p>
<p>If you know the contributors of PhantomJS, say thank you, as it helped us build more solid user features.</p>
<p>That being said, fear not. You can easily replace Poltergeist/PhantomJS with Selenium WebDriver/Chrome Driver in your RSpec/Capybara setup.</p>
<p>My friend <a href="https://www.lucascaton.com.br/2017/06/22/how-to-run-your-feature-specs-using-capybara-and-headless-chrome/"target="_blank" rel="noopener">Lucas Caton</a> wrote about it in June this year. Follow his blog as well.</p>
<p>If you&rsquo;re using Linux with the Chromium browser, you don&rsquo;t need to install anything, as the Chrome Driver comes with Chromium. Otherwise, you need to install the proper packages for your operating system. For example, <code>brew install chromedriver</code> for OS X or <code>pacaur -S chromedriver</code> on Arch if you don&rsquo;t like to have Chromium around. You may need to <a href="https://askubuntu.com/questions/539498/where-does-chromedriver-install-to"target="_blank" rel="noopener">tweak your PATH on Ubuntu</a> though.</p>
<p>Rule of thumb: install Chromium.</p>
<p>In my case, this is what had to change:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"># Gemfile
</span></span><span class="line"><span class="cl"><span class="gd">- gem &#34;poltergeist&#34;
</span></span></span><span class="line"><span class="cl"><span class="gi">+ gem &#34;selenium-webdriver&#34;
</span></span></span><span class="line"><span class="cl"><span class="gi">+ gem &#34;rspec-retry&#34;
</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Then in the Capybara setup:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl">Capybara.server = :puma # Until your setup is working
</span></span><span class="line"><span class="cl">Capybara.server = :puma, { Silent: true } # To clean up your test output
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gd">- Capybara.register_driver :poltergeist do |app|
</span></span></span><span class="line"><span class="cl"><span class="gd">-   options = {
</span></span></span><span class="line"><span class="cl"><span class="gd">-     timeout: 3.minutes,
</span></span></span><span class="line"><span class="cl"><span class="gd">-     phantomjs_options: [
</span></span></span><span class="line"><span class="cl"><span class="gd">-       &#39;--proxy-type=none&#39;,
</span></span></span><span class="line"><span class="cl"><span class="gd">-       &#39;--load-images=no&#39;,
</span></span></span><span class="line"><span class="cl"><span class="gd">-       &#39;--ignore-ssl-errors=yes&#39;,
</span></span></span><span class="line"><span class="cl"><span class="gd">-       &#39;--ssl-protocol=any&#39;,
</span></span></span><span class="line"><span class="cl"><span class="gd">-       &#39;--web-security=false&#39;
</span></span></span><span class="line"><span class="cl"><span class="gd">-     ]
</span></span></span><span class="line"><span class="cl"><span class="gd">-   }
</span></span></span><span class="line"><span class="cl"><span class="gd">-   Capybara::Poltergeist::Driver.new(app, options)
</span></span></span><span class="line"><span class="cl"><span class="gd">- end
</span></span></span><span class="line"><span class="cl"><span class="gd">- Capybara.javascript_driver = :poltergeist
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gi">+ Capybara.register_driver :chrome do |app|
</span></span></span><span class="line"><span class="cl"><span class="gi">+   capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
</span></span></span><span class="line"><span class="cl"><span class="gi">+     chromeOptions: {
</span></span></span><span class="line"><span class="cl"><span class="gi">+       args: %w[ no-sandbox headless disable-popup-blocking disable-gpu window-size=1280,1024]
</span></span></span><span class="line"><span class="cl"><span class="gi">+     }
</span></span></span><span class="line"><span class="cl"><span class="gi">+   )
</span></span></span><span class="line"><span class="cl"><span class="gi">+ 
</span></span></span><span class="line"><span class="cl"><span class="gi">+   Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities)
</span></span></span><span class="line"><span class="cl"><span class="gi">+ end
</span></span></span><span class="line"><span class="cl"><span class="gi">+ 
</span></span></span><span class="line"><span class="cl"><span class="gi">+ Capybara::Screenshot.register_driver :chrome do |driver, path|
</span></span></span><span class="line"><span class="cl"><span class="gi">+   driver.save_screenshot(path)
</span></span></span><span class="line"><span class="cl"><span class="gi">+ end
</span></span></span><span class="line"><span class="cl"><span class="gi">+ 
</span></span></span><span class="line"><span class="cl"><span class="gi">+ Capybara.javascript_driver = :chrome
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Capybara.default_max_wait_time = 5 # you may want to increase this timeout if your app is heavy to load
</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In feature specs, sometimes Rails itself takes a long while to load up, compile assets, etc., and the first feature spec may time out. To avoid a failure in the test run, it&rsquo;s recommended to add the <code>rspec-retry</code> gem, as I did above, and add the following to your <code>spec/rails_helper.rb</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;rspec/retry&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">RSpec</span><span class="o">.</span><span class="n">configure</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># show retry status in spec process</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">verbose_retry</span> <span class="o">=</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Try twice (retry once)</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">default_retry_count</span> <span class="o">=</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Only retry when Selenium raises Net::ReadTimeout</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">exceptions_to_retry</span> <span class="o">=</span> <span class="o">[</span><span class="no">Net</span><span class="o">::</span><span class="no">ReadTimeout</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And that should be it. I didn&rsquo;t have to touch any of my feature specs and they all ran beautifully. So kudos to the respective teams that maintain Capybara, Selenium-WebDriver for supporting this.</p>
<p>If you&rsquo;re a Node.js developer as well, you probably used something like Casper, which is said to support Chrome Headless as well. But while we&rsquo;re at it, you should check out <a href="https://github.com/GoogleChrome/puppeteer"target="_blank" rel="noopener">Puppeteer</a> as well, from the Google team itself. It is a promise-based library where you can code like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">puppeteer</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;puppeteer&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">browser</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">puppeteer</span><span class="p">.</span><span class="nx">launch</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">page</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newPage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s1">&#39;https://example.com&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">screenshot</span><span class="p">({</span><span class="nx">path</span><span class="o">:</span> <span class="s1">&#39;example.png&#39;</span><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">})();</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>So yeah, Chrome Headless seems like a very good option as most users actually use the Chrome browser, so it means we should have more reliable feature specs and also web crawling tools.</p>
]]></content:encoded><category>rails</category><category>testing</category><category>tutorials</category></item><item><title>How NOT to zero out your Pusher development quota</title><link>https://akitaonrails.github.io/en/2017/10/27/how-not-to-zero-out-your-pusher-development-quota/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2017/10/27/how-not-to-zero-out-your-pusher-development-quota/</guid><pubDate>Fri, 27 Oct 2017 13:43:00 -0200</pubDate><description>&lt;p&gt;If you&amp;rsquo;re doing development with WebSockets (real-time notifications, real-time chat, etc), one of the best SaaS options out there is still &lt;a href="pusher.com"&gt;&lt;strong&gt;Pusher&lt;/strong&gt;&lt;/a&gt;. It was always reliable.&lt;/p&gt;
&lt;p&gt;For each application you create, it even offers you separate development, staging, and production environments, with separate key/secret tokens.&lt;/p&gt;
&lt;p&gt;One problem I stumbled upon these days is that I was quickly zeroing out the free development environment message quota (200,000 messages a day). That&amp;rsquo;s because all my team was using the same key and also the Continuous Integration server was doing live connections whenever it ran in the same environment. That can quickly consume all the quota you have and block both your development and CI.&lt;/p&gt;</description><content:encoded><![CDATA[<p>If you&rsquo;re doing development with WebSockets (real-time notifications, real-time chat, etc), one of the best SaaS options out there is still <a href="pusher.com"><strong>Pusher</strong></a>. It was always reliable.</p>
<p>For each application you create, it even offers you separate development, staging, and production environments, with separate key/secret tokens.</p>
<p>One problem I stumbled upon these days is that I was quickly zeroing out the free development environment message quota (200,000 messages a day). That&rsquo;s because all my team was using the same key and also the Continuous Integration server was doing live connections whenever it ran in the same environment. That can quickly consume all the quota you have and block both your development and CI.</p>
<p>It&rsquo;s actually not a good practice to do live connections to external systems in testing situations. The tests can fail randomly for a number of reasons, so we should always mock those. But mocking a complex system (WebSockets and HTTP) like Pusher is not trivial.</p>
<p>Fortunately, I found this <a href="https://github.com/tristandunn/pusher-fake"target="_blank" rel="noopener">Pusher Fake</a>. It basically implements all the necessary APIs and WebSocket endpoints to mimic Pusher and fool both server and js/client to communicate with it.</p>
<p>The idea is for your Rails app, in development mode, to fork a separate server process for the Pusher client to connect to. This gem is both a simple Pusher-clone server and a series of wrappers to load it up in your setup.</p>
<p>First things first.</p>
<p>In your application, you will have both a Ruby side Pusher client connection setup to push messages to a channel in the Pusher server. And a Javascript, client-side, Pusher instance mainly to subscribe to a channel in a WebSocket connection and receive messages.</p>
<p>First we need to set up the Ruby side. Usually it&rsquo;s in a <code>config/initializers/pusher.rb</code> configuration like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">app_id</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_APP&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">key</span>    <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_KEY&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">secret</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_SECRET&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">cluster</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_CLUSTER&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># never set Pusher.host or Pusher.port</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Notice that I am using environment variables to hold the configuration. You should use something like the <a href="https://github.com/laserlemon/figaro"target="_blank" rel="noopener">figaro gem</a> or the <a href="https://github.com/bkeepers/dotenv"target="_blank" rel="noopener">dotenv-rails gem</a>. For example:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">PUSHER_APP</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;xpto&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_KEY</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;abcd1234&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_SECRET</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;abcd1234&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_CLUSTER</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;us2&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>At the very least you must have an application ID, a key, a secret, and a cluster name. All of these are provided by Pusher whenever you register a new application there.</p>
<p>Second, we need to set up the Javascript instance. Usually, you have something in the <code>assets/javascripts</code> directory like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// .js.erb example
</span></span></span><span class="line"><span class="cl"><span class="nb">window</span><span class="p">.</span><span class="nx">pusher</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Pusher</span><span class="p">(</span><span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_APP] %&gt;, {
</span></span></span><span class="line"><span class="cl"><span class="s1">  cluster: &lt;%= ENV[&#39;</span><span class="nx">PUSHER_KEY</span><span class="s1">&#39;] %&gt;,
</span></span></span><span class="line"><span class="cl"><span class="s1">  encrypted: &lt;%= ENV[&#39;</span><span class="nx">PUSHER_ENCRYPTED</span><span class="err">&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">})</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In a Chrome Development Console, you can inspect this instance by typing:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Pusher.instances[0]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This way you can make sure it&rsquo;s picking up the correct configurations for the connection and also debug problems.</p>
<p>The dependencies are the pusher gem in your <code>Gemfile</code> and the javascript client.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Gemfile</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;pusher&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In the case of the javascript file you can either add it to your project and rely on <a href="http://www.akitaonrails.com/en/2017/10/24/iniciante-experimentando-o-rails-5-1-x"target="_blank" rel="noopener">Webpacker</a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yarn add pusher</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Then, in your ES6 javascript file, you can do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Pusher</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;pusher-js&#39;</span><span class="p">);</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Or you can link it directly in your layout:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://js.pusher.com/4.2/pusher.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>For more information on the Pusher-js client, read its <a href="https://github.com/pusher/pusher-js"target="_blank" rel="noopener">README file</a>.</p>
<h2>Adding Pusher Fake<span class="hx:absolute hx:-mt-20" id="adding-pusher-fake"></span>
    <a href="#adding-pusher-fake" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>At this point, you should be able to connect to the real Pusher account and see the real-time magic happening.</p>
<p>You&rsquo;re also already consuming the free quota you have available in your development environment on Pusher. For most people, this should suffice.</p>
<p>But we want to NOT connect to Pusher over the internet and keep everything local for development and testing. Let&rsquo;s start by adding the <a href="https://github.com/tristandunn/pusher-fake"target="_blank" rel="noopener">Pusher Fake</a> to our <code>Gemfile</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">group</span> <span class="ss">:development</span><span class="p">,</span> <span class="ss">:test</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;pusher-fake&#39;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, this is where the Pusher Fake setup can get convoluted if you don&rsquo;t understand what&rsquo;s happening. As I said before, PusherFake must fork a new process to load a local server that mimics Pusher.</p>
<p>To load it up you must point to the local server. Remember our <code>config/initializers/pusher.rb</code> ? We just need to require a simple file like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">app_id</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_APP&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">key</span>    <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_KEY&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">secret</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_SECRET&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">cluster</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_CLUSTER&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">development?</span>
</span></span><span class="line"><span class="cl">  <span class="nb">require</span> <span class="s2">&#34;pusher-fake/support/base&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This alone presents a LOT of problems if you&rsquo;re not careful. This <code>require</code> will <strong>fork</strong> a new process. If you&rsquo;re using a single-process web server like Webrick or Thin, you should be ok. If you&rsquo;re using Puma, Unicorn, or Passenger with a maximum of just one process, you should also be good. But if you load a web server that itself forks new processes, you will have trouble.</p>
<p>In practice, I&rsquo;d rather load the Pusher Fake server separately, in stand-alone mode. Fortunately it provides a command line command to start it up. And it&rsquo;s good practice to set that up in a <code>Procfile.dev</code> file and use <a href="https://github.com/ddollar/foreman"target="_blank" rel="noopener">foreman</a> to start everything for you. The <code>Procfile.dev</code> looks like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>web: bundle exec rails s -p 3000
db: postgres -D /usr/local/var/postgres
redis: redis-server /usr/local/etc/redis.conf
mailcatcher: mailcatcher -f
pusherfake: pusher-fake -i ${PUSHER_APP:-xpto} --socket-host 0.0.0.0 --socket-port ${PUSHER_WS_PORT:-45449} --web-host 0.0.0.0 --web-port ${PUSHER_PORT:-8888} -k ${PUSHER_KEY:-abcd1234} -s ${PUSHER_SECRET:-abcd1234}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>As a bonus, look how I configure other services such as PostgreSQL, Redis, etc.</p>
<p>If you didn&rsquo;t know, you can use <code>${VARIABLE_NAME:-default_value}</code> to use an environment variable or have a default value in case the variable doesn&rsquo;t exist. This means that your environment variable configured with Figaro or Dotenv must have the same values.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">PUSHER_APP</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;xpto&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_KEY</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;abcd1234&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_SECRET</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;abcd1234&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_CLUSTER</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;us2&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_HOST</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;127.0.0.1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_PORT</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;8888&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_WS_HOST</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;127.0.0.1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">PUSHER_WS_PORT</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;45449&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now your <code>config/initializers/pusher.rb</code> should be something like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">app_id</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_APP&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">key</span>    <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_KEY&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">secret</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_SECRET&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">cluster</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_CLUSTER&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">development?</span>
</span></span><span class="line"><span class="cl">  <span class="no">Pusher</span><span class="o">.</span><span class="n">host</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_HOST&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="no">Pusher</span><span class="o">.</span><span class="n">port</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PUSHER_PORT&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And Pusher-js config somewhere in your <code>app/assets/javascripts/</code> directory will resemble something like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="o">&lt;%</span> <span class="k">if</span> <span class="nx">defined</span><span class="o">?</span><span class="p">(</span><span class="nx">PusherFake</span><span class="p">)</span> <span class="o">%&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;%</span> <span class="k">if</span> <span class="nx">Rails</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">test</span><span class="o">?</span> <span class="o">%&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">pusher</span> <span class="o">=</span> <span class="o">&lt;%=</span> <span class="nx">PusherFake</span><span class="p">.</span><span class="nx">javascript</span><span class="p">(</span><span class="nx">cluster</span><span class="o">:</span> <span class="nx">ENV</span><span class="p">[</span><span class="s2">&#34;PUSHER_CLUSTER&#34;</span><span class="p">])</span> <span class="o">%&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;%</span> <span class="k">else</span> <span class="o">%&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">window</span><span class="p">.</span><span class="nx">pusher</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Pusher</span><span class="p">(</span><span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_KEY&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cluster</span><span class="o">:</span> <span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_CLUSTER&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">wsHost</span><span class="o">:</span> <span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_WS_HOST&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">wsPort</span><span class="o">:</span> <span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_WS_PORT&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">encrypted</span><span class="o">:</span> <span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_ENCRYPTED&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">})</span>    
</span></span><span class="line"><span class="cl">    <span class="o">&lt;%</span> <span class="nx">end</span> <span class="o">%&gt;</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;%</span> <span class="k">else</span> <span class="o">%&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">window</span><span class="p">.</span><span class="nx">pusher</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Pusher</span><span class="p">(</span><span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_KEY&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cluster</span><span class="o">:</span> <span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_CLUSTER&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">encrypted</span><span class="o">:</span> <span class="o">&lt;%=</span> <span class="nx">ENV</span><span class="p">[</span><span class="s1">&#39;PUSHER_ENCRYPTED&#39;</span><span class="p">]</span> <span class="o">%&gt;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;%</span> <span class="nx">end</span> <span class="o">%&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Remember that this is a Javascript+ERB file, so we can fetch the same environment variables for the configuration.</p>
<p>Now, whenever you <code>foreman start -f Procfile.dev -p 3000</code> it will load the Pusher Fake server with the proper development configurations and both your ruby server-side and javascript client-side should connect to it without any problems.</p>
<p>Also, notice the <code>if Rails.env.test?</code> bit. This is for your RSpec test suite. In the case of the testing environment, we won&rsquo;t load the fake server manually, instead, we will create something like <code>spec/support/pusher-fake.rb</code> with:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;pusher-fake/support/rspec&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And that&rsquo;s it. The <code>PusherFake.javascript</code> will define default WebSocket connection configuration and the <code>require</code> above will both fork the fake server and set Rspec to clean the channels on each test run (through <code>PusherFake::Channel.reset</code>).</p>
<p>This way your testing environment will also avoid connecting to the external, real, Pusher server.</p>
<p>The key to all this is the environment variables. You must make sure that every piece is loading the same configuration, otherwise you will have the fake server binding to a different port than the Pusher-js is connecting to, and you will have errors. Debug with care.</p>
<p>Most importantly: if you did it all correctly, you are now independent of the real Pusher server for development and testing environments, you won&rsquo;t ever reach any quota limits, and your team and your CI will be able to work uninterruptedly, with a deterministic behavior.</p>
]]></content:encoded><category>rails</category><category>testing</category><category>tutorials</category></item><item><title>Rails 5.1 Deployment Checklist for Heroku</title><link>https://akitaonrails.github.io/en/2017/06/28/rails-5-1-heroku-deployment-checklist-for-heroku/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2017/06/28/rails-5-1-heroku-deployment-checklist-for-heroku/</guid><pubDate>Wed, 28 Jun 2017 15:12:00 -0300</pubDate><description>&lt;p&gt;I released &lt;a href="http://www.theconf.club"target="_blank" rel="noopener"&gt;THE CONF&lt;/a&gt; yesterday. I hope you enjoy the conference program and take advantage of the limited early-bird discount!&lt;/p&gt;
&lt;p&gt;Anyway, the website itself is super simple. A single page site. I chose to use Rails 5.1 as the site structure because it takes care of all the stuff I&amp;rsquo;d have to add manually in any other framework. Especially now that it brings native Yarn and Webpack support it&amp;rsquo;s a breeze to use by any competent front-end developer.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I released <a href="http://www.theconf.club"target="_blank" rel="noopener">THE CONF</a> yesterday. I hope you enjoy the conference program and take advantage of the limited early-bird discount!</p>
<p>Anyway, the website itself is super simple. A single page site. I chose to use Rails 5.1 as the site structure because it takes care of all the stuff I&rsquo;d have to add manually in any other framework. Especially now that it brings native Yarn and Webpack support it&rsquo;s a breeze to use by any competent front-end developer.</p>
<p>But even with the many built-in niceties, a full production setup still requires extra steps that most beginners will not know. So I decided to compile a small checklist of things you must take care of before deploying to production. It&rsquo;s not an extensive and complete list for all use cases, but this should be enough for most small cases.</p>
<ul>
<li><a href="#setup">Setting up the project</a></li>
<li><a href="#cdn">Adding a CDN and configuring CORS support</a> (to be able to load fonts for example)</li>
<li><a href="#cache">Adding Memcached</a></li>
</ul>
<p><a name="setup"></a></p>
<h2>Setting up the project<span class="hx:absolute hx:-mt-20" id="setting-up-the-project"></span>
    <a href="#setting-up-the-project" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>One thing that most people forget, even experienced developers, is that the <code>rails new</code> command used to bootstrap the initial project structure accepts many option flags. Instead of having to manually tweak files later, you can start like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>rails new your_project \
--database=postgresql \
--webpack=react \
--skip-action-cable \
--skip-coffee
--skip-turbolinks</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If you&rsquo;re building with React or another full-featured javascript framework, you will probably want to skip Turbolinks. Otherwise, if it&rsquo;s a simple site, do use <a href="https://sevos.io/2017/02/27/turbolinks-lifecycle-explained.html"target="_blank" rel="noopener">Turbolinks</a>.</p>
<p>Start using PostgreSQL from the get-go, don&rsquo;t use Sqlite3.</p>
<p>Skip Action Cable. Prefer a real solution such as <a href="https://pusher.com/"target="_blank" rel="noopener">Pusher.com</a>. If you really need something done in-house, consider something like my solution with Elixir, the <a href="http://expusherlite.cm42.io/"target="_blank" rel="noopener">Ex Pusher Lite</a>. Take this recommendation with a grain of salt, of course, for small things Action Cable is more than enough. I may write another post just elaborating on this point if people indicate they want to in the comments section.</p>
<p>Anyway, I digress. Make sure you have 2 boot files, first the canonical <code>Procfile</code> to be used by Heroku in production:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>web: bin/rails server -p $PORT -b 0.0.0.0</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Second, a <code>Procfile.dev</code> to be used only in your development environment:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>web: ./bin/rails server
webpacker: ./bin/webpack-dev-server</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This is how you fire up the webpack server that will compile your assets in real-time during development. You need to also remember to run these two dependency commands now:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yarn install
bundle install</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Install javascript dependencies with <code>yarn add [package]</code> and that&rsquo;s it! In production you don&rsquo;t use the webpack server (which is why we don&rsquo;t add it to the production <code>Procfile</code>), instead Heroku automatically detects the <a href="https://github.com/rails/webpacker"target="_blank" rel="noopener">webpacker</a> gem then it installs the nodejs buildpack, runs <code>yarn install</code> for you and when <code>rails assets:precompile</code> runs it will also execute <code>yarn run</code> which will pre-compile all the assets (javascript, stylesheets, images) with the proper fingerprinting for cache busting and everything else we are used to in the normal Rails Asset Pipeline.</p>
<p>So, for Heroku, you basically don&rsquo;t have to do anything. And in a custom deployment server you just need to remember to run the <code>assets:precompile</code> task and have it do everything for you.</p>
<p><a name="cdn"></a></p>
<h3>Adding a CDN and configuring CORS<span class="hx:absolute hx:-mt-20" id="adding-a-cdn-and-configuring-cors"></span>
    <a href="#adding-a-cdn-and-configuring-cors" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>This is really super easy. There is no reason why anyone wouldn&rsquo;t add a CDN to any website. Please just do it.</p>
<p>Open your AWS Management Console and <a href="https://console.aws.amazon.com/cloudfront/"target="_blank" rel="noopener">open CloudFront</a>. From there click on &ldquo;Create Distribution&rdquo; and just follow the defaults in the Wizard. The requirement is that you MUST know the domain and sub-domain you want to point to (for example &ldquo;<a href="https://www.theconf.club"target="_blank" rel="noopener">www.theconf.club</a>&rdquo;) in the &ldquo;Origin Domain Name&rdquo; field.</p>
<p>The only customization you MUST do is to change the <a href="http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-cors"target="_blank" rel="noopener">&ldquo;Forward Headers&rdquo;</a> option to &ldquo;Whitelist&rdquo; in the &ldquo;Default Cache Behavior Settings&rdquo;. Then you need to add &ldquo;Origin&rdquo;, &ldquo;Access-Control-Request-Headers&rdquo;, and &ldquo;Access-Control-Request-Method&rdquo; to the Whitelist. And that&rsquo;s it, now your distribution is CORS enabled.</p>
<p>It will take some time to create (it has to configure many data centers around the world), but you will end up with a URL representing your distribution, something like <code>doz7rtw2u3wg4.cloudfront.net</code>. I recommend you add it as a Heroku environment variable like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>heroku config:set CDN_URL=doz7rtw2u3wg4.cloudfront.net</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, edit your <code>config/environments/production.rb</code> and add the following:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">action_controller</span><span class="o">.</span><span class="n">asset_host</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;CDN_URL&#39;</span><span class="o">]</span> <span class="k">if</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;CDN_URL&#39;</span><span class="o">]</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>To actually use the CDN you must declare every asset you use across your view templates using Rails View Helpers such as <code>image_tag</code>, <code>asset_path</code>, <code>javascript_pack_tag</code>, <code>stylesheet_pack_tag</code>, <code>stylesheet_link_tag</code>, etc. The Rails bootstrap already creates layout templates with such helpers, you just need to follow them.</p>
<p>When webpack runs, it will generate all static, optimized and pre-compiled assets in <code>public/packs</code> with a manifest file declaring the full URL pointing to the CDN. For example, if I fetch the <code>/app/public/packs/manifest.json</code> from the Heroku dyno directly, I will get something like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{
  &#34;Roboto-Bold.woff&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/Roboto-Bold-eed9aab5449cc9c8430d7d258108f602.woff&#34;,
  &#34;Roboto-Bold.woff2&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/Roboto-Bold-c0f1e4a4fdfb8048c72e86aadb2a247d.woff2&#34;,
  &#34;Roboto-Light.woff&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/Roboto-Light-ea36cd9a0e9eee97012a67b8a4570d7b.woff&#34;,
  &#34;Roboto-Light.woff2&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/Roboto-Light-3c37aa69cd77e6a53a067170fa8fe2e9.woff2&#34;,
  &#34;Roboto-Medium.woff&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/Roboto-Medium-cf4d60bc0b1d4b2314085919a00e1724.woff&#34;,
  &#34;Roboto-Medium.woff2&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/Roboto-Medium-1561b424aaef2f704bbd89155b3ce514.woff2&#34;,
  &#34;Roboto-Regular.woff&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/Roboto-Regular-3cf6adf61054c328b1b0ddcd8f9ce24d.woff&#34;,
  &#34;Roboto-Regular.woff2&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/Roboto-Regular-5136cbe62a63604402f2fedb97f246f8.woff2&#34;,
  &#34;Roboto-Thin.woff&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/Roboto-Thin-44b78f142603eb69f593ed4002ed7a4a.woff&#34;,
  &#34;Roboto-Thin.woff2&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/Roboto-Thin-1f35e6a11d27d2e10d28946d42332dc5.woff2&#34;,
  &#34;application.css&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/application-09b53ce9ca3dd595ee99.css&#34;,
  &#34;application.css.map&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/application-09b53ce9ca3dd595ee99.css.map&#34;,
  &#34;application.js&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/application-799300612ff6d6595198.js&#34;,
  &#34;application.js.map&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/application-799300612ff6d6595198.js.map&#34;,
  &#34;home_page.js&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/home_page-ff3b49407a1d01592ad5.js&#34;,
  &#34;home_page.js.map&#34;: &#34;//doz7rtw2u3wg4.cloudfront.net/packs/home_page-ff3b49407a1d01592ad5.js.map&#34;
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>So, if for some reason I had to create a new CDN distribution, you have to remember to update the <code>CDN_URL</code> variable on Heroku and redeploy your app so it regenerates the assets and this manifest file. It will just use those URLs when rendering the final HTML.</p>
<p>When a user opens your site, it will receive the HTML with URLs pointing to the CDN. The very first time it won&rsquo;t have any cached assets, so it will ask your site for them. Your site must return the assets WITH the correct CORS headers so the CDN caches them with the headers and forwards those headers to the browser. The browser needs those headers because it will open from domain <code>www.theconf.club</code>, for example, but fonts are loading from <code>doz7rtw2u3wg4.cloudfront.net</code>, so it would raise a security warning and not load the fonts because they are in different domains. Hence the CORS headers the font provides, indicating that they are safe to load.</p>
<p>For your Rails app to return the proper headers, you need to add the <code>rack-cors</code> gem to your Gemfile. Then you must add a new configuration file at <code>config/initializers/rack-cors.rb</code> with:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">if</span> <span class="n">defined?</span> <span class="no">Rack</span><span class="o">::</span><span class="no">Cors</span>
</span></span><span class="line"><span class="cl">  <span class="no">Rails</span><span class="o">.</span><span class="n">configuration</span><span class="o">.</span><span class="n">middleware</span><span class="o">.</span><span class="n">insert_before</span> <span class="mi">0</span><span class="p">,</span> <span class="no">Rack</span><span class="o">::</span><span class="no">Cors</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">allow</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">origins</span> <span class="sx">%w[
</span></span></span><span class="line"><span class="cl"><span class="sx">        https://theconf.club
</span></span></span><span class="line"><span class="cl"><span class="sx">        http://theconf.club
</span></span></span><span class="line"><span class="cl"><span class="sx">        https://www.theconf.club
</span></span></span><span class="line"><span class="cl"><span class="sx">        http://www.theconf.club
</span></span></span><span class="line"><span class="cl"><span class="sx">        https://theconf.herokuapp.com
</span></span></span><span class="line"><span class="cl"><span class="sx">        http://theconf.herokuapp.com
</span></span></span><span class="line"><span class="cl"><span class="sx">      ]</span>
</span></span><span class="line"><span class="cl">      <span class="n">resource</span> <span class="s1">&#39;/assets/*&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>When you deploy, you know it&rsquo;s working correctly when you Curl an asset and it returns the Access-* headers like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ curl -I -s -X GET -H &#34;Origin: www.theconf.club&#34; http://www.theconf.club/packs/Roboto-Regular-5136cbe62a63604402f2fedb97f246f8.woff2
HTTP/1.1 200 OK
Server: Cowboy
Date: Wed, 28 Jun 2017 17:44:41 GMT
Connection: keep-alive
Access-Control-Allow-Origin: www.theconf.club
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Expose-Headers: 
Access-Control-Max-Age: 1728000
Access-Control-Allow-Credentials: true
Last-Modified: Wed, 28 Jun 2017 17:06:03 GMT
Content-Type: application/font-woff2
Cache-Control: public, max-age=2592000
Vary: Origin
Content-Length: 64832
Via: 1.1 vegur</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And if everything above is already in place, you should be able to see the headers being forwarded through the CDN, like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ curl -I -s -X GET -H &#34;Origin: www.theconf.club&#34; http://doz7rtw2u3wg4.cloudfront.net/packs/Roboto-Regular-5136cbe62a63604402f2fedb97f246f8.woff2
HTTP/1.1 200 OK
Content-Type: application/font-woff2
Content-Length: 64832
Connection: keep-alive
Server: Cowboy
Date: Wed, 28 Jun 2017 17:45:18 GMT
Access-Control-Allow-Origin: www.theconf.club
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Expose-Headers: 
Access-Control-Max-Age: 1728000
Access-Control-Allow-Credentials: true
Last-Modified: Wed, 28 Jun 2017 17:06:03 GMT
Cache-Control: public, max-age=2592000
Via: 1.1 vegur, 1.1 86e9abdb4c15b9d3a542f9b93245e87e.cloudfront.net (CloudFront)
Vary: Origin
X-Cache: Miss from cloudfront
X-Amz-Cf-Id: tVkZ41RRr66iBT6atWTO_oeTY_jG0zCBFuXU8bKyClZDQ8kl-hDegA==</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A CDN is the secret sauce that allows any content-based website to scale way beyond what your server can provide. It&rsquo;s a huge cost-saving and it also makes for a much smoother user experience for your users.</p>
<p>One last caveat. The many Rails View Helpers such as <code>image_tag</code> allow you to add the image file name without an extension in development and they will correctly find the image. But on the server it will fail to render the template this way. As a rule of thumb <strong>ALWAYS</strong> fill in the entire filename and extension, for example <code>image_tag(&quot;logo.png&quot;)</code> instead of just <code>image_tag(&quot;logo&quot;)</code>.</p>
<p>You can see how this fails if you open a console in Heroku and check out how it fails to derive the full image URL:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ heroku run rails c                                                                                                                  
Running rails c on ⬢ theconf... up, run.8271 (Hobby)
Loading production environment (Rails 5.1.2)

irb(main):001:0&gt; ActionController::Base.helpers.asset_path(&#34;icon-goals&#34;)
Sprockets::Rails::Helper::AssetNotFound: The asset &#34;icon-goals&#34; is not present in the asset pipeline.
    from (irb):1

irb(main):002:0&gt; ActionController::Base.helpers.asset_path(&#34;icon-goals.png&#34;)
=&gt; &#34;//d134ipy19a646x.cloudfront.net/assets/icon-goals-b969b3b7325d33ad85a88dbb5b894832909ed738eea9964b9cf535646b93674b.png&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><a name="cache"></a></p>
<h3>Adding Memcached<span class="hx:absolute hx:-mt-20" id="adding-memcached"></span>
    <a href="#adding-memcached" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Talking about caching is always a complicated thing, which is why many people avoid even trying. Even though you can go really crazy with super granular configurations such as using <a href="http://edgeguides.rubyonrails.org/caching_with_rails.html#russian-doll-caching"target="_blank" rel="noopener">Russian Doll Caching</a>, just adding cache in a few spots can greatly benefit you. And it&rsquo;s super easy to boot.</p>
<p>The first thing to do is add <a href="https://devcenter.heroku.com/articles/memcachier"target="_blank" rel="noopener">Memcachier</a> to your Heroku application. It has a free-tier and for most small apps it should be enough.</p>
<p>Configuration is also trivial. Start by adding the following gems to your <code>Gemfile</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">group</span> <span class="ss">:production</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;rack-cors&#39;</span><span class="p">,</span> <span class="nb">require</span><span class="p">:</span> <span class="s1">&#39;rack/cors&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;rack-cache&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;dalli&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;kgio&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;memcachier&#39;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now you must edit your <code>config/environments/production.rb</code> like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">cache_store</span> <span class="o">=</span> <span class="ss">:dalli_store</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">client</span> <span class="o">=</span> <span class="no">Dalli</span><span class="o">::</span><span class="no">Client</span><span class="o">.</span><span class="n">new</span><span class="p">((</span><span class="no">ENV</span><span class="o">[</span><span class="s2">&#34;MEMCACHIER_SERVERS&#34;</span><span class="o">]</span> <span class="o">||</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;,&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                           <span class="ss">:username</span> <span class="o">=&gt;</span> <span class="no">ENV</span><span class="o">[</span><span class="s2">&#34;MEMCACHIER_USERNAME&#34;</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                           <span class="ss">:password</span> <span class="o">=&gt;</span> <span class="no">ENV</span><span class="o">[</span><span class="s2">&#34;MEMCACHIER_PASSWORD&#34;</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                           <span class="ss">:failover</span> <span class="o">=&gt;</span> <span class="kp">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                           <span class="ss">:socket_timeout</span> <span class="o">=&gt;</span> <span class="mi">1</span><span class="o">.</span><span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                           <span class="ss">:socket_failure_delay</span> <span class="o">=&gt;</span> <span class="mi">0</span><span class="o">.</span><span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                           <span class="ss">:value_max_bytes</span> <span class="o">=&gt;</span> <span class="mi">10485760</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">action_dispatch</span><span class="o">.</span><span class="n">rack_cache</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:metastore</span>    <span class="o">=&gt;</span> <span class="n">client</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:entitystore</span>  <span class="o">=&gt;</span> <span class="n">client</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, let&rsquo;s say you have a block in your template that requires a bunch of records from your database. But you know that those records barely change. What can you do? One alternative is to cache the ActiveRecord query entirely like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">HomePageController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@selected_proposals</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">cache</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="s1">&#39;selected_proposals&#39;</span><span class="p">,</span> <span class="ss">expires_in</span><span class="p">:</span> <span class="mi">1</span><span class="o">.</span><span class="n">day</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Proposal</span><span class="o">.</span><span class="n">includes</span><span class="p">(</span><span class="ss">:authors</span><span class="p">)</span><span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="ss">selected</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span><span class="o">.</span><span class="n">to_a</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The <code>#to_a</code> is necessary because ActiveRecord queries are lazy. The <code>#to_a</code> forces it to fetch and it will be cached. Next time, it will not touch the database for an entire day!</p>
<p>I could also have added a <code>cache do ... end</code> block in the template itself. There are many options. Point is that it&rsquo;s not as difficult as most people would think.</p>
<p>What makes caching difficult is adding expiration logic. And the rule of thumb is: never try to manually expire any cache. Just change the lookup key for something else and let the old data just die (memcached will take care of getting rid of unused old data).</p>
<p>You really want to read the <a href="http://edgeguides.rubyonrails.org/caching_with_rails.html"target="_blank" rel="noopener">Rails Guides on Caching</a>. It really is not as difficult as you think and you can cache only the few snippets you know are performance-sensitive and leave the other parts that are highly dynamic and you&rsquo;re unsure how to properly cache.</p>
<p>But as it&rsquo;s super cheap, just use it right now.</p>
<p><a name="ssl"></a></p>
<h3>Adding SSL support<span class="hx:absolute hx:-mt-20" id="adding-ssl-support"></span>
    <a href="#adding-ssl-support" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>If you have any security-sensitive transactions going on (ex. purchases) you want to use SSL. Again, many people avoid it because it&rsquo;s usually difficult to even understand how to properly get a certificate.</p>
<p><a href="https://letsencrypt.org/"target="_blank" rel="noopener">Let&rsquo;s Encrypt</a> made the process super trivial. Kudos to them! And even better: it&rsquo;s free! So, you don&rsquo;t have any excuses to not have SSL.</p>
<p>And over Heroku, it&rsquo;s <a href="https://devcenter.heroku.com/articles/automated-certificate-management"target="_blank" rel="noopener">even easier</a>!</p>
<blockquote>
  <p>ACM (Automated Certificate Management) is enabled by default for all applications created after March 21, 2017 that are running on Hobby or Professional dynos. The following steps are for applications currently running on Free dynos, and for applications created before that date.</p>

</blockquote>
<p>On the Free Tier Dynos, this is what you do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>heroku certs:auto:enable</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Check status with <code>heroku certs:auto</code>.</p>
<p>Done, there is no step 2.</p>
<p>We used to have to use the complicated gem <a href="https://github.com/pixielabs/letsencrypt-rails-heroku"target="_blank" rel="noopener">letsencrypt-rails-heroku</a>, but now it&rsquo;s just too easy.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I believe this covers the very basics of stuff you should be doing before deploying your small app to Heroku. For larger apps there are many more concerns that are beyond the scope of this post.</p>
<p>If you remember about more tips and tricks, please share in the comments section below.</p>
]]></content:encoded><category>rails</category><category>devops</category><category>tutorials</category></item><item><title>Starting with Blender</title><link>https://akitaonrails.github.io/en/2017/06/21/starting-with-blender/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2017/06/21/starting-with-blender/</guid><pubDate>Wed, 21 Jun 2017 17:32:00 -0300</pubDate><description>&lt;p&gt;Blender is a beast. A true marvel of what open source technology can achieve, and you should applaud everyone involved in making this thing work as well as it does. It rivals the most expensive commercial options out there, from Maya to the venerable Pixar&amp;rsquo;s Renderman.&lt;/p&gt;
&lt;p&gt;The Blender community is so passionate and committed that they frequently produce high quality, almost Hollywood grade &lt;a href="https://web.archive.org/web/20151127160645/http://archive.blender.org/features-gallery/movies/"target="_blank" rel="noopener"&gt;short movies&lt;/a&gt; inside Blender. The point is to stress test the tool and flush out bottlenecks and usability issues in a real world workflow.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Blender is a beast. A true marvel of what open source technology can achieve, and you should applaud everyone involved in making this thing work as well as it does. It rivals the most expensive commercial options out there, from Maya to the venerable Pixar&rsquo;s Renderman.</p>
<p>The Blender community is so passionate and committed that they frequently produce high quality, almost Hollywood grade <a href="https://web.archive.org/web/20151127160645/http://archive.blender.org/features-gallery/movies/"target="_blank" rel="noopener">short movies</a> inside Blender. The point is to stress test the tool and flush out bottlenecks and usability issues in a real world workflow.</p>
<p>This is primarily a post for &ldquo;future me&rdquo; to jump back into a single resource list. Being a 3D modeler is not my full time job, so I&rsquo;ll have large gaps between Blender sessions. And I know I&rsquo;ll regret it if I don&rsquo;t dump my brain into a post while it&rsquo;s still fresh :-)</p>
<h3>Beginner First Steps<span class="hx:absolute hx:-mt-20" id="beginner-first-steps"></span>
    <a href="#beginner-first-steps" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>First things first. If you&rsquo;re in this blog, you&rsquo;re probably a programmer. And let me tell you that the Graphics folks see &ldquo;usability&rdquo; very differently from what we, programmers, usually think of. The sheer amount of customization, options, shortcuts and combinations will rival even Vim and Emacs users out there.</p>
<p>Oh, by the way, you&rsquo;re going to need a real 3-button mouse. Touchpads are useless with Blender, and the Mac mouse is terrible. <a href="http://www.dell.com/br/mouse"target="_blank" rel="noopener">Any cheap PC mouse will do.</a></p>
<p>Out of the box, the main button is the right one, not the left one we&rsquo;re used to. Change that in the <a href="https://web.archive.org/web/20181118204135/https://docs.blender.org/manual/en/dev/preferences/input.html"target="_blank" rel="noopener">user preferences</a> to select with Left, and things start to make sense. While you&rsquo;re there, enable Numpad emulation too.</p>
<p>By the way, it&rsquo;s well worth having a keyboard with a numpad, or even a separate numpad. You can use the number row on top of your normal keyboard, but Blender was built assuming both an inverted 3-button mouse and a numpad.</p>
<p><a href="https://web.archive.org/web/20181118204135/https://docs.blender.org/manual/en/dev/preferences/input.html"target="_blank" rel="noopener"><img src="https://web.archive.org/web/20181119110123/https://docs.blender.org/manual/en/dev/_images/preferences_input_tab.png" alt="Input User Preferences"  loading="lazy" /></a></p>
<p>If you have never studied Blender before, you will not figure things out just by randomly exploring the UI. The UI is useless until someone teaches you the ins and outs. There are hundreds of terms you just have no idea what they mean, such as Meshes, Edges, Seams, Nodes, Viewport, Subsurface, Modifiers, etc.</p>
<p>The main thing you MUST do before proceeding is watching this entire 9 part series from Blender Guru, called <a href="https://www.youtube.com/watch?v=VT5oZndzj68&amp;list=PLjEaoINr3zgHs8uzT3yqe4iHGfkCmMJ0P"target="_blank" rel="noopener">Blender Beginner Tutorial Series</a>. Optionally you can slowly do the <a href="https://www.youtube.com/watch?v=Mwzz-Y6t-v8&amp;list=PLjEaoINr3zgEgoyYWE0Yit-cVoZ60WGtt"target="_blank" rel="noopener">Intermediate Blender Tutorials</a> later.</p>
<p>The Beginner Series will teach you enough that you&rsquo;ll finally start feeling confident with the UI and tools. And don&rsquo;t forget to print and hang this handy <a href="https://web.archive.org/web/20170708042817/https://www.blenderguru.com/articles/free-blender-keyboard-shortcut-pdf"target="_blank" rel="noopener">&ldquo;cheat sheet&rdquo;</a>. You won&rsquo;t believe how much easier your life becomes once you get used to the most important keyboard shortcuts.</p>
<h3>Better Defaults<span class="hx:absolute hx:-mt-20" id="better-defaults"></span>
    <a href="#better-defaults" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>For historical reasons, some things are not how they should be. Knowledge in the 3D rendering field is evolving very fast.</p>
<p>The very first thing to do: <a href="https://web.archive.org/web/20160308012834/http://wiki.blender.org/index.php/Doc:2.6/Tutorials/Rendering/Cycles"target="_blank" rel="noopener">change</a> the default render engine from Blender Render to the Cycles Raytracing Engine.</p>
<p>Then, color grading. The default sRGB EOTF is basically wrong. You must download Sobotka&rsquo;s <a href="https://sobotka.github.io/filmic-blender/"target="_blank" rel="noopener">filmic-blender</a> configuration. If you&rsquo;re on Arch Linux you can basically do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>pacaur -S filmic-blender-git</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>I created a script at <code>~/bin/filmic-blender</code> with this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>env OCIO=/usr/share/blender/2.78/datafiles/filmic-blender/config.ocio blender</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And I always start Blender from the terminal like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>optirun ~/bin/filmic-blender</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This does 2 things: first, it pre-configures OpenColorIO to use the Filmic replacement. Second, it enables the external GPU of my notebook to be available to Blender. Read my post on <a href="http://www.akitaonrails.com/en/2017/03/14/enabling-optimus-nvidia-gpu-on-the-dell-xps-15-with-linux-even-on-battery"target="_blank" rel="noopener">&ldquo;Enabling Optimus NVIDIA GPU on the Dell XPS 15 with Linux, even on Battery&rdquo;</a> for more details. On Windows or Mac this is not necessary, but you&rsquo;ll still need to load the filmic configuration.</p>
<p>Then, on the Scene tab you must reconfigure &ldquo;Color Management&rdquo; to be like this:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/636/Screenshot_from_2017-06-21_16-31-05.png" alt="Color Management"  loading="lazy" /></p>
<p>Then you need to configure Cycles. If you&rsquo;re on Linux and Optimus is correctly installed as I explained before, you should have the CUDA option enabled in the User Preferences:</p>
<p><a href="https://web.archive.org/web/20181118204134/https://docs.blender.org/manual/en/dev/preferences/system.html"target="_blank" rel="noopener"><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/637/Screenshot_from_2017-06-21_16-43-29.png" alt="System preferences"  loading="lazy" /></a></p>
<p>In the Render tab, you should have something like this:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/638/Screenshot_from_2017-06-21_16-45-32.png" alt="Render configuration"  loading="lazy" /></p>
<p>In the Dimensions section, you&rsquo;ll notice it has the Full HD (1920x1080 px) size but only 50% (so it will only render half the size). Increase it to 100%. If you want a 4K shot, increase the dimensions to 3840x2160 px. 4K makes it 4 times slower to render than 1080p, obviously.</p>
<p>In the Performance section you&rsquo;ll see 2 input fields for &ldquo;Tile size&rdquo;. Blender will slice your full image into tiles, and each tile renders in parallel on an available CPU or GPU core. My notebook has 8 CPU cores, so 64 is a good size, because it will render 8 tiles of 64 pixels each in parallel.</p>
<p>The fewer cores you have, the larger you should make the tile sizes. For my NVIDIA GPU, I only have 2 available cores (and not a lot of video memory either!), so it&rsquo;s worth bumping both fields to 512. On the GPU it only renders 2 tiles at once, and larger tiles optimize the render.</p>
<p>As you probably guessed, Blender Guru has a very useful <a href="https://www.youtube.com/watch?v=8gSyEpt4-60&amp;t=204s"target="_blank" rel="noopener">&ldquo;18 ways to speed up Cycles Rendering&rdquo;</a> tutorial.</p>
<p>These should cover the basic defaults.</p>
<h3>You must think as a Photographer!<span class="hx:absolute hx:-mt-20" id="you-must-think-as-a-photographer"></span>
    <a href="#you-must-think-as-a-photographer" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>You will want to watch A LOT of online tutorials, because it&rsquo;s really not obvious how to best use the tools. Another channel I really like is <a href="https://www.youtube.com/watch?v=lY6KPrc4uMw&amp;list=PLda3VoSoc_TR7X7wfblBGiRz-bvhKpGkS"target="_blank" rel="noopener">BornCG</a> and <a href="https://www.youtube.com/channel/UCCxay0KiyLlawfgoZ2mVnNQ"target="_blank" rel="noopener">CG Masters</a>. Pick a few of their videos to get a more in-depth view on specific tools, modeling techniques and so on.</p>
<p>And really, you must practice as much as possible while studying a lot along the way. One important area is Photography. This is what you see when you select the Camera tab:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/639/Screenshot_from_2017-06-21_16-55-09.png" alt="Camera settings"  loading="lazy" /></p>
<p>There&rsquo;s a lot of customization you can do here. For example: Focal length: 35.00, which is what photographers know as a 35mm lens, a good default choice. You can also use a really wide angle such as 200mm or 300mm, and indoor shots, in small rooms, could use a 24mm or even 18mm lens. This article <a href="http://www.cambridgeincolour.com/tutorials/camera-lenses.htm"target="_blank" rel="noopener">&ldquo;Understanding Color Lenses&rdquo;</a> should give you the basics.</p>
<p>Then, you must understand &ldquo;Depth of Field&rdquo;. This can be done at this configuration before rendering, or you can simulate it after rendering (if you chose to separate the render in layers), in the <a href="https://web.archive.org/web/20160308021547/http://wiki.blender.org/index.php/Doc:2.6/Tutorials/Composite_Nodes/Setups/Depth_Of_Field"target="_blank" rel="noopener">Compositor</a>.</p>
<p>Speaking of which, another way to control Depth of Field is reconfiguring &ldquo;f-stop&rdquo;, which is the measurement of exposure, or aperture. The default is &ldquo;128.0&rdquo;, which is &ldquo;f/128&rdquo;. As a reference, your iPhone 7s camera has an f/2.2 aperture. Again, let&rsquo;s study more about this starting with the article <a href="https://www.bhphotovideo.com/explora/photography/tips-and-solutions/what%E2%80%99s-best-f-stop"target="_blank" rel="noopener">&ldquo;What&rsquo;s the Best F-Stop?&rdquo;</a></p>
<p>Taking a photo (or rendering a still, in our case) is a whole lot more than just point-and-click. You also have to worry about proper composition techniques such as the <a href="http://www.photographymad.com/pages/view/rule-of-thirds"target="_blank" rel="noopener">Rule of Thirds</a>, the <a href="http://www.makeuseof.com/tag/golden-ratio-photography/"target="_blank" rel="noopener">golden ratio</a>, and so on. You can change that in the &ldquo;Composition Guides&rdquo; combo box as shown above.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/641/3911f4657078a19b4f3677a304e7451d.jpg" alt="Golden Ratio Composition"  loading="lazy" /></p>
<p>You really should dive deep into the field of Photography to improve the final result of the renders. I&rsquo;m an amateur and it&rsquo;s really exciting to be able to apply real world techniques to 3D rendering.</p>
<h3>Material Design and Physically-Based Render (PBR)<span class="hx:absolute hx:-mt-20" id="material-design-and-physically-based-render-pbr"></span>
    <a href="#material-design-and-physically-based-render-pbr" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The golden standard in 3D modeling and rendering is certainly <a href="https://renderman.pixar.com/view/renderman"target="_blank" rel="noopener">Disney/Pixar RenderMan</a>. Every award winning Pixar movie was made with it.</p>
<p>But Blender learns fast. Every Pixar paper eventually becomes part of Blender itself. Material design, for example, has always been quite cumbersome in the tool. I did some of the tutorials myself, and creating materials with the proper real world characteristics, like proper Fresnel and the right dielectric versus metallic distinction, was a challenge.</p>
<p>If you subscribed to Blender Guru&rsquo;s channel you should really watch the tutorials <a href="https://www.youtube.com/watch?v=V3wghbZ-Vh4&amp;t=2668s"target="_blank" rel="noopener">&ldquo;How to Make Photorealistic PBR Materials - Part 1&rdquo;</a> and <a href="https://www.youtube.com/watch?v=m1PkSViBi-M"target="_blank" rel="noopener">&ldquo;Part 2&rdquo;</a>. And you will end up with this complicated Nodes configuration for PBR materials:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/640/Screenshot_from_2017-06-21_17-25-46.png" alt="PBR Materials - Node Editor"  loading="lazy" /></p>
<p>And Blender Guru just posted a new video introducing a feature for the upcoming Blender 2.79 (currently at 2.78): the implementation of the <a href="https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf"target="_blank" rel="noopener">&ldquo;Physically-Based Shader at Disney&rdquo;</a> paper as a proper and optimized new Blender Shader named &ldquo;Principled Shader&rdquo;. It&rsquo;s quite literally the <a href="https://www.youtube.com/watch?v=4H5W6C_Mbck"target="_blank" rel="noopener">Ultimate Shader</a>, because it makes creating and customizing realistic materials <strong>very</strong> easy, and compatible with Renderman and Substance.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I am still a beginner at Blender, there is a very long road to walk here. But it&rsquo;s a very exciting environment and I am learning tons of new and useful stuff all the time. Anyone should try it!</p>
<p>Over time I hope I find the time to integrate the Blender workflow with other tools such as Unreal Engine or Unity3D to create interactive stuff as well.</p>
<p>This is by no means a complete tutorial or reference. The idea was to highlight a few things that are not immediately obvious when you start with Blender and that can give you a broader sense of what Blender can do.</p>
<p>If you want to go really in-depth in the customization, watch this CG Master setup video:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/-_BZasG_UDA"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

]]></content:encoded><category>tutorials</category><category>learning</category><category>audio-and-video</category><category>off-topic</category></item><item><title>Is your Rails app ready for Production?</title><link>https://akitaonrails.github.io/en/2016/03/22/is-your-rails-app-ready-for-production/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2016/03/22/is-your-rails-app-ready-for-production/</guid><pubDate>Tue, 22 Mar 2016 15:11:00 -0300</pubDate><description>&lt;p&gt;This is a checklist. You should follow these instructions if you want some peace of mind after deploying an application to production. If you&amp;rsquo;re experienced, please contribute in the comments section below. But if you&amp;rsquo;re a beginner, you should pay attention.&lt;/p&gt;
&lt;h3&gt;Where to Deploy? TL;DR: Heroku&lt;span class="hx:absolute hx:-mt-20" id="where-to-deploy-tldr-heroku"&gt;&lt;/span&gt;
&lt;a href="#where-to-deploy-tldr-heroku" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Many people will first target the cheap choices. Usually EC2 micro instances or small Digital Ocean droplets. Unless you&amp;rsquo;re aware that you &lt;strong&gt;must&lt;/strong&gt; constantly monitor and upgrade those instances, &lt;strong&gt;DON&amp;rsquo;T&lt;/strong&gt; do it.&lt;/p&gt;</description><content:encoded><![CDATA[<p>This is a checklist. You should follow these instructions if you want some peace of mind after deploying an application to production. If you&rsquo;re experienced, please contribute in the comments section below. But if you&rsquo;re a beginner, you should pay attention.</p>
<h3>Where to Deploy? TL;DR: Heroku<span class="hx:absolute hx:-mt-20" id="where-to-deploy-tldr-heroku"></span>
    <a href="#where-to-deploy-tldr-heroku" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Many people will first target the cheap choices. Usually EC2 micro instances or small Digital Ocean droplets. Unless you&rsquo;re aware that you <strong>must</strong> constantly monitor and upgrade those instances, <strong>DON&rsquo;T</strong> do it.</p>
<p>Instead choose a PaaS solution such as Heroku. It&rsquo;s a no-brainer. Software in production is not a one-off thing. For security alone you should not do it by yourself unless you know how to harden an OS distribution. For example, most people don&rsquo;t even know what <a href="http://www.fail2ban.org/wiki/index.php/Main_Page"target="_blank" rel="noopener">fail2ban</a> is. Don&rsquo;t assume that because you use SSH with keypairs you&rsquo;re secure. You&rsquo;re not.</p>
<p>For example, did you know that we just went through a <a href="https://blog.heroku.com/archives/2016/3/21/patching-glibc-security-hole"target="_blank" rel="noopener">glibc security crisis</a>? If you don&rsquo;t and you haven&rsquo;t touched your production servers in some time to upgrade all your components, you&rsquo;re open, wide open. I had many clients who hired freelancers to implement and deploy their apps and then went without anyone checking. You&rsquo;re all unsafe.</p>
<p>Heroku is no guarantee either, because app libraries get old, vulnerabilities get discovered, and you should upgrade them. Rails has an <a href="https://groups.google.com/forum/#!forum/rubyonrails-security"target="_blank" rel="noopener">active security team</a> releasing security patches all the time. If you haven&rsquo;t been upgrading, you&rsquo;re unsafe.</p>
<p>But 24x7 support is not cheap, if you have your own infrastructure or use an IaaS platform such as AWS EC2, you should have 24x7 personnel, this means at least 2 full-time system administrators/devops taking care of things. Or, you use a PaaS that at least keeps those basic sanitations in check and a part-time developer to at least keep upgrading your apps.</p>
<p>If you don&rsquo;t, you&rsquo;re opening your business to serious liability. And if you&rsquo;re a developer, do the responsible thing: disclose this fact to your clients and let them make an informed choice. They can choose to stay cheap, but at least they know what this actually means.</p>
<h3>Which web server? TL;DR: Passenger or Puma<span class="hx:absolute hx:-mt-20" id="which-web-server-tldr-passenger-or-puma"></span>
    <a href="#which-web-server-tldr-passenger-or-puma" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Most Rails apps lack basic libraries that should be installed to make them work correctly.</p>
<p>I&rsquo;ve seen people running production apps by just firing up <tt>rails server</tt> and nothing else! This is running the basic Webrick web server, in production! You must choose between Puma, Unicorn, Passenger. Unicorn actually does not protect your app against slow clients, so avoid it if you can.</p>
<p>If you&rsquo;re in doubt, <a href="https://www.phusionpassenger.com/library/walkthroughs/deploy/ruby/heroku/standalone/oss/deploy_app_main.html"target="_blank" rel="noopener">install Passenger</a> and call it a day.</p>
<p>The most common choice nowadays is <a href="https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server"target="_blank" rel="noopener">using Puma</a>. But if you&rsquo;re running under Heroku or using small instances, keep in mind your RAM usage. A normal Rails application consumes at least 150Mb to 200Mb or more depending on the size of your app. The smallest Heroku dyno has only 512Mb of RAM, that means you should not configure Puma or Unicorn to have more than 2 instances. I&rsquo;d recommend going for the 2X Dyno (1GB) and have at most 3 instances (which means 3 possible concurrent requests being processed at once).</p>
<p>It&rsquo;s not uncommon for your app to keep increasing RAM usage over time and once every couple of days you see R14 happening, then your dyno reboots and it starts working better for a while until it reaches the ceiling again.</p>
<p>If you don&rsquo;t know why that&rsquo;s happening, and it&rsquo;s infrequent, you can temporarily use <a href="https://github.com/schneems/puma_worker_killer"target="_blank" rel="noopener">Puma Worker Killer</a> - if you&rsquo;re using Puma, of course - to keep monitoring your instances until they reach a certain lower ceiling and they automatically recycle before your users see the R14 errors.</p>
<p>You may also have heard of <a href="http://tmm1.net/ruby21-oobgc"target="_blank" rel="noopener">Out-of-Band GC</a>. Just <a href="https://github.com/puma/puma/issues/450"target="_blank" rel="noopener">be warned</a> that Puma in threaded mode won&rsquo;t work well with it. If you&rsquo;re using Unicorn, by all means do use it.</p>
<h3>Measure, Measure, Measure. TL;DR: New Relic<span class="hx:absolute hx:-mt-20" id="measure-measure-measure-tldr-new-relic"></span>
    <a href="#measure-measure-measure-tldr-new-relic" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/535/big_heroku-metrics.png" alt="Heroku Metrics"  loading="lazy" /></p>
<p>You should <strong>always</strong> monitor your app. Be it with custom installed software such as Nagios, Munin, Zabbix, Zenoss, Ganglia, etc or at the very least the metrics that Heroku provides you, as in the image above.</p>
<p>If your instances are behaving strangely, you should take a look at the warnings. A frequent set of <a href="https://devcenter.heroku.com/articles/ruby-memory-use"target="_blank" rel="noopener">R14</a> errors means that your app&rsquo;s memory consumption is growing over time and blowing the maximum amount of RAM available in your instance (a probable memory leaking). At the very least this means that you should decrease the number of concurrency/workers in your Puma/Unicorn and also increase the number of dynos to compensate if you do have enough traffic to justify it. But this is one metrics-oriented action that you can take because you have the information to make an informed decision.</p>
<p>But you should also be storing and indexing your logs using custom installed software such as Greylog2 or Logstash or using a SaaS alternative such as <a href="https://elements.heroku.com/addons/papertrail"target="_blank" rel="noopener">Papertrail</a>. There are many reasons why an app might be leaking, or why everything seems fine in the metrics but you&rsquo;re having random errors. Always query the logs for answers.</p>
<p>Better yet, <a href="https://docs.newrelic.com/docs/agents/ruby-agent/miscellaneous/ruby-agent-heroku"target="_blank" rel="noopener">install New Relic APM</a>. You will have both the metrics, the automated alerts, the errors measurement and really deep diagnostics to see exactly what line in your source code is guilty for your poor performance or frequent errors or erratic behavior. If you&rsquo;re in doubt, just install it, the basic plan is free anyway.</p>
<h3>Some Rails Dependencies<span class="hx:absolute hx:-mt-20" id="some-rails-dependencies"></span>
    <a href="#some-rails-dependencies" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>You should <strong>NOT</strong> be using Rails below version 4.0 by now. 3.2 is still widely used but it will stop being supported soon. Do not use Rails 5.0 right now unless you&rsquo;re experienced enough to know what to do. Anything from 4.0 to 4.2 should be ok, but be aware of the minor releases that fix security vulnerabilities.</p>
<p>You should <strong>NOT</strong> be using Ruby below version 2.2 by now. Security upgrades for 2.0 ended this February 2016. 2.1 was not a good stable release, but 2.2 and 2.3 are very good and you should be using them.</p>
<p>So the combination of Ruby 2.3.0 and Rails 4.2.5.2 (by March 2016) is the optimal choice for best stability, security and ecosystem support.</p>
<p>You should have at least the following dependencies installed by default:</p>
<ul>
<li><a href="https://github.com/heroku/rails_12factor"target="_blank" rel="noopener">Rails 12 Factor gem</a></li>
<li><a href="http://rtomayko.github.io/rack-cache/"target="_blank" rel="noopener">Rack-Cache middleware</a>
<ul>
<li><a href="https://devcenter.heroku.com/articles/memcachier#rails-3-and-4"target="_blank" rel="noopener">Dalli gem with Memcached or Memcachier</a> - even if you&rsquo;re not using fragment caching or AR caching, this is still useful at least for the <a href="https://devcenter.heroku.com/articles/rack-cache-memcached-rails31"target="_blank" rel="noopener">Rack-Cache gem</a>.</li>
</ul>
</li>
<li><a href="https://github.com/kickstarter/rack-attack"target="_blank" rel="noopener">Rack-Attack</a> - read how to properly configure!</li>
<li><a href="http://www.sinatrarb.com/rack-protection/"target="_blank" rel="noopener">Rack-Protection</a> - read how to properly configure and what protections you want to activate, not everything, but at least CSRF and XSS.</li>
<li><a href="http://www.schneems.com/blogs/2016-02-18-speeding-up-sprockets/"target="_blank" rel="noopener">Sprockets 3.3+</a> - make sure you&rsquo;re using the Sprockets version that fixes the slow speeds and makes it reasonably faster</li>
</ul>
<h3>Which Database? TL;DR: use Postgresql<span class="hx:absolute hx:-mt-20" id="which-database-tldr-use-postgresql"></span>
    <a href="#which-database-tldr-use-postgresql" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>Do NOT</strong> choose MongoDB or similar unless you have a very strong reason to and you are confident that you&rsquo;re very good at what you&rsquo;re doing and you will be sticking around to maintain what you deploy. Otherwise stick to Postgresql and the &ldquo;pg&rdquo; gem, you won&rsquo;t regret it, nor will your client.</p>
<p>Do use small Redis instances (particularly SaaS options such as <a href="https://elements.heroku.com/addons/heroku-redis"target="_blank" rel="noopener">Heroku Redis</a> ) with <a href="https://github.com/mperham/sidekiq/wiki/Deployment"target="_blank" rel="noopener">Sidekiq</a>. You can choose to use the lighter Sucker Punch but be aware that it will increase the RAM usage of your instances considerably depending on the size of your queues and the serialized input data for the workers. It&rsquo;s not always trivial how many Sidekiq workers you should have, but <a href="http://manuel.manuelles.nl/sidekiq-heroku-redis-calc/"target="_blank" rel="noopener">this calculator</a> could help.</p>
<p>There are at least 2 big mistakes most people make using Sidekiq:</p>
<ul>
<li>Having heavy workers hitting your master database. For example a big analytics procedure fetching large sets of data while your main website also has heavy user activity writing data all the time. Definitely add a <a href="https://devcenter.heroku.com/articles/heroku-postgres-follower-databases"target="_blank" rel="noopener">Follower database</a> and point the Sidekiq workers (read-only) there for analytics, reports and similar workloads.</li>
<li>Fetching large datasets in-memory and blowing your worker dynos. If you do anything similar to &ldquo;YourModel.all.each { |r| &hellip; }&rdquo; you&rsquo;re shooting yourself in the foot. Rails has easy ways to <a href="http://api.rubyonrails.org/classes/ActiveRecord/Batches.html"target="_blank" rel="noopener">fetch small sets in batch</a> for you to process, use them.</li>
</ul>
<h3>Are you using a CDN? TL;DR: just do<span class="hx:absolute hx:-mt-20" id="are-you-using-a-cdn-tldr-just-do"></span>
    <a href="#are-you-using-a-cdn-tldr-just-do" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>This is easy, use <a href="https://devcenter.heroku.com/articles/using-amazon-cloudfront-cdn"target="_blank" rel="noopener">AWS CloudFront</a> or <a href="https://devcenter.heroku.com/articles/fastly"target="_blank" rel="noopener">Fastly</a> or something else, but <strong>USE</strong> a CDN. It&rsquo;s a no-brainer.</p>
<p>Otherwise your assets will be served by your already small web dyno instances. And this is heavy lifting that&rsquo;s better served from a much faster CDN. Your users will perceive orders of magnitude in usability by changing just a couple of configuration lines in your code.</p>
<p>If you implemented your Rails views, templates, stylesheets using the &ldquo;image_tag&rdquo;, &ldquo;asset_path&rdquo; helpers, it&rsquo;s just automatic. Just do it already.</p>
<p>And while we&rsquo;re on the subject of assets, if you happen to have image uploading and you&rsquo;re using the simple way of posting a multipart form directly to your Rails app, you will have trouble because Heroku imposes - a correct - maximum of 30 seconds of timeout. If you have many users uploading many high-resolution and heavy sized images, they will see increasing amounts of timeouts.</p>
<p>Use the <a href="http://cloudinary.com/documentation/rails_additional_topics"target="_blank" rel="noopener">Attachinary gem</a> to direct upload via Javascript, from the browser, to the Cloudinary service, completely bypassing your Rails app, which will just receive the ID once the upload finishes. Again, there is a very generous free plan and it&rsquo;s so simple to implement that you must do it right away.</p>
<h3>You laid off your developer, what to do? TL;DR: revoke access!<span class="hx:absolute hx:-mt-20" id="you-laid-off-your-developer-what-to-do-tldr-revoke-access"></span>
    <a href="#you-laid-off-your-developer-what-to-do-tldr-revoke-access" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>This is something that happens a lot: a small company needs to cut costs and lay off developers.</p>
<p>Make sure you revoke access. In AWS it&rsquo;s a lot more convoluted if you don&rsquo;t know your way through their IAM authorization tool. Or worse: if they have the private keys (.pem files) to your EC2 instances!! You will have to create new keypairs, deactivate the old EC2 instances and create new ones. A royal PITA.</p>
<p>You must change passwords to your DNS service, Github/Bitbucket organization, remove SSH keys from the .ssh/authorized_keys files in all your servers, etc.</p>
<p>Make sure your company has good legal support and make every collaborator, employee or 3rd party, sign a non-disclosure agreement, non-compete or confidentiality agreements.</p>
<p>You <strong>MUST</strong> have at least one key person in the company that has control over the keys, passwords and access to every service your company depends on to run. Otherwise don&rsquo;t be surprised when your app goes offline.</p>
<p>And don&rsquo;t fool yourself into thinking that just because an app is online and running it will keep that way for long. Security vulnerabilities are in the wild, it&rsquo;s just a matter of time until some glitch puts your service down and you better have someone to put it back online for you if you don&rsquo;t know how to do it.</p>
<h3>Big Site? Congratulations, what to do now?<span class="hx:absolute hx:-mt-20" id="big-site-congratulations-what-to-do-now"></span>
    <a href="#big-site-congratulations-what-to-do-now" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>If your business is thriving, blooming, congratulations. You&rsquo;re in the very very small percentage that actually made it.</p>
<p>Soon your brand new app will become that ugly, dreaded <strong>Legacy</strong> system. Worse, it&rsquo;s probably one of those feared <strong>monoliths</strong> or even worse, you let a hipster developer create a spaghetti architecture full of unchecked <strong>microservices</strong>. You&rsquo;re doomed either way.</p>
<p>This situation requires you to be grounded: there is no silver bullet, just business as usual.</p>
<p>You must keep ahead of the game, and the monitoring part I mentioned plays a big part to steer you through the many possible directions you can go. For example, for a short period of time you can increase the plans of your SaaS services such as databases, caches, queues, and also add more parallel web dynos.</p>
<p>While that will hold for a while (your metrics can help you estimate for how long), now you need to come up with a plan to optimize what you have. And no, rewriting everything is the very last thing you want to do, unless you intend to not have a life for the next couple of years.</p>
<p>Trust Pareto: in real life, 80% of the main problems can be solved with 20% of the effort. The main big tip: open New Relic - now loaded with real production data - and check the top worst performers and start from there. The first one you solve (be it an ugly N+1 query to your database, slow web service integration), will give you a lot of room to breathe.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>What I just said is nothing but the tip of the iceberg in terms of Rails performance optimization. And I can assure you that there is a lot of room to improve without having to resort to rewriting everything in the next hip language out there. I recommend you buy Nate Berkopec&rsquo;s <a href="https://www.railsspeed.com/"target="_blank" rel="noopener">&ldquo;The Complete Guide to Rails Performance&rdquo;</a> to really learn your turf.</p>
<p>This is a very quick compilation that I just dumped out of my mind, but there is much more that we can consider. Shoot questions in the comments section and other important items that I may have overlooked.</p>
]]></content:encoded><category>tutorials</category><category>devops</category><category>rails</category></item><item><title>Elixir 101 - Introducing the Syntax</title><link>https://akitaonrails.github.io/en/2015/11/25/elixir-101-introducing-the-syntax/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2015/11/25/elixir-101-introducing-the-syntax/</guid><pubDate>Wed, 25 Nov 2015 17:25:00 -0200</pubDate><description>&lt;p&gt;I&amp;rsquo;ve been posting a lot of articles in the last few weeks, check out the &lt;a href="http://www.akitaonrails.com/elixir"target="_blank" rel="noopener"&gt;&amp;ldquo;Elixir&amp;rdquo; tag&lt;/a&gt; to read all of them.&lt;/p&gt;
&lt;p&gt;Many tutorial series start introducing a new language by its syntax. I subverted the order. Elixir is not interesting because of its syntax. Erlang is interesting all by itself, because of its very mature, highly reliable, highly concurrent, distributed nature. But its syntax is not for the faint of heart. It&amp;rsquo;s not &amp;ldquo;ugly&amp;rdquo;, it&amp;rsquo;s just too different for those of us from the C school to easily digest. It derives from Prolog, and this is one small example of a Prolog exercise:&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;ve been posting a lot of articles in the last few weeks, check out the <a href="http://www.akitaonrails.com/elixir"target="_blank" rel="noopener">&ldquo;Elixir&rdquo; tag</a> to read all of them.</p>
<p>Many tutorial series start introducing a new language by its syntax. I subverted the order. Elixir is not interesting because of its syntax. Erlang is interesting all by itself, because of its very mature, highly reliable, highly concurrent, distributed nature. But its syntax is not for the faint of heart. It&rsquo;s not &ldquo;ugly&rdquo;, it&rsquo;s just too different for those of us from the C school to easily digest. It derives from Prolog, and this is one small example of a Prolog exercise:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-prolog" data-lang="prolog"><span class="line"><span class="cl"><span class="c1">% P03 (*): Find the K&#39;th element of a list.</span>
</span></span><span class="line"><span class="cl"><span class="c1">%     The first element in the list is number 1.</span>
</span></span><span class="line"><span class="cl"><span class="c1">%     Example:</span>
</span></span><span class="line"><span class="cl"><span class="c1">%     ?- element_at(X,[a,b,c,d,e],3).</span>
</span></span><span class="line"><span class="cl"><span class="c1">%     X = c</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">% The first element in the list is number 1.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">% element_at(X,L,K) :- X is the K&#39;th element of the list L</span>
</span></span><span class="line"><span class="cl"><span class="c1">%    (element,list,integer) (?,?,+)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">% Note: nth1(?Index, ?List, ?Elem) is predefined</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">element_at</span><span class="p">(</span><span class="nv">X</span><span class="p">,[</span><span class="nv">X</span><span class="p">|</span><span class="k">_</span><span class="p">],</span><span class="mi">1</span><span class="p">).</span>
</span></span><span class="line"><span class="cl"><span class="nf">element_at</span><span class="p">(</span><span class="nv">X</span><span class="p">,[</span><span class="k">_</span><span class="p">|</span><span class="nv">L</span><span class="p">],</span><span class="nv">K</span><span class="p">)</span> <span class="p">:-</span> <span class="nv">K</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">K1</span> <span class="o">is</span> <span class="nv">K</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="nf">element_at</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span><span class="nv">L</span><span class="p">,</span><span class="nv">K1</span><span class="p">).</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Erlang has a similar syntax, with the idea of phrases divided by commas and ending with a dot.</p>
<p>José Valim played it very smart: he chose the best of the available mature platforms and coated it with a layer of modern syntax and easier-to-use standard libraries. This is the same problem implemented in Elixir:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Exercise</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">element_at</span><span class="p">(</span><span class="o">[</span><span class="n">found</span><span class="o">|</span><span class="n">_</span><span class="o">]</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">found</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">element_at</span><span class="p">(</span><span class="o">[</span><span class="n">_</span><span class="o">|</span><span class="n">rest</span><span class="o">]</span><span class="p">,</span> <span class="n">position</span><span class="p">)</span> <span class="k">when</span> <span class="n">position</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">element_at</span><span class="p">(</span><span class="n">rest</span><span class="p">,</span> <span class="n">position</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If I copy and paste the code above in an IEx shell I can test it out like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(7)&gt; Exercise.element_at([&#34;a&#34;, &#34;b&#34;, &#34;c&#34;, &#34;d&#34;, &#34;e&#34;], 3)
&#34;c&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This simple exercise shows us some of the powerful bits of Erlang that Elixir capitalizes upon, such as pattern matching and recursion.</p>
<p>First of all, every function <strong>must</strong> be defined inside a module, which you name with <tt>defmodule My.Module do .. end</tt>. Internally it becomes the atom &ldquo;Elixir.My.Module&rdquo;. Nesting modules is just a longer name concatenated with dots.</p>
<p>Then you can define a public function with the <tt>def my_function(args) do .. end</tt> block, which is just a macro for the same <tt>def my_function(args), do: &hellip;</tt> construct. Private functions are declared with <tt>defp</tt>.</p>
<p>A function is actually identified by the pair of its name and its arity. So above we have <tt>element_at/2</tt> which means it accepts 2 arguments. But we have 2 functions with the same arity: the difference is the <strong>pattern matching</strong>.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">element_at</span><span class="p">(</span><span class="o">[</span><span class="n">found</span><span class="o">|</span><span class="n">_</span><span class="o">]</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">found</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Here we are saying: the first argument will be an array, decompose it. The first element of the array will be stored in the &ldquo;found&rdquo; variable, the rest &ldquo;_&rdquo; will be ignored. And the second argument must be the number &ldquo;1&rdquo;. This is the description of the so-called &ldquo;pattern&rdquo;, and it should &ldquo;match&rdquo; the input arguments received. This is <strong>&ldquo;call-by-pattern&rdquo;</strong> semantics.</p>
<p>But what if we want to pass a position other than &ldquo;1&rdquo;? That&rsquo;s why we have this second definition:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>def element_at([_|rest], position) when position &gt; 1 do</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, the first argument again <strong>needs</strong> to be an array, but this time we don&rsquo;t care about the first element, just the rest of the array without the first element. And any position other than &ldquo;1&rdquo; will be stored in the &ldquo;position&rdquo; variable.</p>
<p>But this function is special: it is <strong>guarded</strong> to only allow a <tt>position</tt> that is larger than 1. What if we try a negative position?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(8)&gt; Exercise.element_at([&#34;a&#34;, &#34;b&#34;, &#34;c&#34;, &#34;d&#34;, &#34;e&#34;], -3)
** (FunctionClauseError) no function clause matching in Exercise.element_at/2
    iex:7: Exercise.element_at([&#34;a&#34;, &#34;b&#34;, &#34;c&#34;, &#34;d&#34;, &#34;e&#34;], -3)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It says that none of the arguments we passed match any of the clauses defined above. We could have added a third definition just to catch those cases:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">element_at</span><span class="p">(</span><span class="n">_list</span><span class="p">,</span> <span class="n">_position</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="kp">nil</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Adding the underscore &ldquo;_&rdquo; before the variable name is the same as having just the underscore, but we are naming it just to make it more readable. Any arguments passed will just be ignored. And this is the more generic case if the previous 2 don&rsquo;t match.</p>
<p>The previous line is the same as writing:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>def element_at(_list, _position) do
  nil
end</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>I won&rsquo;t dive into macros for now. Just know that there is more than one way of doing things in Elixir and you can define those different ways using Erlang&rsquo;s built-in support for macros, dynamic code that is compiled at runtime. It&rsquo;s the way of doing metaprogramming in Elixir.</p>
<p>Now, going back to the implementation, the first function can still look weird; let&rsquo;s review it:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">element_at</span><span class="p">(</span><span class="o">[</span><span class="n">_</span><span class="o">|</span><span class="n">rest</span><span class="o">]</span><span class="p">,</span> <span class="n">position</span><span class="p">)</span> <span class="k">when</span> <span class="n">position</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">element_at</span><span class="p">(</span><span class="n">rest</span><span class="p">,</span> <span class="n">position</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>What happens is: when we call <tt>Exercise.element_at([&ldquo;a&rdquo;, &ldquo;b&rdquo;, &ldquo;c&rdquo;, &ldquo;d&rdquo;, &ldquo;e&rdquo;], 3)</tt> the first argument will pattern match with <tt>[_|rest]</tt>. The first element &ldquo;a&rdquo; is discarded and the new list <tt>[&ldquo;b&rdquo;, &ldquo;c&rdquo;, &ldquo;d&rdquo;, &ldquo;e&rdquo;]</tt> is stored as &ldquo;<tt>rest</tt>&rdquo;.</p>
<p>Finally, we recurse the call, decrementing the &ldquo;<tt>position</tt>&rdquo; variable. So it becomes <tt>element_at([&ldquo;b&rdquo;, &ldquo;c&rdquo;, &ldquo;d&rdquo;, &ldquo;e&rdquo;], 2)</tt>. And it repeats until position becomes &ldquo;1&rdquo;, in which case the pattern matching falls to the other function defined as:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>def element_at([found|_], 1), do: found</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In this case the rest of the array is pattern matched and the first element, &ldquo;c&rdquo;, is stored in the &ldquo;<tt>found</tt>&rdquo; variable; the rest of the array is discarded. It only got here because the position matched as &ldquo;1&rdquo;, so it just returns the variable &ldquo;found&rdquo;, which contains the 3rd element of the original array, &ldquo;c&rdquo;.</p>
<p>This is all nice and fancy, but in Elixir we could have just done this other version:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Exercise</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">element_at</span><span class="p">(</span><span class="n">list</span><span class="p">,</span> <span class="n">position</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="no">Enum</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="n">list</span><span class="p">,</span> <span class="n">position</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And we are done! Several tutorials will talk about how recursion and pattern matching to decompose lists solve a lot of problems, but Elixir gives us the convenience of treating lists as Enumerables and provides us a rich <a href="http://elixir-lang.org/docs/stable/elixir/Enum.html"target="_blank" rel="noopener">Enum</a> module with very useful functions such as <tt>at/2</tt>, <tt>each/2</tt>, <tt>take/2</tt>, and so on. Just pick what you need and you&rsquo;re managing lists like a boss.</p>
<p>Oh, and by the way, there is something called a <a href="http://elixir-lang.org/getting-started/sigils.html"target="_blank" rel="noopener">Sigil</a> in Elixir. Instead of writing the List of Strings explicitly, we could have done it like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(8)&gt; ~w(a b c d e f)
[&#34;a&#34;, &#34;b&#34;, &#34;c&#34;, &#34;d&#34;, &#34;e&#34;, &#34;f&#34;]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Or, if we wanted a List of Atoms, we could do it like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(9)&gt; ~w(a b c d e f)a
[:a, :b, :c, :d, :e, :f]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Lists, Tuples and Keyword Lists<span class="hx:absolute hx:-mt-20" id="lists-tuples-and-keyword-lists"></span>
    <a href="#lists-tuples-and-keyword-lists" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Well, this was too simple. You really need the idea of pattern matching and basic types in your mind to make it flow. Let&rsquo;s get another snippet from the <a href="http://www.akitaonrails.com/en/2015/11/18/ex-manga-downloadr-um-exercicio-com-elixir"target="_blank" rel="noopener">Ex Manga Downloadr</a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">parse_args</span><span class="p">(</span><span class="n">args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">parse</span> <span class="o">=</span> <span class="no">OptionParser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">switches</span><span class="p">:</span> <span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="ss">:string</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">aliases</span><span class="p">:</span> <span class="o">[</span><span class="ss">n</span><span class="p">:</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">u</span><span class="p">:</span> <span class="ss">:url</span><span class="p">,</span> <span class="ss">d</span><span class="p">:</span> <span class="ss">:directory</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">case</span> <span class="n">parse</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="p">}</span> <span class="o">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="ss">:help</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The first part may puzzle you:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">OptionParser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">switches</span><span class="p">:</span> <span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="ss">:string</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">aliases</span><span class="p">:</span> <span class="o">[</span><span class="ss">n</span><span class="p">:</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">u</span><span class="p">:</span> <span class="ss">:url</span><span class="p">,</span> <span class="ss">d</span><span class="p">:</span> <span class="ss">:directory</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The <tt>OptionParser.parse/2</tt> receives just 2 arguments: 2 arrays. If you come from Ruby it feels like it&rsquo;s a Hash with optional brackets, translating to something similar to this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># this is wrong</span>
</span></span><span class="line"><span class="cl"><span class="no">OptionParser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="ss">switches</span><span class="p">:</span> <span class="p">{</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="ss">:string</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="ss">aliases</span><span class="p">:</span> <span class="p">{</span><span class="ss">n</span><span class="p">:</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">u</span><span class="p">:</span> <span class="ss">:url</span><span class="p">,</span> <span class="ss">d</span><span class="p">:</span> <span class="ss">:directory</span><span class="p">}</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This works in Ruby, but it is not the case in Elixir. There are optional brackets, but not where you think they are:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># this is the correct, more explicit version</span>
</span></span><span class="line"><span class="cl"><span class="no">OptionParser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="ss">:switches</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="o">[</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span><span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span><span class="p">},</span> <span class="p">{</span><span class="ss">:url</span><span class="p">,</span> <span class="ss">:string</span><span class="p">},</span> <span class="p">{</span><span class="ss">:directory</span><span class="p">,</span> <span class="ss">:string</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="o">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="ss">aliases</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="o">[</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span><span class="ss">:n</span><span class="p">,</span> <span class="ss">:name</span><span class="p">},</span> <span class="p">{</span><span class="ss">:u</span><span class="p">,</span> <span class="ss">:url</span><span class="p">},</span> <span class="p">{</span><span class="ss">:d</span><span class="p">,</span> <span class="ss">:directory</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="o">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>WHAT!?!?</p>
<p>Yep, the second argument is actually an array with elements that are <strong>Tuples</strong> paired with an atom key and value, and some of the values are themselves arrays with tuples.</p>
<ol>
<li>
<p>in Elixir, Lists are what we usually call an Array, a Linked-List of elements. Linked-Lists, as you know from your Computer Science classes, make it easy to insert and remove elements.</p>
</li>
<li>
<p>in Elixir, Tuples are immutable fixed lists with fixed positions, with elements delimited by the brackets &ldquo;{}&rdquo;</p>
</li>
</ol>
<p>If the previous example was just too much, let&rsquo;s step back a little:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Teste</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">teste</span><span class="p">(</span><span class="n">opts</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span><span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="n">world</span><span class="p">},</span> <span class="p">{</span><span class="ss">:foo</span><span class="p">,</span> <span class="n">bar</span><span class="p">}</span><span class="o">]</span> <span class="o">=</span> <span class="n">opts</span>
</span></span><span class="line"><span class="cl">    <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">world</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">bar</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now we can call it like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(13)&gt; Teste.teste hello: &#34;world&#34;, foo: &#34;bar&#34;
world bar</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Which is the same as calling like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(14)&gt; Teste.teste([{:hello, &#34;world&#34;}, {:foo, &#34;bar&#34;}])
world bar</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This may confuse you, but it&rsquo;s very intuitive. You can just think of this combination of Lists (&quot;[]&quot;) with Tuple elements containing a pair of atom and value (&quot;{:key, value}&quot;) as behaving almost like Ruby Hashes being used for optional named arguments.</p>
<p>Then, we have the Pattern Match section in both previous examples:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">case</span> <span class="n">parse</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="n">process</span><span class="p">(</span><span class="ss">:help</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">[</span><span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="n">world</span><span class="p">},</span> <span class="p">{</span><span class="ss">:foo</span><span class="p">,</span> <span class="n">bar</span><span class="p">}</span><span class="o">]</span> <span class="o">=</span> <span class="n">opts</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The last example is just decomposition. The previous example is pattern matching and decomposition. You match based on the atoms and positions within the tuples within the list. You match from the more narrow case to the more generic case. And in the process, the variables in the pattern are available for you to use in the matching case clause.</p>
<p>Let&rsquo;s understand the meaning of this line:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It is saying: given the results of the <tt>OptionParser.parse/2</tt> function, it must be a tuple with 3 elements. The second and third elements don&rsquo;t matter. But the first element must be a List with at least 3 tuples. And the keys of each tuple must be the atoms <tt>:name</tt>, <tt>:url</tt>, and <tt>:directory</tt>. If they&rsquo;re there, store the values of each tuple in the variables <tt>manga_name</tt>, <tt>url</tt>, and <tt>directory</tt>, respectively.</p>
<p>This may really confuse you in the beginning, but this combination of a List of Tuples is what&rsquo;s called a <a href="http://elixir-lang.org/getting-started/maps-and-dicts.html#keyword-lists"target="_blank" rel="noopener"><strong>Keyword List</strong></a>, and you will find this pattern many times, so get used to it.</p>
<p>Keyword Lists feel like a Map, but a Map has a different syntax:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">list</span> <span class="o">=</span> <span class="o">[</span><span class="ss">a</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="ss">b</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="ss">c</span><span class="p">:</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">map</span> <span class="o">=</span> <span class="sx">%{:a =&gt; 1, :b =&gt; 2, :c =&gt; 3}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This should summarize it:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(1)&gt; list = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]
iex(2)&gt; map = %{:a =&gt; 1, :b =&gt; 2, :c =&gt; 3}
%{a: 1, b: 2, c: 3}

iex(3)&gt; list[:a]
1
iex(4)&gt; map[:a]
1

iex(5)&gt; list.a
** (ArgumentError) argument error
    :erlang.apply([a: 1, b: 2, c: 3], :a, [])
iex(5)&gt; map.a
1

iex(6)&gt; list2 = [{:a, 1}, {:b, 2}, {:c, 3}]
[a: 1, b: 2, c: 3]
iex(7)&gt; list = list2
[a: 1, b: 2, c: 3]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Keyword Lists are convenient as function arguments or return values. But if you want to process a collection of key-value pairs, use a dictionary-like structure, in this case a Map, especially if you need to search the collection using the key. They look similar, but the internal structures are not the same. A Keyword List is not a Map; it&rsquo;s just a convenience for a static list of tuples.</p>
<p>Finally, if this pattern matches the <tt>parse</tt> variable passed to the <tt>case</tt> block, it executes the statement <tt>process(manga_name, url, directory)</tt>, passing the 3 variables captured in the match. Otherwise it proceeds to try the next pattern in the <tt>case</tt> block.</p>
<p>The idea is that the &ldquo;=&rdquo; operator is not an &ldquo;assignment&rdquo;, it&rsquo;s a matcher: you match one side with the other. Read the error message when a pattern is not matched:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(15)&gt; [a, b, c] = 1
** (MatchError) no match of right hand side value: 1</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This is a matching error, not an assignment error. But if it succeeds this is what we have:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(15)&gt; [a, b, c] = [1, 2, 3]
[1, 2, 3]
iex(16)&gt; a
1
iex(17)&gt; c
3</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This is List decomposition. It so happens that in the simple case it feels like a variable assignment, but it&rsquo;s much more complex than that.</p>
<h3>Pipelines<span class="hx:absolute hx:-mt-20" id="pipelines"></span>
    <a href="#pipelines" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>We use exactly those concepts of pattern matching on the elements returned from the HTML parsed by Floki in my Manga Downloadr:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s2">&#34;#listing a&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="s2">&#34;href&#34;</span><span class="p">,</span> <span class="n">url</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">url</span> <span class="k">end</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The <tt>find/2</tt> takes an HTML string from the fetched page and matches against the CSS selectors in the second argument. The result is a List of Tuples representing the structure of each HTML Node found, in this case, this pattern: <tt>{&ldquo;a&rdquo;, [{&ldquo;href&rdquo;, url}], _}</tt></p>
<p>We can then <tt>Enum.map/2</tt>. A map is a function that receives each element of a list and returns a new list with new elements. The first argument is the original list and the second argument is a function that receives each element and returns a new one.</p>
<p>One of the main features of the Elixir language that most languages don&rsquo;t have is the <strong>Pipe</strong> operator (&quot;|&gt;&quot;). It behaves almost like UNIX&rsquo;s pipe operator &ldquo;|&rdquo; in any shell.</p>
<p>In UNIX we usually do stuff like &ldquo;<tt>ps -ef | grep PROCESS | grep -v grep | awk &lsquo;{print $2}&rsquo; | xargs kill -9</tt>&rdquo;</p>
<p>This is essentially the same as doing:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ps -ef &gt; /tmp/ps.txt
grep mix /tmp/ps.txt &gt; /tmp/grep.txt
grep -v grep /tmp/grep.txt &gt; /tmp/grep2.txt
awk &#39;{print $2}&#39; /tmp/grep2.txt &gt; /tmp/awk.txt
xargs kill -9 &lt; /tmp/awk.txt</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Each UNIX process can receive something from standard input (STDIN) and output something to standard output (STDOUT). We can redirect the output using &ldquo;&gt;&rdquo;. But instead of doing all those extra steps and creating all those extra garbage temporary files, we can simply &ldquo;pipe&rdquo; the STDOUT of one command to the STDIN of the next command.</p>
<p>Elixir uses the same principles: we can simply use the returning value of a function as the <strong>first argument</strong> of the next function. So the first example of this section is the same as doing this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">results</span> <span class="o">=</span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s2">&#34;#listing a&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">results</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="s2">&#34;href&#34;</span><span class="p">,</span> <span class="n">url</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">url</span> <span class="k">end</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In the same ExMangaDownloadr project we have this snippet:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">File</span><span class="o">.</span><span class="n">mkdir_p!</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">process_downloads</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">optimize_images</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">compile_pdfs</span><span class="p">(</span><span class="n">manga_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">finish_process</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And we just learned that it&rsquo;s the equivalent of doing the following (I&rsquo;m cheating a bit because the 3 final functions of the workflow are not transforming the input &ldquo;directory&rdquo;, just passing it through):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">File</span><span class="o">.</span><span class="n">mkdir_p!</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">chapters</span>  <span class="o">=</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">pages</span>     <span class="o">=</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span><span class="p">(</span><span class="n">chapters</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">sources</span>   <span class="o">=</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</span><span class="p">(</span><span class="n">pages</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Workflow</span><span class="o">.</span><span class="n">process_downloads</span><span class="p">(</span><span class="n">sources</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Workflow</span><span class="o">.</span><span class="n">optimize_images</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Workflow</span><span class="o">.</span><span class="n">compile_pdfs</span><span class="p">(</span><span class="n">directory</span><span class="p">,</span> <span class="n">manga_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">finish_process</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Or this much uglier version that we must read in reverse:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">File</span><span class="o">.</span><span class="n">mkdir_p!</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">finish_process</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="no">Workflow</span><span class="o">.</span><span class="n">compile_pdfs</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="no">Workflow</span><span class="o">.</span><span class="n">optimize_images</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span> <span class="n">manga_name</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>We can easily see how the Pipe Operator &ldquo;|&gt;&rdquo; makes any transformation pipeline much easier to read. Anytime you are starting from a value and passing the results through a <strong>chain of transformations</strong>, you will use this operator.</p>
<h3>Next Steps<span class="hx:absolute hx:-mt-20" id="next-steps"></span>
    <a href="#next-steps" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The concepts presented in this article are the ones I think most people will find the most challenging upon first glance. If you understand Pattern Matching and Keyword Lists, you will understand all the rest.</p>
<p>The official website offers a great <a href="http://elixir-lang.org/getting-started/introduction.html"target="_blank" rel="noopener">Getting Started</a> that you must read entirely.</p>
<p>From intuition you know most things already. You have &ldquo;do .. end&rdquo; blocks, but you may not yet know that they are just convenience macros to pass a list of statements as an argument inside a Keyword List. The following blocks are equivalent:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">if</span> <span class="kp">true</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">+</span> <span class="mi">10</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="kp">true</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">+</span> <span class="mi">10</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="kp">true</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="ss">:do</span><span class="p">,</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">+</span> <span class="mi">10</span>
</span></span><span class="line"><span class="cl"><span class="p">)}</span><span class="o">]</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mind-blowing, huh? There are many macros that add syntactic sugar using the primitives behind it.</p>
<p>For the most part, Valim made the powerful Erlang primitives more accessible (Lists, Atoms, Maps, etc.) and added higher abstractions using macros (do .. end blocks, the pipe operator, keyword lists, shortcuts for anonymous functions, etc.). This precise combination is what makes Elixir very enjoyable to learn. It&rsquo;s like peeling an onion: you start with the higher abstractions and discover macros of simpler structures underneath. You see a Keyword List first and discover Lists of Tuples. You see a block and discover another Keyword List disguised by a macro. And so on.</p>
<p>So you have a low barrier to entry and you can go as deep as you want into the rabbit hole, all the way to extending the language.</p>
<p>Elixir provides a very clever language design on top of the 25-year-old mature Erlang core. This is not just clever, it&rsquo;s the intelligent choice. Keep learning!</p>
]]></content:encoded><category>tutorials</category><category>elixir-and-erlang</category><category>programming</category></item><item><title>[English] Minimal I18n with Rails 3.2</title><link>https://akitaonrails.github.io/en/2012/07/24/english-minimal-i18n-with-rails-3-2/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2012/07/24/english-minimal-i18n-with-rails-3-2/</guid><pubDate>Tue, 24 Jul 2012 13:42:00 -0300</pubDate><description>&lt;p&gt;After a long gap I finally managed to contribute a bit to &lt;strong&gt;RubyLearning.org&lt;/strong&gt;. I&amp;rsquo;ve written a 2 part article about Internationalization (i18n) in Brazilian Portuguese a few days ago. I&amp;rsquo;ve adapted it into English and you can read it at &lt;a href="http://rubylearning.com/blog/2012/07/24/minimal-i18n-with-rails-3-2/"target="_blank" rel="noopener"&gt;Satish&amp;rsquo;s blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m showing snippets of code from a demonstration app and you can see the code at &lt;a href="https://github.com/akitaonrails/Rails-3-I18n-Demonstration"target="_blank" rel="noopener"&gt;my Github repo&lt;/a&gt; and you can also see the app up and &lt;a href="http://rubylearning.com/blog/2012/07/24/minimal-i18n-with-rails-3-2/i18n-demo.herokuapp.com"target="_blank" rel="noopener"&gt;running here&lt;/a&gt;, thanks to Heroku&amp;rsquo;s free accounts.&lt;/p&gt;</description><content:encoded><![CDATA[<p>After a long gap I finally managed to contribute a bit to <strong>RubyLearning.org</strong>. I&rsquo;ve written a 2 part article about Internationalization (i18n) in Brazilian Portuguese a few days ago. I&rsquo;ve adapted it into English and you can read it at <a href="http://rubylearning.com/blog/2012/07/24/minimal-i18n-with-rails-3-2/"target="_blank" rel="noopener">Satish&rsquo;s blog</a>.</p>
<p>I&rsquo;m showing snippets of code from a demonstration app and you can see the code at <a href="https://github.com/akitaonrails/Rails-3-I18n-Demonstration"target="_blank" rel="noopener">my Github repo</a> and you can also see the app up and <a href="http://rubylearning.com/blog/2012/07/24/minimal-i18n-with-rails-3-2/i18n-demo.herokuapp.com"target="_blank" rel="noopener">running here</a>, thanks to Heroku&rsquo;s free accounts.</p>
<p>I hope you enjoy it!</p>
]]></content:encoded><category>rails</category><category>tutorials</category></item><item><title>[Objective-C] Distributing your Static Library</title><link>https://akitaonrails.github.io/en/2011/04/24/objective-c-distributing-your-static-library/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2011/04/24/objective-c-distributing-your-static-library/</guid><pubDate>Sun, 24 Apr 2011 00:46:00 -0300</pubDate><description>&lt;p&gt;If you didn&amp;rsquo;t read my &lt;a href="http://www.akitaonrails.com/2011/04/23/objective-c-it%27s-a-unix-system-i-know-this"target="_blank" rel="noopener"&gt;last&lt;/a&gt; &lt;a href="http://www.akitaonrails.com/en/2011/04/23/objective-c-categories-static-libraries-e-pegadinhas"target="_blank" rel="noopener"&gt;two articles&lt;/a&gt; I recommend you do so before going any further because I am using the same pet project, &lt;a href="http://www.akitaonrails.com/2011/04/23/objective-c-it%27s-a-unix-system-i-know-this"target="_blank" rel="noopener"&gt;ObjC Rubyfication&lt;/a&gt; as an example for this article. The point is: you are writing reusable code that you want in more than one project.&lt;/p&gt;
&lt;p&gt;Most of this was based on &lt;a href="http://www.cocoanetics.com/2010/04/universal-static-libraries/"target="_blank" rel="noopener"&gt;Cocoanetics&lt;/a&gt; article about universal static libraries. So, if you&amp;rsquo;ve paid attention to my &lt;a href="http://www.akitaonrails.com/en/2011/04/23/objective-c-categories-static-libraries-e-pegadinhas"target="_blank" rel="noopener"&gt;previous article&lt;/a&gt;, you saw this screenshot:&lt;/p&gt;
&lt;!-- broken image (host offline/expired): http://s3.amazonaws.com/akitaonrails/assets/2011/4/23/Screen%20shot%202011-04-23%20at%2011.41.27%20PM_original.png --&gt;
&lt;p&gt;I said that I only had these targets configured: CocoaOniguruma, Kiwi, Rubyfication and RubyficationTests. But there are 3 others: CocoaOniguruma SIM, Rubyfication SIM, and Build &amp;amp; Merge Libraries. The reason is simple:&lt;/p&gt;</description><content:encoded><![CDATA[<p>If you didn&rsquo;t read my <a href="http://www.akitaonrails.com/2011/04/23/objective-c-it%27s-a-unix-system-i-know-this"target="_blank" rel="noopener">last</a> <a href="http://www.akitaonrails.com/en/2011/04/23/objective-c-categories-static-libraries-e-pegadinhas"target="_blank" rel="noopener">two articles</a> I recommend you do so before going any further because I am using the same pet project, <a href="http://www.akitaonrails.com/2011/04/23/objective-c-it%27s-a-unix-system-i-know-this"target="_blank" rel="noopener">ObjC Rubyfication</a> as an example for this article. The point is: you are writing reusable code that you want in more than one project.</p>
<p>Most of this was based on <a href="http://www.cocoanetics.com/2010/04/universal-static-libraries/"target="_blank" rel="noopener">Cocoanetics</a> article about universal static libraries. So, if you&rsquo;ve paid attention to my <a href="http://www.akitaonrails.com/en/2011/04/23/objective-c-categories-static-libraries-e-pegadinhas"target="_blank" rel="noopener">previous article</a>, you saw this screenshot:</p>
<!-- broken image (host offline/expired): http://s3.amazonaws.com/akitaonrails/assets/2011/4/23/Screen%20shot%202011-04-23%20at%2011.41.27%20PM_original.png -->
<p>I said that I only had these targets configured: CocoaOniguruma, Kiwi, Rubyfication and RubyficationTests. But there are 3 others: CocoaOniguruma SIM, Rubyfication SIM, and Build &amp; Merge Libraries. The reason is simple:</p>
<ul>
<li>The &ldquo;CocoaOniguruma&rdquo; Target builds the <tt>libCocoaOniguruma.a</tt> binary that&rsquo;s compatible with the ARM processor for iOS</li>
<li>The &ldquo;CocoaOniguruma SIM&rdquo; Target builds the <tt>libCocoaOnigurumaSimulator.a</tt> binary that&rsquo;s compatible with the i386 processor for the local iPhone Simulator</li>
<li>The &ldquo;Rubyfication&rdquo; Target builds the <tt>libRubyfication.a</tt> binary that&rsquo;s compatible with the ARM processor for iOS</li>
<li>The &ldquo;Rubyfication SIM&rdquo; Target builds the <tt>libRubyficationSimulator.a</tt> binary that&rsquo;s compatible with the i386 processor for the local iPhone Simulator</li>
<li>The &ldquo;Build &amp; Merge Libraries&rdquo; Target merges the corresponding particular binaries of each target into a single Universal (Fat) Binary that we can easily distribute and reuse.</li>
</ul>
<h2>Concepts and History<span class="hx:absolute hx:-mt-20" id="concepts-and-history"></span>
    <a href="#concepts-and-history" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Some explanation is in order. When you&rsquo;re developing iOS applications, you can test it directly on your iPhone device or within the Simulator. Now, Apple was very clever: any other vendor in the market will try to first create an ARM processor emulator to run on top of your i386 processor. Then it will get all the binaries for ARM and run within this emulator. The OS itself will remain compiled for ARM processors and run within the emulator.</p>
<p>Now, this is dead slow, impractical. Anyone that has experienced an emulation environment like this knows how ridiculous it is. Don&rsquo;t confuse it for VMWare or Parallels solutions, which are fast because they are an emulated environment for <strong>the same processor</strong> , so you can run Windows on top of your Mac because the processors and operating systems now support VT-x, which means you mostly don&rsquo;t have to emulate the processor. Now, every smartphone and tablet on the market uses an ARM processor, which has nothing to do with i386.</p>
<p>So, how did Apple deliver a super-fast iPhone/iPad emulator that runs at real-time speed on your Mac? Simple, it didn&rsquo;t. It is called &ldquo;Simulator&rdquo; and not &ldquo;Emulator&rdquo; for a reason: everything that runs within the Simulator is compiled for i386, not for ARM. So what you&rsquo;re running is an actual binary that runs natively over your Mac! No emulation required. The iOS is very portable. The same way Mac OS X was able to transition from the Power PC to Intel back in 2005, the iOS can do the same trick as they are basically the very same operating system.</p>
<p>When you choose the iPhone scheme in XCode, it compiles for ARM and uploads the bits to your iPhone device to run the application. When you choose the Simulator scheme in XCode, it compiles for i386, uploads the bits to your Simulator and runs natively as any other application in your Mac.</p>
<p>Now, going back to the main issue: when I distribute a binary of my Static Library, I have to remember that the developer will be linking against my library to deploy for both the ARM device (iPhone/iPad) and the Simulator (i386). So I would have to deliver at least 2 binary files. But Apple is even smarter than that. Actually, NeXT was. When they first transitioned the NeXTStep operating system from Motorola to Intel processors back in the late 80&rsquo;s, they created <a href="http://en.wikipedia.org/wiki/Fat_binary"target="_blank" rel="noopener">Fat Binaries</a>, which is essentially one binary that contains both processor-specific bits in one single package. When Apple transitioned from the Power PC to Intel they renamed it to <a href="http://en.wikipedia.org/wiki/Universal_binary"target="_blank" rel="noopener">Universal Binaries</a>. And that&rsquo;s essentially what we need to build now.</p>
<h2>Operations<span class="hx:absolute hx:-mt-20" id="operations"></span>
    <a href="#operations" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>We now understand what a Universal Binary is. To make it easier, I right-clicked over the &ldquo;Rubyfication&rdquo; and &ldquo;CocoaOniguruma&rdquo; targets and duplicated them. They will be created as &ldquo;Rubyfication copy&rdquo; and &ldquo;CocoaOniguruma copy&rdquo;. We can change the &ldquo;Product Name&rdquo; in the build settings of each for a more reasonable name:</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2011/4/24/Screen%20shot%202011-04-24%20at%2012.21.02%20AM_original.png?1303615270" alt=""  loading="lazy" /></p>
<p>In my case I renamed them to &ldquo;RubyficationSimulator&rdquo; and &ldquo;CocoaOnigurumaSimulator&rdquo;. That will give me both &ldquo;libRubyficationSimulator.a&rdquo; and &ldquo;libCocoaOnigurumaSimulator.a&rdquo;. As you can see in the Products group:</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2011/4/24/Screen%20shot%202011-04-24%20at%2012.23.16%20AM_original.png?1303615348" alt=""  loading="lazy" /></p>
<p><strong>Important:</strong> remember that the Rubyfication target had CocoaOniguruma as a target dependency. You will need to change the &ldquo;Rubyfication SIM&rdquo; target to point to the new &ldquo;CocoaOniguruma SIM&rdquo; target! The other thing is that you will need to force both new targets to compile to &ldquo;Latest Mac OS X&rdquo; in the <tt>&ldquo;Base <span class="caps">SDK</span>&quot;</tt> option in the Build Settings of each, and &ldquo;32-bit Intel&rdquo; in the <tt>&ldquo;Architectures&rdquo;</tt> build option. This will make them compile for i386 processors. The original targets should have their <tt>&ldquo;Base <span class="caps">SDK</span>&quot;</tt> to &ldquo;Latest iOS&rdquo; and <tt>&ldquo;Architectures&rdquo;</tt> to &ldquo;Standard (armv6 armv7)&rdquo;.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2011/4/24/Screen%20shot%202011-04-24%20at%2012.54.21%20AM_original.png?1303617223" alt=""  loading="lazy" /></p>
<p>Now, we need to create a new target of the kind &ldquo;Aggregate&rdquo; :</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2011/4/24/Screen%20shot%202011-04-24%20at%2012.24.00%20AM_original.png?1303615407" alt=""  loading="lazy" /></p>
<p>I named it &ldquo;Build &amp; Merge Libraries&rdquo;. In the &ldquo;Build Phases&rdquo; tab you will start with just the &ldquo;Target Dependencies&rdquo; phase. You just need to add the new &ldquo;Rubyfication SIM&rdquo; and &ldquo;CocoaOniguruma SIM&rdquo; targets. Then you need to add 3 more phases, one for &ldquo;Run Script&rdquo;, another to &ldquo;Copy Files&rdquo; and a last &ldquo;Run Script&rdquo;.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2011/4/24/Screen%20shot%202011-04-24%20at%2012.25.13%20AM_original.png?1303615678" alt=""  loading="lazy" /></p>
<p>In the &ldquo;Copy Files&rdquo; phase I have just added all the public headers that I want to distribute together with the universal binary library. This is because the other developers will need to add those header files into their projects in order to be able to compile against my library. Now notice that I am copying them to an &ldquo;Absolute Path&rdquo; that states <tt>${TARGET_BUILD_DIR}/../Rubyfication</tt>.</p>
<p>That&rsquo;s where we come to the previous &ldquo;Run Script&rdquo; phase that should have the following code:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 1. make a new output folder</span>
</span></span><span class="line"><span class="cl">mkdir -p <span class="si">${</span><span class="nv">TARGET_BUILD_DIR</span><span class="si">}</span>/../Rubyfication
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 2. combine lib files for various platforms into one</span>
</span></span><span class="line"><span class="cl">lipo create <span class="s2">&#34;</span><span class="si">${</span><span class="nv">TARGET_BUILD_DIR</span><span class="si">}</span><span class="s2">/../Debug-iphoneos/libRubyfication.a&#34;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">TARGET_BUILD_DIR</span><span class="si">}</span><span class="s2">/../Debug-iphonesimulator/libRubyfication.a&#34;</span> -output <span class="s2">&#34;</span><span class="si">${</span><span class="nv">TARGET_BUILD_DIR</span><span class="si">}</span><span class="s2">/../Rubyfication/libRubyfication</span><span class="si">${</span><span class="nv">BUILD_STYLE</span><span class="si">}</span><span class="s2">.a&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The first thing it does is create this new &ldquo;Rubyfication&rdquo; directory. The second command uses the <a href="http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man1/lipo.1.html"target="_blank" rel="noopener">lipo</a> command that merges 2 processor-dependent binaries into a universal binary. Pay attention to the PATHs if you&rsquo;re reusing this script somewhere else. At least with XCode 4 that&rsquo;s where it creates the binaries of each target:</p>
<ul>
<li>${TARGET_BUILD_DIR}/../Debug-iphoneos/libRubyfication.a – the ARM version</li>
<li>${TARGET_BUILD_DIR}/../Debug-iphonesimulator/libRubyfication.a – the i386 version</li>
<li>${TARGET_BUILD_DIR}/../Rubyfication/libRubyfication-Debug.a – the resulting universal binary we created</li>
</ul>
<p>Finally, the last &ldquo;Run Script&rdquo;, after the &ldquo;Copy Files&rdquo; phase described above, requires the following script:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ditto c -k --keepParent <span class="s2">&#34;</span><span class="si">${</span><span class="nv">TARGET_BUILD_DIR</span><span class="si">}</span><span class="s2">/../Rubyfication&#34;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">TARGET_BUILD_DIR</span><span class="si">}</span><span class="s2">/../Rubyfication.zip&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>&ndash;</p>
<p>It just creates a ZIP file with the universal binary library and its companion public header files. Any other developer can get this zip file, unzip it and add the files to their own projects now. If you want to find where this ZIP file is, the easiest way is to go to the project viewer (the left side pane) in XCode, open the &ldquo;Products&rdquo; group, right-click over the &ldquo;libRubyfication.a&rdquo; file (or any other resulting file) and choose &ldquo;Show in Finder&rdquo;. Then you can navigate one folder up in the hierarchy (to the &ldquo;Products&rdquo; folder) and you will see something like this:</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2011/4/24/Screen%20shot%202011-04-24%20at%2012.39.02%20AM_original.png?1303616300" alt=""  loading="lazy" /></p>
<p>And there you go: there&rsquo;s your ZIP file with your brand new redistributable universal binary!</p>
<h2>Using the Universal Binary<span class="hx:absolute hx:-mt-20" id="using-the-universal-binary"></span>
    <a href="#using-the-universal-binary" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>In order to demonstrate how to use this distributable ZIP file, I have created a very simple, bare-bone iOS project called <a href="https://github.com/akitaonrails/ObjC_OnigurumaDemo"target="_blank" rel="noopener">ObjC_OnigurumaDemo</a> that you can download from Github and run in your own iOS device.</p>
<p>As you can see in the screenshot below, I just unzipped the ZIP within a &ldquo;Dependencies&rdquo; folder in my iOS project and added the universal binary &ldquo;libRubyfication-Debug.a&rdquo; within the Library Linking Build Phase:</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2011/4/24/Screen%20shot%202011-04-24%20at%2012.42.09%20AM_original.png?1303616506" alt=""  loading="lazy" /></p>
<p>This allows me to just use anything from this library in my project, in particular a piece of code that uses the Oniguruma regular expressions:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-objc" data-lang="objc"><span class="line"><span class="cl"><span class="p">-</span> <span class="p">(</span><span class="kt">IBAction</span><span class="p">)</span><span class="nf">runRegex:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">sender</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">OnigRegexp</span><span class="o">*</span> <span class="n">regex</span> <span class="o">=</span> <span class="p">[</span><span class="n">OnigRegexp</span> <span class="nl">compile</span><span class="p">:[</span><span class="n">regexPattern</span> <span class="n">text</span><span class="p">]];</span>
</span></span><span class="line"><span class="cl">    <span class="n">OnigResult</span><span class="o">*</span> <span class="n">res</span> <span class="o">=</span> <span class="p">[</span><span class="n">regex</span> <span class="nl">match</span><span class="p">:[</span><span class="n">initialText</span> <span class="n">text</span><span class="p">]];</span>
</span></span><span class="line"><span class="cl">    <span class="n">NSMutableString</span><span class="o">*</span> <span class="n">tmpResult</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSMutableString</span> <span class="nl">stringWithString</span><span class="p">:</span><span class="s">@&#34;&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="p">[</span><span class="n">res</span> <span class="n">count</span><span class="p">];</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="n">tmpResult</span> <span class="nl">appendString</span><span class="p">:</span><span class="s">@&#34;(&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="n">tmpResult</span> <span class="nl">appendString</span><span class="p">:[</span><span class="n">res</span> <span class="nl">stringAt</span><span class="p">:</span><span class="n">i</span><span class="p">]];</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="n">tmpResult</span> <span class="nl">appendString</span><span class="p">:</span><span class="s">@&#34;)&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="n">result</span> <span class="nl">setText</span><span class="p">:</span><span class="n">tmpResult</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This demonstration application has a text-field called &ldquo;initialText&rdquo;, where you can type any string. Then you can prepare a Regular Expression in the &ldquo;regexPattern&rdquo; text-field and when you hit the &ldquo;Run&rdquo; button, it will trigger the action above that will run the Regular Expression against the initial text and write the matches within parenthesis in the &ldquo;result&rdquo; text view. The application looks like this:</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2011/4/24/Screen%20shot%202011-04-24%20at%2012.46.19%20AM_original.png?1303616748" alt=""  loading="lazy" /></p>
<p>And, <em>voilá</em>! Ruby 1.9-like Regular Expression, directly from Oniguruma, within an iOS Application!</p>
]]></content:encoded><category>apple</category><category>programming</category><category>tutorials</category></item><item><title>[Objective-C] Categories, Static Libraries and Gotchas</title><link>https://akitaonrails.github.io/en/2011/04/23/objective-c-categories-static-libraries-and-gotchas/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2011/04/23/objective-c-categories-static-libraries-and-gotchas/</guid><pubDate>Sat, 23 Apr 2011 23:44:00 -0300</pubDate><description>&lt;p&gt;As some of you may know, I have this small pet project called &lt;a href="https://github.com/akitaonrails/ObjC_Rubyfication"target="_blank" rel="noopener"&gt;ObjC Rubyfication&lt;/a&gt;, a personal exercise in writing some Ruby-like syntax within Objective-C. Most of this project uses the fact that we can reopen Objective-C standard classes – very much like Ruby, unlike Java – and insert our own code – through &lt;a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocCategories.html%23//apple_ref/doc/uid/TP30001163-CH20"target="_blank" rel="noopener"&gt;Categories&lt;/a&gt;, similar to Ruby&amp;rsquo;s modules.&lt;/p&gt;
&lt;p&gt;The idea of this pet project is to be a Static Library that I can easily add to any other project and have all of its features. In this article I&amp;rsquo;d like to present how I am organizing its many subprojects within one project (and I am accepting any suggestions and tips to make it better as I am just learning how to organize things within Obj-C projects) and talk about a gotcha that took me hours to figure out and might help someone else.&lt;/p&gt;</description><content:encoded><![CDATA[<p>As some of you may know, I have this small pet project called <a href="https://github.com/akitaonrails/ObjC_Rubyfication"target="_blank" rel="noopener">ObjC Rubyfication</a>, a personal exercise in writing some Ruby-like syntax within Objective-C. Most of this project uses the fact that we can reopen Objective-C standard classes – very much like Ruby, unlike Java – and insert our own code – through <a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocCategories.html%23//apple_ref/doc/uid/TP30001163-CH20"target="_blank" rel="noopener">Categories</a>, similar to Ruby&rsquo;s modules.</p>
<p>The idea of this pet project is to be a Static Library that I can easily add to any other project and have all of its features. In this article I&rsquo;d like to present how I am organizing its many subprojects within one project (and I am accepting any suggestions and tips to make it better as I am just learning how to organize things within Obj-C projects) and talk about a gotcha that took me hours to figure out and might help someone else.</p>
<p>To make this exercise even more fun, I also added a separate target for my unit testing suite (and see how XCode supports tests), then another target for the <a href="http://kiwi-lib.info/"target="_blank" rel="noopener">Kiwi</a> BDD testing framework for Obj-C, and another one for <a href="http://limechat.net/cocoaoniguruma/"target="_blank" rel="noopener">CocoaOniguruma</a> as I have just explained in my <a href="http://www.akitaonrails.com/2011/04/23/objective-c-it%27s-a-unix-system-i-know-this"target="_blank" rel="noopener">previous article</a>.</p>
<p>I&rsquo;ve been playing with ways of reorganizing my project and I realized that I was doing it wrong. I was adding all the source files from my &ldquo;Rubyfication&rdquo; target into my Specs target. So everything was compiling fine, the specs were all passing, but the way I defined dependencies was wrong. It is kind of complicated to understand at first, but it should be something like this:</p>
<ul>
<li>CocoaOniguruma Target: should be a static library, with no target dependencies and no binary libraries to link against, just a dependency to the standard Foundation framework. It exposes the OnigRegexp.h, OnigRegexpUtility.h and oniguruma.h as public headers.</li>
<li>Kiwi Target: should be another static library, with no target dependencies and no binary libraries to link against, just having the Foundation and UIKit framework dependencies.</li>
<li>Rubyfication: should be another static library, with CocoaOniguruma as a target dependency, linking against the libCocoaOniguruma.a binary and depending on the Foundation framework as well. It exposes all of its <tt>.h</tt> files as public headers.</li>
<li>RubyficationTests: should be a Bundle which was created together with the Rubyfication target (you can specify whether you want a unit test target when you create new targets), with both Kiwi and Rubyfication targets as dependencies, linking against the libKiwi.a and libRubyfication.a binaries, and the Foundation and UIKit frameworks as well.</li>
</ul>
<p>If you keep creating new targets manually, XCode 4 will also create a bunch of Schemes that you don&rsquo;t really need. I keep mine clean with just the Rubyfication scheme. You can access the &ldquo;Product&rdquo; menu and the &ldquo;Edit Scheme&rdquo; option. Then my Scheme looks like this:</p>
<!-- broken image (host offline/expired): http://s3.amazonaws.com/akitaonrails/assets/2011/4/23/Screen%20shot%202011-04-23%20at%2011.26.25%20PM_original.png -->
<p>I usually configure all my build settings to use &ldquo;LLVM Compiler 2.0&rdquo; for the Debug settings and &ldquo;LLVM GCC 4.2&rdquo; for the Release settings (actually, I do that for precaution as I am not aware if people are actually deploying binaries in production compiled from LLVM).</p>
<p>I also set the <tt>&ldquo;Targeted Device Family&rdquo;</tt> to &ldquo;iPhone/iPad&rdquo; and I try to make the <tt>&ldquo;iOS Deployment Target&rdquo;</tt> to &ldquo;iOS 3.0&rdquo; whenever possible. People usually leave the default one which will be the latest release – now at 4.3. Be aware that your project may not run on older devices that way.</p>
<p>Finally I also make sure that the <tt>&ldquo;Framework Search Paths&rdquo;</tt> are pointing to these options:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&#34;$(SDKROOT)/Developer/Library/Frameworks&#34;  
&#34;${DEVELOPER_LIBRARY_DIR}/Frameworks&#34;  </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Everything compiles just fine that way. Then I can press &ldquo;Command-U&rdquo; (or go to the &ldquo;Product&rdquo; menu, &ldquo;Test&rdquo; option) to build the &ldquo;RubyficationTests&rdquo; target. It builds all the target dependencies, links everything together and runs the final script to execute the tests (you must make sure that you are selecting the &ldquo;Rubyfication – iPhone 4.3 Simulator&rdquo; in the Scheme Menu). It will fire up the Simulator so it can run the specs.</p>
<p>But then I was receiving:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Test Suite &#39;/Users/akitaonrails/Library/Developer/Xcode/DerivedData/Rubyfication-gfqxbgyxicfpxugauehktilpmwzv/Build/Products/Debug-iphonesimulator/RubyficationTests.octest(Tests)&#39; started at 2011-04-24 02:16:27 &#43;0000  
Test Suite &#39;CollectionSpec&#39; started at 2011-04-24 02:16:27 &#43;0000  
Test Case &#39;-[CollectionSpec runSpec]&#39; started.  
2011-04-23 23:16:27.506 otest[40298:903] [__NSArrayI each:]: unrecognized selector sent to instance 0xe51a30  
2011-04-23 23:16:27.508 otest[40298:903] ***** Terminating app due to uncaught exception &#39;NSInvalidArgumentException&#39;, reason: &#39;[__NSArrayI each:]: unrecognized selector sent to instance 0xe51a30&#39;  </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It says that an instance of <tt>NSArray</tt> is not recognizing the selector <tt>each:</tt> sent to it in the <tt>CollectionSpec</tt> file. It is probably this snippet:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-objc" data-lang="objc"><span class="line"><span class="cl"><span class="cp"># import &#34;Kiwi.h&#34;  
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp"># import &#34;NSArray+functional.h&#34;  
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp"># import &#34;NSArray+helpers.h&#34;  
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp"># import &#34;NSArray+activesupport.h&#34;
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">SPEC_BEGIN</span><span class="p">(</span><span class="n">CollectionSpec</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">describe</span><span class="p">(</span><span class="s">@&#34;NSArray&#34;</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>  
</span></span><span class="line"><span class="cl"> <span class="k">__block</span> <span class="n">NSArray</span><span class="o">*</span> <span class="n">list</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">context</span><span class="p">(</span><span class="s">@&#34;Functional&#34;</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span> <span class="n">beforeEach</span><span class="p">(</span><span class="o">^</span><span class="p">{</span> <span class="n">list</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSArray</span> <span class="nl">arrayWithObjects</span><span class="p">:</span><span class="s">@&#34;a&#34;</span><span class="p">,</span> <span class="s">@&#34;b&#34;</span><span class="p">,</span> <span class="s">@&#34;c&#34;</span><span class="p">,</span> <span class="nb">nil</span><span class="p">];</span> <span class="p">});</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">it</span><span class="p">(</span><span class="s">@&#34;should iterate sequentially through the entire collection of items&#34;</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span> <span class="n">NSMutableArray</span><span class="o">*</span> <span class="n">output</span> <span class="o">=</span> <span class="p">[[</span><span class="n">NSMutableArray</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="n">list</span> <span class="nl">each</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="kt">id</span> <span class="n">item</span><span class="p">)</span> <span class="p">{</span> <span class="p">[</span><span class="n">output</span> <span class="nl">addObject</span><span class="p">:</span><span class="n">item</span><span class="p">];</span> <span class="p">}];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="n">theValue</span><span class="p">([</span><span class="n">output</span> <span class="n">count</span><span class="p">])</span> <span class="n">should</span><span class="p">]</span> <span class="nl">equal</span><span class="p">:</span><span class="n">theValue</span><span class="p">([</span><span class="n">list</span> <span class="n">count</span><span class="p">])];</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="err">…</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Reference: <a href="https://github.com/akitaonrails/ObjC_Rubyfication/blob/master/RubyficationTests/CollectionSpec.m#L1-22"target="_blank" rel="noopener">CollectionSpec.m</a></p>
<p>Notice that at Line 3 there is the correct import statement where the <tt>NSArray(Helpers)</tt> category is defined with the correct <tt>each:</tt> method declared. The error is happening at the spec in line 18 in the above snippet.</p>
<p>Now, this was not a compile time error, it was a runtime error. So the import statement is finding the correct file and compiling but something in the linking phase is not going correctly and at runtime the <tt>NSArray(Helpers)</tt> category, and probably other categories, are not available.</p>
<p>It took me a few hours of research but I finally figured out one simple flag that changed everything, the <a href="http://developer.apple.com/library/mac/#qa/qa1490/_index.html"target="_blank" rel="noopener">-all_load</a> linker flag. As the documentation states:</p>
<blockquote>
  <p><strong>Important:</strong> For 64-bit and iPhone OS applications, there is a linker bug that prevents <tt>-ObjC</tt> from loading objects files from static libraries that contain only categories and no classes. The workaround is to use the <tt>-all_load</tt> or <tt>-force_load</tt> flags.</p>
<p><tt>-all_load</tt> forces the linker to load all object files from every archive it sees, even those without Objective-C code. <tt>-force_load</tt> is available in Xcode 3.2 and later. It allows finer grain control of archive loading. Each <tt>-force_load</tt> option must be followed by a path to an archive, and every object file in that archive will be loaded.</p>

</blockquote>
<p>So every target that depends on external static libraries that loads Categories has to add this <tt>-all_load</tt> flag in the &ldquo;Other Linker Flags&rdquo;, under the &ldquo;Linking&rdquo; category on the &ldquo;Build Settings&rdquo; of the target, like this:</p>
<!-- broken image (host offline/expired): http://s3.amazonaws.com/akitaonrails/assets/2011/4/23/Screen%20shot%202011-04-23%20at%2011.41.27%20PM_original.png -->
<p>So both my <tt>RubyficationTests</tt> and <tt>Rubyfication</tt> targets had to receive this new flag. And now the Tests all pass flawlessly!</p>
]]></content:encoded><category>apple</category><category>programming</category><category>tutorials</category></item><item><title>[Objective-C] It's a Unix System! I know this!</title><link>https://akitaonrails.github.io/en/2011/04/23/objective-c-its-a-unix-system-i-know-this/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2011/04/23/objective-c-its-a-unix-system-i-know-this/</guid><pubDate>Sat, 23 Apr 2011 22:47:00 -0300</pubDate><description>&lt;p&gt;While experimenting with ways of using Objective-C a little bit closer to how I code Ruby, there were two things that annoyed me a bit. First, Date Formatting and, second, Regular Expressions.&lt;/p&gt;
&lt;p&gt;The Cocoa framework has both implemented as &lt;a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DataFormatting/Articles/dfDateFormatting10_4.html%23//apple_ref/doc/uid/TP40002369-SW1"target="_blank" rel="noopener"&gt;NSDateFormatter&lt;/a&gt; and &lt;a href="http://developer.apple.com/library/iOS/#documentation/Foundation/Reference/NSRegularExpression_Class/Reference/Reference.html"target="_blank" rel="noopener"&gt;NSRegularExpression&lt;/a&gt; that also happen to be available for iOS development.&lt;/p&gt;
&lt;p&gt;You can format dates like this:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-objc" data-lang="objc"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;NSDateFormatter&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dateFormatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;NSDateFormatter&lt;/span&gt; &lt;span class="n"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dateFormatter&lt;/span&gt; &lt;span class="nl"&gt;setDateStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;NSDateFormatterMediumStyle&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dateFormatter&lt;/span&gt; &lt;span class="nl"&gt;setTimeStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;NSDateFormatterNoStyle&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;NSDate&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NSDate&lt;/span&gt; &lt;span class="nl"&gt;dateWithTimeIntervalSinceReferenceDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;162000&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;NSString&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;formattedDateString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dateFormatter&lt;/span&gt; &lt;span class="nl"&gt;stringFromDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;NSLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;formattedDateString: %`&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formattedDateString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Output&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt; &lt;span class="nl"&gt;en_US&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;formattedDateString: Jan 2, 2001&amp;#34;&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And you can use Regular Expressions like this:&lt;/p&gt;</description><content:encoded><![CDATA[<p>While experimenting with ways of using Objective-C a little bit closer to how I code Ruby, there were two things that annoyed me a bit. First, Date Formatting and, second, Regular Expressions.</p>
<p>The Cocoa framework has both implemented as <a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DataFormatting/Articles/dfDateFormatting10_4.html%23//apple_ref/doc/uid/TP40002369-SW1"target="_blank" rel="noopener">NSDateFormatter</a> and <a href="http://developer.apple.com/library/iOS/#documentation/Foundation/Reference/NSRegularExpression_Class/Reference/Reference.html"target="_blank" rel="noopener">NSRegularExpression</a> that also happen to be available for iOS development.</p>
<p>You can format dates like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-objc" data-lang="objc"><span class="line"><span class="cl"><span class="n">NSDateFormatter</span> <span class="o">*</span><span class="n">dateFormatter</span> <span class="o">=</span> <span class="p">[[</span><span class="n">NSDateFormatter</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>  
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="n">dateFormatter</span> <span class="nl">setDateStyle</span><span class="p">:</span><span class="n">NSDateFormatterMediumStyle</span><span class="p">];</span>  
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="n">dateFormatter</span> <span class="nl">setTimeStyle</span><span class="p">:</span><span class="n">NSDateFormatterNoStyle</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">NSDate</span> <span class="o">*</span><span class="n">date</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSDate</span> <span class="nl">dateWithTimeIntervalSinceReferenceDate</span><span class="p">:</span><span class="mi">162000</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">NSString</span> <span class="o">*</span><span class="n">formattedDateString</span> <span class="o">=</span> <span class="p">[</span><span class="n">dateFormatter</span> <span class="nl">stringFromDate</span><span class="p">:</span><span class="n">date</span><span class="p">];</span>  
</span></span><span class="line"><span class="cl"><span class="n">NSLog</span><span class="p">(</span><span class="err">`</span><span class="s">&#34;formattedDateString: %`&#34;</span><span class="p">,</span> <span class="n">formattedDateString</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="o">//</span> <span class="n">Output</span> <span class="k">for</span> <span class="n">locale</span> <span class="nl">en_US</span><span class="p">:</span> <span class="s">&#34;formattedDateString: Jan 2, 2001&#34;</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And you can use Regular Expressions like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-objc" data-lang="objc"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">NSError</span> <span class="o">*</span><span class="n">error</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="n">NSRegularExpression</span> <span class="o">*</span><span class="n">regex</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSRegularExpression</span> <span class="nl">regularExpressionWithPattern</span><span class="p">:</span><span class="s">@&#34;</span><span class="se">\\</span><span class="s">b(a|b)(c|d)</span><span class="se">\\</span><span class="s">b&#34;</span>
</span></span><span class="line"><span class="cl"> <span class="nl">options</span><span class="p">:</span><span class="n">NSRegularExpressionCaseInsensitive</span>  
</span></span><span class="line"><span class="cl"> <span class="nl">error</span><span class="p">:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">NSUInteger</span> <span class="n">numberOfMatches</span> <span class="o">=</span> <span class="p">[</span><span class="n">regex</span> <span class="nl">numberOfMatchesInString</span><span class="p">:</span><span class="n">string</span>  
</span></span><span class="line"><span class="cl"> <span class="nl">options</span><span class="p">:</span><span class="mi">0</span>  
</span></span><span class="line"><span class="cl"> <span class="nl">range</span><span class="p">:</span><span class="n">NSMakeRange</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="p">[</span><span class="n">string</span> <span class="n">length</span><span class="p">])];</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>But I have issues with both of these. The Ruby equivalent for the date formatting example would be:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;activesupport&#39;</span>  
</span></span><span class="line"><span class="cl"><span class="n">date</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s2">&#34;2001-01-01&#34;</span><span class="p">)</span> <span class="o">+</span> <span class="mi">162000</span><span class="o">.</span><span class="n">seconds</span>  
</span></span><span class="line"><span class="cl"><span class="n">date</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&#34;%b %d, %Y&#34;</span><span class="p">)</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And the regular expression example would be like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">number_of_matches</span> <span class="o">=</span> <span class="sr">/\W*[a|b][c|d]\W*/</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">string</span><span class="p">)</span><span class="o">.</span><span class="n">size</span>  </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>There are 2 specific things that annoy me:</p>
<ul>
<li>the Obj-C versions feel unnecessarily verbose. Now, I do understand that they are lower level and they will probably allow for more flexibility, but I think they should have higher &ldquo;porcelain&rdquo; versions, that are more straightforward.</li>
<li>in Obj-C, the Date Formatting uses the <a href="http://unicode.org/reports/tr35/tr35-10.html#Date_Format_Patterns"target="_blank" rel="noopener">Unicode TR-35</a> formatting standard and Regular Expressions use the <a href="http://userguide.icu-project.org/strings/regexp"target="_blank" rel="noopener">ICU</a> standard that is inspired by Perl Regular Expression with support for Unicode and loosely based on JDK 1.4 java.util.regex.</li>
</ul>
<p>So, the ideal solution for me would be:</p>
<ul>
<li>to have higher level versions of those features;</li>
<li>to have C-compatible strftime date formatting and Ruby 1.9&rsquo;s Oniguruma level regular expressions.</li>
</ul>
<h2>It&rsquo;s a Unix System<span class="hx:absolute hx:-mt-20" id="its-a-unix-system"></span>
    <a href="#its-a-unix-system" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>That&rsquo;s when the obvious thing came to me: Objective-C is nothing more than a superset of C, so anything that is compatible with C is automatically compatible with Objective-C. More than that, the <strong>iOS is a Unix System</strong>! Meaning that it has all the goodies of Posix support.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/dFUlAQZB9Ng"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>So, how do I get <a href="http://www.cplusplus.com/reference/clibrary/ctime/strftime/"target="_blank" rel="noopener">C-compatible</a> strftime? Easy:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-objc" data-lang="objc"><span class="line"><span class="cl"><span class="cp"># import &#34;time.h&#34;  
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="err">…</span>  
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">-</span> <span class="p">(</span><span class="n">NSString</span><span class="o">*</span><span class="p">)</span> <span class="nl">toFormattedString</span><span class="p">:(</span><span class="n">NSString</span><span class="o">*</span><span class="p">)</span><span class="n">format</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl"> <span class="n">time_t</span> <span class="n">unixTime</span> <span class="o">=</span> <span class="p">(</span><span class="n">time_t</span><span class="p">)</span> <span class="p">[</span><span class="nb">self</span> <span class="n">timeIntervalSince1970</span><span class="p">];</span>  
</span></span><span class="line"><span class="cl"> <span class="k">struct</span> <span class="n">tm</span> <span class="n">timeStruct</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"> <span class="n">localtime_r</span><span class="p">(</span><span class="o">&amp;</span><span class="n">unixTime</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">timeStruct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">30</span><span class="p">];</span> 
</span></span><span class="line"><span class="cl"><span class="n">strftime</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="p">[[</span><span class="n">NSDate</span> <span class="nl">formatter</span><span class="p">:</span><span class="n">format</span><span class="p">]</span> <span class="nl">cStringUsingEncoding</span><span class="p">:[</span><span class="n">NSString</span> <span class="n">defaultCStringEncoding</span><span class="p">]],</span> <span class="o">&amp;</span><span class="n">timeStruct</span><span class="p">);</span> <span class="n">NSString</span><span class="o">*</span> <span class="n">output</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSString</span> <span class="nl">stringWithCString</span><span class="p">:</span><span class="n">buffer</span> <span class="nl">encoding</span><span class="p">:[</span><span class="n">NSString</span> <span class="n">defaultCStringEncoding</span><span class="p">]];</span> 
</span></span><span class="line"><span class="cl"><span class="k">return</span> <span class="n">output</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Reference: <a href="https://github.com/akitaonrails/ObjC_Rubyfication/blob/master/Rubyfication/NSDate&#43;helpers.m#L71-80"target="_blank" rel="noopener">NSDate+helpers.m</a></p>
<p>Now follow each line to understand it:</p>
<ul>
<li>At line 4 it is returning the current time represented as the number of seconds since 1970. That method actually returns a <tt>NSTimeInterval</tt> which is a number that is essentially the same as the C-equivalent <tt>time_t</tt></li>
<li>At line 6 the C function <tt>localtime_r</tt> converts the <tt>unitTime</tt> number into the C-structure <tt>timeStruct</tt></li>
<li>At line 9 we call a custom method I created called <tt>formatter</tt> that just returns a <tt>strftime</tt> compatible string format. The Obj-C string (when we create using the &ldquo;@&rdquo; symbol) is an object that we must convert to an array of chars using the <tt>cStringUsingEncoding:</tt>. C functions don&rsquo;t understand Obj-C strings, hence the conversion. Then we finally call the <tt>strftime</tt> itself that will store the result in the <tt>buffer</tt> array of char that we declared before.</li>
<li>At line 10 we now do the reverse and convert the resulting C-string (array of chars) back into an Obj-C String.</li>
</ul>
<p>Now this is too nice. I have added a few other helper methods that now allow me to use it like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-objc" data-lang="objc"><span class="line"><span class="cl"><span class="n">it</span><span class="p">(</span><span class="err">`</span><span class="s">&#34;should convert the date to the rfc822 format&#34;</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">[[[</span><span class="n">ref</span> <span class="nl">toFormattedString</span><span class="p">:</span><span class="err">`</span><span class="s">&#34;rfc822&#34;</span><span class="p">]</span> <span class="n">should</span><span class="p">]</span> <span class="nl">equal</span><span class="p">:</span><span class="s">@&#34;Fri, 01 Jan 2010 10:15:30&#34;</span><span class="p">];</span>  
</span></span><span class="line"><span class="cl"><span class="p">});</span> </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Reference: <a href="https://github.com/akitaonrails/ObjC_Rubyfication/blob/master/RubyficationTests/DateSpec.m#L69"target="_blank" rel="noopener">DateSpec.m</a></p>
<p>And the <tt>&ldquo;rfc822&rdquo;</tt> string will just be internally converted to <tt>@&quot;%a, %d %b %Y %H:%M:%S&quot;</tt> by the <a href="https://github.com/akitaonrails/ObjC_Rubyfication/blob/master/Rubyfication/NSDate&#43;helpers.m#L82-100"target="_blank" rel="noopener"><tt>formatter:</tt></a> selector in the <tt>NSDate</tt> class.</p>
<p>Now, to add Ruby 1.9-level regular expressions you can go straight to the source and use the original C-based <a href="http://www.geocities.jp/kosako3/oniguruma/"target="_blank" rel="noopener">Oniguruma</a> itself, exactly what Ruby does. There are several ways to integrate a C library into your Cocoa project, but someone already did all the hard work. Satoshi Nakagawa wrote an Obj-C wrapper called <a href="http://limechat.net/cocoaoniguruma/"target="_blank" rel="noopener">CocoaOniguruma</a> that makes it dead easy to integrate into your project.</p>
<p>There are several ways to integrate an external library into your project. The easier way (albeit, not exactly the best) that I am showing here is by creating a new Static Library Target within my project, called <em>CocoaOniguruma</em>:</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2011/4/23/Screen%20shot%202011-04-23%20at%2010.33.47%20PM_original.png?1303608817" alt=""  loading="lazy" /></p>
<p>It will create a new Group called <em>CocoaOniguruma</em> in your project. Then you just add all the files from <a href="https://github.com/psychs/cocoaoniguruma/tree/master/framework/core"target="_blank" rel="noopener">CocoaOniguruma&rsquo;s core folder</a> to that group, select the new target and all the source files and headers will be properly added to the project, like this:</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2011/4/23/Screen%20shot%202011-04-23%20at%2010.37.19%20PM_original.png?1303608987" alt=""  loading="lazy" /></p>
<p>Finally, you need to go to the original main target of your application and add both the new target to the target dependencies and the binary <tt>.a</tt> file to the binary linking section, like this:</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2011/4/23/Screen%20shot%202011-04-23%20at%2010.39.07%20PM_original.png?1303609098" alt=""  loading="lazy" /></p>
<p>With all this set, I recommend you to explore the <tt>OnigRegexp.m</tt> and <tt>OnigRegexpUtility.m</tt>, that are Obj-C wrappers to the Oniguruma library. The author already did some very Ruby-like syntax for you to use.</p>
<p>I have wrapped those helpers in my own classes like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-objc" data-lang="objc"><span class="line"><span class="cl"><span class="p">-</span> <span class="p">(</span><span class="n">NSString</span><span class="o">*</span><span class="p">)</span> <span class="nf">gsub:</span><span class="p">(</span><span class="n">NSString</span><span class="o">*</span><span class="p">)</span><span class="nv">pattern</span> <span class="nf">with:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">replacement</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">([</span><span class="n">replacement</span> <span class="nl">isKindOfClass</span><span class="p">:[</span><span class="n">NSString</span> <span class="k">class</span><span class="p">]])</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="p">[</span><span class="nb">self</span> <span class="nl">replaceAllByRegexp</span><span class="p">:</span><span class="n">pattern</span> <span class="nl">with</span><span class="p">:</span><span class="n">replacement</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">([</span><span class="n">replacement</span> <span class="nl">isKindOfClass</span><span class="p">:[</span><span class="n">NSArray</span> <span class="k">class</span><span class="p">]])</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl"> <span class="k">__block</span> <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="p">[</span><span class="nb">self</span> <span class="nl">replaceAllByRegexp</span><span class="p">:</span><span class="n">pattern</span> <span class="nl">withBlock</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="n">OnigResult</span><span class="o">*</span> <span class="n">obj</span><span class="p">)</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="p">[</span><span class="n">NSString</span><span class="o">*</span><span class="p">](</span><span class="n">replacement</span> <span class="nl">objectAtIndex</span><span class="p">:(</span><span class="o">++</span><span class="n">i</span><span class="p">));</span>  
</span></span><span class="line"><span class="cl"> <span class="p">}];</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>  
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nb">nil</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">-</span> <span class="p">(</span><span class="n">NSString</span><span class="o">*</span><span class="p">)</span> <span class="nf">gsub:</span><span class="p">(</span><span class="n">NSString</span><span class="o">*</span><span class="p">)</span><span class="nv">pattern</span> <span class="nf">withBlock:</span><span class="p">(</span><span class="n">NSString</span><span class="o">*</span> <span class="p">(</span><span class="o">^</span><span class="p">)(</span><span class="n">OnigResult</span><span class="o">*</span><span class="p">))</span><span class="nv">replacement</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="p">[</span><span class="nb">self</span> <span class="nl">replaceAllByRegexp</span><span class="p">:</span><span class="n">pattern</span> <span class="nl">withBlock</span><span class="p">:</span><span class="n">replacement</span><span class="p">];</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Reference: <a href="https://github.com/akitaonrails/ObjC_Rubyfication/blob/master/Rubyfication/NSString&#43;helpers.m#L176-190"target="_blank" rel="noopener">NSString+helpers.m</a></p>
<p>Which now allows me to use this nicer syntax:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-objc" data-lang="objc"><span class="line"><span class="cl"><span class="n">context</span><span class="p">(</span><span class="err">`</span><span class="s">&#34;Regular Expressions&#34;</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">it</span><span class="p">(</span><span class="err">`</span><span class="s">&#34;should replace all substrings that match the pattern&#34;</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>  
</span></span><span class="line"><span class="cl"> <span class="p">[[[</span><span class="err">`</span><span class="s">&#34;hello world, heyho!&#34;</span> <span class="nl">gsub</span><span class="p">:</span><span class="err">`</span><span class="s">&#34;h</span><span class="se">\\</span><span class="s">w+&#34;</span> <span class="nl">with</span><span class="p">:</span><span class="err">`</span><span class="s">&#34;hi&#34;</span><span class="p">]</span> <span class="n">should</span><span class="p">]</span> <span class="nl">equal</span><span class="p">:</span><span class="err">`</span><span class="s">&#34;hi world, hi!&#34;</span><span class="p">];</span>  
</span></span><span class="line"><span class="cl"> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">it</span><span class="p">(</span><span class="s">@&#34;should replace each substrings with one corresponding replacement in the array&#34;</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span> <span class="n">NSArray</span><span class="o">*</span> <span class="n">replacements</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSArray</span> <span class="nl">arrayWithObjects</span><span class="p">:</span><span class="s">@&#34;hi&#34;</span><span class="p">,</span> <span class="s">@&#34;everybody&#34;</span><span class="p">,</span> <span class="nb">nil</span><span class="p">];</span> <span class="p">[[[</span><span class="err">`</span><span class="s">&#34;hello world, heyho!&#34;</span> <span class="nl">gsub</span><span class="p">:</span><span class="err">`</span><span class="s">&#34;h</span><span class="se">\\</span><span class="s">w+&#34;</span> <span class="nl">with</span><span class="p">:</span><span class="n">replacements</span><span class="p">]</span> <span class="n">should</span><span class="p">]</span> <span class="nl">equal</span><span class="p">:</span><span class="s">@&#34;hi world, everybody!&#34;</span><span class="p">];</span> <span class="p">});</span> <span class="n">it</span><span class="p">(</span><span class="s">@&#34;should replace each substring with the return of the block&#34;</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span> <span class="p">[[[</span><span class="err">`</span><span class="s">&#34;hello world, heyho!&#34;</span> <span class="nl">gsub</span><span class="p">:</span><span class="err">`</span><span class="s">&#34;h</span><span class="se">\\</span><span class="s">w+&#34;</span> <span class="nl">withBlock</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="n">OnigResult</span><span class="o">*</span> <span class="n">obj</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="s">@&#34;foo&#34;</span><span class="p">;</span> <span class="p">}]</span> <span class="n">should</span><span class="p">]</span> <span class="nl">equal</span><span class="p">:</span><span class="s">@&#34;foo world, foo!&#34;</span><span class="p">];</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Reference: <a href="https://github.com/akitaonrails/ObjC_Rubyfication/blob/master/RubyficationTests/StringSpec.m#L86-102"target="_blank" rel="noopener">StringSpec.m</a></p>
<p>If you&rsquo;re thinking that it is strange for a snippet of Objective-C code to have keywords such as <tt>context</tt> or <tt>it</tt>, they come from <a href="http://www.kiwi-lib.info/"target="_blank" rel="noopener">Kiwi</a>, which builds an RSpec-like BDD testing framework on top of SenTesting Kit for Objective-C development that you should definitely check out. But the code above should be easy enough to understand without even knowing about Kiwi. If you&rsquo;re a Ruby developer, you will probably notice that the syntax bears some resemblance to what you&rsquo;re used to already.</p>
<p>So, linking to existing standard C libraries or even third-party open source C libraries is a piece of cake for those simple cases, without having to resort to any &ldquo;Native Interface&rdquo; tunneling between virtual machines or any other plumbing. If you want C, they&rsquo;re there for you to easily integrate and use.</p>
]]></content:encoded><category>apple</category><category>programming</category><category>tutorials</category></item></channel></rss>