justin․searls․co

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

And then what happened?…

How should I have known I wasn't supposed to put down my masseuse's phone number as "current therapist" on the patient intake form?

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.

And then what happened?…

Saw a family out to dinner the other day and the kid ordered milk. The waiter responded, "regular or chocolate"?

The mom got very mad at the waiter's mention of chocolate milk. It's a little shocking how far the Ovaltine window has shifted in recent years.

The co-founder of a nascent game studio learned of a significant internal leak when a reporter called him for comment. He notified his publishing partner who responded to the leak by cutting funding. Then, after breathing what I'm sure were a series of very deep, very audible sighs he decided to say, "fuck it," and close the whole damn studio:

As a result of the cancellation of the publishing relationship and after careful consideration, I am closing Possibility Space. Today is your last day of employment at Possibility Space and Prytania Media. Your final paycheck including pay for work through the end of today will be deposited to your account, along with any other required payments, as dictated by your work location.

And exit the industry while he's at it:

As of today I am stepping away from the game industry to focus on my family and care for Annie [Delisi Strain]. I wish all of you the best as you navigate this complex industry and the challenges and opportunities ahead.

In a recent episode of Breaking Change I talked at length about why the "AAA" gaming industry is falling apart before our eyes, so reactions like this don't seem so unreasonable to me. I've been wondering aloud for my entire career why the hell anyone gets into the gaming business when virtually everyone involved could make an order of magnitude more money working half as many hours doing literally anything else involving software. Now that the free money spigot has been shut off, investors are starting to realize the same thing and are looking for any plausible pretense to pull the plug.

I'm sure the journalist named in the piece (incidentally, by his own employer, via this article) doesn't feel awesome that his reporting indirectly led to his sources losing their jobs. And the fact he apparently never bothered to contact Annie Delisi Strain—the rare-for-the-industry woman CEO currently on medical leave—for comment is certainly a bad look. But he's probably got enough to worry about, considering that one of the few industries closer to the brink of collapse than games is games journalism.

Down for everyone because of me?

Just signed up for bandsintown to look for live concerts during my next Japan trip. I created my account using Sign in With Apple and the site immediately started behaving weirdly. Then I opened my devtools and it was spewing an unbelievable number of XHR errors. Within 90 seconds the entire site was down. It didn't come back for about 5 minutes.

I have are a very particular set of skills.

I’ve written more tests than just about anybody. Spent years comparing dynamic and static type systems. My life's work is to maximize correctness and minimize maintenance.

All it's taught me: the single most important thing programmers can do to improve their code is to minimize branching (e.g. if statements). Code that executes the same set of instructions every time behaves the same way every time.

TFW you can’t work up the motivation to program all day and then when you finally get into the zone, you run out of time and have to go be somewhere.

Spatial Personas are terrifyingly cool

Is the Apple Vision Pro "Persona" feature still firmly stuck in the uncanny valley and make me feel like I'm trapped in a Polar Express vortex? Yes.

Is the newly-released Spatial Persona mode way cooler than the default Persona-in-a-box the device shipped with? Yes.

Does all the engineering Apple has poured into the SharePlay API and frameworks finally have a meaningful purpose? Yes.

Yes. Yes. Yes. That earns Spatial Persona's a Certified Cool Stuff™ rating from me.

Super fun experience playing around with this new beta-of-a-beta feature with Aaron today. I'll definitely chat about it on my next podcast.

Before buying Apple Vision Pro, my biggest worry about the ergonomics was that I would ABSOLUTELY HATE touching my forefinger and thumb together to “click” things.

200 hours later: I was right! Still not used to it. Just supremely off-putting and uncomfortable compared to clicking a mouse, tapping a screen. Gross.

Note that this App Store policy change is global, not only to comply with the EU's Digital Markets Act.

You can read the updated policy directly, but The Verge breaks it down:

The move should allow the retro console emulators already on Android — at least those that are left — to bring their apps to the iPhone. Game emulators have long been banned from iOS, leaving iPhone owners in search of workarounds via jailbreaking or other workarounds. They’re also one of the key reasons, so far, that iPhone owners in the European Union might check out third-party app stores now that they’re allowed in the region. Apple’s change today could head that off.

There goes the only reason I'd ever be interested in a third-party marketplace. I suspect I'm not alone in that.

Am I right to use Rails 7’s normalizes feature as a way to set an app-layer default value for a model attribute?

class Basket < ApplicationRecord
  normalizes :fruits_array, with: ->(fruits) { fruits || [] }
end

Takes, re-designed

Today, I updated the design of the short-form takes on this web site to look a little more distinctive and to better conform to the design of other "non-prose" posts. Like each podcast player, the take is rendered "beneath the page" as if the page has been cut out. Each component of the take is then rendered as a roundrect: my avatar, the text of the take, and the action links.

This design is roughly what I'd wanted to make for a while, but figuring out a responsive way to render a two-piece roundrect for takes that extend beyond the height of my avatar took a while to get right.

The approach to achieve this effect requires a lot of absolute positioning, with every component but the prose itself having a fixed height, and the two white-background roundrects that contain the prose being absolutely positioned and pinned relative to all four sides of the widget.

If you're curious how this was implemented, here's the markup and tailwind classes (most of which are stock, but this site uses a custom sizing scale):

<div class="mx-auto max-w-68">
  <div class="relative p-1 md:mt-6 md:mr-2 bg-secondary beneath-the-page list-inside-all">
    <!-- The top portion of the background of each take, pinned to the top and with width adjusted to make room for the avatar -->
    <div class="z-10 bottom-12 absolute w-[calc(100%-124px)] mr-1 top-1 ml-13.5 shadow-lg bg-primary rounded-xl">
    </div>
    <!-- The bottom portion of the background of the take, pinned to the bottom and with fuller width -->
    <div class="z-20 bottom-11.5 absolute w-[calc(100%-16px)] mr-1 top-14.5 shadow-lg bg-primary rounded-tl-xl rounded-bl-xl rounded-br-xl"
      data-min-height="20">
    </div>

    <a href="/about">
      <!-- my face, fixed width/height, floated to the left -->
      <img class="float-left w-12 h-12 mt-px mb-0 mr-2.5 shadow-2xl rounded-xl" src="/img/face.jpg">
    </a>
    <div class="relative z-30 px-1 text-lg pt-sm min-h-11.5 [&amp;>*:first-child]:mt-0 [&amp;>*:last-child]:mb-0">
      <!-- The take's inner HTML, rendered from markdown -->
    </div>
    <div class="flex flex-row justify-between h-5 px-2 mx-auto mt-3 rounded-lg shadow-lg max-w-48 py-sm bg-primary min-w-30">
      <!-- Action links & icons -->
    </div>
    <div class="h-3 pt-1 text-sm text-center mt-sm text-secondary/60">
      <!-- the like count & time -->
    </div>
  </div>
</div>

This was almost enough on its own, but the two take backgrounds needed to overlap to cover the rounded corners on the bottom of the top piece. As a result, sometimes there'd be an extra trailing white doo-dad without any text associated. For this, I needed a bit of JavaScript. So I put a data-min-height="20" on the background and wrote this script:

function enforceRenderingConstraintsFor (el) {
  if (el.dataset.minHeight != null) {
    el.classList.remove('hidden') // have to remove hidden first b/c otherwise el.clientHeight will be 0
    el.classList.toggle('hidden', el.clientHeight < parseInt(el.dataset.minHeight, 10))
  }
}

function enforceRenderingConstraints () {
  const elements = document.querySelectorAll('[data-min-height]')
  elements.forEach(enforceRenderingConstraintsFor)
}

window.addEventListener('DOMContentLoaded', enforceRenderingConstraints);
window.addEventListener('resize', enforceRenderingConstraints);

The design is pretty busy and a little cramped, but I dig it anyway.

Breaking Change artwork

v9 - Learn to Unicode

Breaking Change

If you're itching for a discussion that explains why I'm afraid to move into my new bathroom, what I really think about Andrew Huberman, and why it might make more sense to learn French than Java… prepare to be scratched, I guess?

I am highly confident that at least one person will get angry and unfollow this show as a result of words that came out of my mouth. If you've got thoughts and/or feelings, let me have'm at podcast@searls.co!

Some links to things:

Show those show notes…