Matthew Lindfield Seager

Heading up to the house for a break after back-to-back Zoom meetings, my path was blocked by an empty washing basket.

Yes I had forgotten to hang the washing, but I was still annoyed by the passive-aggressive reminder.

I know he means well but past me can be so condescending.

All hard problems are slow feedback loops in disguise — Andy Neely (https://www.youtube.com/watch?v=jehJ10j8h6s&t=27m26s)

🤔

Enterprise Identity Management on Rails - RailsConf 2020

Reflections on the RailsConf 2020 talk of Brynn Gitt & Oliver Sanford

Brynn and Oliver shared from their experience implementing Single Sign On (SSO) and Identity Management with various protocols and providers. It was interesting thinking about this from the vendor point of view, most of my experience with SSO has been as a customer trying to implement and troubleshoot SSO integrations.


A key lesson up front was how to think about identity when building (or expanding) a business to business (B2B) application, to avoid painting yourself into a corner. Consider:

  • A single person may have multiple identities; they may be a member of multiple organisations, at the same time
  • A single “identifier”, such as an email address, may apply to different users at different times e.g. if an organisation reuses an old email address for a new member
  • A single organisation member may have multiple or mutable identifiers; e.g. for privileged accounts or in the case of name or email address change
  • Different organisations will allow or require different Identity Providers
  • Different Identity Providers have different implementations of the same protocols and standards

There is no single answer that will be correct in all circumstances but in most cases it makes sense to scope every person to an organisation. If you also need individual accounts there are two common ways to deal with that:

  • have many one-person organisations (at least under the hood, I imagine there’d be good arguments not to expose that to customers)
  • add individuals to a single “public” organisation (again, under the hood)

Within this first section Oliver briefly touched on the importance of observability and the need to log identity events. They can prove very useful when tuning a system or implementing new functionality.


The talk then went on to SAML (which is where most of my experience as a customer setting up SSO has been).

One tip was to use OmniAuth MultiProvider to make it easier to allow different customer organisations to each set up their own Identity Providers.

Another tip was to set up a flag you can turn on in production to log SAML assertions. This will allow you to assist customers as they figure out the required format and parameters while setting up SSO in their own organisation.

One last tip I want to remember is to use KeyCloak in development. It’s an open source Identity and Access Management system that can be used as an IDP in development (and can be launched simply using Docker).


After touching on just-in-time account provisioning, the rest of the talk covered SCIM - System for Cross-domain Identity Management. Implementing SCIM enables customers to create and manage accounts in your application directly from their Identity Provider (such as Okta). I’m very interested to dive into that topic a bit more, particularly with regards to how it might apply in the ed-tech space.

Thorough and well-explained post on a particular class of race condition (time of check to time of use) including examples of bugs they can cause and techniques to avoid or mitigate them: thoughtbot.com/blog/toct…

Calculating Dates in JavaScript

I was struggling recently to calculate dates using JavaScript. I wanted a minimum date to be two weeks from today but at the start of the day (midnight). In a Rails app (or a Ruby app with ActiveSupport) I would simply chain calculations on to the end of DateTime.now:

minimum_date = DateTime.now.beginning_of_day + 2.weeks

I figured I could do something similar in JavaScript so I researched out how to calculate the start of today (new Date().setHours(0, 0, 0, 0)) and how to get 14 days from now (day.setDate(day.getDate() + 14)). Separately they both worked, but no matter what I tried I couldn’t combine the two:

// Non-example 1
let minimumDate = new Date().setHours(0, 0, 0, 0).getDate() + 14
> TypeError: new Date().setHours(0, 0, 0, 0).getDate is not a function.

// Non-example 2
let startOfToday = new Date().setHours(0, 0, 0, 0)
let minimumDate = startOfToday.setDate(startOfToday.getDate() + 14)
> TypeError: startOfToday.getDate is not a function.

// Non-example 3
let now = new Date()
let twoWeeksFromNow = now.setDate(now.getDate() + 14)
let minimumDate = twoWeeksFromNow.setHours(0, 0, 0, 0)
> TypeError: twoWeeksFromNow.setHours is not a function.

Eventually I learned that both setHours() and setDate() were updating their receivers but returning an integer representation of the adjusted date (milliseconds since the epoch), not the date itself1,2.

With this knowledge in hand, one way to set the minimum date would be to just use a single variable:

let minimumDate = new Date()
minimumDate.setDate(minimumDate.getDate() + 14)
minimumDate.setHours(0, 0, 0, 0)

It works and it’s quite explicit but I don’t love that the variable name is (temporarily) wrong for the first two lines. I like my code to be self documenting and easy for future me to decipher (hence why I tried to use interim variables in Examples 2 & 3 to spell out the steps I’m taking).

As part of my exploration I went back to Ruby to figure out how I would solve the same problem without ActiveSupport. The clearest way seemed to be to create two (accurately named) dates:

now = Time.now
minimum_date = Time.new(now.year, now.month, now.day + 14)

Sure enough, we can do the same thing in JavaScript:

let now = new Date();
let minimumDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 14)

