Thomas Cannon

Use a PORO for Configuration

This post has been sitting in my drafts for months, so I'm shoving it out into the world!

Garrett Dimon and I have been chatting back & forth about Rails configurations. This is an extension/riff on his idea for Unified Configuration in Rails

While Garrett's approach hooks nicely into Rails and has multiple metaprogramming niceties; my approach is generalized & direct (but verbose). Essentially: create a Configuration class that is a PORO; I named mine AppSettings. You can see a gist of it here!

This is born out of a convention in my iOS apps, which use a Swift struct called Configuration, that pulls the configuration from various sources into a strongly-typed, consistent location that is easy to remember.

My primary focus is to explicitly declare the configuration values and standardize how they’re accessed with a straightforward API, rather than try to standardize how they’re stored. This is because there are so many cases where either:

  • “It depends” on how they should be stored.
  • There’s an external dependency that makes you use a particular storage mechanism.
  • The configuration is so entrenched as part of the environment that it ends up in the environment’s configuration.

Using a PORO with clearly defined methods gives you:

  • Clarity in how the value is retrieved.
  • The flexibility for different value types. Some are single keys, some are nested objects, etc.
  • The same API for dynamically generated values; such as subdomained URIs
  • An easy object to mock out for tests as needed.

I've been using this approach for an internal app I've been commissioned to make; it's worked out very well so far! I'd definitely recommend moving towards this approach in your projects.

"Do it right, or do it twice" Code Quality Edition

Inspired by Lucian’s post, I finally setup code quality for the first Practical Computer app.

This whole process was definitely borderline “do it right, or do it twice.” I wish I’d solved this a bit sooner. I knew it was necessary, but had kept pushing it back because the app isn’t even close to being live yet. But this line from Lucian’s post changed my opinion:

Side projects developed while having a full-time job have a unique characteristic worth noting. The time dedicated to working on the side project is not continuous. For instance, you may work on it for 1-2 hours on Saturday, and the next opportunity to work on it may only arise a week later.

It is then essential to make the code quality built-in and use as much automation as possible.

That’s a very strong argument, and one I hadn’t heard yet. Of course, since I delayed, it caused the past 3 work sessions to be solely about fixing up the repo. But the upside is now I have all the code ready for the next project. And speaking of that, here’s a gist of my customized set of Rubocop & CircleCI configuration. I hope it helps!

I made a few technical differences than Lucian:

  • I trimmed down the Rubocops used. This gives me a balance of expressiveness & the benefits of a linter
  • I chose CircleCI because it’s what I’ve been using for years and it’s Fine™️. Plus it has the distinct advantage of SSH access to debug jobs, and job reporters
  • I’m using Bun, so relying on Dependabot for my JS dependencies
  • I’m using Code Climate for maintainability monitoring

Ruby Passkeys Update

Posted a quick update about the Ruby Passkeys organization in the passkeys issue for Devise

 

The gist is:

  • There are very early alpha implementations for Devise and Warden, including a template repo
  • Once again, I am asking for desperately needed maintainers for this

HTTPS, Subdomained System Tests in Ruby on Rails

Getting puma to use self-signed certificates in Test

Install the localhost gem, which allows Puma to use self-signed certificates in your test environment: https://github.com/puma/puma/#self-signed-ssl-certificates-via-the-localhost-gem-for-development-use

group(:test) do
  gem 'localhost'
end

It’s not documented anywhere in Capybara, but Capybara’s built-in Puma handler allows you to issue a custom bindhttps://github.com/teamcapybara/capybara/pull/2028

Capybara.run_server = true
Capybara.server = :puma, { Host: "ssl://#{Capybara.server_host}"}

I found that you do need to explicitly pass the Capybara.run_server = true argument in your system tests

You also need to have Selenium Webdriver explicitly accept insecure hosts: https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/acceptInsecureCerts

For example:

Capybara.register_driver :chrome do |app|
  options = Selenium::WebDriver::Chrome::Options.new
  options.accept_insecure_certs = true
  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

With that, Capybara runs with self-signed certificates in development, and the WebDriver will accept them.

Running subdomained tests

Because of how macOS does not handle wildcard DNS resolution for localhost by default, if you need to run your tests using subdomains, you’ll need to use a loopback DNS service like nip.io
For example:

Capybara.app_host = "https://myapp.127.0.0.1.nip.io"

Depending on how your existing tests are setup, you might also need to setup a custom initializer block in config/environments/test.rb to override the tld_length

#config/environments/test.rb
Rails.application.configure do
  #...

config.hosts << “.127.0.0.1.nip.io”

if ENV[“PREP_SYSTEM_TESTS”].present? config.action_dispatch.tld_length = 5 # 127.0.0.1.nip.io = 5 top-level host details to throw out Rails.application.routes.default_url_options[:host] = “127.0.0.1.nip.io” # Necessary for routes to play nicely # … end end

Working on a brand-new feature for Noko, and it’s progressing incredibly quickly. I can’t wait for folks to get their hands on it.

Related: Maintaining software is a really tricky thing, but pays off. The theme of this year has to be behind-the-scenes rewrites; we’ve touched a lot of Noko’s core internals, because they needed the maintenance desperately. Since we approached it strategically and tried to double-dip as much as possible; this new feature is coming together pretty seamlessly.

14+ years of a product being around means that stuff needs fixed! Frameworks change, heck, the execution environment is wildly different now with browsers. The best thing you can do for yourself + your team is to build with maintainability in mind as much as possible. Which is different than make everything generic, but that’s a whole other can of worms.