Matthew Lindfield Seager

In the nick of time before semester 2 starts tomorrow, I just finished The Lost Metal by Brandon Sanderson 📚

Finished reading: The Bands of Mourning by Brandon Sanderson 📚

Finished reading: Shadows of Self by Brandon Sanderson 📚

Finished reading: The Alloy of Law by Brandon Sanderson 📚

Finished reading: Mistborn, The Well of Ascension and The Hero of Ages by Brandon Sanderson 📚

PSA for macOS Terminal users:

Terminal Prefs → Profiles → Shell:

  • Ask before closing
  • Close if the shell exited cleanly

No more accidentally killing your local dev server with ⌘Q. To close a terminal tab/window without confirmation close the shell with ^D

Thinking about ways to disambiguate the same username on different federated instances based on a question from @manton

Pic shows the same username on mastodon.social and ruby.social. Avatar colour would be dynamic but always the same for the same service…

Installing Teams for an upcoming meeting and noticed the installer is called “Teamsosx”. OS X was renamed macOS in 2016, almost a year before Teams was released. It’s inattention to details like this that make Microsoft products really stand out 😉

Trying to stay positive online so rather than bash Hover I’ll compliment Cloudflare. Cloudflare domain registration is:

  • 47% cheaper
  • Just as private
  • More transparent
  • More convenient (since my DNS is already hosted there)

80% of my domains now transferred 😉

Our house is all ceramic tiles and concrete, terrible for acoustics! So here’s my improvised recording studio; kneeling on the ground with my head in the sound absorbent cave…

Technology can be good but I’d be okay if I never had to do another video call… VR meetings really don’t appeal to me!

Watch out for GitGuardian. They scan public GitHub repos unbidden & send unsolicited emails about “exposed secrets” to trick you into signing up. Once you’re in, they request access to all your repos then waste your time telling you about dozens of exposed “secrets” that aren’t 👎

If you think you’re leading and no one is following you, then you’re only taking a walk — Afghan Proverb*

*Maybe, I’m choosing not to go down the rabbit hole of looking for the origin of this phrase/sentiment

A bit bummed to hear Salesforce is killing Heroku’s free tiers. Pretty sure there’s other options to prevent/minimise abuse.

I’ve narrowed down my search for nw5k.com’s new home to Railway, Render or Fly(.io)… or maybe old school with Ansible, Capistrano and Linode 🤷‍♂️

A month or two in and I’m super happy to have found Nova as a capable (and native!!!) replacement for Atom.

I tried VS Code but, even without plugins, it felt busy and bloated.

Still learning not to hit ⌘ T for fuzzy open though 🙃

Enjoyed watching WWDC this year! One nice thing about being in Cambodia is that it went from midnight to 2am, rather than 3am-5am in Sydney.

Almost time for a new laptop, struggling to decide between the 14” M1 Pro MBP or 13.6” M2 MBA 🤔

Almost a perfect bell curve of results in Wordle… add in my 2 misses, that I almost certainly would have got in 7, and it’s almost perfectly symmetrical!

Found a useful tip on how to combine a bunch of files from nested sub-folders into the parent folder…

In my case the folder name was Archive:
find Archive/ -mindepth 2 -type f -exec mv -i '{}' Archive/ ';'

User Friendly Error Messages for Multiple Fields in Rails

The built in error system in Rails, specifically displaying errors in an HTML form when data isn’t valid, is mature and works really well for simple cases.

Adding validates :name, presence: true to a user model is all we need to do to prevent people from saving a user without a name. Straight out of the box we automatically get a count of the errors and details of each error: Error messages "out of the box"

Rails also wraps the field and label in a div with class field_with_errors so two lines of CSS in the application layout is all it takes to initially add some basic error highlighting to an “out of the box” application:

<style>
  .field_with_errors { color: #b22; }
  .field_with_errors input { background-color: #fcc; }
</style>

Forms errors with two lines of CSS added

More complicated checks are also possible but displaying the results of those checks in a user-friendly way wasn’t very intuitive to me. This post documents the things I tried and the solution I ended up with.

The end goal is to require at least one of two fields to be present for the form to be valid. To make that clear to the user, I want it to show up as a single error but with both fields highlighted:

Single error but with two fields highlighted


Solution 1

The approach you’ll find suggested multiple times on StackOverflow is to add a check to both fields:

validates :email, presence: {unless: :username?}
validates :username, presence: {unless: :email?}

Naive solution, checking both fields separately

Technically this works but I don’t like the way it shows two different errors or that the error messages aren’t entirely truthful. Even if we customise the error messages to make them more accurate, it doesn’t really improve the situation as the error messages are still redundant (not to mention awkwardly worded as they must start with the name of the field):

validates :email, presence: {unless: :username?, message: "or Username must be present"}
validates :username, presence: {unless: :email?, message: "or Email must be present"}

Updating the messages doesn't really help the situation


Solution 2

The next solution is to try a custom validation. By adding an error to :base it applies to the whole model object, rather than an individual field:

validate :email_or_username

private
def email_or_username
  if email.blank? && username.blank?
    errors.add(:base, message: "At least one of Email and Username must be provided")
  end
end

This gets us closer to our desired end state of one error for one problem but we lose field highlighting:

One error for one problem but no field highlighting

We can modify the validation method to get field highlighting but then we end up with three errors:

if email.blank? && username.blank?
  errors.add(:base, message: "At least one of Email and Username must be provided")
  errors.add(:email)
  errors.add(:username)
end

Correct field highlighting but too many errors

Solution 3

We need some way to distinguish the field errors from the overall record error in a way that will make sense to us in the future. As of Rails 6.1, errors are first class objects and the optional second argument to ActiveModel::Errors.add is a type symbol.

If we add an explicit type to the errors that will help us identify them later:

# app/models/user.rb
if email.blank? && username.blank?
  errors.add(:base, :email_and_username_blank, message: "At least one of Email and Username must be provided")
  errors.add(:email, : highlight_field_but_hide_message)
  errors.add(:username, : highlight_field_but_hide_message)
end

Then in our generated view we need to somehow ignore or delete the ...hide_message errors. At first I thought I could use ActiveModel::Errors.delete to delete them but there are multiple problems with that approach:

  • to match on a type you first have to match on attribute (either looping through all attribute names or listing attributes explicitly)
  • deleting the error prevents the field from being highlighted so you need to render the fields first, then delete the errors, then show the error message (which would also require some CSS shenanigans to move it back to the top as per the original requirement)

The simplest approach seems to be to take advantage of the fact that Errors is Enumerable and simply reject the errors we don’t want to include in the summary:

# app/views/users/_form.html.erb
<%= form_with(model: user) do |form| %>
  <% visible_errors = user.errors.reject{ |e| e.type == :highlight_field_but_hide_message } %>
  <% if visible_errors.any? %>
    <div class="text-red-700 border border-red-700 border-rounded m-2 p-2 bg-red-200 max-w-md">
      <h2 class="text-xl"><%= pluralize(visible_errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul class="list-disc list-inside">
        <% visible_errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
<% end %>

One error message but multiple fields highlighted


Conclusion

With a small amount of additional effort it’s possible to show a single error message but highlight multiple fields. My next task is to override Rails.application.config.action_view.field_error_proc so I can use Tailwind CSS to style the fields with errors.

I’m not sure how I feel about “lossy” Markdown support via autocorrect in a WYSIWYG word processor… workspaceupdates.googleblog.com/2022/03/c…

To me the main point of markdown is portability 🤔

Static Assets in Rails 7

Note for future me (written partially for myself, partially in answer to this SO question).

The home for static or public assets is the /public folder. In a default Rails 7 app you will find some error pages (e.g. /public/404.html) and icons (e.g. /public/favicon.ico) already in that folder. These are then served directly from the “raw” domain (at example.com/404.html and example.com/favicon.ico respectively). They are also accessible under example.com/public.

To reference these static images from ERB you need to make sure you tell Rails it’s an absolute path, not a relative one somewhere in the asset pipeline. This is done with a leading forward slash.

The following compares including a logo called logo.png when it’s stored in app/assets/images vs /public

<%= image_tag "logo.png" %>
<!-- becomes <img src="/assets/logo-1f04fdc3ec379029cee88d56e008774df299be776f88e7a9fe5.png"> or similar -->


<%= image_tag "/logo.png" %>
<!-- note the leading slash. This becomes <img src="/logo.png"> -->

<img src="/logo.png">
<!-- plain HTML if you don't want to use ERB tag -->

You can also use sub-directories; /public/images/logo.png would be available at /images/logo.png (or image_tag "/images/logo.png").

The second paragraph of chapter 2 of The Asset Pipeline Rails Guide contains more information. It mentions that this functionality depends on whether config.public_file_server.enabled is set.

In Rails 7 that config defults to ENV["RAILS_SERVE_STATIC_FILES"].present? in config/production.rb. If you’re using Heroku you can check this setting is present and enabled with heroku config:get RAILS_SERVE_STATIC_FILES.

I know naming is hard but seriously, “standard” vs “default” was the best you could come up with Googlers?!

developers.google.com/apps-scri…

The default type is not standard and the standard type is not the default… 🤷‍♂️

Interesting design pattern in Google Workspace Admin Console: Related settings have arrows. Hovering over an arrow highlights the related setting that must be enabled first in order to turn the dependent setting on

“Cryptocurrencies leave me… feeling like the boy watching the naked emperor… So many significant… institutions are admiring his incredible coat, …so technically complicated and superior that normal people… can’t comprehend it and must take it on trust…”

bit.ly/3o9sdXh

CI on Github Actions with Rails 7, Postgres, esbuild, Tailwind CSS and StandardRB

After a bit of research and a lot of trial and error I finally got Github Actions working for CI on a Rails 7 (alpha 2) app that uses Postgres, esbuild and Tailwind CSS, plus StandardRB for formatting

It’s kind of hard to believe, but it seems you get it all for free!

Here’s what worked for me:

# test_and_lint.yml
name: Test and Lint

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
      - uses: actions/checkout@v2

      - uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true

      - name: Install yarn and build assets
        run: |
          yarn --frozen-lockfile
          yarn build
          yarn build:css

      - name: Install psql
        run: sudo apt-get -yqq install libpq-dev

      - name: Build DB
        env:
          PGHOST: localhost
          PGUSER: postgres
          PGPASSWORD: postgres
          RAILS_ENV: test
        run: bin/rails db:setup

      - name: Run Tests
        env:
          PGHOST: localhost
          PGUSER: postgres
          PGPASSWORD: postgres
          RAILS_ENV: test
        run: |
          bundle exec rake test
          bundle exec rake test:system

  lint:
    runs-on: ubuntu-latest
    steps:
      - name: standardrb
        env:
          GITHUB_TOKEN: $&#123;&#123; secrets.GITHUB_TOKEN }}
        uses: amoeba/standardrb-action@v2

Big thanks to Andy Croll, his instructions were super helpful to get the basic build and test workflow working with Postgres.

Vincent Voyer’s instructions were helpful for the yarn installation step.

From there, I just needed to add the yarn build and yarn build-css commands to trigger the build steps defined in package.json.