Mason Stallmo

← Back to blog

Published on Wed Jan 29 2025 10:38:00 GMT+0000 (Coordinated Universal Time) by Mason Stallmo

The release of Rails 8 in November of 2024 brought some really nice improvements to the amount of extra infrastructure needed to run a Rails app in production. The addition of the solid family of gems (solid_queue, solid_cache, and solid_cable) removed the need for additional services like Redis and Sidekiq for running background jobs in Rails apps. These additions are a benefit to everyone building on Rails and makes it a more effective “one person framework”.

As with any change there has been a teething period when it comes to deploying Rails 8 applications to existing hosting providers. I ran into one of these issues when deploying a new Rails app onto Fly.io. Specifically I had issues with the new databases crated in Postgres that back solid_queue, solid_cache, and solid_cable. Pre-Rails 8 apps by default had one database that the application data was inserted into and then any other services that needed a data persistence layer commonly used things like Redis (see Sidekiq). Rails 8 removes that need and instead relies on Postgres itself for all data persistence for both long term and short term data. This is one of the main sources of infrastructure improvement. Instead of having two different data persistance services you just have one, your main postgres database.

This change can cause some friction with existing platform’s assumptions about the structure of Rails apps. In Fly’s case they run a codemod when deploying a Rails app that addresses this new paradigm. Fly’s deployment tooling modifies the production section of the config/database.yml file in your Rails application to use the DATABASE_URL environemnt variable to configure the database connnection in production. Out of the box the result of that modification looks like this

# Rest of config file...

production:
  primary: &primary_production
    <<: *default
    url: <%= ENV["DATABASE_URL"] %>
  cache:
    <<: *primary_production
    migrations_paths: db/cache_migrate
    url: <%= URI.parse(ENV["DATABASE_URL"]).tap { |url| url.path += "_cache" } if ENV["DATABASE_URL"] %>
  queue:
    <<: *primary_production
    migrations_paths: db/queue_migrate
    url: <%= URI.parse(ENV["DATABASE_URL"]).tap { |url| url.path += "_queue" } if ENV["DATABASE_URL"] %>
  cable:
    <<: *primary_production
    migrations_paths: db/cable_migrate
    url: <%= URI.parse(ENV["DATABASE_URL"]).tap { |url| url.path += "_cable" } if ENV["DATABASE_URL"] %>

At first glance this looks on the up and up. The suffixes _cache, _queue, and _cable are applied to the default DATABASE_URL and everything should be good to go. This is true in the Fly.io environment but has issues in development and test environments.

I specifically ran into this issue with my GitHub Actions runs when trying to run my application’s tests. Before deploying to fly my tests were all passing and then as soon as I ran the deployment (and this codemod) my tests in CI started failing with the error URI::InvalidComponentError: bad component(expected absolute path component): _cache (URI::InvalidComponentError). The error is a bit cryptic but like any good engineer I went and googled for the error hoping to find a solution. The vast majority of results that I found were for other URI:InvalidComponentError issues, many of them being quite old. It took a little bit of digging until I came across this post on the Ruby on Rails forum. At the bottom of this thread is where I found the solution that I was looking for (shoutout Keith Schacht!).

To make the new configuration play nice in development and test environments a check for Rails.env.production? needs to be added to the configuration for the production databases. The final version of the configuration file looks like this

# Rest of config file...

production:
  primary: &primary_production
    <<: *default
    url: <%= ENV["DATABASE_URL"] %>
  cache:
    <<: *primary_production
    migrations_paths: db/cache_migrate
    url: <%= URI.parse(ENV["DATABASE_URL"]).tap { |url| url.path += "_cache" } if ENV["DATABASE_URL"] && Rails.env.production? %>
  queue:
    <<: *primary_production
    migrations_paths: db/queue_migrate
    url: <%= URI.parse(ENV["DATABASE_URL"]).tap { |url| url.path += "_queue" } if ENV["DATABASE_URL"] && Rails.env.production? %>
  cable:
    <<: *primary_production
    migrations_paths: db/cable_migrate
    url: <%= URI.parse(ENV["DATABASE_URL"]).tap { |url| url.path += "_cable" } if ENV["DATABASE_URL"] && Rails.env.production? %>

With this change my CI tests went back to green and all was right in the world again.

Hopefully this was helpful and adds some explaination to this issue. Check back for more Ruby on Rails and programming posts!

Written by Mason Stallmo

← Back to blog