omniconf 0.0.1.pre → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -2
- data/.rvmrc +1 -1
- data/.travis.yml +5 -0
- data/Gemfile +0 -4
- data/LICENSE +5 -0
- data/README.md +75 -34
- data/lib/omniconf/adapters/active_record.rb +38 -35
- data/lib/omniconf/adapters/base.rb +3 -20
- data/lib/omniconf/adapters/read_only.rb +12 -0
- data/lib/omniconf/adapters/yaml.rb +12 -7
- data/lib/omniconf/blank_state.rb +9 -0
- data/lib/omniconf/configuration.rb +91 -0
- data/lib/omniconf/errors.rb +7 -0
- data/lib/omniconf/helpers.rb +30 -0
- data/lib/omniconf/version.rb +1 -1
- data/lib/omniconf.rb +59 -22
- data/omniconf.gemspec +6 -8
- data/spec/fixtures/omniconf/adapters/active_record/db/schema.rb +5 -2
- data/spec/fixtures/omniconf/adapters/yaml/config/settings.yml +6 -7
- data/spec/omniconf/adapters/active_record_spec.rb +54 -18
- data/spec/omniconf/adapters/yaml_spec.rb +72 -12
- data/spec/omniconf/configuration_spec.rb +86 -0
- data/spec/omniconf/helpers_spec.rb +13 -0
- data/spec/omniconf_spec.rb +66 -20
- data/spec/spec_helper.rb +6 -2
- metadata +68 -107
- data/spec/data.sqlite3 +0 -0
- data/spec/support/mock.rb +0 -15
data/.rspec
CHANGED
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm --create
|
1
|
+
rvm --create 1.9.3@omniconf
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -2,7 +2,3 @@ source "http://rubygems.org"
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in omniconf.gemspec
|
4
4
|
gemspec
|
5
|
-
|
6
|
-
# FIXME: remove this as soon as the official Gem works with Ruby 1.8
|
7
|
-
# (see https://github.com/Offirmo/recursive-open-struct/pull/1)
|
8
|
-
gem "recursive-open-struct", :git => "git://github.com/Picklive/recursive-open-struct.git"
|
data/LICENSE
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
2
|
+
|
3
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
4
|
+
|
5
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,42 +1,67 @@
|
|
1
|
-
|
1
|
+
# Omniconf [![Build Status](https://secure.travis-ci.org/Picklive/omniconf.png)](http://travis-ci.org/#!/Picklive/omniconf)
|
2
|
+
|
3
|
+
A _RubyGem_ that provides an application-agnostic configuration merger.
|
2
4
|
|
3
5
|
# Setup
|
4
6
|
|
5
7
|
Configure and load desired backends by creating a new initializer `config/initializers/omniconf.rb`:
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
9
|
+
```ruby
|
10
|
+
Omniconf.setup do |config|
|
11
|
+
config.sources = {
|
12
|
+
:yaml_config => {
|
13
|
+
:type => :yaml,
|
14
|
+
:file => "config/settings.yml"
|
15
|
+
},
|
16
|
+
:database_config => {
|
17
|
+
:type => :active_record,
|
18
|
+
:model_name => :ConfigValue
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
```
|
19
23
|
|
20
24
|
# Usage
|
21
25
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
## Getting values
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
$ rails c
|
30
|
+
> Omniconf.configuration.some_config_from_database # value from ConfigValue model
|
31
|
+
=> "abc"
|
32
|
+
> Omniconf.configuration.some_config_from_yaml # value from config/settings.yml
|
33
|
+
=> 123
|
34
|
+
> Omniconf.configuration.api.username # it works with nested values too
|
35
|
+
=> "root"
|
36
|
+
> Omniconf.configuration.api # outputs a hash if nested
|
37
|
+
=> {"username"=>"root"}
|
38
|
+
> Omniconf.configuration.non_existant.config_value # raises an exception
|
39
|
+
=> Omniconf::UnknownConfigurationValue: cannot get a configuration value with no parent
|
40
|
+
```
|
41
|
+
|
42
|
+
## Setting values
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
> Omniconf.configuration.some_config_from_database = "def" # updates value in DB using ConfigValue model
|
46
|
+
=> "def"
|
47
|
+
> Omniconf.configuration.some_config_from_yaml = 456 # raises an exception because the value comes from YAML - who would want to update a YAML file?!
|
48
|
+
=> Omniconf::ReadOnlyConfigurationValue: cannot set 'some_config_from_yaml' because it belongs to a read-only back-end source (id: :yaml_config, type: Yaml)
|
49
|
+
> Omniconf.configuration.api.username = "admin" # it works with nested values too
|
50
|
+
=> "admin"
|
51
|
+
> Omniconf.configuration.brand_new_value = "whatever" # raises an exception because you've got to tell which back-end will store the new value
|
52
|
+
=> Omniconf::UnknownConfigurationValue: cannot set a configuration value with no parent
|
53
|
+
> Omniconf.sources[:database_config].brand_new_value = "whatever" # adds a new row in ConfigValue model
|
54
|
+
=> "whatever"
|
55
|
+
```
|
56
|
+
|
57
|
+
## Back-end sources
|
33
58
|
|
34
59
|
`:type` is the only required parameter.
|
35
60
|
Other parameters default to standard values for Rails.
|
36
61
|
|
37
62
|
### Yaml
|
38
63
|
|
39
|
-
Nothing to configure apart from a YAML file.
|
64
|
+
Nothing to configure apart from having a YAML file.
|
40
65
|
|
41
66
|
_Note: read-only._
|
42
67
|
|
@@ -46,15 +71,17 @@ Add `gem 'activerecord'` in your `Gemfile`.
|
|
46
71
|
|
47
72
|
Create a new migration to add the config table: _(FIXME: add a rake task for this)_
|
48
73
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
add_index :config_values, :key, :unique => true
|
56
|
-
end
|
74
|
+
```ruby
|
75
|
+
class CreateConfigValues < ActiveRecord::Migration
|
76
|
+
def change
|
77
|
+
create_table :config_values do |t|
|
78
|
+
t.string :key, :null => false
|
79
|
+
t.string :value, :null => false
|
57
80
|
end
|
81
|
+
add_index :config_values, :key, :unique => true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
58
85
|
|
59
86
|
### Redis
|
60
87
|
|
@@ -62,9 +89,23 @@ Add `gem 'redis'` in your `Gemfile`.
|
|
62
89
|
|
63
90
|
Not yet implemented.
|
64
91
|
|
92
|
+
## Reserved words
|
93
|
+
|
94
|
+
/!\ You should not use these names as configuration keys:
|
95
|
+
|
96
|
+
- `to_hash`: it's a helper which returns the configuration as a hash
|
97
|
+
- `inspect`: outputs sub-values as a hash
|
98
|
+
- `get_or_default`: it's a helper which returns the value if it exists or creates it otherwise (usage: `get_or_default(key, default_value)`)
|
99
|
+
- `method_missing`: used internally
|
100
|
+
- everything which starts with `__` (double underscore): used internally
|
101
|
+
|
65
102
|
# Testing
|
66
103
|
|
67
104
|
`rake`
|
68
105
|
|
69
|
-
Tested
|
106
|
+
Tested against _ree_, _ruby-1.9.3_ and _ruby-head_.
|
107
|
+
|
108
|
+
# License
|
109
|
+
|
110
|
+
MIT
|
70
111
|
|
@@ -3,28 +3,57 @@ require 'active_record'
|
|
3
3
|
module Omniconf
|
4
4
|
module Adapter
|
5
5
|
class ActiveRecord < Base
|
6
|
-
|
7
|
-
|
8
|
-
def initialize params
|
6
|
+
def initialize id, params
|
7
|
+
@source_id = id
|
9
8
|
defaults = {
|
10
|
-
:model_name => :ConfigValue
|
9
|
+
:model_name => :ConfigValue
|
10
|
+
}
|
11
|
+
defaults.merge!({
|
11
12
|
:environment => Rails.env,
|
12
13
|
:config_file => File.join(Rails.root, 'config/database.yml')
|
13
|
-
}
|
14
|
+
}) if defined? Rails
|
14
15
|
@params = defaults.merge params
|
15
16
|
end
|
16
17
|
|
17
18
|
def load_configuration!
|
18
19
|
setup
|
19
20
|
|
20
|
-
|
21
|
-
@
|
22
|
-
|
21
|
+
# create an empty config in case ActiveRecord raises
|
22
|
+
@configuration = Configuration.new self
|
23
|
+
|
24
|
+
begin
|
25
|
+
records = @model.all
|
26
|
+
rescue ::ActiveRecord::StatementInvalid => e
|
27
|
+
Omniconf.logger.warn "Could not load #{@params[:model_name]} model, ignoring this configuration source."
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
# build the configuration hash from DB (nesting on dots)
|
32
|
+
hash = {}
|
33
|
+
records.map do |record|
|
34
|
+
key, value = record.key.split('.'), record.value
|
35
|
+
el = key[0..-2].inject(hash) {|r,e| r[e] ||= {} }
|
36
|
+
raise unless el[key.last].nil?
|
37
|
+
el[key.last] = value
|
38
|
+
end
|
39
|
+
@configuration = Configuration.new self, hash
|
40
|
+
|
41
|
+
Omniconf.merge_configuration! @source_id
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_value(key, value)
|
45
|
+
key = key.join('.')
|
46
|
+
if item = @model.find_by_key(key)
|
47
|
+
item.value = value
|
48
|
+
else
|
49
|
+
item = @model.new(:key => key, :value => value)
|
23
50
|
end
|
24
|
-
|
51
|
+
item.save!
|
52
|
+
item.value
|
25
53
|
end
|
26
54
|
|
27
55
|
def setup
|
56
|
+
# define the ActiveRecord model if missing
|
28
57
|
unless Object.const_defined? @params[:model_name]
|
29
58
|
unless ::ActiveRecord::Base.connected?
|
30
59
|
::ActiveRecord::Base.configurations = YAML::load(IO.read(@params[:config_file]))
|
@@ -33,32 +62,6 @@ module Omniconf
|
|
33
62
|
|
34
63
|
klass = Class.new ::ActiveRecord::Base do
|
35
64
|
validates_uniqueness_of :key
|
36
|
-
|
37
|
-
def self.[]=(key, value)
|
38
|
-
if item = self.find_by_key(key.to_s)
|
39
|
-
item.value = value.to_s
|
40
|
-
else
|
41
|
-
item = self.new(:key => key.to_s, :value => value.to_s)
|
42
|
-
end
|
43
|
-
item.save!
|
44
|
-
item.value
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.[](key)
|
48
|
-
if item = find_by_key(key.to_s)
|
49
|
-
return item.value
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.get_or_default(key, default)
|
54
|
-
if item = find_by_key(key.to_s)
|
55
|
-
return item.value
|
56
|
-
else
|
57
|
-
self[key] = default
|
58
|
-
return default
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
65
|
end
|
63
66
|
|
64
67
|
Object.const_set @params[:model_name], klass
|
@@ -1,27 +1,10 @@
|
|
1
|
-
require '
|
1
|
+
require 'omniconf/configuration'
|
2
2
|
|
3
3
|
module Omniconf
|
4
4
|
module Adapter
|
5
5
|
class Base
|
6
|
-
|
7
|
-
|
8
|
-
def configuration
|
9
|
-
RecursiveOpenStruct.new @configuration_hash
|
10
|
-
end
|
11
|
-
|
12
|
-
def merge_configuration! with_config
|
13
|
-
Omniconf.logger.debug "Merged global configuration BEFORE: #{Omniconf.configuration_hash.inspect}"
|
14
|
-
adapter = self.class.ancestors.first.to_s.split(':').last
|
15
|
-
Omniconf.logger.debug "Merging from #{adapter} configuration: #{with_config.inspect}"
|
16
|
-
Omniconf.configuration_hash.merge!(with_config) do |key, old_val, new_val|
|
17
|
-
Omniconf.logger.warn \
|
18
|
-
"'#{key}' has been overriden with value from #{adapter} configuration " <<
|
19
|
-
"(old value: #{old_val.inspect}, new value: #{new_val.inspect})" if new_val != old_val
|
20
|
-
new_val
|
21
|
-
end
|
22
|
-
Omniconf.logger.debug "Merged global configuration AFTER: #{Omniconf.configuration_hash.inspect}"
|
23
|
-
Omniconf.configuration = RecursiveOpenStruct.new Omniconf.configuration_hash
|
24
|
-
end
|
6
|
+
attr_reader :source_id
|
7
|
+
attr_reader :configuration
|
25
8
|
end
|
26
9
|
end
|
27
10
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Omniconf
|
2
|
+
module Adapter
|
3
|
+
module ReadOnly
|
4
|
+
def set_value(key, value)
|
5
|
+
raise ReadOnlyConfigurationValue, "cannot set '#{key.join('.')}' " <<
|
6
|
+
"because it belongs to a read-only back-end source " <<
|
7
|
+
"(id: #{@source_id.inspect}, type: #{self.class.to_s.demodulize})"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
@@ -1,21 +1,26 @@
|
|
1
|
+
require 'omniconf/adapters/read_only'
|
2
|
+
|
1
3
|
module Omniconf
|
2
4
|
module Adapter
|
3
5
|
class Yaml < Base
|
6
|
+
include ReadOnly
|
4
7
|
|
5
|
-
def initialize params
|
6
|
-
|
8
|
+
def initialize id, params
|
9
|
+
@source_id = id
|
10
|
+
defaults = {}
|
11
|
+
defaults.merge!({
|
7
12
|
:environment => Rails.env,
|
8
13
|
:file => File.join(Rails.root, 'config/settings.yml')
|
9
|
-
}
|
14
|
+
}) if defined? Rails
|
10
15
|
@params = defaults.merge params
|
11
16
|
end
|
12
17
|
|
13
18
|
def load_configuration!
|
14
|
-
yaml = ::YAML.load_file(@params[:file])
|
15
|
-
@
|
16
|
-
merge_configuration! @configuration_hash
|
17
|
-
end
|
19
|
+
yaml = ::YAML.load_file(@params[:file])[@params[:environment]]
|
20
|
+
@configuration = Omniconf::Configuration.new(self, yaml)
|
18
21
|
|
22
|
+
Omniconf.merge_configuration! @source_id
|
23
|
+
end
|
19
24
|
end
|
20
25
|
end
|
21
26
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'omniconf/blank_state'
|
2
|
+
|
3
|
+
module Omniconf
|
4
|
+
class Configuration < Omniconf::BlankSlate
|
5
|
+
# XXX: Every method defined in this class overrides config value accessors,
|
6
|
+
# hence it inherits from BlankSlate (and the gross 'method_missing')
|
7
|
+
|
8
|
+
def initialize(adapter, table = {}, parent = nil)
|
9
|
+
@__adapter, @__parent = adapter, parent
|
10
|
+
@__table = table.recursive_stringify_keys!
|
11
|
+
end
|
12
|
+
|
13
|
+
# Helpful helper which returns the object as a hash
|
14
|
+
def to_hash
|
15
|
+
@__table
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
@__table.inspect
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_or_default(key, default)
|
23
|
+
raise "key cannot be nested" if key.include? '.'
|
24
|
+
__send__ :"#{key}=", default unless value = __send__(:"#{key}")
|
25
|
+
value || default
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(method, *args)
|
29
|
+
raise NoMethodError, "undefined method `#{method}' for #{self}" \
|
30
|
+
if method.to_s[0..1] == '__' # will save hours tracking down heisenbugs
|
31
|
+
|
32
|
+
len = args.length
|
33
|
+
if new_key = method.to_s.chomp!('=') # write
|
34
|
+
if len == 1
|
35
|
+
key, value = new_key, args[0]
|
36
|
+
else
|
37
|
+
raise ArgumentError, "wrong number of arguments (#{len} for 1)"
|
38
|
+
end
|
39
|
+
|
40
|
+
if @__adapter # should not be nil expect for testing purposes
|
41
|
+
# update the actual source data (e.g. does the SQL query)
|
42
|
+
full_key, parent = [key], @__parent
|
43
|
+
while parent # build the full nested key from parents
|
44
|
+
full_key = full_key.unshift parent[:root]
|
45
|
+
parent = parent[:object].__parent
|
46
|
+
end
|
47
|
+
|
48
|
+
@__adapter.set_value(full_key, value) # notify the adapter
|
49
|
+
else
|
50
|
+
Omniconf.logger.warn "No adapter to notify"
|
51
|
+
end
|
52
|
+
|
53
|
+
@__table[key] = value # update our internal config hash
|
54
|
+
|
55
|
+
if @__adapter.is_a? Omniconf::Adapter::Base
|
56
|
+
# we need to merge the global config
|
57
|
+
Omniconf.merge_configuration! @__adapter.source_id
|
58
|
+
end
|
59
|
+
|
60
|
+
elsif len == 0 # read
|
61
|
+
key = method.to_s
|
62
|
+
value = @__table[key]
|
63
|
+
if value.is_a?(Hash)
|
64
|
+
Configuration.new(@__adapter, value, {:root => key, :object => self})
|
65
|
+
else
|
66
|
+
unless value
|
67
|
+
# add a catch-all exception
|
68
|
+
# (more descriptive than "undefined method `xxx' for nil:NilClass")
|
69
|
+
class << value
|
70
|
+
def method_missing *args
|
71
|
+
raise UnknownConfigurationValue,
|
72
|
+
"cannot get a configuration value with no parent"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
value
|
77
|
+
end
|
78
|
+
|
79
|
+
else
|
80
|
+
raise NoMethodError, "undefined method `#{method}' for #{self}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
attr_reader :__parent
|
86
|
+
|
87
|
+
private
|
88
|
+
attr_reader :__table
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
module Omniconf
|
2
|
+
# Raised when trying to get or set a non-existent configuration value
|
3
|
+
class UnknownConfigurationValue < StandardError; end
|
4
|
+
|
5
|
+
# Raised when trying to set a read-only configuration value
|
6
|
+
class ReadOnlyConfigurationValue < StandardError; end
|
7
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Hash
|
2
|
+
# Recursive version of Rails' stringify_keys!
|
3
|
+
def recursive_stringify_keys!
|
4
|
+
self.keys.each do |key|
|
5
|
+
self[key.to_s] = self.delete(key) unless key.is_a? String
|
6
|
+
self[key.to_s].recursive_stringify_keys! if self[key.to_s].is_a? Hash
|
7
|
+
end
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def recursive_merge! with_hash
|
12
|
+
self.merge!(with_hash) do |key, old_val, new_val|
|
13
|
+
if new_val.is_a?(Hash) and old_val.is_a?(Hash)
|
14
|
+
# merge sub-hashes together
|
15
|
+
new_val = old_val.recursive_merge! new_val
|
16
|
+
else
|
17
|
+
# yield block when overriding
|
18
|
+
yield key, old_val, new_val if block_given? and new_val != old_val
|
19
|
+
end
|
20
|
+
new_val
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class String
|
26
|
+
def demodulize
|
27
|
+
self.gsub(/^.*::/, '')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
data/lib/omniconf/version.rb
CHANGED
data/lib/omniconf.rb
CHANGED
@@ -1,20 +1,28 @@
|
|
1
1
|
require 'logger'
|
2
|
-
require
|
3
|
-
require
|
2
|
+
require 'omniconf/version'
|
3
|
+
require 'omniconf/helpers'
|
4
|
+
require 'omniconf/errors'
|
5
|
+
require 'omniconf/settings'
|
6
|
+
require 'omniconf/adapters/base'
|
4
7
|
|
5
8
|
module Omniconf
|
6
9
|
class << self
|
7
10
|
attr_reader :settings # Omniconf settings
|
8
|
-
|
9
|
-
|
11
|
+
attr_reader :configuration # global merged configuration
|
12
|
+
alias_method :conf, :configuration # shortcut for the lazy people
|
10
13
|
|
11
|
-
def
|
12
|
-
@
|
14
|
+
def setup
|
15
|
+
@settings ||= Settings.new
|
16
|
+
yield @settings
|
17
|
+
|
18
|
+
register_sources
|
19
|
+
|
20
|
+
@settings.load_configuration = true if @settings.load_configuration.nil?
|
21
|
+
load_configuration! if @settings.load_configuration
|
13
22
|
end
|
14
23
|
|
15
24
|
def logger
|
16
25
|
unless @logger
|
17
|
-
# @logger ||= defined? Rails.logger ? Rails.logger : Logger.new(STDOUT)
|
18
26
|
@logger = Logger.new(STDOUT)
|
19
27
|
@logger.level = @settings.logger_level if @settings
|
20
28
|
@logger.level = Logger::INFO if @logger.level.nil?
|
@@ -22,39 +30,68 @@ module Omniconf
|
|
22
30
|
@logger
|
23
31
|
end
|
24
32
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
@settings.load_configuration = true if @settings.load_configuration.nil?
|
32
|
-
load_configuration! if @settings.load_configuration
|
33
|
+
# Returns a mapping on source adapters
|
34
|
+
def sources
|
35
|
+
@settings.sources.to_a.inject({}) do |result, (source_id, source)|
|
36
|
+
result[source_id] = source[:adapter].configuration
|
37
|
+
result
|
38
|
+
end
|
33
39
|
end
|
34
40
|
|
35
41
|
def load_configuration!
|
42
|
+
@configuration = Omniconf::Configuration.new self
|
36
43
|
@settings.sources.each do |source_id, params|
|
37
44
|
params[:adapter].load_configuration!
|
38
|
-
Omniconf.logger.info "Loaded configuration from #{source_id} source"
|
45
|
+
Omniconf.logger.info "Loaded configuration from #{source_id.inspect} source"
|
39
46
|
end
|
40
47
|
end
|
41
48
|
|
42
|
-
|
49
|
+
alias_method :reload_configuration!, :load_configuration!
|
50
|
+
|
51
|
+
def merge_configuration! source_id
|
52
|
+
Omniconf.logger.debug "Merging from #{source_id.inspect} source"
|
53
|
+
source_config = Omniconf.sources[source_id].to_hash
|
54
|
+
global_config = Omniconf.configuration.to_hash
|
55
|
+
global_config.recursive_merge!(source_config) do |key, old_val, new_val|
|
56
|
+
Omniconf.logger.warn \
|
57
|
+
"'#{key}' has been overriden with value from #{source_id.inspect} source " <<
|
58
|
+
"(old value: #{old_val.inspect}, new value: #{new_val.inspect})"
|
59
|
+
end
|
60
|
+
end
|
43
61
|
|
62
|
+
def set_value(key, value)
|
63
|
+
found = false
|
64
|
+
@settings.sources.to_a.reverse.each do |source_id, source|
|
65
|
+
# We try to find the original source overriding the last merged one,
|
66
|
+
# hence we scan sources backwards
|
67
|
+
adapter = source[:adapter]
|
68
|
+
config = adapter.configuration.to_hash
|
69
|
+
element = key[0..-2].inject(config) { |result, el| result[el] }
|
70
|
+
if element and element[key.last] # we've found it in the current source
|
71
|
+
element[key.last] = value
|
72
|
+
adapter.set_value(key, value)
|
73
|
+
found = true
|
74
|
+
break
|
75
|
+
end
|
76
|
+
end
|
77
|
+
raise UnknownConfigurationValue,
|
78
|
+
"cannot set a configuration value with no parent" unless found
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
44
82
|
def register_sources
|
83
|
+
return unless @settings.sources
|
45
84
|
@settings.sources.each do |source_id, params|
|
46
85
|
adapter_file = params[:type].to_s
|
47
|
-
require "omniconf/adapters/base"
|
48
86
|
require "omniconf/adapters/#{adapter_file}"
|
49
|
-
adapter_class = adapter_file.
|
87
|
+
adapter_class = adapter_file.split('_').map {|w| w.capitalize}.join
|
50
88
|
raise unless params[:adapter].nil?
|
51
89
|
params[:adapter] = Omniconf::Adapter.class_eval do
|
52
|
-
const_get(adapter_class).new(params)
|
90
|
+
const_get(adapter_class).new(source_id, params)
|
53
91
|
end
|
54
|
-
Omniconf.logger.
|
92
|
+
Omniconf.logger.debug "Registered #{adapter_class}::#{source_id} source"
|
55
93
|
end
|
56
94
|
end
|
57
|
-
|
58
95
|
end
|
59
96
|
end
|
60
97
|
|
data/omniconf.gemspec
CHANGED
@@ -5,11 +5,11 @@ require "omniconf/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "omniconf"
|
7
7
|
s.version = Omniconf::VERSION
|
8
|
-
s.authors = ["
|
8
|
+
s.authors = ["Cedric Felizard"]
|
9
9
|
s.email = ["cedric@picklive.com"]
|
10
10
|
s.homepage = "https://github.com/Picklive/omniconf"
|
11
11
|
s.summary = %q{Merge multiple configuration sources into one.}
|
12
|
-
s.description = %q{Merge configurations from multiple
|
12
|
+
s.description = %q{Merge configurations from multiple back-ends for easy use in a complex application.}
|
13
13
|
|
14
14
|
s.rubyforge_project = "omniconf"
|
15
15
|
|
@@ -18,10 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.
|
22
|
-
|
23
|
-
s.add_development_dependency
|
24
|
-
s.add_development_dependency
|
25
|
-
s.add_development_dependency "sqlite3"
|
26
|
-
s.add_development_dependency "activerecord"
|
21
|
+
s.add_development_dependency 'rake', '~> 0.9'
|
22
|
+
s.add_development_dependency 'rspec', '~> 2.8'
|
23
|
+
s.add_development_dependency 'sqlite3', '~> 1.3'
|
24
|
+
s.add_development_dependency 'activerecord', '~> 3.2'
|
27
25
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
ActiveRecord::Migration.verbose = false
|
2
|
+
|
1
3
|
ActiveRecord::Schema.define :version => 0 do
|
2
4
|
create_table :config_values, :force => true do |t|
|
3
5
|
t.string :key
|
@@ -5,6 +7,7 @@ ActiveRecord::Schema.define :version => 0 do
|
|
5
7
|
end
|
6
8
|
end
|
7
9
|
|
8
|
-
ConfigValue.create!({:key => '
|
9
|
-
ConfigValue.create!({:key => '
|
10
|
+
ConfigValue.create!({:key => 'ar_key', :value => 'ar_value'})
|
11
|
+
ConfigValue.create!({:key => 'nested.ar_key', :value => 'nested_ar_value'})
|
12
|
+
ConfigValue.create!({:key => 'nested.overridden', :value => 'by_ar'})
|
10
13
|
|