Auto-reload `config_for` files in Rails
Tags: rails
Published: 2021-12-31 10:34:17

In Rails projects I often use YAML files to organize app-wide values or configurations.

```yaml
# config/settings.yml
shared:
  important_value: 42

development:
  default_from_email: 'development@example.com'

test:
  default_from_email: 'test@example.com'

production:
  default_from_email: 'production@example.com'
```

In the application configuration we create a new configuration:

```ruby
# config/application.rb
module RailsApp
  class Application < Rails::Application
    # ...
    config.settings = config_for(:settings)
  end
end
```

We can access these values using  `Rails.configuration.settings`, for example:

```ruby
# $ RAILS_ENV=test bin/rails c
# Loading test environment (Rails 7.0.0)
Rails.configuration.settings[:important_value]    # => 42
Rails.configuration.settings[:default_from_email] # => "test@example.com"
```

Unfortunately the configuration is **not** reloaded automatically. If we change the `important_value` and refresh the page, we'd still see the old value. In order to see the value, we'd need to restart the server first. This can be confusing and tedious, particularly in development.

Here's a way to reload the configuration every time the file changes.

Create a new file `config/initializers/config_for_reloader.rb`:

```ruby
# config/initializers/config_for_reloader.rb
if Rails.env.development?
  config_for_reloader = ActiveSupport::FileUpdateChecker.new(["config/settings.yml"]) do
    Rails.application.config.settings = Rails.application.config_for(:settings)
  end

  ActiveSupport::Reloader.to_prepare do
    config_for_reloader.execute_if_updated
  end
end
```

* `ActiveSupport::FileUpdateChecker` takes an **Array** of file paths and a block containing the instructions to be executed on change. ([Documentation for `ActiveSupport::FileUpdateChecker`](https://api.rubyonrails.org/classes/ActiveSupport/FileUpdateChecker.html))

In our case the block contains the same instruction as in `config/application.rb` for loading the YAML file.

* `ActiveSupport::Reloader.to_prepare` registers a callback that will run once at application startup and every time the code is reloaded. ([Documentation for `ActiveSupport::Reloader.to_prepare`](https://api.rubyonrails.org/classes/ActiveSupport/Reloader.html#method-c-to_prepare))

At this point, I would have expected that Rails picks up the changes automatically after the file `config/settings.yml` is modififed, but for some reason it does not. It only reloads the file and sets the configuration after an "application file" (a model, template, controller, routes, etc.) is modified.

We can workaround this by adding `config/settings.yml` to the `eager_load_paths`:

```ruby
# config/application.rb
module RailsApp
  class Application < Rails::Application
    # ...
    config.settings = config_for(:settings)
    config.eager_load_paths << Rails.root.join("config/settings.yml")
  end
end
```

(If anybody knows why that is necessary, I'd be interested.)

Now, the configuration file is reloaded after changing it and a page refresh would show the latest value.

(Cover image by [Christopher Alvarenga](https://unsplash.com/@kriztheman?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/dark-alley?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText))