Skip to content

Commit

Permalink
Allow arrays to be passed through env variables (#354)
Browse files Browse the repository at this point in the history
* Allow arrays to be passed through env variables

* Fix "Config::Sources::EnvSource configuration options default configuration arrays when loading nested configurations retains hashes for mixed types" spec

* add `Config.env_parse_arrays`

---------

Co-authored-by: dominh <>
Co-authored-by: Dominik Mlynek <[email protected]>
Co-authored-by: Chris LaRose <[email protected]>
  • Loading branch information
3 people authored Mar 30, 2024
1 parent ddd3ed6 commit fb280e6
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 2 deletions.
1 change: 1 addition & 0 deletions lib/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module Config
env_separator: '.',
env_converter: :downcase,
env_parse_values: true,
env_parse_arrays: false,
fail_on_missing: false,
file_name: 'settings',
dir_name: 'settings',
Expand Down
25 changes: 23 additions & 2 deletions lib/config/sources/env_source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ class EnvSource
attr_reader :separator
attr_reader :converter
attr_reader :parse_values
attr_reader :parse_arrays

def initialize(env,
prefix: Config.env_prefix || Config.const_name,
separator: Config.env_separator,
converter: Config.env_converter,
parse_values: Config.env_parse_values)
parse_values: Config.env_parse_values,
parse_arrays: Config.env_parse_arrays)
@env = env
@prefix = prefix.to_s.split(separator)
@separator = separator
@converter = converter
@parse_values = parse_values
@parse_arrays = parse_arrays
end

def load
Expand Down Expand Up @@ -52,10 +55,28 @@ def load
leaf[keys.last] = parse_values ? __value(value) : value
end

hash
parse_arrays ? convert_hashes_to_arrays(hash) : hash
end

private
def convert_hashes_to_arrays(hash)
hash.each_with_object({}) do |(key, value), new_hash|
if value.is_a?(Hash)
value = convert_hashes_to_arrays(value)
if consecutive_numeric_keys?(value.keys)
new_hash[key] = value.keys.sort_by(&:to_i).map { |k| value[k] }
else
new_hash[key] = value
end
else
new_hash[key] = value
end
end
end

def consecutive_numeric_keys?(keys)
keys.map(&:to_i).sort == (0...keys.size).to_a && keys.all? { |k| k == k.to_i.to_s }
end

# Try to convert string to a correct type
def __value(v)
Expand Down
31 changes: 31 additions & 0 deletions spec/config_env_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
Config.env_separator = '.'
Config.env_converter = :downcase
Config.env_parse_values = true
Config.env_parse_arrays = true
end

it 'should add new setting from ENV variable' do
Expand Down Expand Up @@ -96,6 +97,36 @@
end
end

context 'and parsing ENV variables arrays' do
context 'is enabled' do
before :each do
Config.env_parse_arrays = true
end

it 'should recognize ENV variables with subsequent numeric suffixes starting from 0 as array' do
ENV['Settings.SomeConfig.0'] = 'first'
ENV['Settings.SomeConfig.1'] = 'second'

expect(config.someconfig).to eq(['first', 'second'])
end
end

context 'is disabled' do
before :each do
Config.env_parse_arrays = false
end

it 'should not recognize ENV variables with subsequent numeric suffixes starting from 0 as array' do
ENV['Settings.SomeConfig.0'] = 'first'
ENV['Settings.SomeConfig.1'] = 'second'

expect(config.someconfig).to be_a Config::Options
expect(config.someconfig['0']).to eq('first')
expect(config.someconfig['1']).to eq('second')
end
end
end

context 'and custom ENV variables prefix is defined' do
before :each do
Config.env_prefix = 'MyConfig'
Expand Down
43 changes: 43 additions & 0 deletions spec/sources/env_source_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,49 @@ module Config::Sources
results = source.load
expect(results['action_mailer']['enabled']).to eq('true')
end

describe 'arrays' do
before(:each) do
Config.env_parse_arrays = true
end

let(:source) do
Config.env_converter = nil
EnvSource.new({
'Settings.SomeConfig.0.0' => 'value1',
'Settings.SomeConfig.0.1' => 'value2',
'Settings.SomeConfig.1.1' => 'value3',
'Settings.SomeConfig.1.2' => 'value4',
'Settings.MixedConfig.1.0' => 'value5',
'Settings.MixedConfig.1.1' => 'value6',
'Settings.MixedConfig.1.custom' => 'value7'
})
end

let(:results) { source.load }

context 'when loading nested configurations' do
it 'converts numeric-keyed hashes to arrays' do
expect(results['SomeConfig']).to be_an Array
expect(results['SomeConfig'][0]).to be_an Array
expect(results['SomeConfig'][0][0]).to eq('value1')
expect(results['SomeConfig'][0][1]).to eq('value2')
end

it 'retains hashes for non-sequential numeric keys' do
expect(results['SomeConfig'][1]).to be_a Hash
expect(results['SomeConfig'][1]['1']).to eq('value3')
expect(results['SomeConfig'][1]['2']).to eq('value4')
end

it 'retains hashes for mixed types' do
expect(results['MixedConfig']['1']).to be_a Hash
expect(results['MixedConfig']['1']['0']).to eq('value5')
expect(results['MixedConfig']['1']['1']).to eq('value6')
expect(results['MixedConfig']['1']['custom']).to eq('value7')
end
end
end
end

context 'configuration overrides' do
Expand Down

0 comments on commit fb280e6

Please sign in to comment.