Again, that works but it relies on knowledge of how the Date constructor works (that is, it’s not very explicit). Using the explicit option but extracting it into its own method makes my intent even clearer:

let minimumDate = twoWeeksFromNowAtStartOfDay()

function twoWeeksFromNowAtStartOfDay() {
  let returnValue = new Date()
  returnValue.setDate(minimumDate.getDate() + 14)
  returnValue.setHours(0, 0, 0, 0)
  return returnValue
}

1 Ruby tries to follow the Principle of Least Surprise. Mutating a Date object but returning a number is very suprising to me. The principle in JavaScript seems to be “expect the unexpected”

2 I also learned that let is a way to declare a variable with a limited scope (whereas var creates a global variable). I had assumed that let was how you declared a constant and so I was surprised I was allowed to mutate the contents of the “constants”.

I later tested a variation of example 3 with const and it turns out I can mutate a date constant (see also footnote 1 about expecting the unexpected). I don’t want to go any further into this rabbit hole right now so the many questions this raises will remain unanswered for the time being.

// Using a "constant"
const now = new Date()
> undefined
now
> Sun May 17 2020 13:32:34 GMT+1000 (AEST)
now.setDate(now.getDate() + 14)
> 1590895954088
now
> Sun May 31 2020 13:32:34 GMT+1000 (AEST)
now.setHours(0, 0, 0, 0)
> 1590847200000
Sun May 31 2020 00:00:00 GMT+1000 (AEST) 

Can ActiveStorage Serve Images for the Modern Web? - RailsConf

Reflections on Mark Hutter’s RailsConf 2020 talk

Mark shows that ActiveStorage has some nice features which make it a good option for serving images but points out that some of those options come at a price. For example, serving variants on the fly can be expensive.

Mark has a pragmatic answer on how to measure “is it fast enough?” along with some best practices on ensuring images served through ActiveStorage are performant:

  • pre-process expected variants rather than doing it on the fly
  • better yet, pre-process in the background so as not to block the humans (or admin humans) uploading images your site
  • use eager loading to fetch associated attachment records (where appropriate) to prevent n+1 queries
  • consider monkey patching RepresentationsController if you need to cache ActiveStorage images cached using a CDN

Measure twice, cut once - RailsConf 2020

Reflections on Alec Clarke’s RailsConf 2020 talk

Using lessons from woodworking, Alec gave some practical tips on how to safely and repeatably write better code.

Safety First

Incremental (Staged) Rollouts

A simple but powerful pattern for incremental feature/version release:

  • incrementally roll out a feature over a certain period of time
  • ensure that once a user is using the new feature version they don’t flip flop between versions during the rollout period (giving a poor user experience)
  • provide an admin interface to abort the rollout without needing to re-deploy

Maintenance Jobs

A testable, less hands-on approach to writing and running one-off jobs (one less reason to console in to production servers)

  • create a MaintenanceJob class with a date and timestamped version number to ensure the job only ever gets run once
  • add a class method to run all pending jobs
  • add a line to the deploy script which triggers the pending jobs method

Solid Foundations

Proper Preparation

