Code might run with vendor default settings before it runs with your config

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the dumb-mistakes-and-gotchas category.

Last Updated: 2024-11-21

I was setting up a geocoder library to use redis as a caching backend. This happened in config/initializers/geocoder.rb. Here was my config for the geocoder library:

Geocoder.configure(
  cache: Redis.current,
  cache_prefix: 'geocoder:'
)

And here was my config for redis in config/initializers/redis.rb:

config = YAML.safe_load(...)
# Background: You can have multiple redis databases with independent data
# - thus the `db` config
Redis.current = Redis.new(url: config['url'], db: config['db'])

This "worked" in the sense that the geocoder logs said it was being cached. However, when I inspected my Redis.current instance in a rails console instance, I could not see any keys with the geocoder prefix, indicating that my intended redis DB was not being used after all...

It turned out that the call to Redis.current in geocoder.rb got called before Redis.current was set. This is because the geocoder.rb initializer loaded first, since the letter g in its file name comes before r in the redis.rb one. Calling Redis.current uses the default redis parameters (which entailed a different db). Thus I connected to one redis DB here, and another after the redis.rb initializer had loaded.

The behavior of Redis.current is, in my opinion, regrettable and potentially a source of subtle bugs. I would prefer for it to cry foul if reddis had not been explicitly configured already, that way I would more easily be aware of load order bugs.

The fix:

require_relative './redis'

Geocoder.configure(
  ...
)

Lesson

  1. Code might sometimes run with vendor-default settings before the files you use to customize the settings gets executed.
  2. Files often get loaded in alphabetical order. Knowing this can help with debugging.