chamber 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ced47025f0acaf5898cd70cb5d99f17f9c72820c
4
- data.tar.gz: d155b92fdd26e2a8328296b677485e0c0d01d77a
3
+ metadata.gz: 06481d8263ba75487270bf6bc366357d8ea45e81
4
+ data.tar.gz: 7095ff800b808dae4ba3470fca3cdcab26072ec1
5
5
  SHA512:
6
- metadata.gz: 950b798ef819faf34cb6319f1f08dd75db210aee8f77de133ed474a178c92c79e954267c2ea96fdb70ee9563863ce76bfec1fff8d9b3de6262f6e2c913f0b866
7
- data.tar.gz: 936ea0a71f1f32b4abc36bc0a709bd3770da3f6fda07b068c5c12be9b1c38decf123b570d0b681f7412a6769f13c94262cfbf11f11cf33cae48864ed69f8c283
6
+ metadata.gz: 5d2971516f0228a5fc17ce2b29375b5fb6fd7475918c9ad6df934727b0e15f7176979e16ce93cb9201cf8b69123485186673d0adcdb000680efb665de5298b0e
7
+ data.tar.gz: 6f38b25d90ade7a0ddc3a07ecb00a069d4f078994f8edab98567434a48767b6459c2079679132df941528252b50208f4b590fdec3d6c3422aa09e0f1a9721f91
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ chamber
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p247
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - 1.9.2
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Chamber
1
+ # Chamber [![Build Status](https://travis-ci.org/stevenhallen/chamber.png)](https://travis-ci.org/stevenhallen/chamber)
2
2
 
3
3
  Chamber lets you source your Settings from an arbitrary number of YAML files and
4
4
  provides a simple mechanism for overriding settings from the ENV, which is
