justin․searls․co

Did you come to my blog looking for blog posts? Here they are, I guess. This is where I post traditional, long-form text that isn't primarily a link to someplace else, doesn't revolve around audiovisual media, and isn't published on any particular cadence. Just words about ideas and experiences.


Fix your Rails Fixtures with this one neat trick

If you have any Rails models that define a custom table_name AND you load fixtures in your test database, then you're probably going to have a bad time. Maybe you're here from Google. If so, hi, hello! You're in the right place.

Here's the model I just ran into this issue with:

# app/models/build/program.rb
module Build
  class Program < ApplicationRecord
    self.table_name = "build_programs"
  end
end

What happens next will shock you…

Vision Pro was a better deal than my Mac Studio

As the post-launch hype has cooled, the Apple-watcher zeitgeist has started to turn against the platform—some are even bold enough to invoke the word "failure".

(Aside: if Apple considers Vision Pro a failure, it's not because of sluggish sales figures or a weak App Store lineup. It was clear from the jump that Apple is committed to a ten year roadmap for this thing, regardless what you or your favorite Youtuber thinks. Burback's video was hilarious, though.)

I've been using Apple Vision Pro for no purpose other than Mac Virtual Display for 4-8 hours a day, 7 days a week, since it launched on February 2nd. Meanwhile, my brand-spankin'-new M2 Ultra-equipped Mac Studio and 32" 6K monitor are collecting dust. More than that, I'm getting more done than at any point in my career. So I figured I'd share the Good News with y'all, in case it might sway anyone sitting on the fence into giving Vision Pro a shot.

First, I'll explain why my productivity shot through the roof once I strapped a computer to my face. Then, I'll show why such an expensive device is no more an irresponsible use of funds than other "Pro"-tier equipment in the Apple ecosystem.

Content warning: more content…

HotwireCombobox is pretty damn slick

In a stroke of good fortune, this week's big, overriding to-do item was to figure out how to write a hotwire-friendly "combo box" (one of those drop-down / select boxes for the web that you can type into and filter the options). Then I happened to scan this week's Ruby Weekly and found somebody beat me to the punch!

It's by Jose Farias and he calls it HotwireCombobox. The documentation page contains plenty of demos, so go play with it!

The best part (and my favorite thing about moving to import maps for JavaScript in Rails 7) is that the front-end assets live with the gem, which means there's no risk of version drift causing the backend and front-end to fall out of sync with each other.

In fact, set up was so minimal, I'm going to share the entire changeset of what it took to convert my app's f.collection_select boxes over to f.combobox.

But wait, there's more…

How to control Time in Ruby on Rails

