You have a few different needs when it comes to configuring your gem or app:
- Sane defaults in test. These should be set in the spec_helper.
- Sane defaults in prod, aka zero-configuration defaults.
- Prod efaults specified in a file in a sensible location, probably the user's home directory.
- Some way of setting the above defaults in a clean way.
- Command-line overrides.
- Easy access throughout your application.
Create a Configuration class in your gem that accepts an empty constructor.
I recommend just subclassing OpenStruct, as it has a number of nice semantics
that we want. It should be in a file lib/mygem/configuration.rb. That file
should define the class, and make an instance of it accessible (6) via
MyGem.config. This class is responsible for prod defaults (2).
Your files can now require this file directly to get access to the config while retaining isolation.
# in configuration.rb
require "ostruct"
module MyGem
class Configuration < OpenStruct
# Optional, but it gives us a convenient way to handle
# string vs symbol keys.
def merge(other)
Configuration.new(self.to_h.merge(other.to_h))
end
def some_val
@some_val ||= "some_val_default"
end
end
def self.config=(value)
@config = value
end
def self.config
@config ||= Configuration.new
end
# Avoid having to type MyGem.config.x by delegating here.
def self.method_missing(method, *args)
if config.respond_to?(method)
config.send(method, *args)
else
super
end
end
endAdditionally, if you choose to define a DSL block for configuring your gem, the entry point of the DSL should be defined in this file. I.e.:
def self.configure
instance = self.new
yield instance
instance
endNow, in your spec or test helper, you can create a configuration object with the test defaults (1), and simply assign it
require "mygem/configuration"
MyGem.config = MyGem::Configuration.new(...) # or call configureNow, the CLI is obviously responsible for settings passed on the command line (4).
Simply require mygem/configuration in the cli.rb, and modify it as needed with
the command line options before assigning the instance to MyGem.config.
The CLI is also the entry point for discovering settings from some location (3). If your Configuration class can be instantiated from a hash, you can just have the CLI read in a yaml file--the location of which belongs to the CLI.
To wit:
require "mygem"
require "mygem/configuration"
module MyGem
class CLI
def actual_cli_method(x,y)
MyGem.config = configuration(opts)
MyGem::StuffDoer.new(x).go(y)
end
private
def config_path
Pathname.new(ENV['HOME']) + ".mygem.yml"
end
def config_from_file
Configuration.new(config_path.exist? ? YAML.load_file(config_path) : {})
end
def configuration(opts)
config_from_file.merge(Configuration.new(opts))
end
end
end