There are many ways to manage your Rails app configurations, for example using:
- ENV variables directly
- A gem like e.g. dotenv or figaro
- Using yaml files
- Global ruby variables
These solutions all have pros and cons, and we often use a mix of them in our projects depending on what makes sense for each. But there is one thing that can make your configuration more robust, that is to abstract the differences.
Consider these examples:
FooClient.new(api_key: FOO_API_KEY) # using global variables FooClient.new(api_key: ENV['FOO_API_KEY']) # using Env variables FooClient.new(api_key: Figaro.env.foo_api_key) # using the figaro gem
In all of these cases we are retrieving configurations variables in a specific way tied to the way we set them. This is brittle. If we want to change the way we load configuration for a specific variable, we would have to find all the usages and change them.
Abstracting the differences
A practice I particularly like is to create a single point of entry for all my configuration.
The App gem
A great gem for doing this is App. The App gem let's you define a main configuration file and several configuration files for each Rails environment, then this gem will merge them giving you the right values.
The App configuration file will look something like:
class App < Configurable config.foo_api_key = 'abc' config.bar_api_key = ENV['BAR_API_KEY'] end
To use the configuration we simply do something like:
Or create your own
You can also roll your own if you wish. A
Configuration class is a good place. Then all code in my application only interacts with this class. This class is aware of how to retrieve particular variables from different sources. If we ever change the way something is set, we only need to change this class. Here is an example:
class Configuration def self.get(key) case key when 'MAX_FOO' return MAX_FOO # this is a global variable when 'GOOGLE_API_KEY' if Rails.env.production? '1234567890' # hard coded for production else '55559990000' # hard coded for everything else end else return ENV[key] # assume that everything else in an ENV variable end end end
Then to use it:
Configuration.get('FOO_API_KEY') # e.g. FooClient.new(api_key: Configuration.get('FOO_API_KEY'))
This is used everywhere, even in initializers. So by using a gem like App or doing your own class, the main concept is that there is only place in our application that is aware of the differences, a single point of change. This will help you make your configuration more robust and less brittle to changes in the future.