blueprint_config 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.idea/.gitignore +8 -0
- data/.idea/blue_config.iml +72 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +152 -0
- data/Rakefile +8 -0
- data/blueprint_config.gemspec +31 -0
- data/lib/blueprint_config/backend/active_record.rb +51 -0
- data/lib/blueprint_config/backend/base.rb +34 -0
- data/lib/blueprint_config/backend/credentials.rb +26 -0
- data/lib/blueprint_config/backend/env.rb +47 -0
- data/lib/blueprint_config/backend/yaml.rb +30 -0
- data/lib/blueprint_config/backend_collection.rb +67 -0
- data/lib/blueprint_config/configuration.rb +64 -0
- data/lib/blueprint_config/options_array.rb +81 -0
- data/lib/blueprint_config/options_hash.rb +112 -0
- data/lib/blueprint_config/setting.rb +28 -0
- data/lib/blueprint_config/version.rb +5 -0
- data/lib/blueprint_config/yaml.rb +46 -0
- data/lib/blueprint_config.rb +71 -0
- data/lib/generators/blueprint_config/install/USAGE +8 -0
- data/lib/generators/blueprint_config/install/install_generator.rb +23 -0
- data/lib/generators/blueprint_config/install/templates/migration.rb.erb +18 -0
- data/spec/backend_collection_spec.rb +103 -0
- data/spec/blueprint_config/backend/active_record_spec.rb +41 -0
- data/spec/blueprint_config/backend/env_spec.rb +53 -0
- data/spec/blueprint_config/backend/yaml_spec.rb +35 -0
- data/spec/blueprint_config/options_array_spec.rb +109 -0
- data/spec/blueprint_config/options_hash_spec.rb +211 -0
- data/spec/config/app.yml +24 -0
- data/spec/configuration_spec.rb +98 -0
- data/spec/spec_helper.rb +16 -0
- metadata +163 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require 'blueprint_config/options_hash'
|
5
|
+
require 'blueprint_config/options_array'
|
6
|
+
|
7
|
+
module BlueprintConfig
|
8
|
+
class Configuration
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
attr_accessor :config, :backends
|
12
|
+
|
13
|
+
%i[dig dig! fetch \[\] method_missing].each do |method|
|
14
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
15
|
+
def #{method}(...)
|
16
|
+
reload! unless backends&.fresh?
|
17
|
+
config.#{method}(...)
|
18
|
+
rescue KeyError => e
|
19
|
+
raise KeyError, e.message, caller[1..], cause: nil
|
20
|
+
end
|
21
|
+
RUBY
|
22
|
+
end
|
23
|
+
|
24
|
+
def init(&block)
|
25
|
+
backends = BackendCollection.new
|
26
|
+
block.call(backends)
|
27
|
+
@backends = backends
|
28
|
+
reload!
|
29
|
+
end
|
30
|
+
|
31
|
+
def refine(&block)
|
32
|
+
backends = @backends
|
33
|
+
block.call(backends)
|
34
|
+
@backends = backends
|
35
|
+
reload!
|
36
|
+
end
|
37
|
+
|
38
|
+
def reload!
|
39
|
+
new_config = @backends.each_with_object(OptionsHash.new) do |backend, config|
|
40
|
+
config.deep_merge! OptionsHash.new(backend.load_keys, source: backend.source)
|
41
|
+
end
|
42
|
+
|
43
|
+
@config = new_config
|
44
|
+
@config = process_erb(new_config)
|
45
|
+
end
|
46
|
+
|
47
|
+
def process_erb(object)
|
48
|
+
case object
|
49
|
+
when String
|
50
|
+
if object.start_with?('<%=') && object.end_with?('%>')
|
51
|
+
ERB.new(object).result(binding)
|
52
|
+
else
|
53
|
+
object
|
54
|
+
end
|
55
|
+
when OptionsArray
|
56
|
+
object.each_with_index { |o, index| object[index] = process_erb(o) }
|
57
|
+
when OptionsHash
|
58
|
+
object.each { |k, v| object[k] = process_erb(v) }
|
59
|
+
else
|
60
|
+
object
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BlueprintConfig
|
4
|
+
class OptionsArray < Array
|
5
|
+
attr_accessor :__sources, :__indeces, :__path
|
6
|
+
|
7
|
+
def initialize(options = [], path: nil, source: nil)
|
8
|
+
super() # important - brackets needed to create an empty array
|
9
|
+
@__path = path
|
10
|
+
@__sources = []
|
11
|
+
@__indeces = []
|
12
|
+
options.each do |elem|
|
13
|
+
__push(elem, source:)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def __assign(other)
|
18
|
+
if other.first.to_s == '__append'
|
19
|
+
concat other[1..]
|
20
|
+
@__sources.concat other.__sources[1..]
|
21
|
+
@__indeces.concat other.__indeces[1..]
|
22
|
+
else
|
23
|
+
clear
|
24
|
+
concat(other)
|
25
|
+
__sources.clear
|
26
|
+
__sources.concat(other.__sources)
|
27
|
+
__indeces.clear
|
28
|
+
__indeces.concat(other.__indeces)
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def dig(key, *identifiers)
|
34
|
+
super(key, *identifiers)
|
35
|
+
end
|
36
|
+
|
37
|
+
def merge!(other, &block)
|
38
|
+
@__sources.reverse_merge!(other.__sources) if other.is_a?(OptionsHash)
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def __push(elem, source: nil)
|
43
|
+
elem = OptionsHash.new(elem, path: [@__path, size].compact.join('.'), source:) if elem.is_a?(Hash)
|
44
|
+
elem = self.class.new(elem, path: [@__path, size].compact.join('.'), source:) if elem.is_a?(Array)
|
45
|
+
@__sources.push source
|
46
|
+
@__indeces.push size
|
47
|
+
push elem
|
48
|
+
end
|
49
|
+
|
50
|
+
def source(*args)
|
51
|
+
index = args.shift
|
52
|
+
if index >= size
|
53
|
+
raise IndexError, "Configuration key '#{[@__path, index].compact.join('.')}' is not set", caller[1..],
|
54
|
+
cause: nil
|
55
|
+
end
|
56
|
+
|
57
|
+
if args.empty?
|
58
|
+
"#{@__sources[index]} #{[@__path, @__indeces[index]].compact.join('.')}"
|
59
|
+
else
|
60
|
+
self[index].source(*args)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def dig!(*args, &block)
|
65
|
+
leading = args.shift
|
66
|
+
if args.empty?
|
67
|
+
fetch(leading, &block)
|
68
|
+
else
|
69
|
+
fetch(leading, &block).dig!(*args, &block)
|
70
|
+
end
|
71
|
+
rescue IndexError => e
|
72
|
+
raise e, e.message, caller[1..], cause: nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def fetch(index, *args, &block)
|
76
|
+
super(index, *args, &block)
|
77
|
+
rescue IndexError
|
78
|
+
raise IndexError, "Configuration key '#{[@__path, index].compact.join('.')}' is not set", caller[1..], cause: nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BlueprintConfig
|
4
|
+
class OptionsHash < Hash
|
5
|
+
# include ActiveSupport::DeepMergeable
|
6
|
+
|
7
|
+
attr_accessor :__sources, :__path
|
8
|
+
|
9
|
+
def initialize(options = {}, path: nil, source: nil)
|
10
|
+
super() # important - brackets needed to create an empty hash
|
11
|
+
@__path = path
|
12
|
+
@__sources = {}
|
13
|
+
options.each do |key, value|
|
14
|
+
__set(key, value, source:)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
super(key.to_sym)
|
20
|
+
end
|
21
|
+
|
22
|
+
def dig(key, *identifiers)
|
23
|
+
super(key.to_sym, *identifiers)
|
24
|
+
end
|
25
|
+
|
26
|
+
def merge!(other, &block)
|
27
|
+
@__sources.reverse_merge!(other.__sources) if other.is_a?(OptionsHash)
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
def __set(key, value, source: nil)
|
32
|
+
value = self.class.new(value, path: [@__path, key].compact.join('.'), source:) if value.is_a?(Hash)
|
33
|
+
value = OptionsArray.new(value, path: [@__path, key].compact.join('.'), source:) if value.is_a?(Array)
|
34
|
+
@__sources[key] = source
|
35
|
+
self[key.to_sym] = value
|
36
|
+
end
|
37
|
+
|
38
|
+
def source(*args)
|
39
|
+
key = args.shift
|
40
|
+
if args.empty?
|
41
|
+
"#{@__sources[key]} #{[@__path, key].compact.join('.')}"
|
42
|
+
else
|
43
|
+
unless key?(key)
|
44
|
+
raise KeyError, "Configuration key '#{[@__path, key].compact.join('.')}' is not set", caller[1..], cause: nil
|
45
|
+
end
|
46
|
+
|
47
|
+
self[key].source(*args)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def dig!(*args, &block)
|
52
|
+
leading = args.shift
|
53
|
+
if args.empty?
|
54
|
+
fetch(leading, &block)
|
55
|
+
else
|
56
|
+
fetch(leading, &block).dig!(*args, &block)
|
57
|
+
end
|
58
|
+
rescue KeyError => e
|
59
|
+
raise e, e.message, caller[1..], cause: nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def method_missing(name, *args)
|
63
|
+
name_string = +name.to_s
|
64
|
+
if name_string.chomp!('=')
|
65
|
+
self[name_string] = args.first
|
66
|
+
else
|
67
|
+
questions = name_string.chomp!('?')
|
68
|
+
if questions
|
69
|
+
self[name_string].present?
|
70
|
+
else
|
71
|
+
bangs = name_string.chomp!('!')
|
72
|
+
|
73
|
+
if bangs
|
74
|
+
self[name_string].presence ||
|
75
|
+
raise(KeyError, "Configuration key '#{[@__path, name_string].compact.join('.')}' is not set", caller[1..])
|
76
|
+
else
|
77
|
+
self[name_string]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def fetch(key, *args, &block)
|
84
|
+
super(key.to_sym, *args, &block)
|
85
|
+
rescue KeyError
|
86
|
+
raise KeyError, "Configuration key '#{[@__path, key].compact.join('.')}' is not set", caller[1..], cause: nil
|
87
|
+
end
|
88
|
+
|
89
|
+
# deep_merge methods copied from ActiveSupport to avoid extra dependency
|
90
|
+
def deep_merge(other, &block)
|
91
|
+
dup.deep_merge!(other, &block)
|
92
|
+
end
|
93
|
+
|
94
|
+
def deep_merge!(other, &block)
|
95
|
+
merge!(other) do |key, this_val, other_val|
|
96
|
+
if this_val.is_a?(BlueprintConfig::OptionsHash) && this_val.deep_merge?(other_val)
|
97
|
+
this_val.deep_merge(other_val, &block)
|
98
|
+
elsif this_val.is_a?(BlueprintConfig::OptionsArray) && other_val.is_a?(BlueprintConfig::OptionsArray)
|
99
|
+
this_val.__assign(other_val, &block)
|
100
|
+
elsif block_given?
|
101
|
+
block.call(key, this_val, other_val)
|
102
|
+
else
|
103
|
+
other_val
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def deep_merge?(other)
|
109
|
+
other.is_a?(self.class)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
module BlueprintConfig
|
6
|
+
class Setting < ::ActiveRecord::Base
|
7
|
+
self.inheritance_column = nil
|
8
|
+
|
9
|
+
enum type: { section: 0, string: 1, integer: 2, boolean: 3, json: 4, selection: 5, set: 6 }
|
10
|
+
|
11
|
+
def parsed_json_value
|
12
|
+
parsed = begin
|
13
|
+
JSON.parse(value)
|
14
|
+
rescue StandardError
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
parsed.is_a?(Hash) ? parsed.with_indifferent_access : parsed
|
18
|
+
end
|
19
|
+
|
20
|
+
def parsed_value
|
21
|
+
return value.to_i if integer?
|
22
|
+
return value.to_b if boolean?
|
23
|
+
return parsed_json_value if json? || set?
|
24
|
+
|
25
|
+
value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'erb'
|
5
|
+
|
6
|
+
module Econfig
|
7
|
+
class YAML
|
8
|
+
def initialize(path)
|
9
|
+
@path = path
|
10
|
+
@mutex = Mutex.new
|
11
|
+
@options = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def keys
|
15
|
+
Set.new(options.keys)
|
16
|
+
end
|
17
|
+
|
18
|
+
def get(key)
|
19
|
+
options[key]
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_key?(key)
|
23
|
+
options.key?(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def path
|
29
|
+
raise Econfig::UninitializedError, 'Econfig.root is not set' unless Econfig.root
|
30
|
+
|
31
|
+
File.expand_path(@path, Econfig.root)
|
32
|
+
end
|
33
|
+
|
34
|
+
def options
|
35
|
+
return @options if @options
|
36
|
+
|
37
|
+
@mutex.synchronize do
|
38
|
+
@options ||= if File.exist?(path)
|
39
|
+
::YAML.load(::ERB.new(File.read(path)).result)[Econfig.env] || {}
|
40
|
+
else
|
41
|
+
{}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'blueprint_config/version'
|
4
|
+
require 'blueprint_config/backend/env'
|
5
|
+
require 'blueprint_config/backend/yaml'
|
6
|
+
require 'blueprint_config/configuration'
|
7
|
+
require 'blueprint_config/backend_collection'
|
8
|
+
|
9
|
+
module BlueprintConfig
|
10
|
+
class << self
|
11
|
+
attr_accessor :root, :env, :before_initialize, :after_initialize
|
12
|
+
attr_writer :shortcut_name, :env_options
|
13
|
+
|
14
|
+
def shortcut_name
|
15
|
+
@shortcut_name || 'AppConfig'
|
16
|
+
end
|
17
|
+
|
18
|
+
def env_options
|
19
|
+
@env_options || {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def define_shortcut
|
23
|
+
Object.const_set shortcut_name, instance
|
24
|
+
end
|
25
|
+
|
26
|
+
def instance
|
27
|
+
BlueprintConfig::Configuration.instance
|
28
|
+
end
|
29
|
+
|
30
|
+
def init
|
31
|
+
before_initialize&.call
|
32
|
+
end
|
33
|
+
|
34
|
+
def configure_rails(config)
|
35
|
+
config.before_configuration do |_app|
|
36
|
+
BlueprintConfig.root ||= Rails.root
|
37
|
+
BlueprintConfig.env ||= Rails.env
|
38
|
+
BlueprintConfig.define_shortcut
|
39
|
+
BlueprintConfig.before_initialize.call
|
40
|
+
end
|
41
|
+
|
42
|
+
config.after_initialize do |_app|
|
43
|
+
BlueprintConfig.after_initialize.call
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
BlueprintConfig.env_options ||= {}
|
50
|
+
|
51
|
+
BlueprintConfig.before_initialize ||= proc do
|
52
|
+
require 'blueprint_config/backend/credentials'
|
53
|
+
require 'blueprint_config/backend/active_record'
|
54
|
+
|
55
|
+
BlueprintConfig.instance.init do |backends|
|
56
|
+
backends.use :app, BlueprintConfig::Backend::YAML.new('config/app.yml')
|
57
|
+
backends.use :credentials, BlueprintConfig::Backend::Credentials.new
|
58
|
+
backends.use :env, BlueprintConfig::Backend::ENV.new(BlueprintConfig.env_options)
|
59
|
+
backends.use :app_local, BlueprintConfig::Backend::YAML.new('config/app.local.yml')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
BlueprintConfig.after_initialize ||= proc do
|
64
|
+
BlueprintConfig.instance.refine do |backends|
|
65
|
+
if backends[:env]
|
66
|
+
backends.insert_after :env, :db, BlueprintConfig::Backend::ActiveRecord.new
|
67
|
+
else
|
68
|
+
backends.push :db, BlueprintConfig::Backend::ActiveRecord.new
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
require 'rails/generators/active_record'
|
5
|
+
|
6
|
+
module BlueprintConfig
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
8
|
+
include ActiveRecord::Generators::Migration
|
9
|
+
|
10
|
+
TEMPLATES = File.join(File.dirname(__FILE__), 'templates')
|
11
|
+
source_paths << TEMPLATES
|
12
|
+
|
13
|
+
def create_migration_file
|
14
|
+
migration_template 'migration.rb.erb', 'db/migrate/create_blueprint_settings.rb'
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def migration_version
|
20
|
+
"[#{ActiveRecord::VERSION::STRING.to_f}]"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class CreateBlueConfig < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def change
|
3
|
+
reversible do |dir|
|
4
|
+
dir.up do
|
5
|
+
# Ensure this incremental update migration is idempotent
|
6
|
+
# with monolithic install migration.
|
7
|
+
return if connection.table_exists?(:settings)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
create_table :settings, id: :uuid do |t|
|
12
|
+
t.string :key, null: false, index: { unique: true }
|
13
|
+
t.integer :type, null: false, default: 0
|
14
|
+
t.string :value
|
15
|
+
t.timestamps
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe BlueprintConfig::BackendCollection do
|
4
|
+
let(:collection) { described_class.new }
|
5
|
+
let(:memory) { double('memory') }
|
6
|
+
let(:yaml) { double('yaml') }
|
7
|
+
|
8
|
+
describe '#[]' do
|
9
|
+
it 'retrieves a backend' do
|
10
|
+
collection.push :memory, memory
|
11
|
+
collection[:memory].should equal(memory)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#each' do
|
16
|
+
it 'can be called without a block to receive an enumerator' do
|
17
|
+
collection.push :memory, memory
|
18
|
+
collection.each.take(1).should eq([memory])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#push' do
|
23
|
+
it 'adds a new backend at the bottom' do
|
24
|
+
collection.push :memory, memory
|
25
|
+
collection.push :yaml, yaml
|
26
|
+
collection.to_a.should eq([memory, yaml])
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'is aliased as `use`' do
|
30
|
+
collection.use :memory, memory
|
31
|
+
collection.use :yaml, yaml
|
32
|
+
collection.to_a.should eq([memory, yaml])
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'raises an error if backend already exist' do
|
36
|
+
collection.push :memory, memory
|
37
|
+
expect { collection.push :memory, yaml }.to raise_error(KeyError, 'memory is already set')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#unshift' do
|
42
|
+
it 'adds a new backend at the top' do
|
43
|
+
collection.unshift :memory, memory
|
44
|
+
collection.unshift :yaml, yaml
|
45
|
+
collection.to_a.should eq([yaml, memory])
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'raises an error if backend already exist' do
|
49
|
+
collection.unshift :memory, memory
|
50
|
+
expect { collection.unshift :memory, yaml }.to raise_error(KeyError, 'memory is already set')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#insert_before' do
|
55
|
+
it 'adds a new before the given backend' do
|
56
|
+
collection.push :quox, :quox
|
57
|
+
collection.push :baz, :baz
|
58
|
+
collection.push :foo, :foo
|
59
|
+
collection.insert_before :baz, :memory, memory
|
60
|
+
collection.to_a.should eq([:quox, memory, :baz, :foo])
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'raises an error if the backend does not exist' do
|
64
|
+
expect { collection.insert_before :baz, :memory, memory }.to raise_error(KeyError, /baz is not set/)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'raises an error if the backend already exists' do
|
68
|
+
collection.push :foo, :foo
|
69
|
+
expect { collection.insert_before :foo, :foo, memory }.to raise_error(KeyError, /foo is already set/)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#insert_after' do
|
74
|
+
it 'adds a new after the given backend' do
|
75
|
+
collection.push :quox, :quox
|
76
|
+
collection.push :baz, :baz
|
77
|
+
collection.push :foo, :foo
|
78
|
+
collection.insert_after :baz, :memory, memory
|
79
|
+
collection.to_a.should eq([:quox, :baz, memory, :foo])
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'raises an error if the backend does not exist' do
|
83
|
+
expect { collection.insert_after :baz, :memory, memory }.to raise_error(KeyError, /baz is not set/)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'raises an error if the backend already exists' do
|
87
|
+
collection.push :foo, :foo
|
88
|
+
expect { collection.insert_after :foo, :foo, memory }.to raise_error(KeyError, /foo is already set/)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '#delete' do
|
93
|
+
it 'removes the given backend' do
|
94
|
+
collection.push :memory, memory
|
95
|
+
collection.delete :memory
|
96
|
+
collection[:memory].should be_nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'raises an error if the backend does not exist' do
|
100
|
+
expect { collection.delete :redis }.to raise_error(KeyError, /redis is not set/)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
require 'blueprint_config/backend/active_record'
|
5
|
+
|
6
|
+
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
|
7
|
+
|
8
|
+
ActiveRecord::Base.connection.create_table :settings do |t|
|
9
|
+
t.string :key, null: false, index: { unique: true }
|
10
|
+
t.integer :type, null: false, default: 0
|
11
|
+
t.string :value
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
|
15
|
+
describe BlueprintConfig::Backend::ActiveRecord do
|
16
|
+
let(:options) { {} }
|
17
|
+
let(:subject) { described_class.new(options).load_keys }
|
18
|
+
around do |example|
|
19
|
+
ActiveRecord::Base.transaction do
|
20
|
+
BlueprintConfig::Setting.create(key: 'foo', type: :string, value: 'bar')
|
21
|
+
BlueprintConfig::Setting.create(key: 'x', type: :integer, value: '1')
|
22
|
+
BlueprintConfig::Setting.create(key: 'a.b', type: :string, value: '1')
|
23
|
+
|
24
|
+
example.run
|
25
|
+
raise ActiveRecord::Rollback
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with default options' do
|
30
|
+
it 'loads all keys' do
|
31
|
+
expect(subject).to eq({ foo: 'bar', "a.b": '1', x: 1 })
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when nesting enabled' do
|
36
|
+
let(:options) { { nest: true } }
|
37
|
+
it 'loads all keys' do
|
38
|
+
expect(subject).to eq({ foo: 'bar', a: { b: '1' }, x: 1 })
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe BlueprintConfig::Backend::ENV do
|
4
|
+
let(:options) { {} }
|
5
|
+
let(:subject) { BlueprintConfig::Backend::ENV.new(options).load_keys }
|
6
|
+
|
7
|
+
before do
|
8
|
+
ENV['FOO_BAR'] = 'monkey'
|
9
|
+
ENV['TEST'] = 'test'
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'when everything is allowed', :aggregate_failures do
|
13
|
+
let(:options) { { allow_all: true } }
|
14
|
+
it 'copies all env variables' do
|
15
|
+
expect(subject[:foo_bar]).to eq('monkey')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when everything is allowed and keys are nested', :aggregate_failures do
|
20
|
+
let(:options) { { allow_all: true, nest: true } }
|
21
|
+
it 'copies all env variables nesting em' do
|
22
|
+
expect(subject[:foo][:bar]).to eq('monkey')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'when keys are whitelisted', :aggregate_failures do
|
27
|
+
let(:options) { { whitelist_keys: [:test] } }
|
28
|
+
it 'copies whitelisted keys' do
|
29
|
+
expect(subject.keys).to eq([:test])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when keys are prefix-whitelisted', :aggregate_failures do
|
34
|
+
let(:options) { { whitelist_prefixes: %i[foo f] } }
|
35
|
+
it 'copies whitelisted keys' do
|
36
|
+
expect(subject.keys).to eq([:foo_bar])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'when keys are prefix-whitelisted and', :aggregate_failures do
|
41
|
+
let(:options) { { whitelist_prefixes: %i[foo f], nest: true } }
|
42
|
+
it 'copies whitelisted keys' do
|
43
|
+
expect(subject.keys).to eq([:foo])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when combines prefix-whitelist and whitelist', :aggregate_failures do
|
48
|
+
let(:options) { { whitelist_prefixes: [:foo], whitelist_keys: [:test] } }
|
49
|
+
it 'copies whitelisted keys' do
|
50
|
+
expect(subject.keys).to eq(%i[test foo_bar])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe BlueprintConfig::Backend::YAML do
|
4
|
+
let(:subject) { described_class.new('config/app.yml').load_keys }
|
5
|
+
|
6
|
+
context 'when file exists' do
|
7
|
+
context 'when env is not set' do
|
8
|
+
it 'returns default section' do
|
9
|
+
allow(BlueprintConfig).to receive(:env).and_return(nil)
|
10
|
+
expect(subject).to eq(
|
11
|
+
{
|
12
|
+
array: %w[a b x],
|
13
|
+
array2: [{ a: { d: 1, e: 2 }, b: 2 }, { b: 1, c: 3 }, { x: 4, y: 5 }],
|
14
|
+
nested: { a: 1, b: 2 }
|
15
|
+
}
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when env is set' do
|
21
|
+
it 'returns default section merged with env section' do
|
22
|
+
allow(BlueprintConfig).to receive(:env).and_return('test')
|
23
|
+
expect(subject).to eq(
|
24
|
+
{
|
25
|
+
array: %w[a b x],
|
26
|
+
array2: [{ a: { d: 1, e: 2 }, b: 2 }, { b: 1, c: 3 }, { x: 4, y: 5 }],
|
27
|
+
nested: { a: 3, b: 2, c: 4 },
|
28
|
+
quox: 'baz',
|
29
|
+
envir: "<%= ENV['APP_EXAMPLE'] %>"
|
30
|
+
}
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|