anyway_config 1.4.4 → 2.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +187 -51
  3. data/lib/anyway.rb +22 -2
  4. data/lib/anyway/config.rb +116 -98
  5. data/lib/anyway/dynamic_config.rb +27 -0
  6. data/lib/anyway/env.rb +2 -5
  7. data/lib/anyway/ext/deep_dup.rb +4 -4
  8. data/lib/anyway/ext/deep_freeze.rb +5 -0
  9. data/lib/anyway/ext/jruby.rb +9 -4
  10. data/lib/anyway/ext/string_serialize.rb +1 -7
  11. data/{spec/dummy/config.ru → lib/anyway/loaders/env_loader.rb} +0 -0
  12. data/lib/anyway/loaders/secrets_loader.rb +0 -0
  13. data/lib/anyway/loaders/yaml_loader.rb +0 -0
  14. data/lib/anyway/option_parser_builder.rb +2 -2
  15. data/lib/anyway/optparse_config.rb +90 -0
  16. data/lib/anyway/rails/config.rb +76 -24
  17. data/lib/anyway/railtie.rb +11 -0
  18. data/lib/anyway/testing.rb +13 -0
  19. data/lib/anyway/testing/helpers.rb +36 -0
  20. data/lib/anyway/version.rb +1 -1
  21. metadata +46 -48
  22. data/.gem_release.yml +0 -3
  23. data/.gitignore +0 -40
  24. data/.rubocop.yml +0 -50
  25. data/.travis.yml +0 -30
  26. data/CHANGELOG.md +0 -112
  27. data/Gemfile +0 -21
  28. data/Rakefile +0 -23
  29. data/anyway_config.gemspec +0 -29
  30. data/config/cool.yml +0 -5
  31. data/gemfiles/jruby.gemfile +0 -7
  32. data/gemfiles/rails42.gemfile +0 -6
  33. data/gemfiles/rails5.gemfile +0 -6
  34. data/gemfiles/railsmaster.gemfile +0 -7
  35. data/spec/anyway.yml +0 -2
  36. data/spec/config_spec.rb +0 -337
  37. data/spec/config_spec_norails.rb +0 -86
  38. data/spec/dummy/config/application.rb +0 -13
  39. data/spec/dummy/config/boot.rb +0 -5
  40. data/spec/dummy/config/cool.yml +0 -5
  41. data/spec/dummy/config/cool_custom.yml +0 -2
  42. data/spec/dummy/config/database.yml +0 -25
  43. data/spec/dummy/config/environment.rb +0 -5
  44. data/spec/dummy/config/environments/test.rb +0 -2
  45. data/spec/dummy/config/routes.rb +0 -2
  46. data/spec/dummy/config/secrets.yml +0 -30
  47. data/spec/env_spec.rb +0 -50
  48. data/spec/ext/deep_dup_spec.rb +0 -38
  49. data/spec/ext/deep_freeze_spec.rb +0 -32
  50. data/spec/ext/hash_spec.rb +0 -39
  51. data/spec/ext/string_serialize_spec.rb +0 -32
  52. data/spec/spec_helper.rb +0 -31
  53. data/spec/spec_norails_helper.rb +0 -26
  54. data/spec/support/cool_config.rb +0 -10
  55. data/spec/support/print_warning_matcher.rb +0 -34
  56. data/spec/support/small_config.rb +0 -7
  57. data/spec/support/test_config.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: af77cb70b9fea95ccb938643fcf462f04ef0087a1b3811de9e9f66e696f29ad5
4
- data.tar.gz: 771eeb316a5a11d9bd8072dd7f506ddd5a02d36995ccf2facec3f40dcbc412ea
3
+ metadata.gz: 2d8ed2249df0baa740893d459922e839793eab4cef51b9410e2c618393ba88a7
4
+ data.tar.gz: 587d0578b716af5035e393ab29eabff5aea163d44f705ed9b245eb51d083b4f4
5
5
  SHA512:
6
- metadata.gz: 87d5c17d6ec017474112df69685edcdfe9c7547ae7ad82b4338a4dd3493afeef50d6102fe87e7b0a5070aa6ad48f4872371515f32443a443bc324a67dcdfd918
7
- data.tar.gz: 7c4cb678483573bb6a4db45ea5c78bbf1d077fcc7e5486fe4e420cae2c4de09224611d1925cdddebdc80ea767e140870b81bbcf39ce2be9515761846db70cc13
6
+ metadata.gz: 5fb7bcf6b692b6fc75f4eb0ca3170787defac2063d646ca8c9ec77d3563e526c58dc16af77e42bf6d293da21d8303d6d82afca723e2b97e528b243702f1c38b5
7
+ data.tar.gz: b89c278a7436c024f61894f59c885704c2cf4ae539bb382825181fe13d5f787662c6aafdc39b7d49ba9f4ee1c9535128e3fadb7904016c790907010e165996b9
data/README.md CHANGED
@@ -3,7 +3,10 @@
3
3
 
4
4
  # Anyway Config
5
5
 
6
- Rails/Ruby plugin/application configuration tool which allows you to load parameters from different sources: YAML, Rails secrets, environment.
6
+ **NOTE:** this readme shows doc for the upcoming 2.0 version (`2.0.0.pre` is available on RubyGems).
7
+ For version 1.x see [1-4-stable branch](https://github.com/palkan/anyway_config/tree/1-4-stable).
8
+
9
+ Rails/Ruby plugin/application configuration tool which allows you to load parameters from different sources: YAML, Rails secrets/credentials, environment.
7
10
 
8
11
  Allows you to easily follow the [twelve-factor application](https://12factor.net/config) principles and adds zero complexity to your development process.
9
12
 
@@ -15,6 +18,8 @@ Libraries using Anyway Config:
15
18
 
16
19
  - [Sniffer](https://github.com/aderyabin/sniffer)
17
20
 
21
+ - [Blood Contracts](https://github.com/sclinede/blood_contracts)
22
+
18
23
  - [and others](https://github.com/palkan/anyway_config/network/dependents).
19
24
 
20
25
  ## Installation
@@ -24,9 +29,9 @@ Libraries using Anyway Config:
24
29
  ```ruby
25
30
  # my-cool-gem.gemspec
26
31
  Gem::Specification.new do |spec|
27
- ...
28
- spec.add_dependency "anyway_config", "~> 1.0"
29
- ...
32
+ # ...
33
+ spec.add_dependency "anyway_config", "2.0.0.pre"
34
+ # ...
30
35
  end
31
36
  ```
32
37
 
@@ -34,7 +39,7 @@ end
34
39
 
35
40
  ```ruby
36
41
  # Gemfile
37
- gem "anyway_config", "~> 1.0"
42
+ gem "anyway_config", "2.0.0.pre"
38
43
  ```
39
44
 
40
45
  3) Install globally:
@@ -50,11 +55,11 @@ $ gem install anyway_config
50
55
  Create configuration class:
51
56
 
52
57
  ```ruby
53
- require 'anyway'
58
+ require "anyway"
54
59
 
55
60
  module MyCoolGem
56
61
  class Config < Anyway::Config
57
- attr_config user: 'root', password: 'root', host: 'localhost'
62
+ attr_config user: "root", password: "root", host: "localhost"
58
63
  end
59
64
  end
60
65
  ```
@@ -62,7 +67,7 @@ end
62
67
  `attr_config` creates accessors and default values. If you don't need default values just write:
63
68
 
64
69
  ```ruby
65
- attr_config :user, :password, host: 'localhost'
70
+ attr_config :user, :password, host: "localhost", options: {}
66
71
  ```
67
72
 
68
73
  Then create an instance of the config class and use it:
@@ -74,33 +79,43 @@ module MyCoolGem
74
79
  end
75
80
  end
76
81
 
77
- MyCoolGem.config.user #=> 'root'
82
+ MyCoolGem.config.user #=> "root"
78
83
  ```
79
84
 
80
- #### Customize name
85
+ #### Config name
86
+
87
+ Anyway Config relies on the notion of _config name_ to populate data.
88
+
89
+ By default, Anyway Config uses the config class name to infer the config name using the following rules:
90
+ - if the class name has a form of `<Module>::Config` then use the module name (`SomeModule::Config => "somemodule"`)
91
+ - if the class name has a form of `<Something>Config` then use the class name prefix (`SomeConfig => "some"`)
81
92
 
82
- By default, Anyway Config uses the namespace (the outer module name) as the config name, but you can set it manually:
93
+ **NOTE:** in both cases the config name is a **downcased** module/class prefix, not underscored.
94
+
95
+ You can also specify the config name explicitly (it's required in cases when you class name doesn't match any of the patterns above):
83
96
 
84
97
  ```ruby
85
98
  module MyCoolGem
86
99
  class Config < Anyway::Config
87
100
  config_name :cool
88
- attr_config user: 'root', password: 'root', host: 'localhost', options: {}
101
+ attr_config user: "root", password: "root", host: "localhost", options: {}
89
102
  end
90
103
  end
91
104
  ```
92
105
 
93
106
  #### Customize env variable names prefix
94
107
 
95
- By default, Anyway Config uses underscored config name as a prefix for env variable names (e.g.
96
- `config_name :my_app` will result to parsing `MY_APP_HOST` variable). You can set env prefix
97
- explicitly, and it will be used as is:
108
+ By default, Anyway Config uses upcased config name as a prefix for env variable names (e.g.
109
+ `config_name :my_app` will result to parsing `MY_APP_` prefix).
110
+
111
+ You can set env prefix explicitly:
98
112
 
99
113
  ```ruby
100
114
  module MyCoolGem
101
115
  class Config < Anyway::Config
116
+ config_name :cool_gem
102
117
  env_prefix :really_cool # now variables, starting wih `REALLY_COOL_`, will be parsed
103
- attr_config user: 'root', password: 'root', host: 'localhost', options: {}
118
+ attr_config user: "root", password: "root", host: "localhost", options: {}
104
119
  end
105
120
  end
106
121
  ```
@@ -108,18 +123,16 @@ end
108
123
  #### Provide explicit values
109
124
 
110
125
  Sometimes it's useful to set some parameters explicitly during config initialization.
111
- You can do that using `overrides` option:
126
+ You can do that by passing a Hash into `.new` method:
112
127
 
113
128
  ```ruby
114
129
  config = MyCoolGem::Config.new(
115
- overrides: {
116
- user: 'john',
117
- password: 'rubyisnotdead'
118
- }
130
+ user: "john",
131
+ password: "rubyisnotdead"
119
132
  )
120
133
 
121
134
  # The value would not be overriden from other sources (such as YML file, env)
122
- config.user == 'john'
135
+ config.user == "john"
123
136
  ```
124
137
 
125
138
  ### Dynamic configuration
@@ -127,32 +140,121 @@ config.user == 'john'
127
140
  You can also create configuration objects without pre-defined schema (just like `Rails.application.config_for` but more [powerful](#railsapplicationconfig_for-vs-anywayconfigfor)):
128
141
 
129
142
  ```ruby
130
- # load data from config/my_app.yml, secrets.my_app (if using Rails), ENV["MYAPP_*"]
143
+ # load data from config/my_app.yml, secrets.my_app (if using Rails), ENV["MY_APP_*"]
144
+ # MY_APP_VALUE=42
131
145
  config = Anyway::Config.for(:my_app)
146
+ config.value #=> 42
147
+
148
+ # you can specify the config file path or env prefix
149
+ config = Anyway::Config.for(:my_app, config_path: "my_config.yml", env_prefix: "MYAPP")
132
150
  ```
133
151
 
134
152
  ### Using with Rails
135
153
 
154
+ **NOTE:** version 2.x supports Rails >= 5.0; for Rails 4.x use version 1.x of the gem.
155
+
136
156
  Your config will be filled up with values from the following sources (ordered by priority from low to high):
137
157
 
138
- - `RAILS_ROOT/config/my_cool_gem.yml` (for the current `RAILS_ENV`, supports `ERB`). You can override this setting
139
- through special environment variable – 'MYCOOLGEM_CONF' – containing the path to the YAML file.
158
+ - `RAILS_ROOT/config/my_cool_gem.yml` (for the current `RAILS_ENV`, supports `ERB`):
140
159
 
141
- - `Rails.application.secrets.my_cool_gem`
160
+ ```yml
161
+ test:
162
+ host: localhost
163
+ port: 3002
164
+
165
+ development:
166
+ host: localhost
167
+ port: 3000
168
+ ```
169
+
170
+ **NOTE:** you can override the default YML lookup path by setting `MYCOOLGEM_CONF` env variable.
171
+
172
+ - `Rails.application.secrets.my_cool_gem` (if `secrets.yml` present):
173
+
174
+ ```yml
175
+ # config/secrets.yml
176
+ development:
177
+ my_cool_gem:
178
+ port: 4444
179
+ ```
180
+
181
+ - `Rails.application.credentials` (if supported):
182
+
183
+ ```yml
184
+ my_cool_gem:
185
+ host: secret.host
186
+ ```
142
187
 
143
188
  - `ENV['MYCOOLGEM_*']`.
144
189
 
190
+ #### `app/configs`
191
+
192
+ You can store application-level config classes in `app/configs` folder.
193
+
194
+ Anyway Config automatically adds this folder to Rails autoloading system to make it possible to
195
+ autoload configs even during the configuration phase.
196
+
197
+ Consider an example: setting the Action Mailer host name for Heroku review apps.
198
+
199
+ We have the following config to fetch the Heroku provided [metadata](https://devcenter.heroku.com/articles/dyno-metadata):
200
+
201
+ ```ruby
202
+ # This data is provided by Heroku Dyno Metadadata add-on.
203
+ class HerokuConfig < Anyway::Config
204
+ attr_config :app_id, :app_name,
205
+ :dyno_id, :release_version,
206
+ :slug_commit
207
+
208
+ def hostname
209
+ "#{app_name}.herokuapp.com"
210
+ end
211
+ end
212
+ ```
213
+
214
+ Then in `config/application.rb` you can do the following:
215
+
216
+ ```ruby
217
+ config.action_mailer.default_url_options = {host: HerokuConfig.new.hostname}
218
+ ```
219
+
145
220
  ### Using with Ruby
146
221
 
147
- By default, Anyway Config is looking for a config YAML at `./config/<config-name>.yml` e.g. `./config/my_cool_gem.yml`.
148
- You can override this location the same way as for Rails.
222
+ When you're using Anyway Config in non-Rails environment, we're looking for a YANL config file
223
+ at `./config/<config-name>.yml`.
149
224
 
150
- Environmental variables work the same way too.
225
+ You can override this setting through special environment variable – 'MYCOOLGEM_CONF' – containing the path to the YAML file.
151
226
 
227
+ **NOTE:** in pure Ruby apps we have no knowledge of _environments_ (`test`, `development`, `production`, etc.); thus we assume that the YAML contains values for a single environment:
152
228
 
153
- ### Config clear and reload
229
+ ```yml
230
+ host: localhost
231
+ port: 3000
232
+ ```
233
+
234
+ Environmental variables work the same way as with Rails.
235
+
236
+ ### Local files
237
+
238
+ It's useful to have personal, user-specific configuration in development, which extends the project-wide one.
239
+
240
+ We support this by looking at _local_ files when loading the configuration data:
241
+ - `<config_name>.local.yml` files (next to\* the _global_ `<config_name>.yml`)
242
+ - `config/credentials/local.yml.enc` (for Rails >= 6, generate it via `rails credentials:edit --environment local`).
243
+
244
+ \* If the YAML config path is not default (i.e. set via `<CONFIG_NAME>_CONF`), we lookup the local
245
+ config at this location, too.
246
+
247
+ Local configs are meant for using in development and only loaded if `Anyway::Settings.use_local_files` is `true` (which is true by default if `RACK_ENV` or `RAILS_ENV` env variable is equal to `"development"`).
248
+
249
+ **NOTE:** in Rails apps you can use `Rails.applicaiton.configuration.anyway_config.use_local_files`.
154
250
 
155
- There are `#clear` and `#reload` functions on your config (which do exactly what they state).
251
+ Don't forget to add `*.local.yml` (and `config/credentials/local.*`) to your `.gitignore`.
252
+
253
+ **NOTE:** local YAML configs for Rails app must be environment-free (i.e. you shouldn't have top-level `development:` key).
254
+
255
+ ### Reload configuration
256
+
257
+ There are `#clear` and `#reload` methods which do exactly what they state.
156
258
 
157
259
  Note: `#reload` also accepts `overrides` key to provide explicit values (see above).
158
260
 
@@ -194,7 +296,7 @@ end
194
296
 
195
297
  config = MyConfig.new
196
298
 
197
- config.parse_options!(%w(--host localhost --port 3333 --log-level debug))
299
+ config.parse_options!(%w[--host localhost --port 3333 --log-level debug])
198
300
 
199
301
  config.host # => "localhost"
200
302
  config.port # => 3333
@@ -211,10 +313,12 @@ Rails 4.2 introduced new feature: `Rails.application.config_for`. It looks very
211
313
 
212
314
  | Feature | Rails | Anyway Config |
213
315
  | ------------- |:-------------:| -----:|
214
- | load data from `config/app.yml` | yes | yes |
215
- | load data from `secrets` | no | yes |
216
- | load data from environment | no | yes |
217
- | return Hash with indifferent access | no | yes |
316
+ | load data from `config/app.yml` | yes | yes |
317
+ | load data from `secrets` | no | yes |
318
+ | load data from `credentials` | no | yes |
319
+ | load data from environment | no | yes |
320
+ | local config files | no | yes |
321
+ | return Hash with indifferent access | no | yes |
218
322
  | support ERB within `config/app.yml` | yes | yes* |
219
323
  | raise errors if file doesn't exist | yes | no |
220
324
 
@@ -224,23 +328,18 @@ But the main advantage of Anyway::Config is that it can be used [without Rails](
224
328
 
225
329
  ## How to set env vars
226
330
 
227
- Environmental variables for your config should start with your config name, uppercased and underscore-free.
228
-
229
- For example, if your module is called "MyCoolGem" then the env var "MYCOOLGEM_PASSWORD" is used as `config.password`.
331
+ Environmental variables for your config should start with your config name, uppercased.
230
332
 
231
- Environment variables are type-casted (case-insensitive).
232
-
233
- Examples:
333
+ For example, if your config name is "mycoolgem" then the env var "MYCOOLGEM_PASSWORD" is used as `config.password`.
234
334
 
335
+ Environment variables are automatically serialized:
235
336
  - `"True"`, `"t"` and `"yes"` to `true`;
236
-
237
337
  - `"False"`, `"f"` and `"no"` to `false`;
238
-
239
338
  - `"nil"` and `"null"` to `nil` (do you really need it?);
240
-
241
339
  - `"123"` to 123 and `"3.14"` to 3.14.
242
340
 
243
341
  *Anyway Config* supports nested (_hashed_) env variables. Just separate keys with double-underscore.
342
+
244
343
  For example, "MYCOOLGEM_OPTIONS__VERBOSE" is parsed as `config.options["verbose"]`.
245
344
 
246
345
  Array values are also supported:
@@ -253,13 +352,50 @@ config.ids #=> [1,2,3]
253
352
  If you want to provide a text-like env variable which contains commas then wrap it into quotes:
254
353
 
255
354
  ```ruby
256
- MYCOOLGEM="Nif-Nif, Naf-Naf and Nouf-Nouf"
355
+ MYCOOLGEM = "Nif-Nif, Naf-Naf and Nouf-Nouf"
257
356
  ```
258
357
 
358
+ ## Test helpers
359
+
360
+ We provide the `with_env` test helper to test code in the context of the specified environment variables values:
361
+
362
+ ```ruby
363
+ describe HerokuConfig, type: :config do
364
+ subject { described_class.new }
365
+
366
+ specify do
367
+ # Ensure that the env vars are set to the specified
368
+ # values within the block and reset to the previous values
369
+ # outside of it.
370
+ with_env(
371
+ "HEROKU_APP_NAME" => "kin-web-staging",
372
+ "HEROKU_APP_ID" => "abc123",
373
+ "HEROKU_DYNO_ID" => "ddyy",
374
+ "HEROKU_RELEASE_VERSION" => "v0",
375
+ "HEROKU_SLUG_COMMIT" => "3e4d5a"
376
+ ) do
377
+ is_expected.to have_attributes(
378
+ app_name: "kin-web-staging",
379
+ app_id: "abc123",
380
+ dyno_id: "ddyy",
381
+ release_version: "v0",
382
+ slug_commit: "3e4d5a"
383
+ )
384
+ end
385
+ end
386
+ end
387
+ ```
388
+
389
+ If you want to delete the env var, pass `nil` as the value.
390
+
391
+ This helper is automatically included to RSpec if `RAILS_ENV` or `RACK_ENV` env variable is equal to "test". It's only available for the example with the tag `type: :config` or with the path `spec/configs/...`.
392
+
393
+ You can add it manually by requiring `"anyway/testing/helpers"` and including the `Anyway::Test::Helpers` module (into RSpec configuration or Minitest test class).
394
+
259
395
  ## Contributing
260
396
 
261
- 1. Fork it
262
- 2. Create your feature branch (`git checkout -b my-new-feature`)
263
- 3. Commit your changes (`git commit -am 'Add some feature'`)
264
- 4. Push to the branch (`git push origin my-new-feature`)
265
- 5. Create a new Pull Request
397
+ Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/anyway_config.
398
+
399
+ ## License
400
+
401
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/lib/anyway.rb CHANGED
@@ -2,11 +2,31 @@
2
2
 
3
3
  module Anyway # :nodoc:
4
4
  require "anyway/version"
5
+
5
6
  require "anyway/config"
6
7
  require "anyway/rails/config" if defined?(::Rails::VERSION)
7
8
  require "anyway/env"
8
9
 
9
- def self.env
10
- @env ||= ::Anyway::Env.new
10
+ # Use Settings name to not confuse with Config.
11
+ #
12
+ # Settings contain the library-wide configuration.
13
+ class Settings
14
+ class << self
15
+ # Define whether to load data from
16
+ # *.yml.local (or credentials/local.yml.enc)
17
+ attr_accessor :use_local_files
18
+ end
19
+
20
+ # By default, use local files only in development (that's the purpose if the local files)
21
+ self.use_local_files = (ENV["RACK_ENV"] == "development" || ENV["RAILS_ENV"] == "development")
11
22
  end
23
+
24
+ class << self
25
+ def env
26
+ @env ||= ::Anyway::Env.new
27
+ end
28
+ end
29
+
30
+ require "anyway/railtie" if defined?(::Rails::VERSION)
31
+ require "anyway/testing" if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
12
32
  end
data/lib/anyway/config.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'anyway/ext/jruby' if defined? JRUBY_VERSION
4
- require 'anyway/ext/deep_dup'
5
- require 'anyway/ext/deep_freeze'
6
- require 'anyway/ext/hash'
7
- require 'anyway/ext/string_serialize'
8
- require 'anyway/option_parser_builder'
3
+ require "anyway/optparse_config"
4
+ require "anyway/dynamic_config"
5
+
6
+ require "anyway/ext/jruby" if defined? JRUBY_VERSION
7
+ require "anyway/ext/deep_dup"
8
+ require "anyway/ext/deep_freeze"
9
+ require "anyway/ext/hash"
10
+ require "anyway/ext/string_serialize"
9
11
 
10
12
  module Anyway # :nodoc:
11
13
  if defined? JRUBY_VERSION
@@ -21,108 +23,116 @@ module Anyway # :nodoc:
21
23
  # Provides `attr_config` method to describe
22
24
  # configuration parameters and set defaults
23
25
  class Config
24
- class << self
25
- attr_reader :defaults, :config_attributes, :option_parser_extension
26
+ include OptparseConfig
27
+ include DynamicConfig
26
28
 
29
+ class << self
27
30
  def attr_config(*args, **hargs)
28
- @defaults ||= {}
29
- @config_attributes ||= []
30
-
31
31
  new_defaults = hargs.deep_dup
32
32
  new_defaults.stringify_keys!
33
+
33
34
  defaults.merge! new_defaults
34
35
 
35
36
  new_keys = (args + new_defaults.keys) - config_attributes
36
- @config_attributes += new_keys
37
+ config_attributes.push(*new_keys)
37
38
  attr_accessor(*new_keys)
38
39
  end
39
40
 
40
- def config_name(val = nil)
41
- return (@config_name = val.to_s) unless val.nil?
41
+ def defaults
42
+ return @defaults if instance_variable_defined?(:@defaults)
42
43
 
43
- @config_name = underscore_name unless defined?(@config_name)
44
- @config_name
44
+ @defaults =
45
+ if superclass < Anyway::Config
46
+ superclass.defaults.deep_dup
47
+ else
48
+ {}
49
+ end
45
50
  end
46
51
 
47
- def ignore_options(*args)
48
- args.each do |name|
49
- option_parser_descriptors[name.to_s][:ignore] = true
50
- end
51
- end
52
+ def config_attributes
53
+ return @config_attributes if instance_variable_defined?(:@config_attributes)
52
54
 
53
- def describe_options(**hargs)
54
- hargs.each do |name, desc|
55
- option_parser_descriptors[name.to_s][:desc] = desc
56
- end
55
+ @config_attributes =
56
+ if superclass < Anyway::Config
57
+ superclass.config_attributes.dup
58
+ else
59
+ []
60
+ end
57
61
  end
58
62
 
59
- def flag_options(*args)
60
- args.each do |name|
61
- option_parser_descriptors[name.to_s][:flag] = true
62
- end
63
- end
63
+ def config_name(val = nil)
64
+ return (@explicit_config_name = val.to_s) unless val.nil?
65
+
66
+ return @config_name if instance_variable_defined?(:@config_name)
64
67
 
65
- def extend_options(&block)
66
- @option_parser_extension = block
68
+ @config_name = explicit_config_name || build_config_name
67
69
  end
68
70
 
69
- def option_parser_options
70
- config_attributes.each_with_object({}) do |key, result|
71
- descriptor = option_parser_descriptors[key.to_s]
72
- next if descriptor[:ignore] == true
71
+ def explicit_config_name
72
+ return @explicit_config_name if instance_variable_defined?(:@explicit_config_name)
73
73
 
74
- result[key] = descriptor
75
- end
74
+ @explicit_config_name =
75
+ if superclass.respond_to?(:explicit_config_name)
76
+ superclass.explicit_config_name
77
+ end
78
+ end
79
+
80
+ def explicit_config_name?
81
+ !explicit_config_name.nil?
76
82
  end
77
83
 
78
84
  def env_prefix(val = nil)
79
- return (@env_prefix = val.to_s) unless val.nil?
85
+ return (@env_prefix = val.to_s.upcase) unless val.nil?
80
86
 
81
- @env_prefix
82
- end
87
+ return @env_prefix if instance_variable_defined?(:@env_prefix)
83
88
 
84
- # Load config as Hash by any name
85
- #
86
- # Example:
87
- #
88
- # my_config = Anyway::Config.for(:my_app)
89
- # # will load data from config/my_app.yml, secrets.my_app, ENV["MY_APP_*"]
90
- def for(name)
91
- new(name: name, load: false).load_from_sources
89
+ @env_prefix =
90
+ if superclass < Anyway::Config && superclass.explicit_config_name?
91
+ superclass.env_prefix
92
+ else
93
+ config_name.upcase
94
+ end
92
95
  end
93
96
 
94
97
  private
95
98
 
96
- def option_parser_descriptors
97
- @option_parser_descriptors ||= Hash.new { |h, k| h[k] = {} }
98
- end
99
+ def build_config_name
100
+ unless name
101
+ raise "Please, specify config name explicitly for anonymous class " \
102
+ "via `config_name :my_config`"
103
+ end
99
104
 
100
- def underscore_name
101
- return unless name
105
+ # handle two cases:
106
+ # - SomeModule::Config => "some_module"
107
+ # - SomeConfig => "some"
108
+ unless name =~ /^(\w+)(\:\:)?Config$/
109
+ raise "Couldn't infer config name, please, specify it explicitly" \
110
+ "via `config_name :my_config`"
111
+ end
102
112
 
103
- word = name[/^(\w+)/]
104
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
105
- word.downcase!
106
- word
113
+ Regexp.last_match[1].tap(&:downcase!)
107
114
  end
108
115
  end
109
116
 
110
117
  attr_reader :config_name, :env_prefix
111
118
 
112
- # Instantiate config with specified name, loads the data and applies overrides
119
+ # Instantiate config instance.
113
120
  #
114
121
  # Example:
115
122
  #
116
- # my_config = Anyway::Config.new(name: :my_app, load: true, overrides: { some: :value })
123
+ # my_config = Anyway::Config.new()
124
+ #
125
+ # # provide some values explicitly
126
+ # my_config = Anyway::Config.new({some: :value})
117
127
  #
118
- def initialize(name: nil, load: true, overrides: {})
119
- @config_name = name || self.class.config_name
128
+ def initialize(overrides = {})
129
+ @config_name = self.class.config_name
120
130
 
121
131
  raise ArgumentError, "Config name is missing" unless @config_name
122
132
 
123
- @env_prefix = self.class.env_prefix || @config_name
133
+ @env_prefix = self.class.env_prefix
124
134
 
125
- self.load(overrides) if load
135
+ load(overrides)
126
136
  end
127
137
 
128
138
  def reload(overrides = {})
@@ -139,43 +149,49 @@ module Anyway # :nodoc:
139
149
  end
140
150
 
141
151
  def load(overrides = {})
142
- config = load_from_sources((self.class.defaults || {}).deep_dup)
152
+ base_config = (self.class.defaults || {}).deep_dup
153
+
154
+ base_config.deep_merge!(
155
+ load_from_sources(
156
+ name: config_name,
157
+ env_prefix: env_prefix,
158
+ config_path: resolve_config_path(config_name, env_prefix)
159
+ )
160
+ )
161
+
162
+ base_config.merge!(overrides) unless overrides.nil?
143
163
 
144
- config.merge!(overrides) unless overrides.nil?
145
- config.each do |key, val|
164
+ base_config.each do |key, val|
146
165
  set_value(key, val)
147
166
  end
148
167
  end
149
168
 
150
- def load_from_sources(config = {})
151
- # Handle anonymous configs
152
- return config unless config_name
153
-
154
- load_from_file(config)
155
- load_from_env(config)
169
+ def load_from_sources(**options)
170
+ base_config = {}
171
+ each_source(options) do |config|
172
+ base_config.deep_merge!(config) if config
173
+ end
174
+ base_config
156
175
  end
157
176
 
158
- def load_from_file(config)
159
- config.deep_merge!(parse_yml(config_path) || {}) if config_path && File.file?(config_path)
160
- config
177
+ def each_source(options)
178
+ yield load_from_file(options)
179
+ yield load_from_env(options)
161
180
  end
162
181
 
163
- def load_from_env(config)
164
- config.deep_merge!(env_part)
165
- config
166
- end
182
+ def load_from_file(name:, env_prefix:, config_path:, **_options)
183
+ file_config = load_from_yml(config_path)
167
184
 
168
- def option_parser
169
- @option_parser ||= begin
170
- parser = OptionParserBuilder.call(self.class.option_parser_options) do |key, arg|
171
- set_value(key, arg.is_a?(String) ? arg.serialize : arg)
172
- end
173
- self.class.option_parser_extension&.call(parser, self) || parser
185
+ if Anyway::Settings.use_local_files
186
+ local_config_path = config_path.sub(/\.yml/, ".local.yml")
187
+ file_config.deep_merge!(load_from_yml(local_config_path))
174
188
  end
189
+
190
+ file_config
175
191
  end
176
192
 
177
- def parse_options!(options)
178
- option_parser.parse!(options)
193
+ def load_from_env(name:, env_prefix:, **_options)
194
+ Anyway.env.fetch(env_prefix)
179
195
  end
180
196
 
181
197
  def to_h
@@ -184,26 +200,28 @@ module Anyway # :nodoc:
184
200
  end.deep_dup.deep_freeze
185
201
  end
186
202
 
203
+ def resolve_config_path(name, env_prefix)
204
+ Anyway.env.fetch(env_prefix).delete("conf") || default_config_path(name)
205
+ end
206
+
187
207
  private
188
208
 
189
- def env_part
190
- Anyway.env.fetch(env_prefix)
209
+ def set_value(key, val)
210
+ send("#{key}=", val) if respond_to?(key)
191
211
  end
192
212
 
193
- def config_path
194
- env_part.delete('conf') || default_config_path
195
- end
213
+ def load_from_yml(path)
214
+ return {} unless File.file?(path)
196
215
 
197
- def default_config_path
198
- "./config/#{config_name}.yml"
216
+ parse_yml(path)
199
217
  end
200
218
 
201
- def set_value(key, val)
202
- send("#{key}=", val) if respond_to?(key)
219
+ def default_config_path(name)
220
+ "./config/#{name}.yml"
203
221
  end
204
222
 
205
223
  def parse_yml(path)
206
- require 'yaml'
224
+ require "yaml"
207
225
  if defined?(ERB)
208
226
  YAML.safe_load(ERB.new(File.read(path)).result, [], [], true)
209
227
  else