<!DOCTYPE html>
<html lang="en" data-theme="forest">
	<head>
		<meta charset="utf-8" />
		<link rel="icon" href="./favicon.png" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		
		<link href="./_app/immutable/assets/0.686e387f.css" rel="stylesheet">
		<link href="./_app/immutable/assets/Nav.2cc8aa3a.css" rel="stylesheet"><title>Travis Fantina  | Blog</title><!-- HEAD_svelte-1uo06u1_START --><!-- HEAD_svelte-1uo06u1_END -->
	</head>
	<body data-sveltekit-preload-data="hover">
		<div style="display: contents">   <div class="theme-box"><div class="theme-box--all"><button class="themable forest" aria-label="Change theme to Forest"></button> <button class="themable creamsicle" aria-label="Change theme to Creasicle"></button> <button class="themable nightly" aria-label="Change theme to Nightly"></button></div> <div class="theme-box--current"><button class="themable undefined" aria-label="Current theme: undefined"></button></div></div> <div class="content-container"><header class="d-flex"><div class="header-content d-flex"><div class="logo-img" data-svelte-h="svelte-13t9uxy"><img src="/images/storage/logo.svg" class="header-logo" alt="Travis Fantina initials as an SVG logo"></div> <div class="title-row d-flex align-center"><h1><span slot="title">Blog</span></h1></div></div></header> <button class="nav-mobile" data-svelte-h="svelte-d1fota"><span></span> <span></span> <span></span></button> <div class="nav-container"><div class="links svelte-aapfc6"><div class="content p-3 rounded-3 d-flex flex-column align-items-center"><span class="btn-nav--bg"><a href="/" class="btn btn-nav btn-primary">Home</a></span> <span class="btn-nav--bg"><a href="/projects" class="btn btn-nav btn-primary">Projects</a></span> <span class="btn-nav--bg"><a href="https://tfantina.micro.blog/" class="btn btn-nav btn-primary">Micro Blog</a></span> <span class="btn-nav--bg"><a href="/about" class="btn btn-nav btn-primary">About</a></span></div></div> </div> <main><div class="content rounded-3 p-4"><p data-svelte-h="svelte-1mtka4o">Something akin to a blog. Thoughts, photos, and relevant updates are collected here, click a tag
		to narrow by interest.</p> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/books">books</a> </div><div class="tag my-1"><a href="/blog/tags/current events">current events</a> </div><div class="tag my-1"><a href="/blog/tags/elixir">elixir</a> </div><div class="tag my-1"><a href="/blog/tags/life">life</a> </div><div class="tag my-1"><a href="/blog/tags/meta">meta</a> </div><div class="tag my-1"><a href="/blog/tags/micro">micro</a> </div><div class="tag my-1"><a href="/blog/tags/music">music</a> </div><div class="tag my-1"><a href="/blog/tags/musings">musings</a> </div><div class="tag my-1"><a href="/blog/tags/photo">photo</a> </div><div class="tag my-1"><a href="/blog/tags/portfolio">portfolio</a> </div><div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div><div class="tag my-1"><a href="/blog/tags/the internet">the internet</a> </div><div class="tag my-1"><a href="/blog/tags/til">til</a> </div></div></div> <a href="/blog/micro-9"><div class="card card-hover"><div class="row text-center"><h4></h4></div> <div>Before sending that email to hundreds of thosuands of customers... ask yourself, is announcing "we now have dark mode" broadcast worthy?... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-07-01</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/micro">micro</a> </div></div></div></div> </div></a><a href="/blog/the-nyc-primary"><div class="card card-hover"><div class="row text-center"><h4>The NYC Primary</h4></div> <div>It’s been a rough couple of weeks in world news.  A lot has been going on that I’ve felt moved to comment on but haven’t had the heart to actually write it down. Zohran Mamdani’s victory in the NYC primary is a ray of sunshine in otherwise very dark times.  It’s a powerful reminder that progressives can win  even against massive entrenched interests. In the final weeks of the race billionaires and... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-06-25</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/musings">musings</a> </div><div class="tag my-1"><a href="/blog/tags/current events">current events</a> </div></div></div></div> </div></a><a href="/blog/the-underground-railroad"><div class="card card-hover"><div class="row text-center"><h4>The Underground Railroad</h4></div> <div>I've been on a Colson Whitehead tear in the past year having started five of his books, finishing four of them. This year I raced through the Ray Carney series (*Harlem Shuffle* and *Crook Manifesto*) and I just finished *The Underground Railroad*. While not my favourite of his books *The Underground Railroad* was still a compelling read. Whitehead has this talent that I struggle to explain. He's ... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-06-24</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/books">books</a> </div></div></div></div> </div></a><a href="/blog/white_fragility"><div class="card card-hover"><div class="row text-center"><h4>White Fragility</h4></div> <div>**Full Title**: *White Fragility: Why It's So Hard for White People to Talk About Racism*
Although it's a short read this book was dense. That's not to say it was a difficult read; quite the contrary, it was extremely approachable but every single page was so laden with facts each paragraph served as an essay unto itself. White Fragility asks left-leaning progressively minded folks to examine thei... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-06-16</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/books">books</a> </div></div></div></div> </div></a><a href="/blog/jun-13-2025-weekly-roundup"><div class="card card-hover"><div class="row text-center"><h4> Weekly Round Up: June 13, 2025 👻</h4></div> <div>It was a week of state machines. Two separate Rails projects, two separate state machine libraries (`state_machines` and `aasm`), both sending emails. One is a fairly straightforward project for a department of education, it's an old codebase but Objective built it and has been working on it ever since.  As such, it's fairly clean and straightforward for it's age. I think that the more contractors... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-06-13</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div><div class="tag my-1"><a href="/blog/tags/life">life</a> </div></div></div></div> </div></a><a href="/blog/multi-tenancy-with-phoenix-and-elixir"><div class="card card-hover"><div class="row text-center"><h4>Multi-tenancy with Phoenix and Elixir</h4></div> <div>There are lots of good places to start with multi-tenancy in Elixir (although I'd recommend Ecto's own docs for either [foreign keys](https://hexdocs.pm/ecto/multi-tenancy-with-foreign-keys.html) or [postgres schemas](https://hexdocs.pm/ecto/multi-tenancy-with-query-prefixes.html) ). Most of the write-ups and tutorials start the same way "generate a new Phoenix application with `mix phx.new` ". Wh... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-06-04</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div><div class="tag my-1"><a href="/blog/tags/elixir">elixir</a> </div></div></div></div> </div></a><a href="/blog/til-2025-06-02"><div class="card card-hover"><div class="row text-center"><h4>Today I Learned ~D[2025-06-02]</h4></div> <div>File this under "things I knew but have to look up everytime"...

If you want to spin up a Docker container without a service like `postgres` , for example if you had a fully seeded DB on your machine and didn't want to go through the hassle of copying/re-seeding in Docker, you can do so with `host.docker.internal`.
In `docker-compose.yml` you can write:

```dockerfile
environment:
      - DB_HOST... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-06-02</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/til">til</a> </div></div></div></div> </div></a><a href="/blog/everything-is-tuberculuosis"><div class="card card-hover"><div class="row text-center"><h4>Everything is Tuberculuosis </h4></div> <div>I've never read anything from John Green but I used to watch his YouTube channel. As a young adult fiction author; I felt his handling of such a broad and complex topic, like the history of tuberculosis and it's impacts on the world today would be both engaging and digestable. I was not wrong in this assumption. This book was fast even by quick read standards, I could have read another two or thre... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-05-29</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/books">books</a> </div></div></div></div> </div></a><a href="/blog/the-anxious-generation"><div class="card card-hover"><div class="row text-center"><h4>The Anxious Generation</h4></div> <div>America has a long history of moral panics, the phone, rock music, rap music, etc. I always want to be careful about blaming "that new thing the kids are doing" and therefore I try to offer a balanced perspective when somebody starts talking about "the kids and the phones".  Needless to say I went into this book with a healthy dose of skepticism. 

Right off the bat my skepticism was rewarded in t... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-05-29</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/books">books</a> </div></div></div></div> </div></a><a href="/blog/til-there-is-only-one-false-in-ruby"><div class="card card-hover"><div class="row text-center"><h4>Today I Learned ~D[2025-05-22]</h4></div> <div>There is only one `false` in Ruby... Or more broadly speaking since everything is an object, for the sake of efficency in memory management any object that can be referenced will be. Immutable primitives (such as `true`, `false`, `1`, `2`, etc.) will only ever reference themselves.  For example:
```ruby
false.object_id
=> 0
false.dup.object_id 
=> 0
val = false 
val.object_id 
=> 0
```
Duplicating... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-05-22</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/til">til</a> </div></div></div></div> </div></a><a href="/blog/til-2025-05-15"><div class="card card-hover"><div class="row text-center"><h4>Today I Learned ~D[2025-05-14]</h4></div> <div>I recently switched jobs, which means new BitBucket credentials. However; I remain an occasional consultant with my last agency so I need to keep my public key associated with their BitBucket account... 

### The first thing I learned today
BitBucket won't let you use the same public key for multiple accounts.  I find this a little odd; like how AWS won't let you name a S3 bucket if the name alrea... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-05-15</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/TIL">TIL</a> </div><div class="tag my-1"><a href="/blog/tags/Programming">Programming</a> </div></div></div></div> </div></a><a href="/blog/class-configs-with-lambdas-in-ruby"><div class="card card-hover"><div class="row text-center"><h4>Class Configs with Lambdas in Ruby</h4></div> <div>I've been getting reacquainted with Ruby, diving into a well established project which has been blessed by numerous smart developers over the course of the past 10 years. I discovered an interesting pattern for gathering models (`ApplicationRecord` classes) that may or may not be eligible for some feature:
You start with a mixin that creates a method for your classes to pass options to; as well as... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-05-08</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div></div></div></div> </div></a><a href="/blog/may-2-2025-weekly-roundup"><div class="card card-hover"><div class="row text-center"><h4>Weekly Roundup: May 2, 2025</h4></div> <div>This week I formally transitioned from my fulltime consulting gig at [Objective](https://objective.dev) for a fulltime gig at [Built For Teams](https://www.builtapp.com/) more details on that in a future post.  However; broadly speaking it means that I'm dusting off my Ruby skills, diving deeper into the realm of OO programing then I ever have before. 

### Farewell ASDF
Last Friday night I pulled... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-05-02</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div><div class="tag my-1"><a href="/blog/tags/life">life</a> </div></div></div></div> </div></a><a href="/blog/apr-25-2025-weekly-roundup"><div class="card card-hover"><div class="row text-center"><h4>Weekly Roundup: Apr 25, 2025</h4></div> <div>At the agency, we have a customer who has asked that customers accept terms of service *before* checking out. This is for an Elixir project; mostly fullstack Elixir however the frontend has an odd assortment of sprinkles: StimulusJS and React.  I created a `terms_and_conditions` versions table and an accompanying view helper which will check a `terms_version_accepted` on the `user` record if the l... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-04-25</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div><div class="tag my-1"><a href="/blog/tags/life">life</a> </div></div></div></div> </div></a><a href="/blog/cloud_atlas"><div class="card card-hover"><div class="row text-center"><h4>Cloud Atlas</h4></div> <div>Review of Cloud Atlas <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-04-23</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/books">books</a> </div></div></div></div> </div></a><a href="/blog/apr-18-2025-weekly-roundup"><div class="card card-hover"><div class="row text-center"><h4>Weekly Roundup: Apr 18, 2025</h4></div> <div>Working for a small agency I am fortunate to work on a number of fast moving projects simultaneously. For years I've failed to document what I do during the week but I'm going a little recap of my week. One part historical record, one part general interest. I'm posting it on my blog in the off chance that somebody reads it and, facing a similar problem will reach out I'm always happy to discuss wh... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-04-18</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div><div class="tag my-1"><a href="/blog/tags/life">life</a> </div></div></div></div> </div></a><a href="/blog/micro-8"><div class="card card-hover"><div class="row text-center"><h4></h4></div> <div>Experience has shown that if you put out a bug bounty your server will be hit repeatedly with requests to `/wp_admin` for the rest of eternity.... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-04-16</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/micro">micro</a> </div></div></div></div> </div></a><a href="/blog/make-it-readable"><div class="card card-hover"><div class="row text-center"><h4>Personal Heuristic: Make it Readable</h4></div> <div>I wrote this post back in January, just dusted it off to post today as I attempt to get back on the blogging horse.
***
Today I was refactoring a small module that makes calls to an SAP endpoint. The compiler got hung up because it couldn't find the value `item`.  It was an easy fix, my code looked like this:
```elixir 
for itm &lt;- data do
    %{"MATNR" => material, "PSTYV" => category, "VBELN" => ... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-04-14</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div><div class="tag my-1"><a href="/blog/tags/elixir">elixir</a> </div></div></div></div> </div></a><a href="/blog/wintering"><div class="card card-hover"><div class="row text-center"><h4>Wintering: The Power of Rest and Retreat in Difficult Time</h4></div> <div>Reading 80% of this book was an exercise in torture. I'm always a little wary of personal memoirs cum self-help books but a few have been transformative for me (ie. Pamela Druckerman's *Bringing Up Bébé).* Katherine May hooked me early with this book, the prose was sharp and the anecdotes interesting however it very quickly devolved into anecdote after anecdote from a brief period in her life wher... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-01-22</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/books">books</a> </div></div></div></div> </div></a><a href="/blog/today-i-learned-2025-01-10"><div class="card card-hover"><div class="row text-center"><h4>Today I Learned ~D[2025-01-10]</h4></div> <div>Today's TIL has a twist ending... so stick around. 

 Elixir has a shortcut for creating anonymous functions. I've always written:
 
```elixir
greet = fn name -> "Hello, #{name}!" end 
# which can be invoked
greet.("Travis")
# returns
"Hello, Travis!"
```
 
However; I came across some tricky code with a case statement:
 
