diff --git a/README.md b/README.md index d979634c..efea9fa9 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,22 @@ Example production environment config file: #{Rails.root}/config/environments/production.yml ``` +### Extra sources + +You can load extra sources during initialization by setting the `extra_sources` configuration option. + +```ruby +Config.setup do |config| + config.extra_sources = [ + 'path/to/extra_source.yml', # String: loads extra_source.yml + { api_key: ENV['API_KEY'] }, # Hash: direct hash source + MyCustomSource.new, # Object: custom source object that responds to `load` + ] +end +``` + +This will also overwrite the same config entries from the main file. + ### Developer specific config files If you want to have local settings, specific to your machine or development environment, you can use the following files, which are automatically `.gitignore` : diff --git a/lib/config.rb b/lib/config.rb index e98d1e70..1d9d7207 100644 --- a/lib/config.rb +++ b/lib/config.rb @@ -29,7 +29,8 @@ module Config merge_hash_arrays: false, validation_contract: nil, evaluate_erb_in_yaml: true, - environment: nil + environment: nil, + extra_sources: [] ) def self.setup @@ -57,7 +58,10 @@ def self.load_files(*sources) def self.load_and_set_settings(*sources) name = Config.const_name Object.send(:remove_const, name) if Object.const_defined?(name) - Object.const_set(name, Config.load_files(sources)) + + # Include extra sources in the loading process + all_sources = [sources, Config.extra_sources].flatten.compact + Object.const_set(name, Config.load_files(*all_sources)) end def self.setting_files(config_root, env) diff --git a/lib/generators/config/templates/config.rb b/lib/generators/config/templates/config.rb index f6a222d0..ddd591da 100644 --- a/lib/generators/config/templates/config.rb +++ b/lib/generators/config/templates/config.rb @@ -65,4 +65,13 @@ # # config.file_name = 'settings' # config.dir_name = 'settings' + + # Load extra sources from a path. These can be file paths (strings), + # hashes, or custom source objects that respond to 'load' + # + # config.extra_sources = [ + # 'path/to/extra_source.yml', # String: loads extra_source.yml + # { api_key: ENV['API_KEY'] }, # Hash: direct hash source + # MyCustomSource.new, # Custom source object + # ] end diff --git a/spec/config_env_spec.rb b/spec/config_env_spec.rb index 99e3e863..5ca3109f 100644 --- a/spec/config_env_spec.rb +++ b/spec/config_env_spec.rb @@ -7,7 +7,11 @@ context 'when overriding settings via ENV variables is enabled' do let(:config) do - Config.load_files "#{fixture_path}/settings.yml", "#{fixture_path}/multilevel.yml" + config_instance = Config.load_files "#{fixture_path}/settings.yml", "#{fixture_path}/multilevel.yml" + # Ensure the Settings constant is set to the same instance for Config.reload! to work + Object.send(:remove_const, 'Settings') if Object.const_defined?('Settings') + Object.const_set('Settings', config_instance) + config_instance end after :all do diff --git a/spec/config_spec.rb b/spec/config_spec.rb index 505c6081..79f8c4e2 100644 --- a/spec/config_spec.rb +++ b/spec/config_spec.rb @@ -38,6 +38,30 @@ expect(config.root['google.com']).to eq(3) end + it "should load extra_sources and support different types" do + test_hash = { 'extra_key' => 'extra_value' } + object_source = double 'source' + + allow(object_source).to receive(:load) do + { 'server' => 'google.com' } + end + + Config.setup do |config| + config.extra_sources = [ + "#{fixture_path}/settings2.yml", + test_hash, + object_source + ] + end + + Config.load_and_set_settings("#{fixture_path}/settings.yml") + + expect(Settings.size).to eq(1) + expect(Settings.extra_key).to eq('extra_value') + expect(Settings.another).to eq("something") + expect(Settings.server).to eq('google.com') + end + it "should load 2 basic config files" do config = Config.load_files("#{fixture_path}/settings.yml", "#{fixture_path}/settings2.yml") expect(config.size).to eq(1) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index aff76763..ed84b4cf 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -84,6 +84,18 @@ def fixture_path # Extend Config module with ability to reset configuration to the default values def self.reset + # Clear any existing Settings constant and its sources to prevent mock leakage + current_const_name = self.const_name + if Object.const_defined?(current_const_name) + settings_instance = Object.const_get(current_const_name) + # Clear the config sources to prevent mock doubles from leaking + if settings_instance.respond_to?(:instance_variable_set) + settings_instance.instance_variable_set(:@config_sources, []) + end + Object.send(:remove_const, current_const_name) + end + + # Reset configuration to defaults self.const_name = 'Settings' self.use_env = false self.knockout_prefix = nil @@ -93,6 +105,7 @@ def self.reset self.fail_on_missing = false self.file_name = 'settings' self.dir_name = 'settings' + self.extra_sources = [] instance_variable_set(:@_ran_once, false) end end