Faking time is a frequent topic of conversation in software testing, both because the current time & date influence how many programs should behave and because reading a real system clock can expose edge cases that make tests less reliable (e.g. starting a build just before midnight on New Year's Eve may see assertions fail with respect to what year it is).

I've approached this issue a dozen different ways over the years, and there are a number of tools and practices promoted in every tech stack. Rubyists often lean on the timecop gem and Active Support's TimeHelpers module to manipulate Ruby's time during testing. Regardless, no tool-based solution is robust enough to cover every case: unless the operating system, the language runtime, the database, and every third-party service agree on what time it is, your app is likely to behave unexpectedly.

And before you knew it…

Simultaneously save+copy screenshots on the Mac

[UPDATE: Since publishing, I've simplified these instructions and reduced the latency in bringing up the screenshot tool by about half.]

[UPDATE 2: If you're on macOS 14.4 Sonoma and you want to avoid "Operation Not Permitted" errors, there's no sure-fire way to avoid them whether you set this up via Shortcuts or Automator, so I'd recommend using Keyboard Maestro instead.]

macOS ships with a pretty rad Screenshot app, except that one thing about it totally sucks: it can be configured to either copy screenshots to the clipboard or save them to files, but not both.

Well, I finally got off my ass and cooked up a way to have my save and copy it, too. Read on if you're interested.

Content warning: more content…

Making a nice 2FA / OTP / SMS field with Tailwind & Stimulus

So, I built this little bit of UI today as part of an email-based authentication flow for Becky's new app:

(I haven't shipped this yet and I'm too lazy to record a screencast, so just imagine that this field behaves perfectly, please and thank you.)

If you, like me, have ever found yourself in the thrall of a beautiful-looking 6-digit form when logging into some site, whether when filling a TOTP from your authenticator app or copy-pasting a code that's been texted or e-mailed to you, you've probably wondered "how'd they make that field look nice like that?"

Well, today I actually started digging into it, and I didn't like what I found. At all.

Turns out, there's more to it…

One-shotting git pull-commit-push in VS Code

A frustration I've had since switching to VS Code last year from terminal vim is that the built-in source control extension isn't very keyboard-friendly. As a result, I've been tabbing back and forth between VS Code and Fork and kicking myself every single time, especially when I'm just editing a single file and I really don't need to review my changes before I push.

Well, I finally took the five minutes to write a VS Code macro to do this for me. First, run Open Keyboard Shortcuts (JSON) and add this to the array of keyboard shortcuts:

{
    "command": "runCommands",
    "key": "cmd+alt+ctrl+p",
    "args": {
      "commands": [
        "workbench.action.files.save",
        "git.sync",
        "git.stageAll",
        "git.commitAll",
        "git.push"
      ]
    }
  }

Now when I smoosh command, option, and control, then hit P, it'll pull from the tracked remote branch, stage & commit everything, open a window for me to enter a quick message (usually "lol"), and then when I hit command-w, the result will be pushed. Saves me about 10 seconds per commit.

PSA: This is the first good Vision Pro strap

UPDATE: It works! Photo here

As I mentioned in my review podcast, the two straps that ship with the $3500 Apple Vision Pro are god-awful and mediocre, respectively.

If you just spent that much money on this thing, do yourself a favor and buy two more things:

  1. A BOBOVR M2 Plus strap

  2. This 3D-printed conversion kit for connecting it to Vision Pro (you can also print it yourself)

And boom: for under $50 you'll have a comfortable way to actually use the Vision Pro. Shame on Apple for dropping the ball so badly in the name of aesthetics (what happened to, "design is how it works"?), but hat tip to Mark Miranda for pointing me to this Etsy listing.

HTML fragment caching really works!

I have somehow been using Ruby on Rails since 2005 and have never worked on an app that needed to think seriously about web request caching, probably because of my proclivity to reach for static site generators and simple asset hosting whenever anything I make will be public-facing. But the current app I'm working on is actually mostly accessible without requiring users be logged in, which means it will both (1) run the risk of having bursts of hard-to-anticipate traffic to certain pages and (2) render pretty much the exact same markup for everyone.

I'll start with the results. Here's a mostly-empty, public-facing page my basic Heroku dyno without caching:

Completed 200 OK in 281ms (Views: 201.9ms | ActiveRecord: 47.5ms | Allocations: 37082)

And now with a few lines of caching setup:

Completed 200 OK in 9ms (Views: 3.5ms | ActiveRecord: 1.6ms | Allocations: 2736)

So over 30 times faster. And that's on a very basic page. Once the site is primed with content it'll probably be even more dramatic.

Here's how to do it.

And before you knew it…

Brand-new Rails 7 apps exceed Heroku’s memory quotas

Update: Judging by this commit and the current status of main, this should be fixed for Rails 8. Great PR thread about this, by the way.

In the history of Ruby on Rails, one of the healthiest pressures to keep memory usage down has been the special role Heroku has played as "easiest place to get started hosting Rails apps". In general, Rails Core and Heroku's staff have done a pretty good job of balancing the eternal tension between advancing the framework while still making sure a new app can comfortably run on Heroku's free (or now, cheap) "dyno" servers over the last 17(!) years. Being able to git push an app to a server and have everything "just work" has always been a major driver of Rails' adoption, and it's seen as obviously important to everyone involved that such a low-friction deployment experience—even if developers ultimately move their app elsewhere—is worth preserving. And that means being cognizant of resource consumption at every level in the stack.

Anyway, there have been numerous bumps in the road along the way, and I think I just hit one.

I just pushed what basically amounts to a vanilla Rails 7.1.3 app to Heroku and immediately saw this familiar error everywhere in my logs:

Error R14 (Memory quota exceeded)

It started literally minutes after deploying the app. The app was taking up about 520MB. What gives? I even remembered to avoid loading Rails' most memory-hungry component, Active Mailbox!

Keep reading…

Sending e-mail using AWS SES over SMTP with Rails 7

There are a bunch of blog posts telling you how to configure Action Mailer to send mail via AWS SES in Ruby on Rails, and as far as I can tell they're almost all wrong. The top posts on Google and Stack Exchange include copypasta that either don't work or would send your password in plaintext.

(Why am I sending over SMTP instead of the AWS SDK's API, you ask? Because dependency hell.)

Anyway, here is a configuration I can confirm that works fine in this, the year of our Bezos, 2024:

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  # Will vary by region (e.g. "email-smtp.us-east-1.amazonaws.com")
  address: ENV["AWS_SMTP_ENDPOINT"],
  # Create an SMTP user: https://docs.aws.amazon.com/ses/latest/dg/smtp-credentials.html
  user_name: ENV["AWS_SMTP_USERNAME"],
  password: ENV["AWS_SMTP_PASSWORD"],
  # Encrypt via STARTTLS. See: https://docs.aws.amazon.com/ses/latest/dg/smtp-connect.html
  enable_starttls: true,
  port: 587,
  # :Login authentication encodes the password in base64
  authentication: :login
}

Slap that in your production.rb and you should be slinging e-mails in no time. Good times.

The First Annual Buggy Awards!

Welcome to the 2023 Buggies! The inaugural award ceremony in which I celebrate the most frustrating, hard-to-reproduce, and least-discussed software bug of the year.

Buggy Trophy of a Golden Ladybug

This year's award recipient for Neatest Bug of the Year has been striving for literal years to climb atop the pile of apps that freeze on first launch after install, error pages that themselves trigger additional errors, and save buttons that do nothing until you clear your cookies. But as we say goodbye to 2023, this bug found a way to emerge on top of a more-crowded-than-ever field of hopelessly broken software.

So, without further ado, the Neatest Bug of The year is…

Content warning: more content…

What is organicfruitapps.com?

I was fighting with a home automation this morning and it required me to ping a few high-traffic websites and I got curious looking at the headers that Apple returns:

$ curl -I https://www.apple.com/
HTTP/2 200
server: Apple
content-type: text/html; charset=utf-8
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
x-content-type-options: nosniff
strict-transport-security: max-age=31536000; includeSubdomains; preload
referrer-policy: no-referrer-when-downgrade
content-security-policy: default-src 'self' blob: data: *.akamaized.net *.apple.com *.apple-mapkit.com *.cdn-apple.com *.organicfruitapps.com; child-src blob: embed.music.apple.com embed.podcasts.apple.com https://recyclingprogram.apple.com swdlp.apple.com www.apple.com www.instagram.com platform.twitter.com www.youtube-nocookie.com; img-src 'unsafe-inline' blob: data: *.apple.com *.apple-mapkit.com *.cdn-apple.com *.mzstatic.com; script-src 'unsafe-inline' 'unsafe-eval' blob: *.apple.com *.apple-mapkit.com www.instagram.com platform.twitter.com; style-src 'unsafe-inline' *.apple.com
cache-control: max-age=582
expires: Sat, 23 Dec 2023 12:49:53 GMT
date: Sat, 23 Dec 2023 12:40:11 GMT
x-cache: TCP_MEM_HIT from a23-218-251-35.deploy.akamaitechnologies.com (AkamaiGHost/11.3.3-52660090) (-)
set-cookie: geo=US; path=/; domain=.apple.com

And then what happened?…

Why I started threatening and lying to my computer

As somebody who's spent the majority of his life figuring out how to make computers do what I want by carefully coaxing out the one-and-only correct commands in the one-and-only correct order, the relative chaos of figuring out what works and what doesn't to get LLMs like GPT-4 to do what I want has really pushed me out of my comfort zone.

Case-in-point, I was working on modifying a GPT script to improve the grammar of Japanese text—something I can fire off with a Raycast script command to proofread my text messages before I hit send.

I'd written all the code to talk to the OpenAI API. I'd sent a prompt to the computer to fix any mistakes in the text. It should have just worked.

Instead, running the script with a prompt like this:

What happens next will shock you…

Apple Announces Reality Pro

One thing I've been thinking about for a while is the ability of LLMs to generate convincing-enough-for-somebody-to-run fake leaks, so I decided to try my hand at creating a fake Apple leak.

I'm too cheap to pay for ChatGPT+ and I'm too lazy to have done this via OpenAI's GPT-4 API, so I used Bing Chat to take a crack at generating the Apple Newsroom article that we'll see go up on June 5th after Apple announces its headset. I figured that there was so many recent rumor round-ups and so much prior art of Apple Newsroom articles that it'd probably do a halfway decent job of this.

Here was my prompt:

Write me a press release in the style of an Apple Newsroom story announcing the new Apple Reality Pro headset that's based on only the most recent set of Rumors from Mark Gurman and MacRumors

I thought its response wasn't half-bad:

Content warning: more content…

How to Pack Light

When I first pitched doing a RubyKaigi "Field Report" summary for Test Double, my wonderful and benevolent boss Cathy Colliver suggested that I make a short video explaining how I pack so light for each trip. It took me a few weeks to get around to it, but here it is!

I've been living with this setup for about 6 years now and have barely changed a thing. If there's a headline to be had here, it's that the less you carry, the less you have to worry about.

If you'd like to try out this lifestyle, here are some links of stuff in the video:

But wait, there's more…

Travel Advice from Bing Chat

Over the last three weeks, I've been playing with Bing Chat as a sort of international travel and language advisor, and I've learned a few interesting things along the way that may help others get more creative with how they use AI.

There are three main categories where I've found Bing Chat helpful:

  • Generating ideas of where to go and what to do next
  • Translating phrases that straddle nuanced cultural differences dictionaries and translation software can
  • Answering "why" questions that would normally require a human

There's also one "gotcha" that's particularly interesting, but I'll cover that at the end.

To be continued…

Go To Yakushima

Today I'd like to tell you about a very special place that not very many people will ever get the opportunity to visit.

After concluding my duties as a field reporter of RubyKaigi 2023, I found myself with a luxurious seven or eight days to myself. While at Kaigi, I asked several Japanese friends where I should visit in the southern island of Kyushu. Almost to a person, they said "Kagoshima".

So, I went. And it was great! The weather was warm, the nature was beautiful, and the people were easygoing.

Immediately, I wanted to go deeper. I got it in my head that if I went somewhere even more inaka I could prove I was a Real Vlogger by going on an adventure and then making a YouTube Short set to an epic piano score.

But… where to?

Turns out, there's more to it…

Blue Sky, Red Ocean

I became familiar with Blue Ocean Strategy in the context of Nintendo's decision to forego the "console wars".

Instead of pushing to design consoles with the fastest chips and best graphics, they embarked on quirky industrial designs and user experiences with seeming tangents like the Nintendo DS, the Wii, the 3DS, the Wii U, and the Switch. The big idea (as it was baby-birded to me via amateur videogame journalism) was that competing with Sony and Microsoft to have the highest-performance machine or the best-looking version of multi-platform games was a losing proposition. It represented competing in a so-called "red ocean" (as in, there's blood in the water). Why? Because there were already two well-funded competitors vying to sell the exact same thing. The best Nintendo could hope to do was be marginally better, despite being in a far weaker capital position, with less access to the top chipmakers, and with a stable of IP that didn't necessarily benefit as directly from better graphics. So they pursued a blue ocean strategy by creating bold (and occassionally bizarre) products that couldn't be compared apples-to-apples with the competition.

The rapid, unscheduled disassembly of Twitter-dot-com over the last six months has resulted in an ocean of opportunity emerging. Tons of entrants are getting in. Mastodon was already there. Hive was there for a couple weeks, too. Journalists toyed with giving up the one thing they really care about—drip-feed dopamine from constant notifications—to join Post.news. Meta is apparently building a Twitter-like platform with ActivityPub support. Spoutible is a thing, I guess. And this week, everyone's talking about Bluesky, the open and federated but nevertheless locked-down and invite-only Twitter clone that started under Jack Dorsey Twitter and whose new app looks more like Twitter than Twitter does.

All these real-time, text-based activity streams are pouring chum straight into a deep red ocean.

Okay, I'm interested…

Connecting a gaming PC to Apple Studio Display

…You're right, it shouldn't be this hard

I'll never forget when I bought the first 5K Retina iMac. Almost as soon as I ripped it out of the box, I booted it while holding down Command-F2, assuming it would support Target Display Mode, with the intention of using its one-of-a-kind display with my gaming PC. I was heartbroken when Mac OS X booted anyway and I slowly realized that Target Display Mode hadn't survived the transition to retina resolutions. And it never came back, either. (I haven't really been happy with my setup ever since.)

Well, here we are, 8 years later and Apple has introduced the 5K Studio Display. I ordered one the minute that they hit the store in the hope I would receive what I thought I had purchased in 2014: a single 5K Apple display that could drive both a Mac and a PC desktop. (Nevermind the fact that it's damn near the exact same panel that I bought 8 years ago.) When my Studio Display arrived, I tore it out of its environmentally-friendly origami box and excitedly plugged it into one of my Nvidia RTX 3090's DisplayPorts with a DisplayPort-to-USB-C cable.

I booted up the PC: nothing happened.

Content warning: more content…