```elixir
docs = case type do 
	:billing -> &amp;[billing_act: &amp;1]
    :shipping ... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2025-01-10</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/til">til</a> </div></div></div></div> </div></a><a href="/blog/morning-walk"><div class="card card-hover"><div class="row text-center"><h4>Morning walk</h4></div> <div>Interesting things I passed on the way to the library with my daughter here in Guelph. <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-12-29</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/photo">photo</a> </div></div></div></div> </div></a><a href="/blog/micro-7"><div class="card card-hover"><div class="row text-center"><h4></h4></div> <div>Merry Christmas everyone!... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-12-24</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/micro">micro</a> </div></div></div></div> </div></a><a href="/blog/optconnect-marketing-site"><div class="card card-hover"><div class="row text-center"><h4>OptConnect Marketing Site</h4></div> <div>- Timeline: July 2020 - December 2021
- Status: the client switched to WordPress in May 2024
- Technologies: CraftCMS, Bootstrap
 
I built OptConnect’s Marketing site with CraftCMS, an elegant and opinionated alternative to WordPress. Over the years I’ve been able to work on several different Craft projects. The ecosystem is noticeably smaller than WordPress (as to be expected), but in my experien... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-12-24</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/portfolio">portfolio</a> </div></div></div></div> </div></a><a href="/blog/canada-post-strike-2"><div class="card card-hover"><div class="row text-center"><h4>Canada Post Strike</h4></div> <div>For the past few weeks postal workers in Canada have been on strike.... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-12-16</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/musings">musings</a> </div><div class="tag my-1"><a href="/blog/tags/current events">current events</a> </div></div></div></div> </div></a><a href="/blog/micro-6"><div class="card card-hover"><div class="row text-center"><h4></h4></div> <div>Not to rush Christmas, but I think I'll try my hand at Advent of Code this year.  It will be a good chance to play around with Rust.... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-11-11</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/micro">micro</a> </div><div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div></div></div></div> </div></a><a href="/blog/adding-a-soft_delete-to-ecto-multi-pipelines"><div class="card card-hover"><div class="row text-center"><h4>Adding a `soft_delete` to Ecto Multi pipelines</h4></div> <div>I'm a big fan of `Ecto,` Elixir's database wrapper. The `Multi` library lets you build up a series of operations that happen in order, if one fails the entire operation rolls back. Multi comes with the a lot of standard CRUD built in, `insert/4` , `update/4` , `delete/4` and their bulk counterparts `insert_all/5` , `update_all/5` and `delete_all/5` for acting on multiple records. 

 I've been work... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-10-21</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div><div class="tag my-1"><a href="/blog/tags/elixir">elixir</a> </div></div></div></div> </div></a><a href="/blog/boquets"><div class="card card-hover"><div class="row text-center"><h4>(Untitled)</h4></div> <div>Went walking with my daughter and we picked some autumnal bouquets.... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-10-18</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/micro">micro</a> </div><div class="tag my-1"><a href="/blog/tags/photo">photo</a> </div></div></div></div> </div></a><a href="/blog/til"><div class="card card-hover"><div class="row text-center"><h4>TIL Struct matching in Guards</h4></div> <div>Not so much a TIL but I always get confused with the proper syntax. You can pattern match on a struct and use it in a guard to only let through the structs you want:
 
```elixir
@spec address_formater(BillAddress.t() | ShipAddress.t()) :: String.t()
def address_formatter(%struct{} = address) when struct in [BillAddress, ShipAddress] do
 ...
end 

def address_formatter(_), do: raise "AddressError :... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-10-10</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/til">til</a> </div><div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div></div></div></div> </div></a><a href="/blog/micro-5"><div class="card card-hover"><div class="row text-center"><h4></h4></div> <div>I'm 33 years old and I only learned how to spell "doesn't" this year. Getting enough practice where I almost spell it right the first time.  (I think I used to default to dosen't).... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-09-29</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/micro">micro</a> </div></div></div></div> </div></a><a href="/blog/til-uuidv4-vs-uuid-v7"><div class="card card-hover"><div class="row text-center"><h4>TIL UUIDv4 vs UUIDv7</h4></div> <div>I've always run with UUID v4 because it's the default for the `Ecto.UUID` library in Elixir. However a coworker recommended UUID v7. Having never really looked into UUID other than to implement as a primary key the distinction was news to me. 

 Effectively;
 
- UUID v4 is a totally random hash that is generated and extremely unlikely to ever conflict with any other generated UUID.
- UUID v7 also ... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-09-25</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/til">til</a> </div><div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div></div></div></div> </div></a><a href="/blog/til-insert-into-with-select-constraints"><div class="card card-hover"><div class="row text-center"><h4>TIL INSERT INTO with SELECT constraints</h4></div> <div>In the past month I've had to write a lot of SQL to migrate a system and split existing "locations" into tenants ie. migrating data from a `public` schema to a tenant's schema is gets messy due to foreign key constraints. Order of operations is important but sometimes you still find yourself in a corner. 

 In instances where I already have data in the tenant schema, for example `customers` and I ... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-09-18</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/til">til</a> </div><div class="tag my-1"><a href="/blog/tags/programming">programming</a> </div></div></div></div> </div></a><a href="/blog/july-2024-music-recap"><div class="card card-hover"><div class="row text-center"><h4>July 2024 Music Recap</h4></div> <div>One of the things I miss most about Spotify is Wrapped.  I always feel a pang of envy at the end of the year when people are sharing their Wrapped stats, to the point where I've thought about trying to hook into Tidal's underdeveloped API and create something similar for myself. Alas, maybe someday, it's about #5 on the project backlog.
 
In the meantime I make due with Tidal's monthly email of my... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-08-06</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/music">music</a> </div></div></div></div> </div></a><a href="/blog/micro-3"><div class="card card-hover"><div class="row text-center"><h4></h4></div> <div>Why Cybertruck when you could [Cyberduck](https://cyberduck.io/) ! I think I've been using Cyberduck for 100% of my FTP needs for at least 15 years.  Such a rock solid piece of software.... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-07-24</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/micro">micro</a> </div></div></div></div> </div></a><a href="/blog/micro-2"><div class="card card-hover"><div class="row text-center"><h4></h4></div> <div>SQL is the way, SQL is always the way!  I killed myself for hours this morning trying to query/clean some data in Rails, started writing raw SQL and had it sorted in 30 minutes!... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-07-04</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/micro">micro</a> </div></div></div></div> </div></a><a href="/blog/micro"><div class="card card-hover"><div class="row text-center"><h4></h4></div> <div>Apparently I destroyed my fingerprint while climbing over the weekend- my MBP fingerprint no longer seems to work. It's a small feature but very missed when it doesn't work.... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-07-03</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/micro">micro</a> </div></div></div></div> </div></a><a href="/blog/micro-1"><div class="card card-hover"><div class="row text-center"><h4></h4></div> <div>Morning Jog in Guelph. Rainy days like yesterday are perfect! <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-07-01</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/micro">micro</a> </div><div class="tag my-1"><a href="/blog/tags/photo">photo</a> </div></div></div></div> </div></a><a href="/blog/just-setup-microblogs-here"><div class="card card-hover"><div class="row text-center"><h4>(Untitled)</h4></div> <div>I just modified the Journal theme to allow for microblogging.... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-06-21</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/micro">micro</a> </div></div></div></div> </div></a><a href="/blog/the-uw-encampment"><div class="card card-hover"><div class="row text-center"><h4>The UW Encampment</h4></div> <div>I havve been watching with admiration and humility as students around the world have set up encampments demanding <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-06-14</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/musings">musings</a> </div><div class="tag my-1"><a href="/blog/tags/life">life</a> </div></div></div></div> </div></a><a href="/blog/on-rewilding-the-internet"><div class="card card-hover"><div class="row text-center"><h4>On “Rewilding The Internet”</h4></div> <div>&lt;script>
    import Image from '../../lib/components/content/Image.svelte'
&lt;/script>

&lt;Image path="/images/blog/2024/wild.webp" alt="A wild place my daughter and I discoverd in West Vancover"/>
I’ve been hearing this  term a lot lately and, frankly, I think it is a fairly myopic view. If you live in a walled garden such as Facebook, Instagram, TikTok, The Artist Formally Known as Twitter, etc. the... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-06-04</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/the internet">the internet</a> </div><div class="tag my-1"><a href="/blog/tags/musings">musings</a> </div></div></div></div> </div></a><a href="/blog/back-up-and-blogging"><div class="card card-hover"><div class="row text-center"><h4>Back up and blogging? Sort of.</h4></div> <div>In conjunction with me finally overhauling my website I've decided to get on the blogging train. I've had a personal blog on and off since at least 2005.  I started when I was 12 or 13 just adding text to a an HTML file and copying it into Geocities then Tripod then Dreamhost.  Sometime around 2006 or 2007 I did the "Famous Five Minute Install" of WordPress and stayed the course there for a few ye... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-05-27</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/meta">meta</a> </div><div class="tag my-1"><a href="/blog/tags/life">life</a> </div></div></div></div> </div></a><a href="/blog/til-2024-01-03"><div class="card card-hover"><div class="row text-center"><h4>Today I Learned ~D[2024-01-03]</h4></div> <div>You can use Erlang's `tc` function to see how many microseconds a function takes.  For example, say you were curious if `Enum.filter/2` or `Kernel.--/2` took longer:

Example: 
```elixir
$iex> vals = [1, 2, 3, 4, 5]
$iex> :timer.tc(Enum, :filter, [vals, &amp;rem(&amp;1, 2) == 1])
{20, [1, 3, 5]}

$iex> :timer.tc(Kernel, :--, [vals, [2, 4]])
{3, [1, 3, 5]}
```

`Kernel.--` or `vals -- [2, 4]` took 3 micro ... <div class="post-card--meta meta d-flex align-center"><span>🗓️️ 2024-01-03</span> <div class="d-flex flex-wrab ml-5 align-center"><span data-svelte-h="svelte-10z6e9h">🔖</span> <div class="tag my-1"><a href="/blog/tags/TIL">TIL</a> </div><div class="tag my-1"><a href="/blog/tags/Elixir">Elixir</a> </div><div class="tag my-1"><a href="/blog/tags/Programming">Programming</a> </div></div></div></div> </div></a></main></div> <footer class="col align-items-center"><span class="btn-nav--bg"><a href="/" class="btn btn-nav btn-primary">Home</a></span> <span class="btn-nav--bg"><a href="https://blog.travisfantina.com" class="btn btn-nav btn-primary">Blog</a></span> <span class="btn-nav--bg"><a href="/about" class="btn btn-nav btn-primary">About</a></span> <span class="btn-nav--bg"><a href="/projects" class="btn btn-nav btn-primary">Projects</a></span></footer> 
			<script type="application/json" data-sveltekit-fetched data-url="/api/feed">{"status":200,"statusText":"","headers":{},"body":"{\"content\":[{\"meta\":{\"title\":\"\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2025-07-01\",\"tags\":[\"micro\"],\"rawContent\":\"Before sending that email to hundreds of thosuands of customers... ask yourself, is announcing \\\"we now have dark mode\\\" broadcast worthy?\"},\"path\":\"/blog/micro-9\"},{\"meta\":{\"title\":\"The NYC Primary\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2025-06-25\",\"tags\":[\"musings\",\"current events\"],\"rawContent\":\"It’s been a rough couple of weeks in world news.  A lot has been going on that I’ve felt moved to comment on but haven’t had the heart to actually write it down. Zohran Mamdani’s victory in the NYC primary is a ray of sunshine in otherwise very dark times.  It’s a powerful reminder that progressives can win  even against massive entrenched interests. In the final weeks of the race billionaires and powerful centrist democrats such as Bill Clinton were pouring millions of dollars and coveted endorsements, respectively, into Cuomo’s campaign in what amounted to an attack on Mamdani. The attack failed.  The voice of the people could not be silenced. \\nBig as New York is, on the scale of everything else going on in the world this is kind of small potatoes, but a win for progressives anywhere is a victory for progressives everywhere.  I’ll take it.\"},\"path\":\"/blog/the-nyc-primary\"},{\"meta\":{\"title\":\"The Underground Railroad\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"book_author\":\"Colsen Whitehead\",\"date\":\"2025-06-24\",\"tags\":[\"books\"],\"rawContent\":\"I've been on a Colson Whitehead tear in the past year having started five of his books, finishing four of them. This year I raced through the Ray Carney series (*Harlem Shuffle* and *Crook Manifesto*) and I just finished *The Underground Railroad*. While not my favourite of his books *The Underground Railroad* was still a compelling read. Whitehead has this talent that I struggle to explain. He's very good at writing historical fiction that makes you sad or angry at the *history* without feeling sad or angry with the *story*. That's what buoys up books like  *The Underground Railroad*; it was a fantastic read, I daresay a borderline *fun* read but it also served as a poignant reminder of the atrocities of chattel slavery to the point of being physically moved. This is undoubtedly a hard balance to strike but Whitehead has managed to do it in nearly every book I've written.\"},\"path\":\"/blog/the-underground-railroad\"},{\"meta\":{\"title\":\"White Fragility\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"book_author\":\"Robin DiAngelo\",\"date\":\"2025-06-16\",\"tags\":[\"books\"],\"rawContent\":\"**Full Title**: *White Fragility: Why It's So Hard for White People to Talk About Racism*\\nAlthough it's a short read this book was dense. That's not to say it was a difficult read; quite the contrary, it was extremely approachable but every single page was so laden with facts each paragraph served as an essay unto itself. White Fragility asks left-leaning progressively minded folks to examine their own attitudes towards race; are we more concerned with being racist or being perceived as racist? Do we only think of racists as \\\"very bad people\\\" the kind who form lynch mobs or march with tiki torches? Or are we able to see how our own race has given us an unfair advantage? Are we able to see how we silently perpetuate racial disparities to suit our own needs? Do we do this in subtle subconscious ways or more overtly by proclaiming that we are \\\"colour blind\\\" and therefore race doesn't matter?\\n\\nNot only did *White Fragility* implicate me in my own racism, it also gave  me e pause to reflect other areas in which I have blindspots. Benefiting from the various privileges I have, not just as a consequence of my race but also my gender, sexual identity, appearance etc. What things have I said or done over the years that uphold and reenforce the patriarchy? Am I excluding disabled people in my actions (a very salient question for somebody who designs and builds websites, I reckon this site is not fully WCAG compliant).\\n\\nDefinitely worth a read, likely a second in a few years.\"},\"path\":\"/blog/white_fragility\"},{\"meta\":{\"title\":\" Weekly Round Up: June 13, 2025 👻\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2025-06-13\",\"tags\":[\"programming\",\"life\"],\"rawContent\":\"It was a week of state machines. Two separate Rails projects, two separate state machine libraries (`state_machines` and `aasm`), both sending emails. One is a fairly straightforward project for a department of education, it's an old codebase but Objective built it and has been working on it ever since.  As such, it's fairly clean and straightforward for it's age. I think that the more contractors and firms a codebase passes through the more muddled it gets. I've been working on this codebase for about two years now. The entire time I've been working to convert an old paper process to a digital one, it's not an overly ambitious project but the budgeting has necessitated a slower pace of development. With only a few months left in the yearly budget (in education I guess the fiscal year ends with the school year) I was asked to quickly implement a form that allows users to draft a custom email message and attach a PDF. It's been a while since I've done this with Rails, my last experience doing so was in the Paperclip days and that was not too fun. I've been pleasantly surprised with ActiveStorage, it's much more plug-and-play then I recall (I've also been developing a lot longer now).\\n\\nThe other project is far more involved, my new full-time at gig at [Built](https://www.builtapp.com/). It's been exciting to work in tandem with another developer who has been handling the front-end work.  Coming from a small agency I've always developed features full stack. Part of why I wanted to switch to a dedicated product team was to have experiences like this one where a greater degree of planning and coordination between developers was required. I started by creating a model last week and writing as many tests as I thought would be relevant. I've been through TDD phases in the past; but I think in small teams and projects TDD offers diminishing returns. It makes a lot of sense in a scenario like this, even on a fairly small team, since I'm developing features that I can't be able to test in the browser until the other developer has her features in place. She in turn won't be able to know if the front end works until my code is merged into her branch. This feature was the bulk of my week but it came together in time for some Friday afternoon QA of which I'm sure there will be several things to fix on Monday morning.\"},\"path\":\"/blog/jun-13-2025-weekly-roundup\"},{\"meta\":{\"title\":\"Multi-tenancy with Phoenix and Elixir\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\u003Cspan style=\\\"white-space: pre-wrap;\\\">Photo by \u003C/span>\u003Ca href=\\\"https://unsplash.com/@jentheodore?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit\\\">\u003Cspan style=\\\"white-space: pre-wrap;\\\">Jen Theodore\u003C/span>\u003C/a>\u003Cspan style=\\\"white-space: pre-wrap;\\\"> / \u003C/span>\u003Ca href=\\\"https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit\\\">\u003Cspan style=\\\"white-space: pre-wrap;\\\">Unsplash\u003C/span>\u003C/a>\",\"date\":\"2025-06-04\",\"tags\":[\"programming\",\"elixir\"],\"rawContent\":\"There are lots of good places to start with multi-tenancy in Elixir (although I'd recommend Ecto's own docs for either [foreign keys](https://hexdocs.pm/ecto/multi-tenancy-with-foreign-keys.html) or [postgres schemas](https://hexdocs.pm/ecto/multi-tenancy-with-query-prefixes.html) ). Most of the write-ups and tutorials start the same way \\\"generate a new Phoenix application with `mix phx.new` \\\". While, this is great if your starting an enterprise SASS app from scratch but it leaves something to be desired if you, like I was, are migrating an existing codebase with thousands of users, and products to a multi tenant application. I recently went through this with an enterprise client and there were enough pitfalls and interesting problems to solve that it seemed to warrant a detailed post. \\n\\n I believe the solution I put together is both effective and elegant but it is not without it's pain points. Mainly, if you are going to use PostgreSQL schemas (which I did) you are going to have to migrate your existing data into said prefixes. There is no easy way around this, it's just a slog you have to do; more on that later.\\n \\n## Schemas?\\n \\nI went back and forth for a while, I finally settled on query prefixes as they felt a little more elegant; segmenting data without having to add new foreign keys to any columns. It also makes it easy to migrate or wipe customer data if needed. Admittedly, if your managing tens of thousands of tenants in a single database this approach will be a bottleneck. In my case that was not a concern; there are two current tenants and the client only expects to add a few tenants ever year if that.\\nAs mentioned, Ecto has great docs on setting up schemas; however I opted to use a dependency called `Triplex` mostly for the sake of time (about a week in I realized I could have rewritten most the required features in a day or two but we had about a month to make this transition so a refactor at this point seemed like overkill). Schemas work because we are using PostgreSQL, you can kind of hack together \\\"schemas\\\" with MySQL but under the veil it's just separate databases, I can't vouch for that approach because my Elixir projects are mostly in Postgres.\\n \\nThe first big hurdle is ensuring that your queries are run in the the right schema. By default Ecto is going to run queries in the `public` schema. On any given query you can change this by passing in a `prefix:` option, ie: `Repo.one(query, prefix: \\\"some_prefix\\\")`. Now rewriting hundreds or thousands of `Repo` actions with a variable prefix is not exactly convenient but it's imperative to ensure queries are scoped to the correct schema. Just imagine the catastrophic breach if you had Customer A getting back Customer B's data!\\n \\nThankfully you do not have to rewrite all your queries explicitly calling a prefix. There are some handy built-in behaviours from `Ecto.Repo`. Enter Repo hooks! `Ecto.Repo` comes with some great behaviours that allow one to effectively write `Repo.one(query, prefix: \\\"some_prefix\\\")` without actually writing it for *every single* query! You can implement `prepare_query/3` which to filter and modify the prefix. You add these hooks to `YourApp.Repo` This is `prepare_query/3` in it's simplest form:\\n \\n```elixir\\n@impl true \\ndef prepare_query(_operation, query, opts) do \\n\\topts = Keyword.put(opts, :prefix, \\\"some_prefix\\\")\\n\\t{query, opts}\\nend\\n```\\n \\nNow all queries will be looking at the `some_prefix` prefix rather than the `public` prefix. In our app we had a few tables that we _wanted_ scoped to the `public` query? For example you may have an `admins` table, or possibly `oban_jobs` , `tenants` , etc. You can handle this in a few ways:\\n \\n```elixir\\n@impl true \\ndef prepare_query(_operation, query, opts) do \\n\\tif opts[:skip_prefix] do \\n\\t\\t{query, opts}\\n\\telse \\n\\t\\topts = Keyword.put(opts, :prefix, \\\"some_prefix\\\")\\n\\t\\t{query, opts}\\n\\tend \\nend\\n```\\n \\nThis works although it necessitates passing `skip_prefix: true` to all your Repo calls; likely fewer then before but still kind of defeating the purpose of `prepare_query/3` .\\n \\n```elixir\\n@sources ~w[admins oban_jobs oban_peers customer_pricing]\\n\\n@impl true \\ndef prepare_query(_operation, %Ecto.Query{from: %{source: {source, _}}} = query, opts) when source in @sources do \\n\\t{query, opts}\\nend \\n\\ndef prepare_query(_operation, query, opts) do \\n... \\nend\\n```\\n \\nBy pattern matching on your allowed tables you can bypass your `prefix` override. I used a combination of both of the above approaches with a list of allowed source tables as well as the option to `skip_prefix` which adds an manual override to the API. In theory you shouldn't need it but you never know, tests, edge cases, shrugs...\\n \\n## Tenant Selection\\n \\nAt this point we've converted every query in the application to use a dynamic prefix in about 10 lines of code. Not bad but it's also not dynamic, I've hard coded `some_prefix` into my queries. Before we make the actual hook dynamic we need to determine how Phoenix is going to recognize the tenant. There are many ways of doing this, in my case, for now, we are using subdomains. \\n\\n Since the subdomain is available on the `conn.host`, I set up a plug to fetch the subdomain:\\n \\n```elixir\\ndefmodule MyApp.TenantPlug \\n...\\n\\ndef selct_organization_from_domain(conn, _opts) do \\n\\tsubdomain =  get_subdomain(conn) \\n\\tput_session(conn, :tenant, subdomain)\\nend\\n\\ndefp get_subdomain(%{host: host}) do \\n\\t[subdomain | _] = String.split(host, \\\".\\\")\\n\\tsubdomain\\nend\\n```\\n \\nThis gets the subdomain and puts it in the session (which is not strictly necessary but is nice to have). Next lets pass it to Repo; as with the queries, one need not rewrite all `Repo` calls passing in a `:subdomain` option, here Elixir/Phoenix has your back. In Phoenix, each browser session is a unique process and that process can pass data to itself. Back in `Repo` I added these little helpers:\\n \\n```elixir\\n@tenant_key {__MODULE__, :tenant}\\n\\ndef put_tenant_subdomain(subdomain) do \\n\\tProcess.put(@tennat_key, subdomain)\\nend\\t\\n\\ndef get_tenant_subdomain do \\n\\tProcess.get(@tenant_key)\\nend\\n```\\n \\nNow back in the `TennatPlug` we can add the subdomain to the process:\\n \\n```elixir\\ndef selct_organization_from_domain(conn, _opts) do \\n\\tsubdomain =  get_subdomain(conn)\\n\\tRepo.put_tenant_subdomain(subdomain) \\n\\tput_session(conn, :tenant, subdomain)\\nend\\n```\\n \\nA second `Repo` behaviour can be used to pass options to the Repo call: `default_options/1` . Rather than explicitly writing `opts = Keyword.put(opts, :prefix, \\\"some_prefix\\\")` in the `prepare_query/3` hook `default_options/1` will set up your `opts` before the Repo function runs. From there we call `get_tenant_subdomain/0` to retrieve the subdomain/query prefix we set in the plug:\\n \\n```elixir\\n@impl true \\ndef default_options(_operation) do \\n\\t[prefix: get_tenant_subdomain()]\\nend \\n\\n@tenant_key {__MODULE__, :tenant_subdomain}\\ndef get_tenant_subdomain, do: Process.get(@tenant_key)\\n```\\n \\nLike `prepare_query/3` , `default_options/1` will run with every query.\\n \\nWith this implemented, navigating to a specific subdomain will set the tenant in the current process (as well as in the session) and any database queries in that session will be scoped to the tenant's schema. Putting it all together we have something like this in `repo.ex`\\n \\n---\\n\\n```elixir\\n@allowed_sources ~w[oban_jobs tenants]\\n\\n  @impl true\\n  def default_options(_operation) do\\n    [prefix: get_tenant_subdomain.get()]\\n  end\\n\\n  @impl true\\n  def prepare_query(_operation, %Ecto.Query{from: %{source: {source, _}}} = query, opts)\\n      when source in @allowed_sources do\\n    opts = Keyword.put(opts, :prefix, \\\"public\\\")\\n    {query, opts}\\n  end\\n\\n  def prepare_query(_operation, query, opts) do \\n  \\tif opts[:skip_prefix] do \\n\\t\\t{query, opts}\\n\\telse \\n\\t\\topts = Keyword.put(opts, :prefix, \\\"some_prefix\\\")\\n\\t\\t{query, opts}\\n\\tend \\n  end \\n\\n  @tenant_key {__MODULE__, :tenant}\\n\\n  def put_tenant_subdomain(subdomain) do \\n\\t   Process.put(@tennat_key, subdomain)\\n  end\\t\\n\\n  def get_tenant_subdomain do \\n\\t   Process.get(@tenant_key)\\n  end\\n```\\n \\nThe simplified version of my `tenant_selection_plug.ex` looks like:\\n```\\n  def selct_organization_from_domain(conn, _opts) do \\n\\t   subdomain =  get_subdomain(conn)\\n\\t   Repo.put_tenant_subdomain(subdomain) \\n\\t   put_session(conn, :tenant, subdomain)\\n  end\\n\\n  defp get_subdomain(%{host: host}) do \\n   \\t[subdomain | _] = String.split(host, \\\".\\\")\\n\\t  subdomain\\n  end\\nend\\n```\\n\\nIn production we are handling a lot more such as authorization with Guardian but this show how simple it is to get a subdomain and add it to the session. \\nThe above is a fairly bare-bones approach our final project had a lot more customization and ended up being organized a bit differently; for example, we extracted functions dealing with getting and setting `@tenant_key`s in the process to their own module. My hope is that the above lays the groundwork for anyone looking to do something similar.\\n\\n \\n## Data Migration\\n \\nI wish I had a solution half as slick as Ecto's behaviours make querying database schemas. I was unable to find an elegant way to migrate relevant data to specific schemas so I was forced to do it with good old SQL.\\n \\n```sql\\n-- compy customers\\nINSERT INTO salt_lake.locations SELECT * FROM public.locations WHERE id = 'salt_lake_location_id';\\n\\n-- copy customers \\nINSERT INTO salt_lake.customers SELECT * FROM public.customers WHERE location_id = 'salt_lake_location_id';\\n```\\n \\nI had about 50 queries similar to this. Fortunately, tenants were mapped to locations and at the time of the migration the client only had two tenants (the system was migrating from a product business to a consulting business). I ran these queries twice replacing `salt_lake` with `bakersfield` on the second iteration. In my case due to the way the system was originally designed to work with an external system (look'en at you Quickbooks) and some changes the customer was making to how that system would be used this migration ended up being a bit more harry than expected.  I had to write several ad-hoc queries that looked less like the above and more like:\\n \\n```sql\\nINSERT INTO salt_lake.qb_orders SELECT qb.* FROM qb_orders qb JOIN orders o ON o.qb_order_id = qb.id JOIN customers c on o.customer_id = c.id WHERE NOT EXISTS (SELECT 1 FROM salt_lake.qb_orders slcqb WHERE slcqb.id = qb.id) AND c.name ILIKE '%A Problematic Customer%'\\n```\\n \\nAgain, that's not the fault of the multi-tenancy setup, migrating data in any complex system is always going to have it's prickly bits. If anyone has ideas for a more elegant migration pattern (first two queries, ignore the last one that an unfortunate specific), I'm all ears, shoot me an email self[at]travisfantina.com.\"},\"path\":\"/blog/multi-tenancy-with-phoenix-and-elixir\"},{\"meta\":{\"title\":\"Today I Learned ~D[2025-06-02]\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2025-06-02\",\"tags\":[\"til\"],\"rawContent\":\"File this under \\\"things I knew but have to look up everytime\\\"...\\n\\nIf you want to spin up a Docker container without a service like `postgres` , for example if you had a fully seeded DB on your machine and didn't want to go through the hassle of copying/re-seeding in Docker, you can do so with `host.docker.internal`.\\nIn `docker-compose.yml` you can write:\\n\\n```dockerfile\\nenvironment:\\n      - DB_HOST=host.docker.internal\\n      - DB_PORT=5432\\n      - DB_USERNAME=your_pg_user\\n      - DB_PASSWORD=your_password\\n      - DB_NAME=your_db\\n```\\n\\nBecause I switch projects a lot (agency life) there are occasions where a legacy codebase just stops working (system updates, depercations, etc.) at times like these I like falling back to the Docker container (upgrading the project is not always an option) but I may not want to loose/copy all my data from when I worked on the project before.  Yes, I know dev data should be ephemeral and easy to reseed but in the real world this is not always how things work!\"},\"path\":\"/blog/til-2025-06-02\"},{\"meta\":{\"title\":\"Everything is Tuberculuosis \",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"book_author\":\"John Green\",\"date\":\"2025-05-29\",\"tags\":[\"books\"],\"rawContent\":\"I've never read anything from John Green but I used to watch his YouTube channel. As a young adult fiction author; I felt his handling of such a broad and complex topic, like the history of tuberculosis and it's impacts on the world today would be both engaging and digestable. I was not wrong in this assumption. This book was fast even by quick read standards, I could have read another two or three volumns but I think Green was able to say what he wanted to: Tuberculuosis is not an archaic/cured disease it still impacts hundreds of millions of people who are for the most part neglected due to their poverty and or race. There is hope for the future but only if people speak up and speak out advocating both for themselves (Green points to several activists in both India and Sierra Leone) and others as we all must.\\n\\nSome quotes and ideas that stayed with me:\\n\\n\\\"Framing illness as even involving morality seems to me to be a mistake... biology has no moral compass it does not punish the evil and reward the good, it doesn't even know about evil and good.  Stigma is a way of saying 'you deserve to have this happen' but implied within the stigma is also 'and I don't deserve it so I don't need to worry about it happening to me'\\\".\\n\\n\\\"There are many acronyms within the field of tuberculosis global health, like any field, loves to shorten its phrases to make them obvious to experts and inaccessable to neophites. \\n\\nTB has always been racially charged, first it was seen as a disease of sophisication and as such those of \\\"darker skin\\\" could not get it; then with industrialization it became known as a disease of the poor and has since been used, infurieatingly, as proof of white racial superiority. This is ancient history; in my life time:\\n* J&J actively priced third world health systems out of bedaquiline - unconscionable! \\n* In 2001 the head of  USAid insinuated that HIV medicines couldn't be distributed to Africa because: \\\"Africans don't know what watches and clocks are... when you say 'take it at ten o'clock' people will say 'what do you mean by ten o'clock'\\\" 🤯\\n* Between the mid-1980s the mid 2000s the commingling of tuberculuosis and HIV led to more deaths than the combined fatalities of WWI and WII combined!\\n\\nEnding the post with a positive quote toward the end of the book:\\n\\\"Mere dispare never tells the whole human story, as much as dispare would like to insist otherwise. Hopelessness has the insdious talent of explaining everything; the reason that x or y sucks is that everything sucks. The reason your misearble is that misery is the correct response to the world as we find it and so on. I am prone to dispare so I know it's powerful vdespairoice, it just doesn't happen to be true. Here's the truth as I see it; vicious cycles are common, injustice and unfairness permeate every aspect of human life, but virtous cycles are also possible.\\\"\"},\"path\":\"/blog/everything-is-tuberculuosis\"},{\"meta\":{\"title\":\"The Anxious Generation\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"book_author\":\"Jonathan Haidt\",\"date\":\"2025-05-29\",\"tags\":[\"books\"],\"rawContent\":\"America has a long history of moral panics, the phone, rock music, rap music, etc. I always want to be careful about blaming \\\"that new thing the kids are doing\\\" and therefore I try to offer a balanced perspective when somebody starts talking about \\\"the kids and the phones\\\".  Needless to say I went into this book with a healthy dose of skepticism. \\n\\nRight off the bat my skepticism was rewarded in the form of a lengthy anecdote about sending kids to Mars as test subjects, which Haidt uses as some kind of metaphor for the way big tech uses kids as test subjects in social media, advertising, etc. And while I agree with the point I felt the metaphor was heavy handed and it kind of commenced the book with a bad taste in my mouth, although I still kept an open mind.  \\nFortunately, the Mars story was the first and last of such abstract anecdotes and the book settles into a  well researched and factual deconstruction of the way children have been raised for the past 30+ years (essentially my from my childhood to now: my daughters' childhoods) and the impact that phones and social media has played on that. I was pleased that Haidt didn't only point fingers at \\\"the phones\\\". He deconstructed the \\\"stranger danger\\\" panics of the 80s and 90s which directly led to more sedentary inside time for kids and more fear mongering for both kids and parents. \\n\\nNothing happens in a vacuum and Haidt got the approach right; yes social media drives young adult anxiety and depression but it it's particularly virulent because of these devices which are in our pockets all the time. And yes we have our phones on us all the time but we also are less equipped to have real-world interactions because of parental fears about kidnappers and molesters which arose directly from 1980s culture. Yes there was a lot of fear in the 80s and 90s most of it misplaced, but there was also a new emphasis on child rearing as an active pursuit rather than a passive one. This \\\"active\\\" parent was more than just being loving, supportive and attentive it led to the phenomenon of \\\"helicopter parenting\\\" because heaven forbid kids were on their own for five minutes. \\n\\nBack to my original skepticism I think it was largely misplaced because the scope of this books was far broader than just \\\"phones are bad\\\", _The Anxious Generation_ is a survey of the evolution of children over two generations. As much as I like to push back against the panic around phones; there is a reason we don't have a TV in our home. There is a reason we send our daughter to a Waldorf school. I acutely feel so much of what Haidt's saying, I think too much media (social or traditional) at a young age is harmful not only as a leaver on anxiety and depression. Kids have wonderful imaginations and substituting this natural creativity with a screen of any kind can severely damage it. I see a pipeline from kids in front of the TV; to kids in school finding the textbook answer; to compliant adults who do what their told- adding value to the military industrial complex, and being good consumers. (Now who's panicking‽) \\n\\nI volunteered for a few years at a youth group for kids roughly 6-9 it was fascinating to observe the kids who, at that young age, had phones and those who did not. The ones who had phones were almost always on them; although they were still able to engage with the group and participate in meaningful ways the phone was almost always in hand. Not only that but these children seemed older than they should have been, more worldly. They would reference jokes and memes laughing at the humour but not understanding the point because they were 9 years old not 25. Their lived experience did not match the content they were viewing and it was ageing them without making them wise. \\n\\nI read this book *because* I have two daughters but even for those without children it serves as a stark cultural survey. A rebuke not only for parents whose children faces are always aglow in blue light but even for adults such as myself who spend too much time doom scrolling.\"},\"path\":\"/blog/the-anxious-generation\"},{\"meta\":{\"title\":\"Today I Learned ~D[2025-05-22]\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2025-05-22\",\"tags\":[\"til\"],\"rawContent\":\"There is only one `false` in Ruby... Or more broadly speaking since everything is an object, for the sake of efficency in memory management any object that can be referenced will be. Immutable primitives (such as `true`, `false`, `1`, `2`, etc.) will only ever reference themselves.  For example:\\n```ruby\\nfalse.object_id\\n=> 0\\nfalse.dup.object_id \\n=> 0\\nval = false \\nval.object_id \\n=> 0\\n```\\nDuplicating `false` only creates a reference to `false`. As opposed to a mutable primitive like a string:\\n```ruby\\ntrain = \\\"choo choo\\\"\\ntrain.object_id \\n=> 784160\\ntrain.dup.object_id\\n=> 784180\\n```\\nOf course this intuitively makes sense but I had never run up against it until I had a spec fail:\\n```ruby\\nexpect(response[:enabled]).to be true\\nexpect(response[:value]).to be \\\"location\\\"\\n\\n=> expected #\u003CString:140300> => \\\"location\\\"\\n  got #\u003CString:140320> => \\\"location\\\"\\n```\\nI did a double take before I realized that the `object_ids` were different.  The first spec passes because `true` is in immutable object. The second one fails because `location` is not!\\nFix that with: `expect(response[:value]).to eq \\\"location\\\"`\"},\"path\":\"/blog/til-there-is-only-one-false-in-ruby\"},{\"meta\":{\"title\":\"Today I Learned ~D[2025-05-14]\",\"hero\":\"\",\"alt\":\"\",\"date\":\"2025-05-15\",\"tags\":[\"TIL\",\"Programming\"],\"rawContent\":\"I recently switched jobs, which means new BitBucket credentials. However; I remain an occasional consultant with my last agency so I need to keep my public key associated with their BitBucket account... \\n\\n### The first thing I learned today\\nBitBucket won't let you use the same public key for multiple accounts.  I find this a little odd; like how AWS won't let you name a S3 bucket if the name already exists. It feels like a website telling you \\\"hey somebody is using this password lets try something else!\\\" I know RSA key pairs are more secure and unique than passwords but still 🤷\\n\\n### Making multiple pushes to git easy \\nYou can adjust your `~/.ssh/config` to easily push to separate git accounts with different keys:\\n```\\n# Assume this is your defaut\\nHost *\\n    UseKeychain yes \\n\\n# Key 2\\nHost altkey\\n    HostName bitbucket.org\\n    IdentityFile ~/.ssh/alt-key\\n    # you likely don't need this but it's nice to specify \\n    User git \\n```\\nThen add/update your remote origin:\\n```\\ngit remote add origin  git@altkey:bitbucket_account/repo.git\\n```\\nInstead of `bitbucket.org:account` you're just subbing in the `Host` alias. From there SSH doesn't care because it's been pointed to an `IdentityFile` it may not be the system default but it works.\\n### The git problems begin\\n`git push` and:\\n```\\nfatal: Could not read from remote repository.\\n\\nPlease make sure you have the correct access rights\\n```\\nOk fairly common lets go through the checklist:\\n1. The key is in BitBucket\\n2. BitBucket access is \\\"write\\\"\\n3. Check origin (see above)\\n4. Check permissions on the public key\\nAnd that's about where my expertise ended. \\n### Diving in\\nIt's useful to learn a bit of debugging, you can get pretty verbose with git logging by adding the environment variable`GIT_SSH_COMMAND=\\\"ssh -vvv` \\nPretty cool, and I was able to confirm a few differences between pushes to a working repo and the broken one. I was also able to give this log to an LLM and bounce a few ideas off it but ultimtally I don't feel like these logs gave me a lot of valuable info.\\n`git config --list` likewise is a handy flag to use but it didn't show me any glaring issues.\\nSo I started looking into the SSH config:\\n`ssh-add -l` which lists the RSA keys you have configured. To be sure I did `ssh-add -D` which removes your keys and then explicitly added both keys back with `ssh-add ~/.ssh/[key name]`\\nThen I ran `ssh -T git@altkey` this runs a test with the alias configured in the config file.  Infuriatingly, this returned: \\n```\\nauthenticated via ssh key.\\n\\nYou can use git to connect to Bitbucket. Shell access is disabled\\n```\\nSo my config was correct, I had access, but I could not push.\\nIt took me an hour but eventually I set the key for git to use explicitly:\\n```\\nGIT_SSH_COMMAND=\\\"ssh -i ~/.ssh/alt-key -o IdentitiesOnly=yes\\\" git clone git@altkey:bitbucket_account/repo.git\\n```\\n No further issues (with either repo).  \\nIt's unlikelly I'll remember specifically setting the `GIT_SSH_COMMAND` which is the main reason I'm writing this!\"},\"path\":\"/blog/til-2025-05-15\"},{\"meta\":{\"title\":\"Class Configs with Lambdas in Ruby\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2025-05-08\",\"tags\":[\"programming\"],\"rawContent\":\"I've been getting reacquainted with Ruby, diving into a well established project which has been blessed by numerous smart developers over the course of the past 10 years. I discovered an interesting pattern for gathering models (`ApplicationRecord` classes) that may or may not be eligible for some feature:\\nYou start with a mixin that creates a method for your classes to pass options to; as well as a method for determining if those options enable the feature or not:\\n```ruby \\nmodule ProvidesFeature \\n    class_methods do \\n        # pass this to the model class\\n        def features_provided(model, **opts)\\n            (@features ||= []) \u003C\u003C [model, opts]\\n        end\\n\\n        # call this to initialize class feature checks\\n        def feature_models(ctxt)\\n            features_provided.map do |args|\\n                DynamicFeature.new(ctxt, args)\\n            end\\n        end\\n    end \\nend \\n```\\nHere is an example `DynamicFeature` class instantiated above. This could be a bit simpler if you didn't want to pass any `context` in but a lot of the power of this approach comes from the flexibility an argument like context gives you:\\n```ruby\\nclass DyanmicFeature do \\n    def initialize(ctxt, config_args)\\n        @ctxt = ctxt\\n        configure(config_args)  \\n    end\\n\\n    def configure(ctxt, args = {})\\n        @should_provide_feature = args.fetch(:should_feature_be_provided) do \\n            -> (ctxt) { ctxt&.fetch(:person_is_admin, false) }\\n        end\\n    end \\n\\n    def can_feature?\\n        @should_provide_feature.call(@ctxt)\\n    end\\nend \\n```\\nPausing for a moment and breaking this down.  The `#configure` method is the main source of the magic.\\nFirst we try to get the keyword `:should_feature_be_provided` (implemented below).  If we get it we can return it's value; however, there is built in flexibility to this.  If `args` does not have a `:should_feature_be_provided` key then we can call a lambda with additional context.  Again, you don't need to pass anything else but I view this flexability as a strength if used strategically. \\nNow implement; in an active record ie. `Person`\\n```ruby \\nclass Person \u003C ApplicationRecord \\n    include ProvidesFeature \\n\\n    features_provided :person, \\n        should_feature_be_provided: -> (ctxt) { ctxt.person.is_admin? }\\n    \\n```\\n\\nYou can then easily gather any models that `ProvidesFeature`:\\n```ruby \\nApplicationRecord.subclasses.select { |klass| klass \u003C ProvidesFeature }\\n```\\nInstantiate `DynamicFeature` on each class (note we are passing some context that assumes there is a `person` with an `is_admin?` method. It's a little contrived but it illustrates the point: you can pass additional context in when the `feature_models` are built.\\n```ruby\\n.flat_map { |klass| klass.feature_models(ctxt) }\\n```\\nThen filter with `can_feature?`\\n```ruby\\n.select { |klass| klass.can_feature? }\\n```\\n\\nAt the start of this post I said this was an \\\"interesting pattern\\\"; not necessarily saying it's a _good_ one. I'm still fairly new to Ruby (despite having built a few production projects back in 2016 and 2018) and the OO pattern. Personally; I found the above extremely difficult to grok and even though I understand it I've found that, within the context of the project I'm working on, I've myself treadmilling through various files. In some ways I feel like, clever, as it is, this pattern may obfuscate a little too much but I'm open to feedback from those who have been in the OO world longer.\"},\"path\":\"/blog/class-configs-with-lambdas-in-ruby\"},{\"meta\":{\"title\":\"Weekly Roundup: May 2, 2025\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2025-05-02\",\"tags\":[\"programming\",\"life\"],\"rawContent\":\"This week I formally transitioned from my fulltime consulting gig at [Objective](https://objective.dev) for a fulltime gig at [Built For Teams](https://www.builtapp.com/) more details on that in a future post.  However; broadly speaking it means that I'm dusting off my Ruby skills, diving deeper into the realm of OO programing then I ever have before. \\n\\n### Farewell ASDF\\nLast Friday night I pulled a Flutter repo I'm working on with a friend. I started having all kinds of issues trying to install Cocoapods. `gem install cocoapods` but then `flutter run` produced this error:\\n```\\nWarning: CocoaPods is installed but broken. Skipping pod install.\\n...\\nError: CocoaPods not installed or not in valid state.\\n```\\nOk. So do some more research throw in a `sudo`, no luck. `pod version` produces this error:\\n```\\n\u003Cinternal:/Users/travis/.asdf/installs/ruby/3.3.5/lib/ruby/3.3.0/rubygems/core_ext/kernel_require.rb>:136:in `require': linked to incompatible /Users/travis/.asdf/installs/ruby/3.1.6/lib/libruby.3.1.dylib -\\n```\\nAh! I've seen this more than once! Ever since I shifted to a Ruby focused team at the start of the year I feel like Ruby version management has been an uphill slog.  I've `reshim`'d multiple times, removed versions of Ruby, removed the Ruby plugin, and reinstalled ASDF. Things work for a time but eventually I run into errors like the above. My hunch, which may be ovbious, is that something was wrong with my setup that was placing versions of Ruby inside other versions (`ruby/3.3.5/lib/ruby/3.3.0`); I'm not sure if the path is supposed to look like that but it doesn't make sensee to me. \\nI'm willing to take responsability here, it may be that my `$PATH` was misconfigured (although I attempted multiple times to proide a concise path for ASDF) or that something in _my_ system was messing with ASDF.  I love ASDF, it's served me very well for years.  Being able to remove `rvm` and `nvm` and seamlessly manage Elixir versions between projects was a breath of fresh air.  The docs are clear and concise, the tool provides enough functionality to get stuff done without getting in the way.  However; for whatever reason, the slog to get Ruby working just took its toll.  One of my coworkers mentioned Mise which is a drop in replacement for ASDF.  I installed it in about 30 seconds and in 45 seconds my project was running with Mise.  👏\"},\"path\":\"/blog/may-2-2025-weekly-roundup\"},{\"meta\":{\"title\":\"Weekly Roundup: Apr 25, 2025\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2025-04-25\",\"tags\":[\"programming\",\"life\"],\"rawContent\":\"At the agency, we have a customer who has asked that customers accept terms of service *before* checking out. This is for an Elixir project; mostly fullstack Elixir however the frontend has an odd assortment of sprinkles: StimulusJS and React.  I created a `terms_and_conditions` versions table and an accompanying view helper which will check a `terms_version_accepted` on the `user` record if the last `terms_and_conditions.inserted_at` date matches the `terms_version_accepted` then the user is shown an active \\\"proceed to checkout\\\" button, if not the button is disabled and a note asking them to acccept the terms of service will display.  \\nSince most of the Elixir projects I work on are fullstack (Phoenix LiveView) I don't often get to write API endpoints. The API work on this was admittidly very small, a simpl endpoint that takes the user's ID and updates the `terms_version_accepted` timestamp when they click \\\"accept\\\" in the modal. It returns a URL which we then append to checkout link allowing the user to proceed.\\nThis feature is due May 5th but I'm hoping to get onto the staging server on Monday or Tuesday.\\n\\n## Internal Tooling:\\nI've been using `fzf` for a while but I've wanted to filter only unstaged files, ideally whenever I type `git add ` I just want to see a list of unstaged files that I can add. Admittidly I got some help from AI to do write this up:\\n```\\nfunction git_add_unstaged() {\\n    local files\\n    files=$(git diff --name-only --diff-filter=ACMR | fzf --multi --preview 'git diff --color=always -- {}')\\n    if [[ -n \\\"$files\\\" ]]; then\\n        BUFFER=\\\"git add $files\\\"\\n        CURSOR=$#BUFFER\\n    fi\\n}\\n\\nfunction git_add_unstaged_widget() {\\n    if [[ $BUFFER == 'git add' ]] && [[ $CURSOR -eq $#BUFFER ]]; then \\n        git_add_unstaged \\n        zle redisplay\\n    else \\n        zle self-insert\\n    fi\\n}\\n\\nzle -N git_add_unstaged_widget \\nbindkey ' ' git_add_unstaged_widget\\n```\\nI'm wondering if I'll find the automatic `git add ` to be jarring or have situations such as a merge conflict where this may not work.  If so I can always fiddle with the `bindkey` but for right now I'm enjoying my new found `git add` speeds.\"},\"path\":\"/blog/apr-25-2025-weekly-roundup\"},{\"meta\":{\"title\":\"Cloud Atlas\",\"hero\":\"images/programming/wildelements/hero.png\",\"excerpt\":\"Review of Cloud Atlas\",\"alt\":\"Author: David Mitchell\",\"date\":\"2025-04-23\",\"tags\":[\"books\"],\"rawContent\":\"A phenomenal read, I was thoroughly hooked into this book from 1849 to 2346. I haven't read anything quite like this; in the hands of a less talented writer, structurally, it could have been a bit gimmicky.\\nHowever; Mitchell is talented provide one compelling story after another. Initially I worried that _The Pacific Journal of Adam Ewing_ and _Sloosha's Crossin' an' Evrythin' After_ were going to drag because of the verbosity or eccentricity (respectively) of the language but after a few pages I was engrossed in both.\"},\"path\":\"/blog/cloud_atlas\"},{\"meta\":{\"title\":\"Weekly Roundup: Apr 18, 2025\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2025-04-18\",\"tags\":[\"programming\",\"life\"],\"rawContent\":\"Working for a small agency I am fortunate to work on a number of fast moving projects simultaneously. For years I've failed to document what I do during the week but I'm going a little recap of my week. One part historical record, one part general interest. I'm posting it on my blog in the off chance that somebody reads it and, facing a similar problem will reach out I'm always happy to discuss what worked for me and what didn't work. It also doesn't hurt to put this stuff into the world to show that yes I actually _do_ work; I haven't always had the \\nmost active GitHub but most of my client projects a private/propriety. I'm easing into this, all week I was looking forward to this post; now, however, I realize I should have been working on this not cramming it in from memory on a Friday night.\\n\\nThis week was a balance between my ongoing Elixir projects and a newer (to me) Ruby project.\\n\\n   * For the past five years I've either supported, or been the lead dev on a large B2B ecommerce platform which handles a few million in daily sales. Over the winter the company began consolidating their North American and European processes which includes using said platform for sales in the EU. Although the hope is that the European process will align with the North American there are some relevant differences. For example in North America the client's product is technically considered a \\\"raw material\\\" which means there is no \\\"Value Added Tax\\\" (VAT); however in Europe, depending on the country of origin and the destination VAT may be charged, other relevant changes are shipping across borders, truck loading calculations and different invoicing procedures. At this point we are still in the research and discovery phase but I've been working with another developer to scope this project out and write some preliminary tests as research. \\n   * For another client I've been moving from a Quickbooks Online integration to Quickbooks Desktop, this is a multi-tenancy Elixir Phoenix app so I'll be keeping the Online functionality and just adding a connection to Quickbooks Desktop. The API docs for QBOnline are fairly good, this is not the case with QB Desktop, it's evident that Intuit either has the platform on life support or intentionally obfuscates the functionality to foster a consulting industry around the product. QB Desktop uses an SOAP XML type endpoint. Having wrangled fairly nasty endpoints with SAP I wanted to, if at all possible, avoid dealing directly with QB Desktop. I discovered a service called [Conductor](https://conductor.is/) that does the bulk of the heavy lifting and allows you to hit a very concise REST endpoint. \\n   * Since the beginning of the year I've been transitioning from primarily Elixir projects at the agency to a single Ruby based product. On that front I've been involved in an ongoing integration with BambooHR; partnering with Bamboo to pull employee data from their endpoint.\\n   * On a personal front I finished the migration of this blog from Ghost back to markdown files. I still love Ghost but managing my own instance and integrating it with my Garden proved to be more management than I wanted.\"},\"path\":\"/blog/apr-18-2025-weekly-roundup\"},{\"meta\":{\"title\":\"\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2025-04-16\",\"tags\":[\"micro\"],\"rawContent\":\"Experience has shown that if you put out a bug bounty your server will be hit repeatedly with requests to `/wp_admin` for the rest of eternity.\"},\"path\":\"/blog/micro-8\"},{\"meta\":{\"title\":\"Personal Heuristic: Make it Readable\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2025-04-14\",\"tags\":[\"programming\",\"elixir\"],\"rawContent\":\"I wrote this post back in January, just dusted it off to post today as I attempt to get back on the blogging horse.\\n***\\nToday I was refactoring a small module that makes calls to an SAP endpoint. The compiler got hung up because it couldn't find the value `item`.  It was an easy fix, my code looked like this:\\n```elixir \\nfor itm \u003C- data do\\n    %{\\\"MATNR\\\" => material, \\\"PSTYV\\\" => category, \\\"VBELN\\\" => so} = item\\n    %{material: material, category: category, so: so}\\nend\\n```\\nIt's easy to spot (especially if the compiler tells you exactly where it is); in the function head I wrote `itm` but down below I'm looking for `item`. Simple; yet this is not the first time something similar has happened to me. It's also not the first time I've specifically confused `itm` with `item` which led me to this conclusion: just write `item` every time. There is an odd switch in my brain that thinks I'm penalized by the character, and leaving `e` out of `item` will somehow make my code more terse. \\nWhile technically true, it's not worth it. It never is; just write `item`, everytime. People know what `item` is. `itm` is more ambiguous, not just because it only saves one letter, but it could be an abbreviation or some weird naming convention. Why put that mental load on someone, even yourself, reading through this code? \\nThis is a tiny example but it's magnified in function names. While `check_preq` may be quick to type and take up less horizontal space in an editor it's not immediately clear what this function does. I would argue that `get_purchase_requisition_number` is a much better function name; even if you know nothing about the function, the codebase, or programming in general you can read that and know what's supposed to happen.\\nOf course there are conventions, ie. `!` dangerous or `?` bankbook method endings in Ruby ie. `exitst?` will throw an error. These sorts of things require one to be a little familiar with the patterns of a language but that's ok that just means that I can write a function `get_purchase_requisition_number!` and anyone familiar with Ruby or Elixir will expect the function to raise or return an explicit value (as opposed to something wrapped in an `:ok` tuple).\\n\\nMoving forward I'm calling things what they are even if it comes with a dash of verbosity.\"},\"path\":\"/blog/make-it-readable\"},{\"meta\":{\"title\":\"Wintering: The Power of Rest and Retreat in Difficult Time\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"book_author\":\"Katherine May\",\"date\":\"2025-01-22\",\"tags\":[\"books\"],\"rawContent\":\"Reading 80% of this book was an exercise in torture. I'm always a little wary of personal memoirs cum self-help books but a few have been transformative for me (ie. Pamela Druckerman's *Bringing Up Bébé).* Katherine May hooked me early with this book, the prose was sharp and the anecdotes interesting however it very quickly devolved into anecdote after anecdote from a brief period in her life where, I guess, she was forced to work less?\\n \\nThis book is rife with privilege, which doesn't always bother me but in this particular case it seems to hallow where half of of the book is dedicated to the message of \\\"slow down, take it easy\\\" and the other half is, \\\"go to Iceland, trek the northern tundras of Sweden\\\".\\n \\nUltimately this book fell into the same trap as Gretchen Rubin's *Happier At Home* (a did not finish from last year); an extremely self absorbed upper-middle class person thinking that their experience = wisdom and is therefore worth writing an entire book.\\n \\nZero stars. Did not finish.\"},\"path\":\"/blog/wintering\"},{\"meta\":{\"title\":\"Today I Learned ~D[2025-01-10]\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2025-01-10\",\"tags\":[\"til\"],\"rawContent\":\"Today's TIL has a twist ending... so stick around. \\n\\n Elixir has a shortcut for creating anonymous functions. I've always written:\\n \\n```elixir\\ngreet = fn name -> \\\"Hello, #{name}!\\\" end \\n# which can be invoked\\ngreet.(\\\"Travis\\\")\\n# returns\\n\\\"Hello, Travis!\\\"\\n```\\n \\nHowever; I came across some tricky code with a case statement:\\n \\n```elixir\\ndocs = case type do \\n\\t:billing -> &[billing_act: &1]\\n    :shipping -> &[shipping_act: &1]\\nend \\n\\n# invocation\\ntype = :billing\\ndocs.(\\\"some customer\\\")\\n# returns \\n[billing_act: \\\"some customer\\\"]\\n```\\n \\nThis was very confusing to me, the fact that the anonymous function was a `case` form only further obfuscated what was happening. I thought it might be some `case` magic. \\n\\n No. Apparently you can short cut the aforementioned anonymous function declaration:\\n \\n```elixir\\ngreet = & \\\"Hello, #{&1}!\\\"\\n```\\n \\nYou treat this as any other anonymous function. You can even have multi-arity functions:\\n \\n```elixir\\ngreet = & \\\"Hello, #{&1} #{&2}!\\\"\\n# invocation \\ngreet.(\\\"Travis\\\", \\\"Fantina\\\")\\n# returns \\n\\\"Hello, Travis Fantina!\\\"\\n```\\n \\nIn my case the `case` statement could have also been written:\\n \\n```elixir\\ndocs = fn customer -> \\n\\tcase type do\\n\\t\\t:billing -> [billing_act: customer]\\n    \\t:shipping -> [shipping_act: customer]\\n\\tend\\nend\\n```\\n \\nPlot twist: This is not a TIL, apparently I learned this at least four years ago. That initial `case` function... the author was me four years ago!\"},\"path\":\"/blog/today-i-learned-2025-01-10\"},{\"meta\":{\"title\":\"Morning walk\",\"hero\":\"\",\"excerpt\":\"Interesting things I passed on the way to the library with my daughter here in Guelph.\",\"alt\":\"\u003Cspan style=\\\"white-space: pre-wrap;\\\">“Even pavement gives way to flowers” mural…\u003C/span>\",\"date\":\"2024-12-29\",\"tags\":[\"photo\"],\"rawContent\":\"\u003Cscript>\\n  import Image from '../../lib/components/content/Image.svelte'\\n\u003C/script>\\n\\n\\n\u003CImage path=\\\"/images/blog/2024/even-pavement-gives-way.jpg\\\" alt=\\\"Mural in Guelph: Even Pavement Gives Way To Flowers\\\"/>\\n\\nInteresting things I passed on the way to the library with my daughter here in Guelph.\\n\\n\u003CImage path=\\\"/images/blog/2024/antiquarian.jpg\\\" alt=\\\"An incredible house-converted-antique-shop-house? near my home\\\"/>\"},\"path\":\"/blog/morning-walk\"},{\"meta\":{\"title\":\"\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-12-24\",\"tags\":[\"micro\"],\"rawContent\":\"Merry Christmas everyone!\"},\"path\":\"/blog/micro-7\"},{\"meta\":{\"title\":\"OptConnect Marketing Site\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-12-24\",\"tags\":[\"portfolio\"],\"rawContent\":\"- Timeline: July 2020 - December 2021\\n- Status: the client switched to WordPress in May 2024\\n- Technologies: CraftCMS, Bootstrap\\n \\nI built OptConnect’s Marketing site with CraftCMS, an elegant and opinionated alternative to WordPress. Over the years I’ve been able to work on several different Craft projects. The ecosystem is noticeably smaller than WordPress (as to be expected), but in my experience the sites are easier to setup, manage and troubleshoot. Craft is a bit more opinionated, you won’t find any plugins to reskin your entire admin panel, but once you know it you can jump into any Craft instance and be up and running in no time.\\n \\nOptConnect was looking for a ground up redesign for their entire site and had chosen Craft. I worked with their design team to bring their vision to life.\\n \\nOver the years I’ve continued to offer support and updates to OptConnect as needed.\\n **We used an innovative mobile menu with categories displayed as a horizontal slider at the top.** \\nOptConnect had a lot of interesting shapes and elements which made for some front-end challenges. On mobile screens we opted to have the products displayed via a slider (see above) which allowed you to scroll horizontally to see more categories (above).\\n **Unique design specifications such as these rounded link buttons presented some challenges due to the varied sizes of the titles and descriptions.** **Press release cards that appeared on the site.** \\nPart of the work I did for OptConnect included migrating a WordPress blog to Craft. This included building a custom search tool with CraftCMS.\\n **Blog search** \\nThe final site was OptConnect's main marketing resource for over three years before it was replaced in the spring of 2024. During those three years I would occasionally provide support or help the marketing department build new content types in the CMS but for the most part once the project was turned over it ran smoothly.\"},\"path\":\"/blog/optconnect-marketing-site\"},{\"meta\":{\"title\":\"Canada Post Strike\",\"hero\":\"\",\"excerpt\":\"For the past few weeks postal workers in Canada have been on strike....\",\"alt\":\"\u003Cspan style=\\\"white-space: pre-wrap;\\\">Photo by \u003C/span>\u003Ca href=\\\"https://unsplash.com/@birkenwald?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit\\\">\u003Cspan style=\\\"white-space: pre-wrap;\\\">Birk Enwald\u003C/span>\u003C/a>\u003Cspan style=\\\"white-space: pre-wrap;\\\"> / \u003C/span>\u003Ca href=\\\"https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit\\\">\u003Cspan style=\\\"white-space: pre-wrap;\\\">Unsplash\u003C/span>\u003C/a>\",\"date\":\"2024-12-16\",\"tags\":[\"musings\",\"current events\"],\"rawContent\":\"\u003Cscript>\\n    import Image from '../../lib/components/content/Image.svelte'\\n\u003C/script>\\n\\n\\n\u003CImage path=\\\"/images/blog/2024/canada-post.jpg\\\" alt=\\\"Canada Post Strike\\\"/>\\n\\nPhoto by [Birk Enwald](https://unsplash.com/@birk_enwald)\\n\\nFor the past few weeks postal workers in Canada have been on strike. This has, of course, caused a fair amount of disruption during the Christmas season. Before I even knew the details of the strike I was naturally on the side of the workers. This is my default position: support labour. In general, I have found that if you are on the side of labour you’re on the right side of history. That’s not to say there aren’t corrupt unions, pointless strikes, or strikes that are regressive in their outcomes. However, in general strikes are a way for “the means of production” to get more of the benefits of that production. In other words; the people doing the work should be reaping the biggest rewards. Not the government, not billionaires, not some mythological “job creator” who took some risks a few years ago and has sat around collecting a “passive income” ever since. No, labor deserves the wealth, because they are the ones creating it.\\n\\nThe arguments against strikes have always been a bit baffling to me. There is no multi-millionaire union. The picket line almost always represents the low and middle class. These are our people, and they deserve more for what they do. Even highly paid people, in my view, deserve more for what they do. In Ontario it’s taken for granted that public teachers are “highly paid”. OTF, the Ontario Teachers Federation is very powerful and well monied. So what are these rich teachers making? After 10 years salaries appear to be capped at $102,000*. Great money for a single person, maybe more than you make, maybe more than you take home but I would ask; “why not more?” Teachers work damn hard, long hours, lots of stress and there is the years-long grind of even becoming a full time placed teacher in Ontario. Not to mention these are people who spend as much time with your kids as you do! Yes, I also think you should make more money, but it’s not a zero sum game; that’s kind of the point of unions. We are living through a time when the wealth gap looks more like France before the revolution; the middle class is shrinking and not because families are getting rich. A “high paying” job ten years ago may barely get you by; but billionaires own more than ever. In the past 25 years the world’s billionaires have added six trillion to their collective wealth. Anybody, any union, who takes a stand is fighting for all of us not just their workers.\\n\\nSure it’s an inconvenient time for a strike, but spare a thought. You may not get all your Christmas packages in time, but there are workers who literally cannot make ends meet 365 days a year, not just at Christmas. Strong labour is good for society as a whole!\\n\\n\\n\\n* [https://www150.statcan.gc.ca/n1/pub/81-582-x/2023001/tbl/tblc.6.3-eng.htm](https://www150.statcan.gc.ca/n1/pub/81-582-x/2023001/tbl/tblc.6.3-eng.htm)\\n** [https://www150.statcan.gc.ca/n1/pub/12-581-x/2023001/sec14-eng.htm](https://www150.statcan.gc.ca/n1/pub/12-581-x/2023001/sec14-eng.htm)\"},\"path\":\"/blog/canada-post-strike-2\"},{\"meta\":{\"title\":\"\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-11-11\",\"tags\":[\"micro\",\"programming\"],\"rawContent\":\"Not to rush Christmas, but I think I'll try my hand at Advent of Code this year.  It will be a good chance to play around with Rust.\"},\"path\":\"/blog/micro-6\"},{\"meta\":{\"title\":\"Adding a `soft_delete` to Ecto Multi pipelines\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-10-21\",\"tags\":[\"programming\",\"elixir\"],\"rawContent\":\"I'm a big fan of `Ecto,` Elixir's database wrapper. The `Multi` library lets you build up a series of operations that happen in order, if one fails the entire operation rolls back. Multi comes with the a lot of standard CRUD built in, `insert/4` , `update/4` , `delete/4` and their bulk counterparts `insert_all/5` , `update_all/5` and `delete_all/5` for acting on multiple records. \\n\\n I've been working on a project where we make use of the soft delete pattern, rather than calling `delete/4` on a record we generally `update/4` the record passing in a `deleted_at` timestamp:\\n \\n```elixir\\n|> Multi.update(:soft_delete, fn %{customer: customer} -> \\n\\tChangeset.change(customer, %{deleted_at: now})\\nend)\\n```\\n \\nThis works fine, and even updating multiple records one could take this approach:\\n \\n```elixir\\n|> Multi.update_all(:soft_delete, fn %{customers: customers} ->\\n\\tids = Enum.map(customers, & &1.id)\\n\\tfrom(c in Customer, where: c.id in ^ids, update: [set: [deleted_at: ^now]])\\nend, [])\\n```\\n \\nI was working on a new feature that will require a cascade of soft deletes, deleting multiple records, their associated records, their children, etc.  (As the second example above is doing).  Admittedly, I could have just utilized this `Multi.update_all/5` and put multiple steps into the `multi` . However; I thought continuously mapping specific ids, passing in `set: [deleted_at: ^now]` was a little cumbersome and not very idiomatic.  Mostly, I wanted to have a bit of fun wondering: \\\"what if Ecto.Multi had a `soft_delete_all/5` function?\\\" Of course it doesn't, this is a niche use case but it makes sense in this application so I dug in and found the task to be (as is the case with a lot of Elixir) surprisingly easy.\\n \\nJust like `update_all/5` I wanted to make sure `soft_delete_all` would handle queries or functions passed in. Pattern matching here using the `is_function/1` guard. This made it a fairly straightforward operation:\\n \\n```elixir\\n@spec soft_delete_all(Multi.t(), atom(), fun() | Query.t(), keyword()) :: Multi.t()\\n  def soft_delete_all(multi, name, func, opts \\\\\\\\ [])\\n\\n  def soft_delete_all(multi, name, func, opts) when is_function(func) do\\n    Multi.run(\\n      multi,\\n      name,\\n      operation_fun({:soft_delete_all, func, [set: [deleted_at: Timex.now()]]}, opts)\\n    )\\n  end\\n\\n  def soft_delete_all(multi, name, queryable, opts) do\\n    add_operation(multi, name, {:update_all, queryable, [set: [deleted_at: Timex.now()]], opts})\\n  end\\n```\\n \\nThe first function matches against functions while the second matches against a queryable. I'll explain the distinction between both. \\n\\n Under the hood `Multi` is already equipped to handle functions or queryables; by reading the source of the `Multi` module I was able to,matches, forward along the proper structure for the Multi to run, and in another case recreate the same functionality that `Multi.update_all` uses. Both `operation_fun/2` and `add_operation/3` are nearly copy-pasted from the Multi core. \\n\\n In the first instance the multi is passed a function, something like:\\n \\n```elixir\\n|> soft_delete_all(:remove_customer, &remove_customer/1)\\n```\\n \\nIn this case Ecto adds a new Multi operation to the pipeline: `Multi.run/3` but it needs to run the function it's passed. It does this with `operation_fun/2` . The multi has several matchers for each of the bulk operations, in my case I only needed one `:soft_delete_all` .\\n \\n```elixir\\ndefp operation_fun({:soft_delete_all, fun, updates}, opts) do\\n    fn repo, changes ->\\n      {:ok, repo.update_all(fun.(changes), updates, opts)}\\n    end\\n  end\\n```\\n \\nAgain, this is identical (save the `:soft_delete_all` atom) to the Multi module. It runs our function which creates a query, it passes our update: `[set: [deleted_at: Timex.now()]]` to the query and then updates the record.\\n \\nIn cases where we pass a query in:\\n \\n```elixir\\n|> soft_delete_all(:remove_customer, Query.from(c in Customer, where: c.id == 123))\\n```\\n \\nWe match on the next function head, here again I used Ecto's pattern writing my own custom `add_operation/3`\\n \\n```elixir\\ndefp add_operation(%Multi{} = multi, name, operation) do\\n    %{operations: operations, names: names} = multi\\n\\n    if MapSet.member?(names, name) do\\n      raise \\\"#{Kernel.inspect(name)} is already a member of the Ecto.Multi: \\\\n#{Kernel.inspect(multi)}\\\"\\n    else\\n      %{multi | operations: [{name, operation} | operations], names: MapSet.put(names, name)}\\n    end\\n  end\\n```\\n \\nThis is going to first check that the operation name isn't already in the Multi. If it's not, we append the operation into the Multi. This works because of the parameters we've passed it:\\n \\n```elixir\\nadd_operation(multi, name, {:update_all, queryable, [set: [deleted_at: Timex.now()]], opts})\\n  end\\n```\\n \\nSpecifically: `{:update_all, queryable, [set: [deleted_at: Timex.now()]], opts}` once again, we aren't doing anything fancy to soft delete these records, we are using Multi's ability to `:update_all` with our provided queryable. The update we are making is `[set: [deleted_at: Timex.now()]]` .\\n \\nThere you have it, it's `:update_all` all the way down, which makes sense because we are updating a record instead of deleting it, but I think it's a lot cleaner to write something like this:\\n \\n```elixir\\nquery1 = from(c in Customer, where: c.last_purchase \u003C= ^old_date)\\nquery2 = from(u in User, join: c in assoc(u, :customer), on: c.last_purchase \u003C= ^old_date)\\n\\nMulti.new()\\n|> soft_delete_all(:customers, query1)\\n|> soft_delete_all(:users, query2)\\n#👆don't judge this contrived example it's not production code\\n```\"},\"path\":\"/blog/adding-a-soft_delete-to-ecto-multi-pipelines\"},{\"meta\":{\"title\":\"(Untitled)\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-10-18\",\"tags\":[\"micro\",\"photo\"],\"rawContent\":\"Went walking with my daughter and we picked some autumnal bouquets.\"},\"path\":\"/blog/boquets\"},{\"meta\":{\"title\":\"TIL Struct matching in Guards\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-10-10\",\"tags\":[\"til\",\"programming\"],\"rawContent\":\"Not so much a TIL but I always get confused with the proper syntax. You can pattern match on a struct and use it in a guard to only let through the structs you want:\\n \\n```elixir\\n@spec address_formater(BillAddress.t() | ShipAddress.t()) :: String.t()\\ndef address_formatter(%struct{} = address) when struct in [BillAddress, ShipAddress] do\\n ...\\nend \\n\\ndef address_formatter(_), do: raise \\\"AddressError :: Not my address!\\\"\\n```\\n \\nAs with a lot of my examples it may be a little contrived but it is *based* on a real world but I fixed today where `address_formatter/2` was getting an `%Ecto.Association.NotLoaded{}` and trying to format it.\"},\"path\":\"/blog/til\"},{\"meta\":{\"title\":\"\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-09-29\",\"tags\":[\"micro\"],\"rawContent\":\"I'm 33 years old and I only learned how to spell \\\"doesn't\\\" this year. Getting enough practice where I almost spell it right the first time.  (I think I used to default to dosen't).\"},\"path\":\"/blog/micro-5\"},{\"meta\":{\"title\":\"TIL UUIDv4 vs UUIDv7\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-09-25\",\"tags\":[\"til\",\"programming\"],\"rawContent\":\"I've always run with UUID v4 because it's the default for the `Ecto.UUID` library in Elixir. However a coworker recommended UUID v7. Having never really looked into UUID other than to implement as a primary key the distinction was news to me. \\n\\n Effectively;\\n \\n- UUID v4 is a totally random hash that is generated and extremely unlikely to ever conflict with any other generated UUID.\\n- UUID v7 also contains a random hash but is also based on a timestamp, this means you can sort them and index them. \\n\\n For further reference, yes there are UUIDs v1-v8 as of this writing. If you want a good description of each you can check out this [helpful link](https://www.ntietz.com/blog/til-uses-for-the-different-uuid-versions/) .\"},\"path\":\"/blog/til-uuidv4-vs-uuid-v7\"},{\"meta\":{\"title\":\"TIL INSERT INTO with SELECT constraints\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-09-18\",\"tags\":[\"til\",\"programming\"],\"rawContent\":\"In the past month I've had to write a lot of SQL to migrate a system and split existing \\\"locations\\\" into tenants ie. migrating data from a `public` schema to a tenant's schema is gets messy due to foreign key constraints. Order of operations is important but sometimes you still find yourself in a corner. \\n\\n In instances where I already have data in the tenant schema, for example `customers` and I need to load a subset of data from another table, eg. `customer_addreses` it's possible to run the query with `tenant.customers` as a constraint for what your inserting:\\n \\n```sql\\nINSERT INTO tenant.customer_addresses SELECT * FROM public.customer_addresses AS pc WHERE EXISTS (SELECT 1 FROM tenant.customers AS tc WHERE tc.id == pc.customer_id)\\n```\\n \\nThis will insert `public.customer_addresses` into `tenant.customer_addresses` for every `teant.customer` that already exists. I've gotten around a lot of tricky constraint issues with missing/incomplete data this way.\"},\"path\":\"/blog/til-insert-into-with-select-constraints\"},{\"meta\":{\"title\":\"July 2024 Music Recap\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\u003Cspan style=\\\"white-space: pre-wrap;\\\">Car Seat Headrest - Matador Records\u003C/span>\",\"date\":\"2024-08-06\",\"tags\":[\"music\"],\"rawContent\":\"One of the things I miss most about Spotify is Wrapped.  I always feel a pang of envy at the end of the year when people are sharing their Wrapped stats, to the point where I've thought about trying to hook into Tidal's underdeveloped API and create something similar for myself. Alas, maybe someday, it's about #5 on the project backlog.\\n \\nIn the meantime I make due with Tidal's monthly email of my most streamed artists. I love this and I rarely have the same artist appear on the top five two months in a row. I'd like to say I'm constantly seeking out new artists but more often than not my most streamed artists are dictated by a singular mood that may prevail for a week or even a day. Listening to a few albums in a single day can really shoot an artist towards the top of the personal chart.\\n \\nWithout further ado:\\n \\n1. Car Seat Headrest - 47 songs\\n2. Mondo Cozmo - 38 songs\\n3. Broken Bells - 35 songs\\n4. Wet Leg - 30 songs\\n5. The Decemberists - 29 songs\\n \\nYou can see even Car Seat Headrest represents the equivalent of about four albums from a prolific catalog, this isn't me sitting in my room for hours a day listening to an artist on repeat.\\n \\nThis month was mostly reliable listening, looking over my personal chart I wouldn't look at this list and say it's representative of my \\\"favorites\\\" but each one of these artists has something that really gets under my skin in the best way possible.\\n \\n### **Car Seat Headrest**\\n \\nI only discovered this gem of a band a few years back.  They present an odd mixture of DYI-kid-in-his-basement and anthemic pop hooks with a dash of Brand New style emo.  While fans of the band have jumped down deep rabbit holes of  mythology; frankly, I just like the music and the passion that Will Toledo manages to put into every word he sings. Call me basic but \\\"Teens of Style\\\" is my favorite album, I feel like it treads a fine line between their garage rock roots and their more avant-garde sounds.\\n \\n### Mondo Cozmo\\n \\nNever on the heavy rotation, but in this day and age there is a rare quality about Mondo Cozmo: his albums feel extremely cohesive.  He lacks the textures that I find in the early albums of, say, Bruce Springsteen but there aren't any filler tracks. The melodies are catchy, and some of the cheesy poetry of lyrics can be forgiven as the music drowns them out.  \\\"New Medicine\\\" or \\\"This is For the Barbarians\\\" are his best.\\n \\n### Broken Bells\\n \\nBroken Bells *is* one of my favorites and how could they not be? I'm a big fan of The Shins early work and Danger Mouse has long been one of my most reliable producers so a collaboration between James Mercer and Brian Burton couldn't go wrong.  The first two albums (\\\"Broken Bells\\\" and \\\"After The Disco\\\") are, essentially equal in my eyes, with their latest effort \\\"Into the Blue\\\" taking a close third (second?). That may be due to the problem of time and place, our experience with a band is forever frozen in the formative years when we first discovered them, all later work will be compared to this gilded memory.\\n \\nThe third ingredient that makes the Broken Bells potion sizzle is veteran Cartoon Network artist and designer Jacob Escobedo who does phenomenal artwork for them.\\n \\n### Wet Leg\\n \\nCatchy hooks, witty (and naughty) lyrics blend to make a very fun listening experience. Wet Leg writes about all the alt-rock standards; wanting someone who doesn't want you back, quarter-life crises, post breakup sex, and a general malaise of the human condition but they manage to just be a bit more witty than most. Chaise Lounge was their breakout single but almost any of the songs on 2022's \\\"Wet Leg\\\" could be singles. I'm interested to see what comes next.\\n \\n### The Demberists\\n \\nSometimes I think The Decemberists are rebels from the young America who somehow managed to time travel to our age. This may, in part, be due to their recording the hilarious Hamilton discard; \\\"Ben Fanklin's Song\\\". More likely it's the out of place nature of their music; it's very much 00s indie rock (you may notice a theme with me here) but neither the subject matter nor the heavy acoustics seem to quite fit with the genre.  That's not to mention Colin Meloy's voice, which is unlike any of his contemporaries.\\n \\nUnlike Broken Bells, Wet Leg or Mondo Cozmo, I find The Decemberists's catalog to be rather hit and miss. Every album has some standout songs, but every album also has a few that don't connect at all with me.\\n \\nLets see what happens in August! Maybe some repeats (in the process of writing this up I've been listening to a lot of these artists over).\"},\"path\":\"/blog/july-2024-music-recap\"},{\"meta\":{\"title\":\"\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-07-24\",\"tags\":[\"micro\"],\"rawContent\":\"Why Cybertruck when you could [Cyberduck](https://cyberduck.io/) ! I think I've been using Cyberduck for 100% of my FTP needs for at least 15 years.  Such a rock solid piece of software.\"},\"path\":\"/blog/micro-3\"},{\"meta\":{\"title\":\"\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-07-04\",\"tags\":[\"micro\"],\"rawContent\":\"SQL is the way, SQL is always the way!  I killed myself for hours this morning trying to query/clean some data in Rails, started writing raw SQL and had it sorted in 30 minutes!\"},\"path\":\"/blog/micro-2\"},{\"meta\":{\"title\":\"\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-07-03\",\"tags\":[\"micro\"],\"rawContent\":\"Apparently I destroyed my fingerprint while climbing over the weekend- my MBP fingerprint no longer seems to work. It's a small feature but very missed when it doesn't work.\"},\"path\":\"/blog/micro\"},{\"meta\":{\"title\":\"\",\"hero\":\"\",\"excerpt\":\"Morning Jog in Guelph. Rainy days like yesterday are perfect!\",\"alt\":\"\u003Cspan style=\\\"white-space: pre-wrap;\\\">Morning Jog in Guelph. Rainy days like yesterday are perfect!\u003C/span>\",\"date\":\"2024-07-01\",\"tags\":[\"micro\",\"photo\"],\"rawContent\":\"\u003Cscript>\\n    import Image from '../../lib/components/content/Image.svelte'\\n\u003C/script>\\n\\n\\n\u003CImage path=\\\"/images/blog/2024/rainy-morning.jpg\\\" alt=\\\"Rainy Morning in Guelph ON\\\"/>\\n\\nMorning Jog in Guelph. Rainy days like yesterday are perfect!\"},\"path\":\"/blog/micro-1\"},{\"meta\":{\"title\":\"(Untitled)\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-06-21\",\"tags\":[\"micro\"],\"rawContent\":\"I just modified the Journal theme to allow for microblogging.\"},\"path\":\"/blog/just-setup-microblogs-here\"},{\"meta\":{\"title\":\"The UW Encampment\",\"hero\":\"\",\"excerpt\":\"I havve been watching with admiration and humility as students around the world have set up encampments demanding\",\"alt\":\"\u003Cspan style=\\\"white-space: pre-wrap;\\\">The University Of Waterloo is complicity in their partnerships with Israeli defense contractors and funds, so they have been taken over by the \\\"Popular University for Gaza\\\" \u003C/span>\",\"date\":\"2024-06-14\",\"tags\":[\"musings\",\"life\"],\"rawContent\":\"\u003Cscript>\\n    import Image from '../../lib/components/content/Image.svelte'\\n\u003C/script>\\n\\n\u003CImage path=\\\"/images/blog/2024/uw-encampment-1.jpg\\\" alt=\\\"UW Sign covered in solidarity with the people of Gaza\\\"/>\\n\\nI've been watching with admiration and humility as students around the world have set up encampments demanding their schools disclose and divest from Israeli/defense industries. While the situation in Palestine has been gut-wrenching; it's been heartening to see the reactions of these brave students. Media coverage, however; has been less than favorable. Even fairly pro-Palestinian outlets such as The Guardian use incendiary language such as, \\\"protest erupted\\\" and the emphasis is generally on the concerns of the administrators, police, and potential safety issues. My reading of this coverage portrays these student activists, at best, as dangerous anarchists looking for a fight, at worst entitled white kids shirking summer internships.\\n\\n\u003CImage path=\\\"/images/blog/2024/uw-encampment-2.jpg\\\" alt=\\\"An angle of the encampment\\\"/>\\n **The encampment is covered in art and slogans! Advertising for the people by the people.**\\nWhen the encampment went up at the local university, The University of Waterloo, I rode up with my dad to take a look. He brought a few books to donate to the encampment's library (I found it charming that amid the tents and living space the students had already set up a thriving library). The atmosphere was calm and cordial we were offered snacks and there appeared to be a lecture circle going on next to the library.\\n\\nI found the atmosphere so tranquil and invigorating (a mix of being back at school and a yoga retreat). It was so enticing I went back the next day with Viv and Ruby. The entire day was planned out (they communicate their donation needs and the daily program via Telegram), we went for kite making and poppy planting - kid friendly activities. Ruby got right to work painting her kite, as parents Viv and I relish any time Ruby spends on her own working on a project so we sat back and tried not to hover. The care and concern that was shown for our daughter was remarkable. Over the course of half an hour multiple people approached her offering her water (it was very hot), a woman came around with bite brownies and asked if Ruby wanted one and if she was allowed. As the sun moved I helped a volunteer move the tent to provide better shade for the kids. This was a scene from a community picnic not an anarchist commune sizzling to the point of boiling.\\n\\n\u003CImage path=\\\"/images/blog/2024/uw-encampment-3.jpg\\\" alt=\\\"Another angle of the encampment from the Grad House\\\"/>\\n **A few from in front of the Grad House.**\\nAs I watched the care and compassion shown towards my daughter as well as everyone else who had come to the encampment that day I couldn't help but wonder why *this* wasn't reported? Why is the media so hell-bent on making these students, out to be a volatile and potentially dangerous element? It's a tired trope. Of course there have been violent incidents, counter protesters, police forcibly removing students there are many echos of the 1970 Vietnam protests but from my experience an average day in the encampment is calm, communal, even jovial at times. Everyone is gathered for a higher purpose, and it is a grave one, but even in the face of this genocide a community is being built and it is thriving. My hope is that their time is shortened only by divestment and not batons.\"},\"path\":\"/blog/the-uw-encampment\"},{\"meta\":{\"title\":\"On “Rewilding The Internet”\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\u003Cspan style=\\\"white-space: pre-wrap;\\\">A wild place my daughter and I discovered in West Vancouver\u003C/span>\",\"date\":\"2024-06-04\",\"tags\":[\"the internet\",\"musings\"],\"rawContent\":\"\u003Cscript>\\n    import Image from '../../lib/components/content/Image.svelte'\\n\u003C/script>\\n\\n\u003CImage path=\\\"/images/blog/2024/wild.webp\\\" alt=\\\"A wild place my daughter and I discoverd in West Vancover\\\"/>\\nI’ve been hearing this  term a lot lately and, frankly, I think it is a fairly myopic view. If you live in a walled garden such as Facebook, Instagram, TikTok, The Artist Formally Known as Twitter, etc. then yes, it's easy to the feel that the internet is a barren hardscrabble where all efforts must be made to conserve even the tiniest shrub of originality. However; simply peaking over these walls will reveal a vast untamed wilderness waiting to be discovered/rediscovered.\\n\\nMy internet experience began around 2000 when I got a @hotmail email. I could barely read and write but there it was: email. A few years later I learned to type and to type fast by chatting with both friends and strangers on AIM. It was around 2003 or 2004 that I really started to \\\"be online\\\" and it coincided with my learning a bit of HTML. Here was this thing you could do at home without talking to anyone, lobbying anyone, going through gatekeepers; you could put it online and anyone in the world could see it.\\n\\nWhen people talk about a *wild* internet I think this and everything that came before is the era most people are talking about. It was a bit of a wild-west, there were few best practices, and even those were contested and debated. Fewer people were there for the money. Of course the dot-com bubble had already burst so it's not like people *weren't* aware of the potential to make large fortunes on the internet, but there wasn't a lot of pressure on the individual to make money. People wrote blogs because they had something to say, or because they had nothing to say but wanted to shout into the void. People learned HTML not to become developers but because they had a passion for something and *needed* to get that thing online. People dumped snippets into Geocities forms to create weird and wonderful pages.  Later, even MySpace; the closest thing to the era's Facebook, allowed users an insane amount of control over the look and feel of their profiles.  It was a little messy, but it was *wild.*\\n\\nGmail and Google Maps had yet to be released, MySpace was nascent, and while there were were other social network prototypes they didn't \\\"capture\\\" users the same way they do today. Social networks, these silos, just weren't compelling enough to spend hours a day on. So you surfed. How did one surf? People had blogrolls which guided you to their favorite blogs. There were director pages and recommendations. Much like wandering a library rather than approaching a librarian directly one spent a lot more time aimlessly wandering.\\n\\nEventually Google et, al. got so good you didn't need to rely on third parties for recommendations, you didn't need to browse as much, you could type in a query and get a result. The results were so good that you didn't even need to go past the first page, and within a few years you didn't need to get past the first result. At this stage it may feel like something was lost, that weirdness, those early sites, where did they go? Was the internet finally tamed?\\n\\nThey were still there.\\n\\nAt first the algorithm was designed to return useful results. This was, in large part, a worthwhile step. It disenfranchised a lot of the wild and weird internet but it made the place as a whole a lot more easy to juice. Answers were now literally at your fingertips, not several blocks away.\\n\\nThat's when the enshittification began. Enshittification, coined by Cory Doctorow, is the process by which platforms die:\\n\\n1. They begin by being good to their users\\n2. Then they begin to abuse their users to make things better for their business partners\\n3. Then they abuse their business partners to gain more value for themselves/shareholders\\n4. Then they die\\n\\nAs Google became less of an experiment in *finding* things and more of an engine for making money the search experience began to slowly degrade. More and more of the first results were advertisements, those advertisements became less and less relevant to the query. Additionally, anyone trying to \\\"rank\\\" highly in the Google search results had to play by their rules. We've seen several iterations of Page Rank's bizarre rules from link stuffing (where pages would put an *insane* number of links to related/the same content), to the latest trend where long-form content is rewarded. (This is why you have to scroll through someone's life story to get to the recipe).\\n\\nWe were so hooked on the search that we barely noticed that the experience was degrading! Searching was so easy and the results were still so good that we put up with more ads, more invasions on our privacy for worse results. And all of this algorithmic gaming to make Google as much money as possible continued to cut out the weird and wonderful websites that made the original internet wild.\\n\\nBut they were still there.\\n\\nThe latest trend in search is AI. For two years I used a web browser called Arc, early this year with much fanfare they released an iOS app for search with an accompanying video about the future of the internet. Their vision is to have you type in your query, and have AI search for you and return a generated answer to your query. The pitch is, \\\"no more searching, no more sifting through bad results\\\". Ironically, search has become so bad we need AI to do the most basic thing it's there for. Perplexity AI and and a host of others are promising the same thing. This is **not** a net positive for the internet. If you are interested in re-wilding the internet you need to do more hunting not less. An AI that promises generative answers to search queries is far worse for the health of the internet than Google's crappiest results, it is eliminating sources, content, experts, humans and anything else that might make the internet weird. This is a dark and dangerous path for a wild internet.\\n\\nBut the wild internet is still there. You can't find it in walled gardens, get rid of Facebook, jump ship from Instagram and stop looking for answers with Google. You will never find the wilderness if you remain in the concrete jungle. Where is the wild internet?\\n\\nIt's everywhere entrenched tech isn't. I realize that's a bit daunting, so where to start? That's a huge question and it deserves a huge answer. As I write this blog I'm sure I'll dive into more detailed answers but below I've provided a few of my favorites as a jumping off point:\\n\\n[Marginalia Search](https://search.marginalia.nu/) is a small DIY search engine experiment that focuses on non-commercial sites you didn't know existed.\\n\\n[Kagi](https://kagi.com/) is a bit more mainstream, you get 100 searches for free/month after that it's about $10/month.  Mostly relying on Bing and other search providers it strips most of the cruft from these services. Kagi is powerful because you can create your own rankings and filters, what do you want to see? What do you want to see more of? Tired of StackOverflow? Rank it low. Like The Guardian. Rank it high.\\n\\n[IndieWeb](https://indieweb.org/) is a wealth of knowledge on *the wild* internet and a good place to start learning.\\n\\n[Micro.blog](https://micro.blog/) imagine Instagram, Twitter, and Tumblr rolled into one also with podcast hosting. Micro blog is an easy way to get thoughts out of your head and into the world along with a supportive and interesting community. As is required with an open internet, there is no lock-in everything is transportable and it connects seamlessly with several \\\"walled gardens\\\" so you can cross post to Threads if you wanted.\\n\\n[The Useless Web](https://theuselessweb.com) it is what it says it is.\\n\\nRSS I believe that RSS is one of the most powerful tools we have in the wild internet. RSS deserves a post in it's own right but give a high level overview here. RSS stands for RDF Site Summary or better yet Really Simple Syndication. RSS lets you subscribe to feeds and receive an update every-time new content is added. This allows you to curate your own experience for online reading, no algorithms, you choose what you want and posts are shown in chronological order. You'll need an RSS reader; I use [NetNewsWire](https://netnewswire.com/) but other good options are [FeedBin](https://feedbin.com/) (paid) and [The Old Reader](https://theoldreader.com/) (free), I believe Vivaldi has one out of the box as well (Safari used too 😢).  Once you have a reader you start adding feeds, most content that gets updated regularly will have an RSS feed it can usually be found by adding `/rss` or `/feed` to the end of the url. You can try this [here](https://blog.travisfantina.com/rss/) on my site!\\n\\nA few of the feeds I read regularly are:\\n\\n- [https://feeds.kottke.org/main](https://feeds.kottke.org/main) Jason Kottke one of the OG bloggers\\n- [https://www.theguardian.com/profile/arwa-mahdawi/rss](https://www.theguardian.com/profile/arwa-mahdawi/rss) Guardian columnist Arwa Mahdawi (you can get the RSS feed for specific writers on the Guardian by adding `/rss` after their profile.\\n- [https://www.manton.org/feed/json](https://www.manton.org/feed/json) Manton Reece is the founder of Micro.blog (mentioned above) there's something about his blog that I find very soothing.\\n- [https://www.thisiscolossal.com/feed/](https://www.thisiscolossal.com/feed/) Colossal's feed\\n- [http://manuelmoreale.com/feed/rss](http://manuelmoreale.com/feed/rss) Manuel runs a interview series called People and Blogs where he interviews people about their personal blogs I've found some interesting people to follow through this.\\n\\nThose are just a few general interest ones to get you started. I've curated about 30 that feeds that I actively follow some people have just one or two others have hundreds.\\n\\nIf you've made it this far, I sincerely thank you. If you have questions about the open internet, RSS feeds, or anything else please drop me a line; trav@hey.com\\n [Marginalia Search\\n search.marginalia.nu is a small independent do-it-yourself search engine for surprising but content-rich websites that never ask you to accept cookies or subscribe to newsletters. The goal is to bring you the sort of grass fed, free range HTML your grandma used to write.\\n search.marginalia.nu](https://search.marginalia.nu/) [Kagi Search - A Premium Search Engine\\n Better search results with no ads. Welcome to Kagi (pronounced kah-gee), a paid search engine that gives power back to the user.\\n A Premium Search Engine\\n ![](https://assets.kagi.com/v2/kagi_assets/doggo/doggo_1.png)](https://kagi.com/) [IndieWeb\\n The IndieWeb is a people-focused alternative to the “corporate web”.\\n IndieWeb\\n ![](https://indieweb.org/img/indiewebcamp.svg)](https://indieweb.org/) [Micro.blog\\n Post short thoughts or long essays, share photos, all on your own blog. Micro.blog makes it easy, and provides a friendly community where you can share and engage with others.\\n Micro.blog\\n ![](https://micro.blog/images/icons/favicon_64.png)](https://micro.blog/) [The Useless Web\\n The Useless Web Button... just press it and find where it takes you. The perfect button for the bored, or those looking to find useless sites online!\\n The Useless Web\\n ![](https://theuselessweb.com/share-image-large.png)](https://theuselessweb.com)\"},\"path\":\"/blog/on-rewilding-the-internet\"},{\"meta\":{\"title\":\"Back up and blogging? Sort of.\",\"hero\":\"\",\"excerpt\":\"\",\"alt\":\"\",\"date\":\"2024-05-27\",\"tags\":[\"meta\",\"life\"],\"rawContent\":\"In conjunction with me finally overhauling my website I've decided to get on the blogging train. I've had a personal blog on and off since at least 2005.  I started when I was 12 or 13 just adding text to a an HTML file and copying it into Geocities then Tripod then Dreamhost.  Sometime around 2006 or 2007 I did the \\\"Famous Five Minute Install\\\" of WordPress and stayed the course there for a few years. As I've been reworking my site with SvelteKit I toyed with the idea of doing a Markdown blog. While I like this approach, in theory, I really missed the administration tools that a more full-fledged platform offers, drafts, the ability to post on the fly without having to run a deployment, etc. In the past I've solved those pain points with TinaCMS for clients but frankly it's just more trouble than I'm willing to put in for a personal site.\\n \\nSo with that I've chosen Ghost.  I've always liked the Ghost ethos, small, remote-first, transparent non-profit. I used Ghost a few years back to document [my year of consuming less](https://consume.travisfantina.com/) . I'm not a huge fan of the Medium-like editor they use but their overall aesthetic is on point. I'm not 100% positive what I'm going to post here. I'm a developer by trade so I may write some technical articles; but for the most part this is going to be a collection of my public thoughts and ideas.  Mostly short-form with some longer form stuff peppered in.  Moving forward I'm going to tweak this template to handle single photos and short form micro posts. The goal being, this can be the central repository for all my online life.\"},\"path\":\"/blog/back-up-and-blogging\"},{\"meta\":{\"title\":\"Today I Learned ~D[2024-01-03]\",\"hero\":\"\",\"alt\":\"\",\"date\":\"2024-01-03\",\"tags\":[\"TIL\",\"Elixir\",\"Programming\"],\"rawContent\":\"You can use Erlang's `tc` function to see how many microseconds a function takes.  For example, say you were curious if `Enum.filter/2` or `Kernel.--/2` took longer:\\n\\nExample: \\n```elixir\\n$iex> vals = [1, 2, 3, 4, 5]\\n$iex> :timer.tc(Enum, :filter, [vals, &rem(&1, 2) == 1])\\n{20, [1, 3, 5]}\\n\\n$iex> :timer.tc(Kernel, :--, [vals, [2, 4]])\\n{3, [1, 3, 5]}\\n```\\n\\n`Kernel.--` or `vals -- [2, 4]` took 3 micro seconds while `Enum.filter/2` (`Enum.filter(vals, & &1rem(&1, 2) == 1)`) took 20.  \\n\\nThis is a fairly trivial example but I could see this coming in handy with larger operations.\\nFor more detailed analysis you can always use [Benchee](https://github.com/bencheeorg/benchee).\\nThanks to [chriserin](https://til.hashrocket.com/posts/9jxsfxysey-timing-a-function-in-elixir) for helping me get the right Erlang syntax for `tc`\"},\"path\":\"/blog/til-2024-01-03\"}],\"tags\":[\"books\",\"current events\",\"elixir\",\"life\",\"meta\",\"micro\",\"music\",\"musings\",\"photo\",\"portfolio\",\"programming\",\"the internet\",\"til\"]}"}</script>
			<script>
				{
					__sveltekit_1a66b3z = {
						base: new URL(".", location).pathname.slice(0, -1),
						env: {}
					};

					const element = document.currentScript.parentElement;

					const data = [null,null];

					Promise.all([
						import("./_app/immutable/entry/start.4d871830.js"),
						import("./_app/immutable/entry/app.b7597b91.js")
					]).then(([kit, app]) => {
						kit.start(app, element, {
							node_ids: [0, 4],
							data,
							form: null,
							error: null
						});
					});
				}
			</script>
		</div>
	</body>
</html>
