Everything is Tuberculuosis

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.

Some quotes and ideas that stayed with me:

“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’”.

“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.

TB 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:

  • J&J actively priced third world health systems out of bedaquiline - unconscionable!
  • 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’” 🤯
  • 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!

Ending the post with a positive quote toward the end of the book: “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.”

Today I Learned ~D[2025-05-22]

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:

false.object_id
=> 0
false.dup.object_id 
=> 0
val = false 
val.object_id 
=> 0

Duplicating false only creates a reference to false. As opposed to a mutable primitive like a string:

train = "choo choo"
train.object_id 
=> 784160
train.dup.object_id
=> 784180

Of course this intuitively makes sense but I had never run up against it until I had a spec fail:

expect(response[:enabled]).to be true
expect(response[:value]).to be "location"

=> expected #<String:140300> => "location"
  got #<String:140320> => "location"

I 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! Fix that with: expect(response[:value]).to eq "location"

Today I Learned ~D[2025-05-14]

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 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 🤷

Making multiple pushes to git easy

You can adjust your ~/.ssh/config to easily push to separate git accounts with different keys:

# Assume this is your defaut
Host *
    UseKeychain yes 

# Key 2
Host altkey
    HostName bitbucket.org
    IdentityFile ~/.ssh/alt-key
    # you likely don't need this but it's nice to specify 
    User git 

Then add/update your remote origin:

git remote add origin  git@altkey:bitbucket_account/repo.git

Instead 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.

The git problems begin

git push and:

fatal: Could not read from remote repository.

Please make sure you have the correct access rights

Ok fairly common lets go through the checklist:

  1. The key is in BitBucket
  2. BitBucket access is “write”
  3. Check origin (see above)
  4. Check permissions on the public key And that’s about where my expertise ended.

Diving in

It’s useful to learn a bit of debugging, you can get pretty verbose with git logging by adding the environment variableGIT_SSH_COMMAND="ssh -vvv Pretty 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. git config --list likewise is a handy flag to use but it didn’t show me any glaring issues. So I started looking into the SSH config: 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] Then I ran ssh -T git@altkey this runs a test with the alias configured in the config file. Infuriatingly, this returned:

authenticated via ssh key.

You can use git to connect to Bitbucket. Shell access is disabled

So my config was correct, I had access, but I could not push. It took me an hour but eventually I set the key for git to use explicitly:

GIT_SSH_COMMAND="ssh -i ~/.ssh/alt-key -o IdentitiesOnly=yes" git clone git@altkey:bitbucket_account/repo.git

No further issues (with either repo).
It’s unlikelly I’ll remember specifically setting the GIT_SSH_COMMAND which is the main reason I’m writing this!

Class Configs with Lambdas in Ruby

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 a method for determining if those options enable the feature or not:

module ProvidesFeature 
    class_methods do 
        # pass this to the model class
        def features_provided(model, **opts)
            (@features ||= []) << [model, opts]
        end

        # call this to initialize class feature checks
        def feature_models(ctxt)
            features_provided.map do |args|
                DynamicFeature.new(ctxt, args)
            end
        end
    end 
end 

Here 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:

class DyanmicFeature do 
    def initialize(ctxt, config_args)
        @ctxt = ctxt
        configure(config_args)  
    end

    def configure(ctxt, args = {})
        @should_provide_feature = args.fetch(:should_feature_be_provided) do 
            -> (ctxt) { ctxt&.fetch(:person_is_admin, false) }
        end
    end 

    def can_feature?
        @should_provide_feature.call(@ctxt)
    end
end 

Pausing for a moment and breaking this down. The #configure method is the main source of the magic. First 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. Now implement; in an active record ie. Person

class Person < ApplicationRecord 
    include ProvidesFeature 

    features_provided :person, 
        should_feature_be_provided: -> (ctxt) { ctxt.person.is_admin? }
    

You can then easily gather any models that ProvidesFeature:

ApplicationRecord.subclasses.select { |klass| klass < ProvidesFeature }

Instantiate 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.

.flat_map { |klass| klass.feature_models(ctxt) }

Then filter with can_feature?

.select { |klass| klass.can_feature? }

At 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.

Weekly Roundup: May 2, 2025

This week I formally transitioned from my fulltime consulting gig at Objective for a fulltime gig at Built For Teams 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 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:

Warning: CocoaPods is installed but broken. Skipping pod install.
...
Error: CocoaPods not installed or not in valid state.

Ok. So do some more research throw in a sudo, no luck. pod version produces this error:

<internal:/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 -

Ah! 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. I’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. 👏

Weekly Roundup: Apr 25, 2025

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.
Since 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. This feature is due May 5th but I’m hoping to get onto the staging server on Monday or Tuesday.

Internal Tooling:

I’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:

function git_add_unstaged() {
    local files
    files=$(git diff --name-only --diff-filter=ACMR | fzf --multi --preview 'git diff --color=always -- {}')
    if [[ -n "$files" ]]; then
        BUFFER="git add $files"
        CURSOR=$#BUFFER
    fi
}

function git_add_unstaged_widget() {
    if [[ $BUFFER == 'git add' ]] && [[ $CURSOR -eq $#BUFFER ]]; then 
        git_add_unstaged 
        zle redisplay
    else 
        zle self-insert
    fi
}

zle -N git_add_unstaged_widget 
bindkey ' ' git_add_unstaged_widget

I’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.

Cloud Atlas

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. However; 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.

Weekly Roundup: Apr 18, 2025

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 most 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.

This week was a balance between my ongoing Elixir projects and a newer (to me) Ruby project.

  • 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.
  • 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 that does the bulk of the heavy lifting and allows you to hit a very concise REST endpoint.
  • 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.
  • 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.

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.

Personal Heuristic: Make it Readable

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:

for itm <- data do
    %{"MATNR" => material, "PSTYV" => category, "VBELN" => so} = item
    %{material: material, category: category, so: so}
end

It’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. While 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? This 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. Of 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).

Moving forward I’m calling things what they are even if it comes with a dash of verbosity.