@@ -20,7 +20,193 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- TODO: Write usage instructions here
23
+ The following instructions are for a Rails app hosted on heroku with both
24
+ staging and production heroku environments.
25
+
26
+ Create a Settings class that extends Chamber in `app/models/settings.rb`:
27
+
28
+ ```ruby
29
+ class Settings
30
+ extend Chamber
31
+ end
32
+ ```
33
+
34
+ Create a `config/settings.yml` that has this structure:
35
+
36
+ ```yml
37
+ development:
38
+ some_setting: value for dev
39
+ some_password:
40
+ environment: ENV_VAR_NAME
41
+ test:
42
+ some_setting: value for test
43
+ some_password:
44
+ environment: ENV_VAR_NAME
45
+ staging:
46
+ some_setting: value for staging
47
+ some_password:
48
+ environment: ENV_VAR_NAME
49
+ production:
50
+ some_setting: value for production
51
+ some_password:
52
+ environment: ENV_VAR_NAME
53
+ ```
54
+
55
+ Call `source` in your Settings class:
56
+
57
+ ```ruby
58
+ class Settings
59
+ extend Chamber
60
+
61
+ source Rails.root.join('config', 'settings.yml'), namespace: Rails.env, override_from_environment: true
62
+ end
63
+ ```
64
+
65
+ Add environment-specific files for development and test to supply the values for
66
+ those environments. Make sure to add these to .gitignore.
67
+
68
+ Add another call to `source` for these files:
69
+
70
+ ```ruby
71
+ class Settings
72
+ extend Chamber
73
+
74
+ source Rails.root.join('config', 'settings.yml'), namespace: Rails.env, override_from_environment: true
75
+ source Rails.root.join('config', "credentials-#{Rails.env}.yml")
76
+ end
77
+ ```
78
+
79
+ Use `heroku config` to set the `ENV_VAR_NAME` value for the staging and
80
+ production remotes.
81
+
82
+ Now you can access your settings in your code from `Settings.instance` (assuming
83
+ you extended Chamber in a class named `Settings`).
84
+
85
+ In other words, given a configuration file like this:
86
+
87
+ ```yml
88
+ s3:
89
+ access_key_id: value
90
+ secret_access_key: value
91
+ bucket: value
92
+ ```
93
+
94
+ the corresponding Paperclip configuration would look like this:
95
+
96
+ ```ruby
97
+ Paperclip::Attachment.default_options.merge!(
98
+ storage: 's3',
99
+ s3_credentials: {
100
+ access_key_id: Settings.instance.s3.access_key_id,
101
+ secret_access_key: Settings.instance.s3.secret_access_key
102
+ },
103
+ bucket: Settings.instance.s3.bucket,
104
+ ...
105
+ ```
106
+
107
+ ## General Principles
108
+
109
+ ### Support best practices with sensitive information
110
+
111
+ Generally this is expressed in this overly simplified form: "Don't store
112
+ sensitive information in git." A better way to say it is that you should store
113
+ sensitive information separate from non-sensitive information. There's nothing
114
+ inherently wrong with storing sensitive information in git. You just wouldn't
115
+ want to store it in a public repository.
116
+
117
+ If it weren't for this concern, managing settings would be trivial, easily
118
+ solved use any number of approaches (e.g., [like using YAML and ERB in an
119
+ initializer](http://urgetopunt.com/rails/2009/09/12/yaml-config-with-erb.html).
120
+
121
+ I recommend adding a pattern like this to `.gitignore`:
122
+
123
+ ```
124
+ # Ignore the environment-specific files that contain the real credentials:
125
+ /config/credentials-*.yml
126
+
127
+ # But don't ignore the example file that shows the structure:
128
+ !/config/credentials-example.yml
129
+ ```
130
+
131
+ You would then use Chamber like this:
132
+
133
+ ```ruby
134
+ class Settings
135
+ extend Chamber
136
+ source Rails.root.join('config', "credentials-#{Rails.env}.yml")
137
+ end
138
+ ```
139
+
140
+ ### Support arbitrary organization
141
+
142
+ You should be able to organize your settings files however you like. You want
143
+ one big jumbo settings.yml? You can do that with Chamber. You want a distinct
144
+ settings file for each specific concern? You can do that too.
145
+
146
+ Chamber supports this by allowing:
147
+
148
+ * Arbitrary number of files:
149
+
150
+ ```ruby
151
+ class Settings
152
+ extend Chamber
153
+
154
+ source Rails.root.join('config', 'settings.yml')
155
+ source Rails.root.join('config', 'facebook.yml')
156
+ source Rails.root.join('config', 'twitter.yml')
157
+ source Rails.root.join('config', 'google-plus.yml')
158
+ end
159
+ ```
160
+
161
+ * Environment-specific filenames (e.g., `settings-#{Rails.env}.yml`)
162
+
163
+ * Namespaces:
164
+
165
+ ```ruby
166
+ class Settings
167
+ extend Chamber
168
+
169
+ source Rails.root.join('config', 'settings.yml'), namespace: Rails.env
170
+ end
171
+ ```
172
+
173
+ ### Support overriding setting values at runtime from ENV
174
+
175
+ [heroku](http://heroku.com) addons are configured from ENV. To support this,
176
+ Chamber's `source` method provides an `override_from_environment` option; e.g.,
177
+
178
+ ```ruby
179
+ class Settings
180
+ extend Chamber
181
+
182
+ source Rails.root.join('config', 'settings.yml'), override_from_environment: true
183
+ end
184
+ ```
185
+
186
+ ## Ideas
187
+
188
+ * Add a rake task for validating environments (do all environments have the same
189
+ settings?)
190
+
191
+ * Add a rake task for setting Heroku environment variables.
192
+
193
+ ## Alternatives
194
+
195
+ ### figaro
196
+
197
+ [figaro](https://github.com/laserlemon/figaro)
198
+
199
+ ### idkfa
200
+
201
+ [idkfa](https://github.com/bendyworks/idkfa)
202
+
203
+ ### settingslogic
204
+
205
+ [settingslogic](https://github.com/binarylogic/settingslogic)
206
+
207
+ ### Others?
208
+
209
+ I'd love to hear of other gems and/or approaches to settings!
24
210
 
25
211
  ## Contributing
26
212
 
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/chamber.gemspec CHANGED
@@ -24,6 +24,8 @@ CHAMBER
24
24
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
25
25
  spec.require_paths = ["lib"]
26
26
 
27
+ spec.add_runtime_dependency "hashie", "~> 2.0"
28
+
27
29
  spec.add_development_dependency "bundler", "~> 1.3"
28
30
  spec.add_development_dependency "rake"
29
31
  spec.add_development_dependency "rspec", "~> 2.14"
data/lib/chamber.rb CHANGED
@@ -1,5 +1,84 @@
1
- require "chamber/version"
1
+ require 'erb'
2
+ require 'hashie'
3
+ require 'yaml'
4
+
5
+ require 'chamber/version'
2
6
 
3
7
  module Chamber
4
- # Your code goes here...
8
+ class ChamberInvalidOptionError < ArgumentError; end
9
+
10
+ def source(filename, options={})
11
+ assert_valid_keys(options)
12
+
13
+ add_source(filename, options)
14
+ end
15
+
16
+ def load!
17
+ sources.each do |source|
18
+ filename, options = source
19
+
20
+ load_source!(filename, options)
21
+ end
22
+ end
23
+
24
+ def clear!
25
+ @chamber_instance = nil
26
+ @chamber_sources = nil
27
+ end
28
+
29
+ def reload!
30
+ @chamber_instance = nil
31
+ load!
32
+ end
33
+
34
+ def instance
35
+ @chamber_instance ||= Hashie::Mash.new
36
+ end
37
+
38
+ private
39
+
40
+ def assert_valid_keys(options)
41
+ unknown_keys = options.keys - [:namespace, :override_from_environment]
42
+
43
+ raise(ChamberInvalidOptionError, options) unless unknown_keys.empty?
44
+ end
45
+
46
+ def sources
47
+ @chamber_sources ||= []
48
+ end
49
+
50
+ def add_source(filename, options)
51
+ sources << [filename, options]
52
+ end
53
+
54
+ def load_source!(filename, options)
55
+ return unless File.exists?(filename)
56
+
57
+ hash = hash_from_source(filename, options[:namespace])
58
+ if options[:override_from_environment]
59
+ override_from_environment!(hash)
60
+ end
61
+
62
+ instance.deep_merge!(hash)
63
+ end
64
+
65
+ def hash_from_source(filename, namespace)
66
+ contents = open(filename).read
67
+ hash = YAML.load(ERB.new(contents).result).to_hash || {}
68
+ hash = Hashie::Mash.new(hash)
69
+
70
+ namespace ? hash.fetch(namespace) : hash
71
+ end
72
+
73
+ def override_from_environment!(hash)
74
+ hash.each_pair do |key, value|
75
+ next unless value.is_a?(Hash)
76
+
77
+ if value.environment
78
+ hash[key] = ENV[value.environment]
79
+ else
80
+ override_from_environment!(value)
81
+ end
82
+ end
83
+ end
5
84
  end
@@ -1,3 +1,3 @@
1
1
  module Chamber
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,224 @@
1
+ require 'spec_helper'
2
+
3
+ require 'tempfile'
4
+
5
+ class Settings
6
+ extend Chamber
7
+ end
8
+
9
+ describe Chamber do
10
+ before do
11
+ Settings.clear!
12
+ end
13
+
14
+ describe '.source' do
15
+ context 'when an invalid option is specified' do
16
+ let(:options) do
17
+ { foo: 'bar' }
18
+ end
19
+
20
+ it 'raises ChamberInvalidOptionError' do
21
+ expect { Settings.source('filename', options) }.to raise_error(Chamber::ChamberInvalidOptionError)
22
+ end
23
+ end
24
+
25
+ context 'when no options are specified' do
26
+ it 'does not raise an error' do
27
+ expect { Settings.source('filename') }.not_to raise_error
28
+ end
29
+ end
30
+
31
+ context 'when valid options are specified' do
32
+ context 'and options only contains :namespace' do
33
+ let(:options) do
34
+ { namespace: 'bar' }
35
+ end
36
+
37
+ it 'does not raise an error' do
38
+ expect { Settings.source('filename', options) }.not_to raise_error
39
+ end
40
+ end
41
+
42
+ context 'and options only contains :override_from_environment' do
43
+ let(:options) do
44
+ { override_from_environment: 'bar' }
45
+ end
46
+
47
+ it 'does not raise an error' do
48
+ expect { Settings.source('filename', options) }.not_to raise_error
49
+ end
50
+ end
51
+
52
+ context 'and options contains both :namespace and :override_from_environment' do
53
+ let(:options) do
54
+ {
55
+ namespace: 'bar',
56
+ override_from_environment: 'bar'
57
+ }
58
+ end
59
+
60
+ it 'does not raise an error' do
61
+ expect { Settings.source('filename', options) }.not_to raise_error
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ describe '.load!' do
68
+ context 'when a non-existent file is specified' do
69
+ let(:file) { Tempfile.new('test') }
70
+ let!(:filename) { file.path }
71
+
72
+ before do
73
+ file.close
74
+ file.unlink
75
+ expect(File.exists?(filename)).to be_false
76
+ Settings.source filename
77
+ end
78
+
79
+ it 'does not raise an error' do
80
+ expect { Settings.load! }.not_to raise_error
81
+ end
82
+
83
+ it 'leaves the instance empty' do
84
+ Settings.load!
85
+ expect(Settings.instance).to be_empty
86
+ end
87
+ end
88
+
89
+ context 'when an existing file is specified' do
90
+ let(:file) { Tempfile.new('test') }
91
+ let(:filename) { file.path }
92
+ let(:content) do
93
+ <<-CONTENT
94
+ secret:
95
+ environment: CHAMBER_TEST
96
+ development:
97
+ foo: bar dev
98
+ test:
99
+ foo: bar test
100
+ CONTENT
101
+ end
102
+
103
+ before do
104
+ file.write(content)
105
+ file.close
106
+ end
107
+
108
+ after do
109
+ file.unlink
110
+ end
111
+
112
+ context 'and no options are specified' do
113
+ before { Settings.source(filename) }
114
+
115
+ let(:expected) do
116
+ {
117
+ 'secret' => {
118
+ 'environment' => 'CHAMBER_TEST'
119
+ },
120
+ 'development' => {
121
+ 'foo' => 'bar dev'
122
+ },
123
+ 'test' => {
124
+ 'foo' => 'bar test'
125
+ }
126
+ }
127
+ end
128
+
129
+ it 'loads all settings' do
130
+ Settings.load!
131
+ expect(Settings.instance.to_hash).to eq expected
132
+ end
133
+ end
134
+
135
+ context 'and the :namespace option is specified' do
136
+ before { Settings.source(filename, namespace: namespace) }
137
+
138
+ context 'and it is valid' do
139
+ let(:namespace) { 'development' }
140
+ let(:expected) do
141
+ {
142
+ 'foo' => 'bar dev'
143
+ }
144
+ end
145
+
146
+ it 'loads settings for the specified namespace' do
147
+ Settings.load!
148
+ expect(Settings.instance.to_hash).to eq expected
149
+ end
150
+ end
151
+
152
+ context 'and it is not valid' do
153
+ let(:namespace) { 'staging' }
154
+
155
+ it 'raises a KeyError' do
156
+ expect { Settings.load! }.to raise_error(KeyError)
157
+ end
158
+ end
159
+ end
160
+
161
+ context 'and the :override_from_environment option is specified' do
162
+ before { Settings.source(filename, override_from_environment: true) }
163
+
164
+ context 'and the environment variable is present' do
165
+ before { ENV['CHAMBER_TEST'] = 'value' }
166
+
167
+ it 'overrides the settings from the environment' do
168
+ Settings.load!
169
+ expect(Settings.instance.secret).to eq 'value'
170
+ end
171
+ end
172
+
173
+ context 'and the environment variable is not present' do
174
+ before { ENV.delete('CHAMBER_TEST') }
175
+
176
+ it 'sets the value to nil' do
177
+ Settings.load!
178
+ expect(Settings.instance.secret).to be_nil
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ describe '.reload!' do
186
+ context 'when a filename is changed after it is sourced and loaded' do
187
+ let(:file) { Tempfile.new('test') }
188
+ let!(:filename) { file.path }
189
+ let(:content) do
190
+ <<-CONTENT
191
+ initial: value
192
+ CONTENT
193
+ end
194
+ let(:modified) do
195
+ <<-MODIFIED
196
+ modified: changed
197
+ MODIFIED
198
+ end
199
+
200
+ before do
201
+ file.write(content)
202
+ file.close
203
+ Settings.source(filename)
204
+ Settings.load!
205
+ end
206
+
207
+ after do
208
+ file.unlink
209
+ end
210
+
211
+ it 'reloads the settings' do
212
+ File.open(filename, 'w') { |writer| writer.write(modified) }
213
+
214
+ expect { Settings.reload! }.to change { Settings.instance.to_hash }.from({ 'initial' => 'value' }).to({ 'modified' => 'changed' })
215
+ end
216
+ end
217
+ end
218
+
219
+ describe '.instance' do
220
+ it 'is a Hashie::Mash' do
221
+ expect(Settings.instance).to be_a(Hashie::Mash)
222
+ end
223
+ end
224
+ end
data/spec/spec_helper.rb CHANGED
@@ -2,3 +2,11 @@ require 'simplecov'
2
2
  SimpleCov.start
3
3
 
4
4
  require 'rspec'
5
+ require 'chamber'
6
+
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.filter_run focused: true
10
+ config.alias_example_to :fit, focused: true
11
+ config.run_all_when_everything_filtered = true
12
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chamber
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - stevenhallen
@@ -9,8 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-29 00:00:00.000000000 Z
12
+ date: 2013-11-15 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: hashie
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: '2.0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: '2.0'
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: bundler
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -79,6 +93,9 @@ extensions: []
79
93
  extra_rdoc_files: []
80
94
  files:
81
95
  - .gitignore
96
+ - .ruby-gemset
97
+ - .ruby-version
98
+ - .travis.yml
82
99
  - Gemfile
83
100
  - LICENSE.txt
84
101
  - README.md
@@ -86,6 +103,7 @@ files:
86
103
  - chamber.gemspec
87
104
  - lib/chamber.rb
88
105
  - lib/chamber/version.rb
106
+ - spec/lib/chamber_spec.rb
89
107
  - spec/spec_helper.rb
90
108
  homepage: http://github.com/stevenhallen/chamber
91
109
  licenses:
@@ -107,9 +125,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
125
  version: '0'
108
126
  requirements: []
109
127
  rubyforge_project:
110
- rubygems_version: 2.1.10
128
+ rubygems_version: 2.0.3
111
129
  signing_key:
112
130
  specification_version: 4
113
131
  summary: Heroku-friendly Settings
114
132
  test_files:
133
+ - spec/lib/chamber_spec.rb
115
134
  - spec/spec_helper.rb
135
+ has_rdoc: