justin․searls․co

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.

Okay, I'm interested…

Not a big fan of the two big three-letter acronyms to emerge in the Apple blogosphere so far in 2024. To recap, these already have robust definitions in online discourse:

  • AVP: Alien versus Predator
  • CTF: Capture the Flag

My useless superpower is the ability to accurately predict in advance exactly how miserable something will be, and with no ability to do anything about it but to wait in lucid anticipation for said misery to unfold.

Apple is in discussions with Google to integrate its Gemini AI engine into the iPhone as part of iOS 18, according to Bloomberg's Mark Gurman.

Through iOS 5, Maps and YouTube were native apps that Apple built and which were backed by Google services. This was advantageous for both parties at first. Apple wasn't nearly ready to roll out its own mapping service and Google was more focused on growing YouTube's reach than monetizing it. Eventually, it stopped making sense for either party, and they went their separate ways.

The primary media narratives about this focused on Steve Jobs' "thermonuclear" threat over Android's copying of the iOS UI and the degree to which the two companies had begun to compete on services. But one thing that was lost in the discussion—which never really squared with the fact Google has continued to pay Apple tens of billions a year to be Safari's default search engine—was that both companies maintain relatively-tenuous moats to lock in customers.

Right now, Google needs people to reach for its AI and search stack before a generation of users learn to "GPT it", and Apple needs an AI stack for its platform that can compete with the dozens of devices set to launch that are little more than thin candy shells on top of OpenAI's API.

I really hate the idea of this deal, and I bet executives at both companies do, too. Which is why it's so unfortunate that it also makes sense.

Decided to “treat myself” and pull in a third party dependency to render breadcrumb links in this new app I’m building. Apologies to future me for when this inevitably breaks someday! I was being lazy.

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.

You'll never guess what happens next…

Unifi has a neat way of sorting dates

This UI, which appears to be the result of a query like ORDER BY year DESC, date ASC is a violence.

Gripping story, overall, and worth a read. This bit stuck out to me as something I'd never considered before, but felt obvious as soon as I was exposed to it:

Political communicators are sticking to approaches developed for an era when ticket-splitters and swing voters composed a sizeable chunk of the electorate. But with a body politic that has sorted into two highly polarized parties—with just one-tenth of voters torn between them—the logic of persuading voters to support a candidate has grown obsolete. Ad campaigns should instead promote the Democratic Party itself, Malchow proposes, particularly at moments when news events might help it win new adherents, such as after a mass shooting, which thrusts gun-control policy back into the news and voters might be ready to reconsider their allegiances.

To wit: in an era of extreme party polarization, 90% of people in the US are voting based on party affiliation, but campaign advertising is still centered on candidate choice. This isn't just inefficient, it's counter-productive, since most candidates run away from their parties in general elections because both parties' brands are so toxic. Focusing money and messaging on bolstering a party's brand seems like a much smarter way to meet this moment of overwhelmingly party-line voting.

I can only hope I'll still have meaningful insights to offer others during my final week on earth.

My Twitter account had been peacefully lying dormant since November 18th, 2022, but so many people are still using X that I finally caved a few weeks ago and spent 45 minutes wiring up a syndication strategy, effectively adding it to this site's POSSE.

If you want to do this, here's what you'll need:

  • The azu/rss-to-twitter GitHub Action and a modest amount of a free account's budget
  • A Twitter developer account and app
  • An Atom feed to read from (here's mine, specially crafted to cram the full content of each tweet in the <title> of each <entry>)
  • The handle you're posting as must be marked as an automated account and registered as managed by some other account

Once you have all that, you can define a YAML file defining the action in your GitHub repo like the one for this site:

# .github/workflows/rss_to_twitter.yml
name: rss-to-twitter
on:
  schedule:
    - cron: "*/30 * * * *"
  workflow_dispatch:
jobs:
  twitter:
    runs-on: ubuntu-latest
    steps:
      - uses: azu/rss-to-twitter@v1
        with:
          RSS_URL: "https://justin.searls.co/shorter-form.xml"
          TWEET_TEMPLATE: '%title%'
          TWITTER_APIKEY: ${{ secrets.TWITTER_APIKEY }}
          TWITTER_APIKEY_SECRET: ${{ secrets.TWITTER_APIKEY_SECRET }}
          TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
          TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}

The above cron schedule translates to checking the Atom feed "every 30 minutes", which is more frequent than it needs to be in my case. And as for all those ${{ secrets… }} directives, here's how to configure your repository's GitHub Action secrets.

And that's it. Easy!

Enter your iPhone passcode to set up your Mac

Gun-to-head, there’s no way in hell I would successfully draw the flowchart of account states and security responses necessary for me to land on this screen.

(I have no idea where this iPhone 11 Pro even is!)

visionOS Algorithm Failure Detected

Neat.

Breaking Change artwork

v7 - Outside your app's business hours

Breaking Change

This was a long episode, so here are some short notes.

As always, your e-mails delight and inspire me. Send one to podcast@searls.co with anything you want in it. I don't care if it's just emoji and gifs.

Citations follow:

Show those show notes…

In hindsight, when I was under a lot of pressure to drop CoffeeScript a decade ago in favor of "modern" JavaScript (defined as using tools like babel and webpack), I wish I'd held my ground.

Without a doubt I'd be better off today if I'd just gone straight from CoffeeScript to real ESM and import maps. Just spent three hours dealing with bitrot in a webpack config with no end in sight. The JavaScript Dream was always a house of cards.

Apart from being one of my favorite people—and someone whose wisdom had a big impact on my own professional development—Joel Helbing shares a bit about his experience giving himself just enough of a crash-course on whatever skill a new job needs to be able to show up to work on Monday:

Then I ended the call, got in my car, and drove an hour to the nearest Borders bookstore. I purchased two promising books on Microsoft SQL Server, went to the bookstore’s in-house Starbucks, purchased a venti iced coffee, sat down with those two books and a legal pad, and mapped out my weekend in fine detail. It came down to 15 minutes for this chapter, 10 for that chapter, skip this other chapter, etc. Then I drove home and followed my script meticulously for the whole weekend. This was not easy for me; I’m a curiosity-driven learner who loves to follow a thread and go deeper. Not this weekend, though. I stuck to the plan, and on Sunday night I got back in my car and started the long drive to my new gig.

(Imagine I spent the thirty seconds to insert a "this is the way" GIF here.)

But seriously, Joel's not kidding. He's indeed one of the most deliberate, curious people I've ever met, and I bet rushing through a bunch of content in order to get his arms around a topic was acutely painful for him. But when you're a consultant, your clients need you to be conversant in whatever they're focused on, and frequently that means brushing up on topics and technologies your previous ten clients weren't focused on.

Doing this was always painful for me, too. In part, because I'm a perfectionist who really struggles to move onto chapter two until I've absorbed, critiqued, and improved on everything the author posited in chapter one. For me, the act of learning is an exhausting war of attrition. But when my job depended on me showing up knowing something, I had no choice but to swallow my pride and immerse myself in a topic uncritically in order to learn enough to be dangerous.

This is an intensely uncomfortable activity. That immersion feels like drowning at first. I'm even feeling it this morning, as I'm upgrading a database I haven't touched in 3 years and which I presently can't remember how to back up—my gut is churning in worry as a result. As a consultant, though, I always understood that the stakes were always higher for my client than for me, and every ounce of discomfort I shouldered almost always translated to an ounce of burden I could remove from their plate. Sometimes that meant using their preferred technology over mine. Or assigning myself to their legacy systems so their full-timers could be the ones to break ground on the next green-field app. Or, and this was always the hardest for me, relenting and using their janky issue trackers and time-keeping systems.

Some consultancies blunt this reality by deploying large teams where there are levels of indirection between clients and practitioners—placing an engagement manager in front of the client who can run interference while the team of consultants behind him ramp up at a more leisurely and comfortable pace (as they bill every hour at a full rate). Other consultancies prioritize their convenience over their clients' needs by narrowing their services and prescribing a single monolithic "Way" to work with them—often requiring clients to build systems in the agency's favorite tech stack—firmly ensconcing each consultant in a cocoon of comfort. But at Test Double, it never occurred to do anything other than lean in and rise to the challenge of actually meeting our clients where they are. We spent years demonstrating to our clients that no matter how arcane their technology or byzantine their org chart, our people will get up to speed so fast it'll make their heads spin. You get good at what you do, and if the thing you do is stomach discomfort as you learn hard things in service of others, then there's almost no limit to what you can accomplish.

One last note: showing up to a client following a weekend-long crash course in a particular technology doesn't make you a fraud. Nearly twenty years in consulting has taught me that the people most worried about misrepresenting themselves and their abilities are the people who have the least reason to worry. The fact they care so much almost always means they'll put in the work when they need to. The real frauds, meanwhile, don't worry at all. And while Joel was holed up in a Starbucks for 72 hours, I'm sure they were having a delightful and relaxing weekend. And Joel's much richer for it, as he's gotten four careers' worth of experience by repeatedly diving into new industries, organizations, and technologies, whereas the real imposters only learned how to talk a good game as they skated through life without ever stretching themselves.