a9n 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1278accbf3b20ef8dcf2084995996dbaa1bdabda
4
- data.tar.gz: d444fa5ca535f636c4d0eefb03895d3bf12813da
3
+ metadata.gz: e1fa842e61082deb4d82eb3059efed6cb0ce0e50
4
+ data.tar.gz: a3250f33bf6df0ae3b06de1bb30774acf958d65f
5
5
  SHA512:
6
- metadata.gz: 71e3c6444b5418cf69547a9e81cd88dcefd6d346648268de14f2adf045f0920100865041cd0f7637c25f5d2fe376036b39c4b3be24df2ddb8729531bcee02d1b
7
- data.tar.gz: 5b924cb72e0a6d6813056ab6ef43e1b3a07ac4eb5c3615ae74c26834b3bc8ce0938fabbe0d07eeeaf06a23b94708255cf8ad192114405d40c00458a7bd345090
6
+ metadata.gz: ca996bb79d7331734d27e51eb5b35c1759a95bc680056ca6a25c78ec138d2c6c2272e613cb2bbd18aa4cbb01155ef981d8fa4719f909239abfb3c15c273e9b2a
7
+ data.tar.gz: d9e4c62825e16d882d3f9dadb56e4689e64b7036a47b77557208ff9c89a2f5798992fde85eb77fbe26c97d310c35dfd8967b627135497fd18e0f6a1ad0e37841
data/README.md CHANGED
@@ -10,8 +10,7 @@
10
10
  [codeclimate]: https://codeclimate.com/github/knapo/a9n
11
11
  [coveralls]: https://coveralls.io/r/knapo/a9n
12
12
 
13
- Simple tool for managing extra configuration in ruby/rails apps. Supports Rails 2.x, 3.x, 4.x and Ruby 1.9, 2.0.
14
- Ruby 1.8 is not supported in version 0.1.2 and higher.
13
+ A9n is a simple tool for managing extra configuration in ruby/rails apps. It supports Rails 2.x, 3.x, 4.x and Ruby 1.9, 2.0. 2.1. Ruby 1.8 is not supported since version 0.1.2.
15
14
 
16
15
  ## Installation
17
16
 
@@ -37,15 +36,14 @@ after budler requires:
37
36
  A9n.root = File.expand_path('../..', __FILE__)
38
37
  A9n.load
39
38
 
40
-
41
39
  It works with `Rails` by default. If you want to use `A9n` with non-rails app
42
- you may need to tell it A9n:
40
+ you may need to tell that to A9n by:
43
41
 
44
42
  A9n.local_app = MyApp
45
43
 
46
44
  ## Usage
47
45
 
48
- You can access any variable defined in configuration files but delegating it to
46
+ You can access any variable defined in configuration files by delegating it to
49
47
  `A9n`. E.g:
50
48
 
51
49
  defaults:
@@ -58,21 +56,22 @@ You can access any variable defined in configuration files but delegating it to
58
56
  is accessible by:
59
57
 
60
58
  A9n.app_host # => `knapo.net` in production and `localhost:3000` in development
61
- A9n.email_from # => `no-reply@knapo.net` in all envs
59
+ A9n.email_from # => `no-reply@knapo.net` in both envs
62
60
 
63
61
  ## Custom and multiple configuration files
64
62
 
65
- If you need to load config from custom files (e.g `config/mongo.yml`, `config/other.yml` and `config/custom_dir/extra.yml`), add:
66
-
67
- A9n.load('mongo.yml', 'other.yml', 'custom_dir/extra')
63
+ If you want to scope configuration you may split to multiple files. All files from `config/a9n` are loaded by default, but you may pass custom paths as an argument to `A9n.load` e.g. `A9n.load('lib/facebook/api.yml', 'config/mongoid.yml')`. In such cases config items are accessialbe via scope consistient with the file name.
68
64
 
69
- and the configuration is availble under `mongo`, `other` and `extra` scopes:
65
+ E.g. if you have `config/a9n/mandrill.yml`:
66
+
67
+ defaults:
68
+ username: "joe"
69
+ api_key: "1234asdf"
70
70
 
71
- A9n.mongo.varname
71
+ you can access it by:
72
72
 
73
- A9n.other.varname
74
-
75
- A9n.extra.varname
73
+ A9n.mandrill.username # => `joe`
74
+ A9n.mandrill.api_key # => `1234asdf`
76
75
 
77
76
  ## Contributing
78
77
 
@@ -82,7 +81,3 @@ and the configuration is availble under `mongo`, `other` and `extra` scopes:
82
81
  4. Push to the branch (`git push origin my-new-feature`)
83
82
  5. Create new Pull Request
84
83
 
85
- ### Contributors
86
-
87
- * [Grzegorz Świrski](https://github.com/sognat)
88
- * [Jakub Łopusiński](https://github.com/siemakuba)
data/lib/a9n.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "a9n/version"
2
2
  require "a9n/struct"
3
- require "a9n/core_ext/hash"
3
+ require "a9n/ext/hash"
4
+ require "a9n/loader"
4
5
  require "yaml"
5
6
  require "erb"
6
7
 
@@ -15,82 +16,63 @@ module A9n
15
16
 
16
17
  class << self
17
18
  def env
18
- @env ||= local_app_env || get_env_var("RAILS_ENV") || get_env_var("RACK_ENV") || get_env_var("APP_ENV")
19
+ @env ||= app_env || get_env_var("RAILS_ENV") || get_env_var("RACK_ENV") || get_env_var("APP_ENV")
19
20
  end
20
21
 
21
- def local_app_env
22
- local_app.env if local_app && local_app.respond_to?(:env)
22
+ def app_env
23
+ app.env if app && app.respond_to?(:env)
23
24
  end
24
25
 
25
- def local_app
26
- @local_app ||= get_rails
26
+ def app
27
+ @app ||= get_rails
27
28
  end
28
29
 
29
- def local_app=(local_app)
30
- @local_app = local_app
30
+ def app=(app_instance)
31
+ @app = app_instance
31
32
  end
32
33
 
33
34
  def root
34
- @root ||= local_app.root
35
+ @root ||= app.root
35
36
  end
36
37
 
37
38
  def root=(path)
38
- path = path.to_s
39
- @root = path.empty? ? nil : Pathname.new(path.to_s)
39
+ @root = path.to_s.empty? ? nil : Pathname.new(path.to_s)
40
40
  end
41
41
 
42
- def scope(name)
43
- instance_variable_get(var_name_for(name)) || (name == DEFAULT_SCOPE && load.first)
42
+ def get_rails
43
+ defined?(Rails) ? Rails : nil
44
44
  end
45
45
 
46
- def load(*files)
47
- files = [DEFAULT_FILE] if files.empty?
48
- files.map do |file|
49
- default_and_env_config = load_config(file)
50
-
51
- instance_variable_set(var_name_for(file), A9n::Struct.new(default_and_env_config))
52
- end
46
+ def get_env_var(name)
47
+ ENV[name]
53
48
  end
54
49
 
55
- def load_config(file)
56
- env_example = load_yml("config/#{file}.example", env)
57
- env_local = load_yml("config/#{file}", env)
58
- default_example = load_yml("config/#{file}.example", "defaults")
59
- default_local = load_yml("config/#{file}", "defaults")
60
-
61
- if env_example.nil? && env_local.nil? && default_example.nil? && default_local.nil?
62
- raise MissingConfigurationData.new("Configuration data was not found in neither config/#{file}.example nor config/#{file}")
63
- end
64
-
65
- example = Hash.merge(default_example, env_example)
66
- local = Hash.merge( default_local,env_local)
67
-
68
- if !example.nil? && !local.nil?
69
- verify!(example, local)
70
- end
71
-
72
- local || example
50
+ def fetch(*args)
51
+ scope(DEFAULT_SCOPE).fetch(*args)
73
52
  end
74
53
 
75
- def load_yml(file, env)
76
- path = File.join(self.root, file)
77
- return nil unless File.exists?(path)
78
- yml = YAML.load(ERB.new(File.read(path)).result)
54
+ def scope(name)
55
+ load unless instance_variable_defined?(var_name_for(name))
56
+ instance_variable_get(var_name_for(name))
57
+ end
79
58
 
80
- if yml[env].is_a?(Hash)
81
- return yml[env].deep_symbolize_keys
82
- else
83
- return nil
84
- end
59
+ def var_name_for(file)
60
+ :"@#{File.basename(file.to_s, '.*')}"
85
61
  end
86
62
 
87
- # Fixes rspec issue
88
- def to_ary
89
- nil
63
+ def default_files
64
+ [root.join("config/#{DEFAULT_SCOPE}.yml").to_s] + Dir[root.join("config/a9n/*.yml")]
90
65
  end
91
66
 
92
- def fetch(*args)
93
- scope(DEFAULT_SCOPE).fetch(*args)
67
+ def load(*files)
68
+ if files.empty?
69
+ files = default_files
70
+ else
71
+ files = get_absolute_paths_for(files)
72
+ end
73
+ files.map do |file|
74
+ instance_variable_set(var_name_for(file), A9n::Loader.new(file, env).get)
75
+ end
94
76
  end
95
77
 
96
78
  def method_missing(name, *args)
@@ -101,25 +83,10 @@ module A9n
101
83
  end
102
84
  end
103
85
 
104
- def get_rails
105
- defined?(Rails) ? Rails : nil
106
- end
107
-
108
- def get_env_var(name)
109
- ENV[name]
110
- end
111
-
112
- def var_name_for(file)
113
- :"@#{file.to_s.split('/').last.split('.').first}"
114
- end
115
-
116
86
  private
117
87
 
118
- def verify!(example, local)
119
- missing_keys = example.keys - local.keys
120
- if missing_keys.any?
121
- raise MissingConfigurationVariables.new("Following variables are missing in your configuration file: #{missing_keys.join(",")}")
122
- end
88
+ def get_absolute_paths_for(files)
89
+ files.map { |file| Pathname.new(file).absolute? ? file : self.root.join('config', file).to_s }
123
90
  end
124
91
  end
125
92
  end
@@ -0,0 +1,21 @@
1
+ module A9n
2
+ class HashExt
3
+ class << self
4
+ # Hash#deep_symbolize_keys
5
+ # based on
6
+ # https://github.com/svenfuchs/i18n/blob/master/lib/i18n/core_ext/hash.rb
7
+ def deep_symbolize_keys(hash)
8
+ hash.inject({}) { |result, (key, value)|
9
+ value = deep_symbolize_keys(value) if value.is_a?(::Hash)
10
+ result[(key.to_sym rescue key) || key] = value
11
+ result
12
+ }
13
+ end
14
+
15
+ def merge(*items)
16
+ return nil if items.compact.empty?
17
+ items.compact.inject({}){|sum, item| sum.merge!(item)}
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,57 @@
1
+ require 'ostruct'
2
+
3
+ module A9n
4
+ class Loader
5
+ attr_reader :env, :local_file, :example_file
6
+
7
+ def initialize(file_path, env)
8
+ @env = env.to_s
9
+ @local_file = file_path
10
+ @example_file = "#{file_path}.example"
11
+ end
12
+
13
+ def get
14
+ @struct ||= load
15
+ end
16
+
17
+ def load
18
+ env_example = self.class.load_yml(example_file, env)
19
+ env_local = self.class.load_yml(local_file, env)
20
+ default_example = self.class.load_yml(example_file, "defaults")
21
+ default_local = self.class.load_yml(local_file, "defaults")
22
+
23
+ if env_example.nil? && env_local.nil? && default_example.nil? && default_local.nil?
24
+ raise A9n::MissingConfigurationData.new("Configuration data for *#{env}* env was not found in neither *#{example_file}* nor *#{local_file}*")
25
+ end
26
+
27
+ example = A9n::HashExt.merge(default_example, env_example)
28
+ local = A9n::HashExt.merge(default_local, env_local)
29
+
30
+ if !example.nil? && !local.nil?
31
+ verify!(example, local)
32
+ end
33
+
34
+ @struct = A9n::Struct.new(local || example)
35
+ end
36
+
37
+ def self.load_yml(file_path, env)
38
+ return nil unless File.exists?(file_path)
39
+ yml = YAML.load(ERB.new(File.read(file_path)).result)
40
+
41
+ if yml[env].is_a?(::Hash)
42
+ A9n::HashExt.deep_symbolize_keys(yml[env])
43
+ else
44
+ return nil
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def verify!(example, local)
51
+ missing_keys = example.keys - local.keys
52
+ if missing_keys.any?
53
+ raise A9n::MissingConfigurationVariables.new("Following variables are missing in #{local_file} file: #{missing_keys.join(",")}")
54
+ end
55
+ end
56
+ end
57
+ end
@@ -7,7 +7,7 @@ module A9n
7
7
  end
8
8
 
9
9
  def fetch(name, default = nil)
10
- @table[name.to_sym] || default
10
+ @table.fetch(name.to_sym, default)
11
11
  end
12
12
 
13
13
  def method_missing(name, *args)
@@ -1,3 +1,3 @@
1
1
  module A9n
2
- VERSION = "0.2.3"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -0,0 +1,41 @@
1
+ require "spec_helper"
2
+
3
+ describe A9n do
4
+ subject { described_class }
5
+
6
+ let(:env) { "test" }
7
+
8
+ before {
9
+ subject.app = double(env: env)
10
+ subject.root = File.expand_path("../../../test_app", __FILE__)
11
+ }
12
+
13
+ after {
14
+ subject.instance_variable_set(:@env, nil)
15
+ subject.root = nil
16
+ subject.app = nil
17
+ }
18
+
19
+ context "base config file" do
20
+ it {
21
+ expect(subject.default_dwarf).to eq("default dwarf")
22
+ expect(subject.overriden_dwarf).to eq("already overriden dwarf")
23
+ }
24
+ end
25
+
26
+ context "undefined env" do
27
+ let(:env) { "tropical" }
28
+ it {
29
+ expect(subject.default_dwarf).to eq("default dwarf")
30
+ expect(subject.overriden_dwarf).to eq("not yet overriden dwarf")
31
+ }
32
+ end
33
+
34
+ context "extra config file" do
35
+ it {
36
+ expect(subject.mandrill).to be_kind_of(A9n::Struct)
37
+ expect(subject.mandrill.username).to eq("joe")
38
+ expect(subject.mandrill.api_key).to eq("asdf1234")
39
+ }
40
+ end
41
+ end
@@ -15,7 +15,8 @@ require 'bundler/setup'
15
15
  require 'a9n'
16
16
 
17
17
  RSpec.configure do |config|
18
+ config.treat_symbols_as_metadata_keys_with_true_values = true
18
19
  config.order = "random"
19
20
  config.color_enabled = true
20
21
  config.tty = true
21
- end
22
+ end
@@ -0,0 +1,198 @@
1
+ require "spec_helper"
2
+
3
+ describe A9n do
4
+ subject { described_class }
5
+
6
+ after {
7
+ subject.instance_variable_set(:@env, nil)
8
+ subject.root = nil
9
+ subject.app = nil
10
+ }
11
+
12
+ describe ".env" do
13
+ before {
14
+ subject.instance_variable_set(:@env, nil)
15
+ }
16
+
17
+ context "app_env is set" do
18
+ before {
19
+ expect(subject).to receive(:app).and_return(double(env: "dwarf_env")).exactly(3).times
20
+ expect(subject).to receive(:get_env_var).never
21
+ }
22
+
23
+ it { expect(subject.env).to eq("dwarf_env") }
24
+ end
25
+
26
+ context "when APP_ENV is set" do
27
+ before {
28
+ expect(subject).to receive(:app_env).and_return(nil)
29
+ expect(subject).to receive(:get_env_var).with("RAILS_ENV").and_return(nil)
30
+ expect(subject).to receive(:get_env_var).with("RACK_ENV").and_return(nil)
31
+ expect(subject).to receive(:get_env_var).with("APP_ENV").and_return("dwarf_env")
32
+ }
33
+
34
+ it { expect(subject.env).to eq("dwarf_env") }
35
+ end
36
+ end
37
+
38
+ describe ".app" do
39
+ context "when rails not found" do
40
+ before {
41
+ expect(subject).to receive(:get_rails).and_return(nil)
42
+ }
43
+ specify {
44
+ expect(subject.app).to be_nil
45
+ }
46
+ end
47
+
48
+ context "when rails app is being used" do
49
+ let(:app) { double(env: "test", root: "/apps/a9n") }
50
+ before {
51
+ expect(subject).to receive(:get_rails).and_return(app)
52
+ }
53
+
54
+ specify { expect(subject.app).to eq(app) }
55
+ end
56
+
57
+ context "when custom non-rails app is being used" do
58
+ let(:app) { double(env: "test", root: "/apps/a9n") }
59
+ before { subject.app = app }
60
+
61
+ specify { expect(subject.app).to eq(app) }
62
+ end
63
+ end
64
+
65
+ describe ".root" do
66
+ let(:app) { double(env: "test", root: "/apps/a9n") }
67
+ before { subject.app = app }
68
+
69
+ context "with custom path" do
70
+ before {
71
+ subject.root = "/home/knapo/workspace/a9n"
72
+ }
73
+ specify {
74
+ expect(subject.root).to eq(Pathname.new("/home/knapo/workspace/a9n"))
75
+ }
76
+ end
77
+
78
+ context "with local app path" do
79
+ specify {
80
+ expect(subject.root).to eq("/apps/a9n")
81
+ }
82
+ end
83
+ end
84
+
85
+ describe ".get_rails" do
86
+ context "when defined" do
87
+ before {
88
+ Object.const_set(:Rails, Module.new)
89
+ }
90
+ after {
91
+ Object.send(:remove_const, :Rails)
92
+ }
93
+ it {
94
+ expect(subject.get_rails).to be_kind_of(Module)
95
+ }
96
+ end
97
+ context "when not defined" do
98
+ it { expect(subject.get_rails).to be_nil }
99
+ end
100
+ end
101
+
102
+ describe ".get_env_var" do
103
+ before { ENV["DWARF"] = "little dwarf" }
104
+ it { expect(subject.get_env_var("DWARF")).to eq("little dwarf")}
105
+ it { expect(subject.get_env_var("IS_DWARF")).to be_nil}
106
+ end
107
+
108
+ describe ".var_name_for" do
109
+ it { expect(subject.var_name_for(:configuration)).to eq(:@configuration) }
110
+ it { expect(subject.var_name_for("configuration.yml")).to eq(:@configuration) }
111
+ it { expect(subject.var_name_for("custom_dir/extra.yml")).to eq(:@extra) }
112
+ end
113
+
114
+ describe ".fetch" do
115
+ let(:example_config) { A9n::Struct.new({hello: "world"}) }
116
+ before {
117
+ expect(subject).to receive(:scope).with(subject::DEFAULT_SCOPE).twice.and_return(example_config)
118
+ }
119
+ it {
120
+ expect(subject.fetch(:hello)).to eq("world")
121
+ expect(subject.fetch(:wold)).to eq(nil)
122
+ }
123
+ end
124
+
125
+ describe ".scope" do
126
+ context "when config has been loaded" do
127
+ before {
128
+ subject.instance_variable_set(:@custom1, { api_key: '1234asdf'})
129
+ expect(subject).to receive(:load).never
130
+ }
131
+ it {
132
+ expect(subject.scope(:custom1)).to eq({ api_key: '1234asdf'})
133
+ }
134
+ end
135
+ context "when config has not been loaded yet" do
136
+ it {
137
+ expect(subject).to receive(:load)
138
+ subject.scope(:custom2)
139
+ }
140
+ end
141
+ end
142
+
143
+ describe ".default_files" do
144
+ before {
145
+ subject.root = File.expand_path("../../../test_app", __FILE__)
146
+ }
147
+ it {
148
+ expect(subject.default_files[0]).to include("configuration.yml")
149
+ expect(Pathname.new(subject.default_files[0])).to be_absolute
150
+ expect(subject.default_files[1]).to include("a9n/mandrill.yml")
151
+ expect(Pathname.new(subject.default_files[1])).to be_absolute
152
+ }
153
+ end
154
+
155
+ describe ".load" do
156
+ before {
157
+ expect(described_class).to receive(:env).exactly(2).times.and_return("dev")
158
+ subject.root = "/apps/test_app"
159
+ files.each do |f, cfg|
160
+ expect(A9n::Loader).to receive(:new).with(f, "dev").and_return(double(get: cfg))
161
+ end
162
+ }
163
+ context "when no files given" do
164
+ let(:files) {
165
+ {
166
+ "/apps/test_app/config/file1.yml" => { host: "host1.com" },
167
+ "/apps/test_app/config/dir/file2.yml" => { host: "host2.com" }
168
+ }
169
+ }
170
+ before {
171
+ expect(subject).to receive(:default_files).and_return(files.keys)
172
+ expect(subject).to receive(:get_absolute_paths_for).never
173
+ }
174
+ it {
175
+ expect(subject.load).to eq(files.values)
176
+ }
177
+ end
178
+
179
+ context "when custom files given" do
180
+ let(:given_files) {
181
+ ["file3.yml", "/apps/test_app/config/dir/file4.yml"]
182
+ }
183
+ let(:files) {
184
+ {
185
+ "/apps/test_app/config/file3.yml" => { host: "host3.com" },
186
+ "/apps/test_app/config/dir/file4.yml" => { host: "host4.com" }
187
+ }
188
+ }
189
+ before {
190
+ expect(subject).to receive(:default_files).never
191
+ expect(subject).to receive(:get_absolute_paths_for).with(given_files).and_call_original
192
+ }
193
+ it {
194
+ expect(subject.load(*given_files)).to eq(files.values)
195
+ }
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,163 @@
1
+ require "spec_helper"
2
+
3
+ describe A9n::Loader do
4
+ let(:env) { "test" }
5
+ let(:root) { File.expand_path("../../../test_app", __FILE__) }
6
+ let(:file_path) { File.join(root, "config/configuration.yml") }
7
+ subject { described_class.new(file_path, env) }
8
+
9
+ describe "#intialize" do
10
+ it { expect(subject.env).to eq(env) }
11
+ it { expect(subject.local_file).to eq(file_path) }
12
+ it { expect(subject.example_file).to eq("#{file_path}.example") }
13
+ end
14
+
15
+ describe "#load" do
16
+ let(:example_env_config) {
17
+ { app_url: "http://127.0.0.1:3000", api_key: "base1234" }
18
+ }
19
+ let(:local_env_config) {
20
+ { app_host: "127.0.0.1:3000", api_key: "local1234" }
21
+ }
22
+ let(:example_default_config) {
23
+ { page_title: "Base Kielbasa", api_key: "example1234default" }
24
+ }
25
+ let(:local_default_config) {
26
+ { page_title: "Local Kielbasa", api_key: "local1234default" }
27
+ }
28
+ let(:env){
29
+ "tropical"
30
+ }
31
+ let(:config) { subject.get }
32
+
33
+ context "when no configuration file exists" do
34
+ before do
35
+ expect(described_class).to receive(:load_yml).with(subject.example_file, env).and_return(nil)
36
+ expect(described_class).to receive(:load_yml).with(subject.local_file, env).and_return(nil)
37
+ expect(described_class).to receive(:load_yml).with(subject.example_file, "defaults").and_return(nil)
38
+ expect(described_class).to receive(:load_yml).with(subject.local_file, "defaults").and_return(nil)
39
+ expect(subject).to receive(:verify!).never
40
+ end
41
+ it "raises expection" do
42
+ expect {
43
+ subject.load
44
+ }.to raise_error(A9n::MissingConfigurationData)
45
+ end
46
+ end
47
+
48
+ context "when example configuration file exists with defaults" do
49
+ before do
50
+ expect(described_class).to receive(:load_yml).with(subject.example_file, env).and_return(example_env_config)
51
+ expect(described_class).to receive(:load_yml).with(subject.local_file, env).and_return(nil)
52
+ expect(described_class).to receive(:load_yml).with(subject.example_file, "defaults").and_return(example_default_config)
53
+ expect(described_class).to receive(:load_yml).with(subject.local_file, "defaults").and_return(nil)
54
+
55
+ expect(described_class).to receive(:verify!).never
56
+ subject.load
57
+ end
58
+
59
+ it { expect(config.app_url).to eq("http://127.0.0.1:3000") }
60
+ it { expect(config.page_title).to eq("Base Kielbasa") }
61
+ it { expect(config.api_key).to eq("base1234") }
62
+
63
+ it {
64
+ expect { config.app_host }.to raise_error(A9n::NoSuchConfigurationVariable)
65
+ }
66
+ end
67
+
68
+ context "when local configuration file exists with defaults" do
69
+ before do
70
+ expect(described_class).to receive(:load_yml).with(subject.example_file, env).and_return(nil)
71
+ expect(described_class).to receive(:load_yml).with(subject.local_file, env).and_return(local_env_config)
72
+ expect(described_class).to receive(:load_yml).with(subject.example_file, "defaults").and_return(nil)
73
+ expect(described_class).to receive(:load_yml).with(subject.local_file, "defaults").and_return(local_default_config)
74
+ expect(described_class).to receive(:verify!).never
75
+ subject.load
76
+ end
77
+ it { expect(config.app_host).to eq("127.0.0.1:3000") }
78
+ it { expect(config.page_title).to eq("Local Kielbasa") }
79
+ it { expect(config.api_key).to eq("local1234") }
80
+
81
+ it {
82
+ expect { config.app_url }.to raise_error(A9n::NoSuchConfigurationVariable)
83
+ }
84
+ end
85
+
86
+ context "when both local and base configuration file exists without defaults" do
87
+ context "with same data" do
88
+ before do
89
+ expect(described_class).to receive(:load_yml).with(subject.example_file, env).and_return(example_env_config)
90
+ expect(described_class).to receive(:load_yml).with(subject.local_file, env).and_return(example_env_config)
91
+ expect(described_class).to receive(:load_yml).with(subject.example_file, "defaults").and_return(nil)
92
+ expect(described_class).to receive(:load_yml).with(subject.local_file, "defaults").and_return(nil)
93
+ subject.load
94
+ end
95
+
96
+ it { expect(config.app_url).to eq("http://127.0.0.1:3000") }
97
+ it { expect(config.api_key).to eq("base1234") }
98
+
99
+ it {
100
+ expect { config.page_title }.to raise_error(A9n::NoSuchConfigurationVariable)
101
+ }
102
+ it {
103
+ expect { config.app_host }.to raise_error(A9n::NoSuchConfigurationVariable)
104
+ }
105
+ end
106
+
107
+ context "with different data" do
108
+ before do
109
+ expect(described_class).to receive(:load_yml).with(subject.example_file, env).and_return(example_env_config)
110
+ expect(described_class).to receive(:load_yml).with(subject.local_file, env).and_return(local_env_config)
111
+ expect(described_class).to receive(:load_yml).with(subject.example_file, "defaults").and_return(nil)
112
+ expect(described_class).to receive(:load_yml).with(subject.local_file, "defaults").and_return(nil)
113
+ end
114
+ it "raises expection" do
115
+ expect {
116
+ subject.load
117
+ }.to raise_error(A9n::MissingConfigurationVariables)
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ describe ".load_yml" do
124
+ let(:env) { "test" }
125
+ subject { described_class.load_yml(file_path, env) }
126
+
127
+ context "when file not exists" do
128
+ let(:file_path) { "file_not_existing_in_universe.yml" }
129
+
130
+ it{ expect(subject).to be_nil }
131
+ end
132
+
133
+ context "when file exists" do
134
+ let(:file_path) { File.join(root, "config/configuration.yml") }
135
+
136
+ before {
137
+ ENV["DWARF"] = "erbized dwarf"
138
+ }
139
+
140
+ context "and has data" do
141
+ it "returns non-empty hash" do
142
+ expect(subject).to be_kind_of(Hash)
143
+ expect(subject.keys).to_not be_empty
144
+ end
145
+
146
+ it "has symbolized keys" do
147
+ expect(subject.keys.first).to be_kind_of(Symbol)
148
+ expect(subject[:hash_dwarf]).to be_kind_of(Hash)
149
+ expect(subject[:hash_dwarf].keys.first).to be_kind_of(Symbol)
150
+ end
151
+
152
+ it "parses erb" do
153
+ expect(subject[:erb_dwarf]).to eq("erbized dwarf")
154
+ end
155
+ end
156
+
157
+ context "and has no data" do
158
+ let(:env) { "production" }
159
+ it{ expect(subject).to be_nil }
160
+ end
161
+ end
162
+ end
163
+ end
@@ -47,8 +47,20 @@ describe A9n::Struct do
47
47
  expect(subject.fetch(:non_empty_dwarf)).to eq('dwarf')
48
48
  end
49
49
 
50
- it 'not returns nil for non existing value' do
50
+ it 'return false value' do
51
+ expect(subject.fetch(:false_dwarf)).to eq(false)
52
+ end
53
+
54
+ it 'return nil value' do
55
+ expect(subject.fetch(:nil_dwarf)).to eq(nil)
56
+ end
57
+
58
+ it 'return nil for non existing value' do
51
59
  expect(subject.fetch(:non_existing_dwarf)).to eq(nil)
52
60
  end
61
+
62
+ it 'return default for non existing value' do
63
+ expect(subject.fetch(:non_existing_dwarf, 'default')).to eq('default')
64
+ end
53
65
  end
54
66
  end
@@ -0,0 +1,3 @@
1
+ defaults:
2
+ username: "joe"
3
+ api_key: "asdf1234"
@@ -20,4 +20,4 @@ test:
20
20
  overriden_dwarf: "already overriden dwarf"
21
21
  hash_dwarf:
22
22
  dwarf_1: "hello 1"
23
- dwarf_2: "hello 2"
23
+ dwarf_2: "hello 2"
@@ -0,0 +1,23 @@
1
+ defaults:
2
+ default_dwarf: "example default dwarf"
3
+ overriden_dwarf: "example not yet overriden dwarf"
4
+ development:
5
+ nil_dwarf: ~
6
+ false_dwarf: false
7
+ true_dwarf: true
8
+ string_dwarf: "dwarf"
9
+ erb_dwarf: "<%= ENV['DWARF'] %>"
10
+ overriden_dwarf: "example already overriden dwarf"
11
+ hash_dwarf:
12
+ dwarf_1: "hello 1"
13
+ dwarf_2: "hello 2"
14
+ test:
15
+ nil_dwarf: ~
16
+ false_dwarf: false
17
+ true_dwarf: true
18
+ string_dwarf: "dwarf"
19
+ erb_dwarf: "<%= ENV['DWARF'] %>"
20
+ overriden_dwarf: "example already overriden dwarf"
21
+ hash_dwarf:
22
+ dwarf_1: "hello 1"
23
+ dwarf_2: "hello 2"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: a9n
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Krzysztof Knapik
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-16 00:00:00.000000000 Z
11
+ date: 2014-05-29 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Simple tool for managing extra configuration in ruby/rails apps
14
14
  email:
@@ -27,13 +27,18 @@ files:
27
27
  - a9n.gemspec
28
28
  - lib/a9n.rb
29
29
  - lib/a9n/capistrano.rb
30
- - lib/a9n/core_ext/hash.rb
30
+ - lib/a9n/ext/hash.rb
31
+ - lib/a9n/loader.rb
31
32
  - lib/a9n/struct.rb
32
33
  - lib/a9n/version.rb
33
- - spec/a9n_spec.rb
34
- - spec/fixtures/configuration.yml
34
+ - spec/integration/a9n_spec.rb
35
35
  - spec/spec_helper.rb
36
- - spec/struct_spec.rb
36
+ - spec/unit/a9n_spec.rb
37
+ - spec/unit/loader_spec.rb
38
+ - spec/unit/struct_spec.rb
39
+ - test_app/config/a9n/mandrill.yml
40
+ - test_app/config/configuration.yml
41
+ - test_app/config/configuration.yml.example
37
42
  homepage: https://github.com/knapo/a9n
38
43
  licenses:
39
44
  - MIT
@@ -59,7 +64,8 @@ signing_key:
59
64
  specification_version: 4
60
65
  summary: a9n is a simple tool for managing extra configuration in ruby/rails apps
61
66
  test_files:
62
- - spec/a9n_spec.rb
63
- - spec/fixtures/configuration.yml
67
+ - spec/integration/a9n_spec.rb
64
68
  - spec/spec_helper.rb
65
- - spec/struct_spec.rb
69
+ - spec/unit/a9n_spec.rb
70
+ - spec/unit/loader_spec.rb
71
+ - spec/unit/struct_spec.rb
@@ -1,17 +0,0 @@
1
- class Hash
2
- # Hash#deep_symbolize_keys
3
- # based on
4
- # https://github.com/svenfuchs/i18n/blob/master/lib/i18n/core_ext/hash.rb
5
- def deep_symbolize_keys
6
- inject({}) { |result, (key, value)|
7
- value = value.deep_symbolize_keys if value.is_a?(self.class)
8
- result[(key.to_sym rescue key) || key] = value
9
- result
10
- }
11
- end unless self.method_defined?(:deep_symbolize_keys)
12
-
13
- def self.merge(*items)
14
- return nil if items.compact.empty?
15
- items.compact.inject({}){|sum, item| sum.merge!(item)}
16
- end
17
- end
@@ -1,295 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe A9n do
4
- describe '.local_app' do
5
- context 'when rails not found' do
6
- before {
7
- expect(described_class).to receive(:get_rails).and_return(nil)
8
- }
9
- specify {
10
- expect(described_class.local_app).to be_nil
11
- }
12
- end
13
-
14
- context 'when custom non-rails app is being used' do
15
- let(:local_app) { double(env: 'test', root: '/apps/a9n') }
16
- before { described_class.local_app = local_app }
17
-
18
- specify { expect(described_class.local_app).to eq(local_app) }
19
- end
20
-
21
- after { described_class.local_app = nil }
22
- end
23
-
24
- describe '.root' do
25
- let(:local_app) { double(env: 'test', root: '/apps/a9n') }
26
- before { described_class.local_app = local_app }
27
-
28
- context 'with custom path' do
29
- before {
30
- described_class.root = '/home/knapo/workspace/a9n'
31
- }
32
- specify {
33
- expect(described_class.root).to eq(Pathname.new('/home/knapo/workspace/a9n'))
34
- }
35
- end
36
-
37
- context 'with local app path' do
38
- specify {
39
- expect(described_class.root).to eq('/apps/a9n')
40
- }
41
- end
42
-
43
- after {
44
- described_class.root = nil
45
- described_class.local_app = nil
46
- }
47
- end
48
-
49
- describe '.load' do
50
- let(:base_file) { described_class::DEFAULT_FILE }
51
- let(:extra_file) { 'mongo.yml' }
52
- let(:base_sample_config){
53
- { app_url: 'http://127.0.0.1:3000', api_key: 'base1234' }
54
- }
55
- let(:local_sample_config){
56
- { app_host: '127.0.0.1:3000', api_key: 'local1234' }
57
- }
58
- let(:base_default_config){
59
- { page_title: 'Base Kielbasa', api_key: 'base1234default' }
60
- }
61
- let(:local_default_config){
62
- { page_title: 'Local Kielbasa', api_key: 'local1234default' }
63
- }
64
- let(:env){
65
- 'tropical'
66
- }
67
- subject {
68
- described_class
69
- }
70
- before do
71
- allow(described_class).to receive(:env).and_return(env)
72
- end
73
-
74
- context 'when no configuration file exists' do
75
- before do
76
- expect(described_class).to receive(:load_yml).with("config/#{base_file}.example", env).and_return(nil)
77
- expect(described_class).to receive(:load_yml).with("config/#{base_file}", env).and_return(nil)
78
- expect(described_class).to receive(:load_yml).with("config/#{base_file}.example", 'defaults').and_return(nil)
79
- expect(described_class).to receive(:load_yml).with("config/#{base_file}", 'defaults').and_return(nil)
80
- expect(described_class).to receive(:verify!).never
81
- end
82
- it 'raises expection' do
83
- expect {
84
- described_class.load
85
- }.to raise_error(described_class::MissingConfigurationData)
86
- end
87
- end
88
-
89
- context 'when base configuration file exists with defaults' do
90
- before do
91
- expect(described_class).to receive(:load_yml).with("config/#{base_file}.example", env).and_return(base_sample_config)
92
- expect(described_class).to receive(:load_yml).with("config/#{base_file}", env).and_return(nil)
93
- expect(described_class).to receive(:load_yml).with("config/#{base_file}.example", 'defaults').and_return(base_default_config)
94
- expect(described_class).to receive(:load_yml).with("config/#{base_file}", 'defaults').and_return(nil)
95
-
96
- expect(described_class).to receive(:verify!).never
97
- described_class.load
98
- end
99
-
100
- its(:app_url) { should_not be_nil }
101
- its(:app_url) { should == subject.fetch(:app_url) }
102
- its(:page_title) { should == 'Base Kielbasa' }
103
- its(:api_key) { should == 'base1234' }
104
- specify {
105
- expect(subject.instance_variable_get("@configuration")).to be_kind_of(A9n::Struct)
106
- }
107
- specify {
108
- expect { subject.app_host }.to raise_error(described_class::NoSuchConfigurationVariable)
109
- }
110
- end
111
-
112
- context 'when local configuration file exists with defaults' do
113
- before do
114
- expect(described_class).to receive(:load_yml).with("config/#{base_file}.example", env).and_return(nil)
115
- expect(described_class).to receive(:load_yml).with("config/#{base_file}", env).and_return(local_sample_config)
116
- expect(described_class).to receive(:load_yml).with("config/#{base_file}.example", 'defaults').and_return(nil)
117
- expect(described_class).to receive(:load_yml).with("config/#{base_file}", 'defaults').and_return(local_default_config)
118
- expect(described_class).to receive(:verify!).never
119
- described_class.load
120
- end
121
-
122
- its(:app_host) { should_not be_nil }
123
- its(:page_title) { should == 'Local Kielbasa' }
124
- its(:api_key) { should == 'local1234' }
125
- specify {
126
- expect { subject.app_url }.to raise_error(described_class::NoSuchConfigurationVariable)
127
- }
128
- end
129
-
130
- context 'when both local and base configuration file exists without defaults' do
131
- context 'with same data' do
132
- before do
133
- expect(described_class).to receive(:load_yml).with("config/#{base_file}.example", env).and_return(base_sample_config)
134
- expect(described_class).to receive(:load_yml).with("config/#{base_file}", env).and_return(base_sample_config)
135
- expect(described_class).to receive(:load_yml).with("config/#{base_file}.example", 'defaults').and_return(nil)
136
- expect(described_class).to receive(:load_yml).with("config/#{base_file}", 'defaults').and_return(nil)
137
- described_class.load
138
- end
139
-
140
- its(:app_url) { should_not be_nil }
141
- its(:api_key) { should == 'base1234' }
142
- specify {
143
- expect { subject.page_title }.to raise_error(described_class::NoSuchConfigurationVariable)
144
- }
145
- specify {
146
- expect { subject.app_host }.to raise_error(described_class::NoSuchConfigurationVariable)
147
- }
148
- end
149
-
150
- context 'with different data' do
151
- before do
152
- expect(described_class).to receive(:load_yml).with("config/#{base_file}.example", env).and_return(base_sample_config)
153
- expect(described_class).to receive(:load_yml).with("config/#{base_file}", env).and_return(local_sample_config)
154
- expect(described_class).to receive(:load_yml).with("config/#{base_file}.example", 'defaults').and_return(nil)
155
- expect(described_class).to receive(:load_yml).with("config/#{base_file}", 'defaults').and_return(nil)
156
- end
157
- it 'raises expection' do
158
- expect {
159
- described_class.load
160
- }.to raise_error(described_class::MissingConfigurationVariables)
161
- end
162
- end
163
- end
164
-
165
- context 'when extra file is loaded' do
166
- before do
167
- expect(described_class).to receive(:load_yml).with("config/#{extra_file}.example", env).and_return(base_sample_config)
168
- expect(described_class).to receive(:load_yml).with("config/#{extra_file}", env).and_return(nil)
169
- expect(described_class).to receive(:load_yml).with("config/#{extra_file}.example", 'defaults').and_return(base_default_config)
170
- expect(described_class).to receive(:load_yml).with("config/#{extra_file}", 'defaults').and_return(nil)
171
-
172
- expect(described_class).to receive(:verify!).never
173
- described_class.load('mongo.yml')
174
- end
175
-
176
- it { expect(subject.mongo).to be_kind_of(A9n::Struct) }
177
- it { expect(subject.instance_variable_get("@mongo")).to be_kind_of(A9n::Struct) }
178
- it { expect(subject.mongo.app_url).to eq("http://127.0.0.1:3000") }
179
- it { expect(subject.mongo.page_title).to eq('Base Kielbasa') }
180
- it { expect(subject.mongo.api_key).to eq('base1234') }
181
- specify {
182
- expect { subject.mongo.app_host }.to raise_error(described_class::NoSuchConfigurationVariable)
183
- }
184
- end
185
- end
186
-
187
- describe '.load_yml' do
188
- let(:root) { File.dirname(__FILE__) }
189
- let(:env) { 'test' }
190
- subject { described_class.load_yml(file_path, env) }
191
-
192
- before do
193
- expect(described_class).to receive(:root).at_least(:once).and_return(root)
194
- expect(described_class).to receive(:env).never
195
- end
196
-
197
- context 'when file not exists' do
198
- let(:file_path) { 'file_not_existing_in_universe.yml' }
199
-
200
- it 'returns nil' do
201
- expect(subject).to be_nil
202
- end
203
- end
204
-
205
- context 'when file exists' do
206
- let(:file_path) { 'fixtures/configuration.yml'}
207
- before {
208
- ENV['DWARF'] = 'erbized dwarf'
209
- }
210
-
211
- context 'and has data' do
212
- it 'returns non-empty hash' do
213
- expect(subject).to be_kind_of(Hash)
214
- expect(subject.keys).to_not be_empty
215
- end
216
-
217
- it 'has symbolized keys' do
218
- expect(subject.keys.first).to be_kind_of(Symbol)
219
- expect(subject[:hash_dwarf]).to be_kind_of(Hash)
220
- expect(subject[:hash_dwarf].keys.first).to be_kind_of(Symbol)
221
- end
222
-
223
- it 'parses erb' do
224
- expect(subject[:erb_dwarf]).to eq('erbized dwarf')
225
- end
226
- end
227
-
228
- context 'and has no data' do
229
- let(:env) { 'production' }
230
- it { should be_nil }
231
- end
232
- end
233
- end
234
-
235
- describe '.env' do
236
- before {
237
- described_class.instance_variable_set(:@env, nil)
238
- }
239
-
240
- context 'local_app_env is set' do
241
- before {
242
- expect(described_class).to receive(:local_app).and_return(double(env: 'dwarf_env')).exactly(3).times
243
- expect(described_class).to receive(:get_env_var).never
244
- }
245
-
246
- describe '#env' do
247
- subject { super().env }
248
- it { should == 'dwarf_env' }
249
- end
250
- end
251
-
252
- context "when APP_ENV is set" do
253
- before {
254
- expect(described_class).to receive(:local_app_env).and_return(nil)
255
- expect(described_class).to receive(:get_env_var).with('RAILS_ENV').and_return(nil)
256
- expect(described_class).to receive(:get_env_var).with('RACK_ENV').and_return(nil)
257
- expect(described_class).to receive(:get_env_var).with('APP_ENV').and_return('dwarf_env')
258
- }
259
-
260
- describe '#env' do
261
- subject { super().env }
262
- it { should == 'dwarf_env' }
263
- end
264
- end
265
- end
266
-
267
- describe '.get_env_var' do
268
- before { ENV['DWARF'] = 'little dwarf' }
269
- it { expect(described_class.get_env_var('DWARF')).to eq('little dwarf')}
270
- it { expect(described_class.get_env_var('IS_DWARF')).to be_nil}
271
- end
272
-
273
- describe '.get_rails' do
274
- context 'when defined' do
275
- before {
276
- Object.const_set(:Rails, Module.new)
277
- }
278
- after {
279
- Object.send(:remove_const, :Rails)
280
- }
281
- it {
282
- expect(described_class.get_rails).to be_kind_of(Module)
283
- }
284
- end
285
- context 'when not defined' do
286
- it { expect(described_class.get_rails).to be_nil }
287
- end
288
- end
289
-
290
- describe '.var_name_for' do
291
- it { expect(described_class.var_name_for(:configuration)).to eq(:@configuration) }
292
- it { expect(described_class.var_name_for('configuration.yml')).to eq(:@configuration) }
293
- it { expect(described_class.var_name_for('custom_dir/extra.yml')).to eq(:@extra) }
294
- end
295
- end