This lesson reminds me of the “make the change easy, and then make the easy change” principle (https://twitter.com/KentBeck/status/250733358307500032).

Alec shows an example of improving (refactoring) the old version before trying to build the new version on top (behind the aforementioned staged rollout flag).

Quality Control

Building a Tool

Building on a previous example, Alec shows an example of creating a Rails generator to make it easy for future developers to create new MaintenanceJobs the right way:

  • Create a generator that builds a MaintenanceJob AND a failing test to implement
  • Build important logic into the generator so we don’t have to remember important steps

Combined, these techniques lower the barriers to doing things the right way (or at least a good way). I particularly like the way the staged rollout gives an easy way to fix a problem at what otherwise might be a very stressful time… when error monitoring is blowing up and I’m scrambling to figure out a fix.

Thank you to the Ruby Central team for organising the COVID-19 version of RailsConf… RailsConf 2020.2 - Couch Edition (https://railsconf.com)

I plan to do short write-ups of each talk I watch to help cement what I learn.

I’ve used Zoom, Cisco WebEx, MS Teams, Slack and GoToMeeting for video calls or meetings of late. The Zoom experience was superior EVERY time.

But installing backdoors on Macs last year and exfiltrating iOS user data to Facebook this year makes me question Zoom’s company culture

I hoped to reinvent myself through this lockdown period… like maybe the change of routine and circumstance would be a catalyst for rapid change.

Cue: disappointment.

I often hope for a dramatic change, even though I know small, consistent, compounding changes are required.

Today I learned you can show hidden files and folders in the macOS finder with the keyboard shortcut Command-Shift-period (⌘ ⇧ .)

And researching it more, apparently this key combo got expanded to the Finder in Mojave, it has always worked in open/save dialog boxes 🤯

Today I learned you can use git to compare files, even if they aren’t in a repo!

git diff --no-index file1.txt file2.txt

An issue with Spring caching lead me on a journey of discovery

TLDR; I spent quite a while trying to figure out why ENV variables weren’t being loaded by dotenv-rails. Reloading spring with spring stop was the surprise fix. I learned a lot in the meantime, and since!


I decided to encrypt all personally identifying information (e.g. names and email addresses) in an app I’m hacking away on. It won’t protect them if the application server gets compromised but it adds some protection for the data at rest (you might be surprised how often data is compromised from logs or backups).

Encryption keys are part of an apps configuration and as such, I learned, they don’t belong in the code. In production I will manage the encryption keys through Heroku config vars but I wanted a simple way to manage environment variables in development so I chose dotenv (via the dotenv-rails gem).

Once I had the gem installed and my .env file populated, I fired up the Rails console and was surprised my variables weren’t in ENV. Manually running Dotenv.load worked so I knew the gem was installed and my .env file was able to be found.

After restarting the console a couple more times, the next thing I tried was adding Dotenv::Railtie.load to config/application.rb as per the instructions on making it load earlier. I fired up the console again and they STILL weren’t loaded.

I’d looked through the code on Github and felt like I had a pretty good understanding of what should be happening (and when) but it wasn’t behaving as documented.

At this point I felt like I needed to debug what was going on inside the gem so I figured out how to get the local path to the gem (bundle show dotenv-rails - thanks Stackoverflow!) and then opened the gem in my text editor. In fish shell that can be combined into a single command:
atom (bundle show dotenv-rails)

From there I did some caveman debugging and added puts statements to #load and #self.load to see if I could see them being called. I then restarted the console… still nothing. But now that I had access to the gem I could start testing with rails server rather than rails console. I restarted my dev server and straight away saw:

`self.load` got called
`load` got called
`self.load` got called
`load` got called
=> Booting Puma
=> Rails 6.0.2.1 application starting in development

Sure enough, it works when I start the server (twice, thanks to my addition of Dotenv::Railtie.load) and so the problem is only in Rails console.

After some more digging around in the Github issues I found some reference to a similar problem being caused by spring. As soon as I ran spring stop and restarted the console it worked.

To get to the bottom of this issue I started researching Spring but according to the README, changing the Gem file AND changing config/application.rb should both have caused Spring to restart.

I’ve opened an issue on Spring to see if anyone can help me figure it out but in the meantime I’m happy to have learned a fair bit…

Lessons Learned

  1. Config belongs in the environment, not in the code: https://12factor.net/config
  2. The dotenv gem makes managing environment variables simple (including support for different variables in different environments)
  3. You can find a Gem’s installation path with bundle show <gem-name>
  4. You can’t pipe the output of that command to open the gem in Atom (at least in fish shell) but you can run it as a sub-command using brackets: atom (bundle show dotenv-rails)
  5. Spring is a built-in rails mechanism for caching the application to speed up boot times in development (particularly console and tests)
  6. You can restart Spring with spring stop or by closing your terminal (the next time you launch something it will start again)
  7. You can tell Spring which files to watch by editing the config/spring.rb file

Just read that Bob will take over from Bob as CEO at Disney. Interestingly, there are as many Alans on the Disney board as there are women (three of each).

Reminds me of a diversity report on ASX200 CEOs; 32 Johns, 32 Peters, 21 Davids and 19 women

I’m reluctantly trying to sign up for Facebook so we can post micro.blog updates to friends and family during some upcoming travel.

I’ve registered with a dedicated email but now Facebook “checkpoint” won’t let us go any further without providing a mobile number 🙄

No thanks!

Finished listening to MatchUp this morning. Collection of 11 short stories written by pairs of well known thriller writers. 📖 🎧

Great stories! And I discovered some new authors to read!!!

www.goodreads.com/book/show…

Yesterday I learned you can update a single gem without updating dependencies using bundle update --conservative gem-name

If that update REQUIRES another gem to be updated I’m told you can include just it: bundle update --conservative gem-name other-gem

Thoughtbot: Name the Abstraction, Not the Data - thoughtbot.com/blog/name…

This makes a lot of sense to me. Sacrifice a small amount of DRYness (potentially) to increase clarity and loosen coupling.

📖 Force of Nature - Jane Harper (https://www.goodreads.com/book/show/34275222-force-of-nature)

Set in the Aussie bush, this was an intriguing mystery story and very well told.

(This is the 4th of 5 books I read on a cruise recently)

📖 Alex Cross’s Trial - James Patterson and Richard Dilallo (https://www.goodreads.com/book/show/6266907-alex-cross-s-trial)

This isn’t an Alex Cross book, nor (arguably) a James Patterson book. Despite the cynical marketing ploy, it was a pretty good, albeit dark at times, read

📖 Bad Luck and Trouble - Lee Child (https://www.goodreads.com/book/show/108942.Bad_Luck_and_Trouble)

Another enjoyable Jack Reacher novel. Lots of action and intrigue… and the baddies seemed like more formidable opponents than others he has faced.

📖 Scarecrow and the Army or Thieves by Matthew Reilly - www.booktopia.com.au/scarecrow…

Over the top, ridiculous and impossible to believe. Exactly what I was hoping for to start my holiday reading 🙂

Is Your Ticketing System a Tool or a Weapon?

In the hands of a carpenter, sportsperson or chef, hammers, bats and knives are all useful tools. In other hands, or even just other circumstances, they are useful weapons. In fact, just about all tools and technologies can be used constructively or destructively.

A ticketing system is no exception. At its best, a ticketing system helps agents collaborate, facilitates communication with customers and ensures issues aren’t forgotten.

At its worst, a ticketing system can be gamed by agents to avoid work they don’t like (or colleagues they find annoying), they can be weaponised by customers to prove how poor the service they received was or they can simply devolve into a distraction from the actual work of helping people.

Some quick rules of thumb I have for running a ticketing system are: - never ask someone who has just told you about a problem to log a ticket… they made the effort to visit you or call you to tell you there’s a problem, don’t make them tell the story all over again to an impersonal ticketing system - focus on solving their problem, not closing the ticket. If the problem is solved and the customer is happy, the ticket can stay open forever for all I care, just as long as it doesn’t stop you from noticing the next ticket - don’t use the ticketing system as a wishlist or backlog. Anything that hasnt been touched for two weeks (and probably isn’t going to be in the next two weeks) doesn’t belong in the ticketing system; it just gives the customer false hope and the agent unnecessary stress - if you can fix it straight away, just do it, no ticket required. We’re here to solve problems, not track time or put notches in our belts - try to fix problems and have conversations in person (or at least on the phone). Put a (very brief) summary of the conversation or solution in the ticket for collaboration/posterity - find ways to measure what matters, rather than what’s easy to measure - ignore any or all of these rules when doing so makes more sense or gives the customer better service

A Tale of Two MacBooks

My 2016 MacBook Pro had keyboard problems within about 6 months.

My 2019 MacBook Air (less than 5 months old) is having keyboard problems.

My MBP was owned by work and came with an on-site (edu) warranty.

My MBA is owned by me and has a back to base warranty.

I kept using my MBP right up until the tech arrived… and I had it back in less than an hour!

My MBA will be out of action for a predicted 5-7 business days.

My livelihood as a salaried employee was completely independent of my MBP. If it failed, I would still have been paid (AND I could have immediately borrowed another Mac from work).

My livelihood as a self-employed contractor is highly dependent on my MBA. To be without it for over a week is hugely inconvenient and potentially very costly.

Despite (or perhaps because of) my MBP experience, I used to think the tech uproar about Apple’s keyboard problems was overblown. After all, “it’s only affecting a small percentage of users” and Apple (Unisys) fixed mine almost instantly.

Now that I’m experiencing it on my almost new MBA with a third generation butterfly keyboard, and facing more than a week without THE crucial part of my toolkit, I’m feeling a lot less forgiving.


It’s just a shame my 2010 MacBook Pro can’t run a modern operating system! Other than dreadful battery life and a spongey trackpad its hardware is still going strong! (Including the keyboard, despite spending six months in the dust and grime of Afghanistan, a literal war zone)

I had a couple of hours to kill in Parramatta so I dropped into UWS’ new building near the station. They had lots of good workspaces, comfortable furniture, plenty of 240V/USB power outlets and clean facilities 👍

The table tennis table in the quiet zone made me chuckle though