runger_config 2.6.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 +7 -0
- data/CHANGELOG.md +562 -0
- data/LICENSE.txt +22 -0
- data/README.md +1121 -0
- data/lib/anyway/auto_cast.rb +53 -0
- data/lib/anyway/config.rb +473 -0
- data/lib/anyway/dynamic_config.rb +31 -0
- data/lib/anyway/ejson_parser.rb +40 -0
- data/lib/anyway/env.rb +73 -0
- data/lib/anyway/ext/deep_dup.rb +48 -0
- data/lib/anyway/ext/deep_freeze.rb +44 -0
- data/lib/anyway/ext/flatten_names.rb +37 -0
- data/lib/anyway/ext/hash.rb +40 -0
- data/lib/anyway/ext/string_constantize.rb +24 -0
- data/lib/anyway/loaders/base.rb +21 -0
- data/lib/anyway/loaders/doppler.rb +63 -0
- data/lib/anyway/loaders/ejson.rb +89 -0
- data/lib/anyway/loaders/env.rb +18 -0
- data/lib/anyway/loaders/yaml.rb +84 -0
- data/lib/anyway/loaders.rb +79 -0
- data/lib/anyway/option_parser_builder.rb +29 -0
- data/lib/anyway/optparse_config.rb +92 -0
- data/lib/anyway/rails/autoload.rb +42 -0
- data/lib/anyway/rails/config.rb +23 -0
- data/lib/anyway/rails/loaders/credentials.rb +64 -0
- data/lib/anyway/rails/loaders/secrets.rb +37 -0
- data/lib/anyway/rails/loaders/yaml.rb +9 -0
- data/lib/anyway/rails/loaders.rb +5 -0
- data/lib/anyway/rails/settings.rb +83 -0
- data/lib/anyway/rails.rb +24 -0
- data/lib/anyway/railtie.rb +28 -0
- data/lib/anyway/rbs.rb +92 -0
- data/lib/anyway/settings.rb +111 -0
- data/lib/anyway/testing/helpers.rb +36 -0
- data/lib/anyway/testing.rb +13 -0
- data/lib/anyway/tracing.rb +188 -0
- data/lib/anyway/type_casting.rb +144 -0
- data/lib/anyway/utils/deep_merge.rb +21 -0
- data/lib/anyway/utils/which.rb +18 -0
- data/lib/anyway/version.rb +5 -0
- data/lib/anyway.rb +3 -0
- data/lib/anyway_config.rb +54 -0
- data/lib/generators/anyway/app_config/USAGE +9 -0
- data/lib/generators/anyway/app_config/app_config_generator.rb +17 -0
- data/lib/generators/anyway/config/USAGE +13 -0
- data/lib/generators/anyway/config/config_generator.rb +51 -0
- data/lib/generators/anyway/config/templates/config.rb.tt +12 -0
- data/lib/generators/anyway/config/templates/config.yml.tt +13 -0
- data/lib/generators/anyway/install/USAGE +4 -0
- data/lib/generators/anyway/install/install_generator.rb +47 -0
- data/lib/generators/anyway/install/templates/application_config.rb.tt +17 -0
- data/sig/anyway_config.rbs +149 -0
- data/sig/manifest.yml +6 -0
- metadata +202 -0
data/README.md
ADDED
@@ -0,0 +1,1121 @@
|
|
1
|
+
[](https://cultofmartians.com/tasks/anyway-config-options-parse.html#task)
|
2
|
+
[](https://rubygems.org/gems/anyway_config) [](https://github.com/palkan/anyway_config/actions)
|
3
|
+
[](https://github.com/palkan/anyway_config/actions)
|
4
|
+
[](https://github.com/palkan/anyway_config/actions)
|
5
|
+
|
6
|
+
# Anyway Config
|
7
|
+
|
8
|
+
> One configuration to rule all data sources
|
9
|
+
|
10
|
+
Anyway Config is a configuration library for Ruby gems and applications.
|
11
|
+
|
12
|
+
As a library author, you can benefit from using Anyway Config by providing a better UX for your end-users:
|
13
|
+
|
14
|
+
- **Zero-code configuration** — no more boilerplate initializers.
|
15
|
+
- **Per-environment and local** settings support out-of-the-box.
|
16
|
+
|
17
|
+
For application developers, Anyway Config could be useful to:
|
18
|
+
|
19
|
+
- **Keep configuration organized** and use _named configs_ instead of bloated `.env`/`settings.yml`/whatever.
|
20
|
+
- **Free code of ENV/credentials/secrets dependency** and use configuration classes instead—your code should not rely on configuration data sources.
|
21
|
+
|
22
|
+
**NOTE:** this readme shows documentation for 2.x version.
|
23
|
+
For version 1.x see the [1-4-stable branch](https://github.com/palkan/anyway_config/tree/1-4-stable).
|
24
|
+
|
25
|
+
<a href="https://evilmartians.com/?utm_source=anyway_config">
|
26
|
+
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
27
|
+
|
28
|
+
## Links
|
29
|
+
|
30
|
+
- [Anyway Config: Keep your Ruby configuration sane](https://evilmartians.com/chronicles/anyway-config-keep-your-ruby-configuration-sane?utm_source=anyway_config)
|
31
|
+
|
32
|
+
## Table of contents
|
33
|
+
|
34
|
+
- [Main concepts](#main-concepts)
|
35
|
+
- [Installation](#installation)
|
36
|
+
- [Usage](#usage)
|
37
|
+
- [Configuration classes](#configuration-classes)
|
38
|
+
- [Dynamic configuration](#dynamic-configuration)
|
39
|
+
- [Validation & Callbacks](#validation-and-callbacks)
|
40
|
+
- [Using with Rails applications](#using-with-rails)
|
41
|
+
- [Data population](#data-population)
|
42
|
+
- [Organizing configs](#organizing-configs)
|
43
|
+
- [Generators](#generators)
|
44
|
+
- [Using with Ruby applications](#using-with-ruby)
|
45
|
+
- [Environment variables](#environment-variables)
|
46
|
+
- [Type coercion](#type-coercion)
|
47
|
+
- [Local configuration](#local-files)
|
48
|
+
- [Data loaders](#data-loaders)
|
49
|
+
- [Doppler integration](#doppler-integration)
|
50
|
+
- [EJSON support](#ejson-support)
|
51
|
+
- [Custom loaders](#custom-loaders)
|
52
|
+
- [Source tracing](#tracing)
|
53
|
+
- [Pattern matching](#pattern-matching)
|
54
|
+
- [Test helpers](#test-helpers)
|
55
|
+
- [OptionParser integration](#optionparser-integration)
|
56
|
+
- [RBS support](#rbs-support)
|
57
|
+
|
58
|
+
## Main concepts
|
59
|
+
|
60
|
+
Anyway Config abstractize the configuration layer by introducing **configuration classes** which describe available parameters and their defaults. For [example](https://github.com/palkan/influxer/blob/master/lib/influxer/config.rb):
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
module Influxer
|
64
|
+
class Config < Anyway::Config
|
65
|
+
attr_config(
|
66
|
+
host: "localhost",
|
67
|
+
username: "root",
|
68
|
+
password: "root"
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
Using Ruby classes to represent configuration allows you to add helper methods and computed parameters easily, makes the configuration **testable**.
|
75
|
+
|
76
|
+
The `anyway_config` gem takes care of loading parameters from **different sources** (YAML, credentials/secrets, environment variables, etc.). Internally, we use a _pipeline pattern_ and provide the [Loaders API](#data-loaders) to manage and [extend](#custom-loaders) its functionality.
|
77
|
+
|
78
|
+
Check out the libraries using Anyway Config for more examples:
|
79
|
+
|
80
|
+
- [Influxer](https://github.com/palkan/influxer)
|
81
|
+
- [AnyCable](https://github.com/anycable/anycable)
|
82
|
+
- [Sniffer](https://github.com/aderyabin/sniffer)
|
83
|
+
- [Blood Contracts](https://github.com/sclinede/blood_contracts)
|
84
|
+
- [and others](https://github.com/palkan/anyway_config/network/dependents).
|
85
|
+
|
86
|
+
## Installation
|
87
|
+
|
88
|
+
Adding to a gem:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
# my-cool-gem.gemspec
|
92
|
+
Gem::Specification.new do |spec|
|
93
|
+
# ...
|
94
|
+
spec.add_dependency "anyway_config", ">= 2.0.0"
|
95
|
+
# ...
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
Or adding to your project:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
# Gemfile
|
103
|
+
gem "anyway_config", "~> 2.0"
|
104
|
+
```
|
105
|
+
|
106
|
+
### Supported Ruby versions
|
107
|
+
|
108
|
+
- Ruby (MRI) >= 2.5.0
|
109
|
+
- JRuby >= 9.2.9
|
110
|
+
|
111
|
+
## Usage
|
112
|
+
|
113
|
+
### Configuration classes
|
114
|
+
|
115
|
+
Using configuration classes allows you to make configuration data a bit more than a bag of values:
|
116
|
+
you can define a schema for your configuration, provide defaults, add validations and additional helper methods.
|
117
|
+
|
118
|
+
Anyway Config provides a base class to inherit from with a few DSL methods:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
require "anyway_config"
|
122
|
+
|
123
|
+
module MyCoolGem
|
124
|
+
class Config < Anyway::Config
|
125
|
+
attr_config user: "root", password: "root", host: "localhost"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
Here `attr_config` creates accessors and populates the default values. If you don't need default values you can write:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
attr_config :user, :password, host: "localhost", options: {}
|
134
|
+
```
|
135
|
+
|
136
|
+
**NOTE**: it's safe to use non-primitive default values (like Hashes or Arrays) without worrying about their mutation: the values would be deeply duplicated for each config instance.
|
137
|
+
|
138
|
+
Then, create an instance of the config class and use it:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
MyCoolGem::Config.new.user #=> "root"
|
142
|
+
```
|
143
|
+
|
144
|
+
**Bonus:**: if you define attributes with boolean default values (`false` or `true`), Anyway Config would automatically add a corresponding predicate method. For example:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
attr_config :user, :password, debug: false
|
148
|
+
|
149
|
+
MyCoolGem::Config.new.debug? #=> false
|
150
|
+
MyCoolGem::Config.new(debug: true).debug? #=> true
|
151
|
+
```
|
152
|
+
|
153
|
+
**NOTE**: since v2.0 accessors created by `attr_config` are not `attr_accessor`, i.e. they do not populate instance variables. If you used instance variables before to override readers, you must switch to using `super` or `values` store:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
class MyConfig < Anyway::Config
|
157
|
+
attr_config :host, :port, :url, :meta
|
158
|
+
|
159
|
+
# override writer to handle type coercion
|
160
|
+
def meta=(val)
|
161
|
+
super JSON.parse(val)
|
162
|
+
end
|
163
|
+
|
164
|
+
# or override reader to handle missing values
|
165
|
+
def url
|
166
|
+
super || (self.url = "#{host}:#{port}")
|
167
|
+
end
|
168
|
+
|
169
|
+
# untill v2.1, it will still be possible to read instance variables,
|
170
|
+
# i.e. the following code would also work
|
171
|
+
def url
|
172
|
+
@url ||= "#{host}:#{port}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
We recommend to add a feature check and support both v1.x and v2.0 in gems for the time being:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
# Check for the class method added in 2.0, e.g., `.on_load`
|
181
|
+
if respond_to?(:on_load)
|
182
|
+
def url
|
183
|
+
super || (self.url = "#{host}:#{port}")
|
184
|
+
end
|
185
|
+
else
|
186
|
+
def url
|
187
|
+
@url ||= "#{host}:#{port}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
```
|
191
|
+
|
192
|
+
#### Config name
|
193
|
+
|
194
|
+
Anyway Config relies on the notion of _config name_ to populate data.
|
195
|
+
|
196
|
+
By default, Anyway Config uses the config class name to infer the config name using the following rules:
|
197
|
+
|
198
|
+
- if the class name has a form of `<Module>::Config` then use the module name (`SomeModule::Config => "somemodule"`)
|
199
|
+
- if the class name has a form of `<Something>Config` then use the class name prefix (`SomeConfig => "some"`)
|
200
|
+
|
201
|
+
**NOTE:** in both cases, the config name is a **downcased** module/class prefix, not underscored.
|
202
|
+
|
203
|
+
You can also specify the config name explicitly (it's required in cases when your class name doesn't match any of the patterns above):
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
module MyCoolGem
|
207
|
+
class Config < Anyway::Config
|
208
|
+
config_name :cool
|
209
|
+
attr_config user: "root", password: "root", host: "localhost", options: {}
|
210
|
+
end
|
211
|
+
end
|
212
|
+
```
|
213
|
+
|
214
|
+
#### Customize env variable names prefix
|
215
|
+
|
216
|
+
By default, Anyway Config uses upper-cased config name as a prefix for env variable names (e.g.
|
217
|
+
`config_name :my_app` will result to parsing `MY_APP_` prefix).
|
218
|
+
|
219
|
+
You can set env prefix explicitly:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
module MyCoolGem
|
223
|
+
class Config < Anyway::Config
|
224
|
+
config_name :cool_gem
|
225
|
+
env_prefix :really_cool # now variables, starting wih `REALLY_COOL_`, will be parsed
|
226
|
+
attr_config user: "root", password: "root", host: "localhost", options: {}
|
227
|
+
end
|
228
|
+
end
|
229
|
+
```
|
230
|
+
|
231
|
+
#### Explicit values
|
232
|
+
|
233
|
+
Sometimes it's useful to set some parameters explicitly during config initialization.
|
234
|
+
You can do that by passing a Hash into `.new` method:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
config = MyCoolGem::Config.new(
|
238
|
+
user: "john",
|
239
|
+
password: "rubyisnotdead"
|
240
|
+
)
|
241
|
+
|
242
|
+
# The value would not be overridden from other sources (such as YML file, env)
|
243
|
+
config.user == "john"
|
244
|
+
```
|
245
|
+
|
246
|
+
#### Reload configuration
|
247
|
+
|
248
|
+
There are `#clear` and `#reload` methods that do exactly what they state.
|
249
|
+
|
250
|
+
**NOTE**: `#reload` also accepts an optional Hash for [explicit values](#explicit-values).
|
251
|
+
|
252
|
+
### Dynamic configuration
|
253
|
+
|
254
|
+
You can also fetch configuration without pre-defined schema:
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
# load data from config/my_app.yml,
|
258
|
+
# credentials.my_app, secrets.my_app (if using Rails), ENV["MY_APP_*"]
|
259
|
+
#
|
260
|
+
# Given MY_APP_VALUE=42
|
261
|
+
config = Anyway::Config.for(:my_app)
|
262
|
+
config["value"] #=> 42
|
263
|
+
|
264
|
+
# you can specify the config file path or env prefix
|
265
|
+
config = Anyway::Config.for(:my_app, config_path: "my_config.yml", env_prefix: "MYAPP")
|
266
|
+
```
|
267
|
+
|
268
|
+
This feature is similar to `Rails.application.config_for` but more powerful:
|
269
|
+
|
270
|
+
| Feature | Rails | Anyway Config |
|
271
|
+
| ------------- |-------------:| -----:|
|
272
|
+
| Load data from `config/app.yml` | ✅ | ✅ |
|
273
|
+
| Load data from `secrets` | ❌ | ✅ |
|
274
|
+
| Load data from `credentials` | ❌ | ✅ |
|
275
|
+
| Load data from environment | ❌ | ✅ |
|
276
|
+
| Load data from [other sources](#data-loaders) | ❌ | ✅ |
|
277
|
+
| Local config files | ❌ | ✅ |
|
278
|
+
| Type coercion | ❌ | ✅ |
|
279
|
+
| [Source tracing](#tracing) | ❌ | ✅ |
|
280
|
+
| Return Hash with indifferent access | ❌ | ✅ |
|
281
|
+
| Support ERB\* within `config/app.yml` | ✅ | ✅ |
|
282
|
+
| Raise if file doesn't exist | ✅ | ❌ |
|
283
|
+
| Works without Rails | 😀 | ✅ |
|
284
|
+
|
285
|
+
\* Make sure that ERB is loaded
|
286
|
+
|
287
|
+
### Validation and callbacks
|
288
|
+
|
289
|
+
Anyway Config provides basic ways of ensuring that the configuration is valid.
|
290
|
+
|
291
|
+
There is a built-in `required` class method to define the list of parameters that must be present in the
|
292
|
+
configuration after loading (where present means non-`nil` and non-empty for strings):
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
class MyConfig < Anyway::Config
|
296
|
+
attr_config :api_key, :api_secret, :debug
|
297
|
+
|
298
|
+
required :api_key, :api_secret
|
299
|
+
end
|
300
|
+
|
301
|
+
MyConfig.new(api_secret: "") #=> raises Anyway::Config::ValidationError
|
302
|
+
```
|
303
|
+
|
304
|
+
`Required` method supports additional `env` parameter which indicates necessity to run validations under specified
|
305
|
+
environments. `Env` parameter could be present in symbol, string, array or hash formats:
|
306
|
+
|
307
|
+
```ruby
|
308
|
+
class EnvConfig < Anyway::Config
|
309
|
+
required :password, env: "production"
|
310
|
+
required :maps_api_key, env: :production
|
311
|
+
required :smtp_host, env: %i[production staging]
|
312
|
+
required :aws_bucket, env: %w[production staging]
|
313
|
+
required :anycable_rpc_host, env: {except: :development}
|
314
|
+
required :anycable_redis_url, env: {except: %i[development test]}
|
315
|
+
required :anycable_broadcast_adapter, env: {except: %w[development test]}
|
316
|
+
end
|
317
|
+
```
|
318
|
+
|
319
|
+
If your current `Anyway::Settings.current_environment` is mismatch keys that specified
|
320
|
+
`Anyway::Config::ValidationError` error will be raised.
|
321
|
+
|
322
|
+
If you need more complex validation or need to manipulate with config state right after it has been loaded, you can use _on load callbacks_ and `#raise_validation_error` method:
|
323
|
+
|
324
|
+
```ruby
|
325
|
+
class MyConfig < Anyway::Config
|
326
|
+
attr_config :api_key, :api_secret, :mode
|
327
|
+
|
328
|
+
# on_load macro accepts symbol method names
|
329
|
+
on_load :ensure_mode_is_valid
|
330
|
+
|
331
|
+
# or block
|
332
|
+
on_load do
|
333
|
+
# the block is evaluated in the context of the config
|
334
|
+
raise_validation_error("API key and/or secret could be blank") if
|
335
|
+
api_key.blank? || api_secret.blank?
|
336
|
+
end
|
337
|
+
|
338
|
+
def ensure_mode_is_valid
|
339
|
+
unless %w[production test].include?(mode)
|
340
|
+
raise_validation_error "Unknown mode; #{mode}"
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
```
|
345
|
+
|
346
|
+
## Using with Rails
|
347
|
+
|
348
|
+
**NOTE:** version 2.x supports Rails >= 5.0; for Rails 4.x use version 1.x of the gem.
|
349
|
+
|
350
|
+
We recommend going through [Data population](#data-population) and [Organizing configs](#organizing-configs) sections first,
|
351
|
+
and then use [Rails generators](#generators) to make your application Anyway Config-ready.
|
352
|
+
|
353
|
+
### Data population
|
354
|
+
|
355
|
+
Your config is filled up with values from the following sources (ordered by priority from low to high):
|
356
|
+
|
357
|
+
1) **YAML configuration files**: `RAILS_ROOT/config/my_cool_gem.yml`.
|
358
|
+
|
359
|
+
Rails environment is used as the namespace (required); supports `ERB`:
|
360
|
+
|
361
|
+
```yml
|
362
|
+
test:
|
363
|
+
host: localhost
|
364
|
+
port: 3002
|
365
|
+
|
366
|
+
development:
|
367
|
+
host: localhost
|
368
|
+
port: 3000
|
369
|
+
```
|
370
|
+
|
371
|
+
**NOTE:** You can override the environment name for configuration files via the `ANYWAY_ENV` environment variable or by setting it explicitly in the code: `Anyway::Settings.current_environment = "some_other_env"`.
|
372
|
+
|
373
|
+
### Multi-env configuration
|
374
|
+
|
375
|
+
_⚡️ This feature will be turned on by default in the future releases. You can turn it on now via `config.anyway_config.future.use :unwrap_known_environments`._
|
376
|
+
|
377
|
+
If the YML does not have keys that are one of the "known" Rails environments (development, production, test)—the same configuration will be available in all environments, similar to non-Rails behavior:
|
378
|
+
|
379
|
+
```yml
|
380
|
+
host: localhost
|
381
|
+
port: 3002
|
382
|
+
# These values will be active in all environments
|
383
|
+
```
|
384
|
+
|
385
|
+
To extend the list of known environments, use the setting in the relevant part of your Rails code:
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
Rails.application.config.anyway_config.known_environments << "staging"
|
389
|
+
```
|
390
|
+
|
391
|
+
If your YML defines at least a single "environmental" top-level, you _have_ to separate all your settings per-environment. You can't mix and match:
|
392
|
+
|
393
|
+
```yml
|
394
|
+
staging:
|
395
|
+
host: localhost # This value will be loaded when Rails.env.staging? is true
|
396
|
+
|
397
|
+
port: 3002 # This value will not be loaded at all
|
398
|
+
```
|
399
|
+
|
400
|
+
To provide default values you can use YAML anchors, but they do not deep-merge settings, so Anyway Config provides a way to define a special top-level key for default values like this:
|
401
|
+
|
402
|
+
```ruby
|
403
|
+
config.anyway_config.default_environmental_key = "default"
|
404
|
+
```
|
405
|
+
|
406
|
+
After that, Anyway Config will start reading settings under the `"default"` key and then merge environmental settings into them.
|
407
|
+
|
408
|
+
```yml
|
409
|
+
default:
|
410
|
+
server: # This values will be loaded in all environments by default
|
411
|
+
host: localhost
|
412
|
+
port: 3002
|
413
|
+
|
414
|
+
staging:
|
415
|
+
server:
|
416
|
+
host: staging.example.com # This value will override the defaults when Rails.env.staging? is true
|
417
|
+
# port will be set to the value from the defaults — 3002
|
418
|
+
```
|
419
|
+
|
420
|
+
You can specify the lookup path for YAML files in one of the following ways:
|
421
|
+
|
422
|
+
- By setting `config.anyway_config.default_config_path` to a target directory path:
|
423
|
+
|
424
|
+
```ruby
|
425
|
+
config.anyway_config.default_config_path = "/etc/configs"
|
426
|
+
config.anyway_config.default_config_path = Rails.root.join("etc", "configs")
|
427
|
+
```
|
428
|
+
|
429
|
+
- By setting `config.anyway_config.default_config_path` to a Proc, which accepts a config name and returns the path:
|
430
|
+
|
431
|
+
```ruby
|
432
|
+
config.anyway_config.default_config_path = ->(name) { Rails.root.join("data", "configs", "#{name}.yml") }
|
433
|
+
```
|
434
|
+
|
435
|
+
- By overriding a specific config YML file path via the `<NAME>_CONF` env variable, e.g., `MYCOOLGEM_CONF=path/to/cool.yml`
|
436
|
+
|
437
|
+
2) (Rails <7.1) **Rails secrets**: `Rails.application.secrets.my_cool_gem` (if `secrets.yml` present).
|
438
|
+
|
439
|
+
```yml
|
440
|
+
# config/secrets.yml
|
441
|
+
development:
|
442
|
+
my_cool_gem:
|
443
|
+
port: 4444
|
444
|
+
```
|
445
|
+
|
446
|
+
**NOTE:** If you want to use secrets with Rails 7.1 (still supported, but deprecated) you must add the corresponding loader manually: `Anyway.loaders.insert_after :yml, :secrets, Anyway::Rails::Loaders::Secrets`.
|
447
|
+
|
448
|
+
3) **Rails credentials**: `Rails.application.credentials.my_cool_gem` (if supported):
|
449
|
+
|
450
|
+
```yml
|
451
|
+
my_cool_gem:
|
452
|
+
host: secret.host
|
453
|
+
```
|
454
|
+
|
455
|
+
**NOTE:** You can backport Rails 6 per-environment credentials to Rails 5.2 app using [this patch](https://gist.github.com/palkan/e27e4885535ff25753aefce45378e0cb).
|
456
|
+
|
457
|
+
4) **Environment variables**: `ENV['MYCOOLGEM_*']`.
|
458
|
+
|
459
|
+
See [environment variables](#environment-variables).
|
460
|
+
|
461
|
+
### Organizing configs
|
462
|
+
|
463
|
+
You can store application-level config classes in `app/configs` folder just like any other Rails entities.
|
464
|
+
|
465
|
+
However, in that case you won't be able to use them during the application initialization (i.e., in `config/**/*.rb` files).
|
466
|
+
|
467
|
+
Since that's a pretty common scenario, we provide a way to do that via a custom autoloader for `config/configs` folder.
|
468
|
+
That means, that you can put your configuration classes into `config/configs` folder, use them anywhere in your code without explicitly requiring them.
|
469
|
+
|
470
|
+
Consider an example: setting the Action Mailer hostname for Heroku review apps.
|
471
|
+
|
472
|
+
We have the following config to fetch the Heroku provided [metadata](https://devcenter.heroku.com/articles/dyno-metadata):
|
473
|
+
|
474
|
+
```ruby
|
475
|
+
# This data is provided by Heroku Dyno Metadadata add-on.
|
476
|
+
class HerokuConfig < Anyway::Config
|
477
|
+
attr_config :app_id, :app_name,
|
478
|
+
:dyno_id, :release_version,
|
479
|
+
:slug_commit
|
480
|
+
|
481
|
+
def hostname
|
482
|
+
"#{app_name}.herokuapp.com"
|
483
|
+
end
|
484
|
+
end
|
485
|
+
```
|
486
|
+
|
487
|
+
Then in `config/application.rb` you can do the following:
|
488
|
+
|
489
|
+
```ruby
|
490
|
+
config.action_mailer.default_url_options = {host: HerokuConfig.new.hostname}
|
491
|
+
```
|
492
|
+
|
493
|
+
You can configure the configs folder path:
|
494
|
+
|
495
|
+
```ruby
|
496
|
+
# The path must be relative to Rails root
|
497
|
+
config.anyway_config.autoload_static_config_path = "path/to/configs"
|
498
|
+
```
|
499
|
+
|
500
|
+
**NOTE:** Configs loaded from the `autoload_static_config_path` are **not reloaded in development**. We call them _static_. So, it makes sense to keep only configs necessary for initialization in this folder. Other configs, _dynamic_, could be stored in `app/configs`.
|
501
|
+
Or you can store everything in `app/configs` by setting `config.anyway_config.autoload_static_config_path = "app/configs"`.
|
502
|
+
|
503
|
+
**NOTE 2**: Since _static_ configs are loaded before initializers, it's not possible to use custom inflection Rules (usually defined in `config/initializers/inflections.rb`) to resolve constant names from files. If you rely on custom inflection rules (see, for example, [#81](https://github.com/palkan/anyway_config/issues/81)), we recommend configuration Rails inflector before initialization as well:
|
504
|
+
|
505
|
+
```ruby
|
506
|
+
# config/application.rb
|
507
|
+
|
508
|
+
# ...
|
509
|
+
|
510
|
+
require_relative "initializers/inflections"
|
511
|
+
|
512
|
+
module SomeApp
|
513
|
+
class Application < Rails::Application
|
514
|
+
# ...
|
515
|
+
end
|
516
|
+
end
|
517
|
+
```
|
518
|
+
|
519
|
+
### Generators
|
520
|
+
|
521
|
+
Anyway Config provides Rails generators to create new config classes:
|
522
|
+
|
523
|
+
- `rails g anyway:install`—creates an `ApplicationConfig` class (the base class for all config classes) and updates `.gitignore`
|
524
|
+
|
525
|
+
You can specify the static configs path via the `--configs-path` option:
|
526
|
+
|
527
|
+
```sh
|
528
|
+
rails g anyway:install --configs-path=config/settings
|
529
|
+
|
530
|
+
# or to keep everything in app/configs
|
531
|
+
rails g anyway:install --configs-path=app/configs
|
532
|
+
```
|
533
|
+
|
534
|
+
- `rails g anyway:config <name> param1 param2 ...`—creates a named configuration class and optionally the corresponding YAML file; creates `application_config.rb` is missing.
|
535
|
+
|
536
|
+
The generator command for the Heroku example above would be:
|
537
|
+
|
538
|
+
```sh
|
539
|
+
$ rails g anyway:config heroku app_id app_name dyno_id release_version slug_commit
|
540
|
+
|
541
|
+
generate anyway:install
|
542
|
+
rails generate anyway:install
|
543
|
+
create config/configs/application_config.rb
|
544
|
+
append .gitignore
|
545
|
+
create config/configs/heroku_config.rb
|
546
|
+
Would you like to generate a heroku.yml file? (Y/n) n
|
547
|
+
```
|
548
|
+
|
549
|
+
You can also specify the `--app` option to put the newly created class into `app/configs` folder.
|
550
|
+
Alternatively, you can call `rails g anyway:app_config name param1 param2 ...`.
|
551
|
+
|
552
|
+
**NOTE:** The generated `ApplicationConfig` class uses a singleton pattern along with `delegate_missing_to` to re-use the same instance across the application. However, the delegation can lead to unexpected behaviour and break Anyway Config internals if you have attributes named as `Anyway::Config` class methods. See [#120](https://github.com/palkan/anyway_config/issues/120).
|
553
|
+
|
554
|
+
### Loading Anyway Config before Rails
|
555
|
+
|
556
|
+
Anyway Config activates Rails-specific features automatically on the gem load only if Rails has been already required (we check for the `Rails::VERSION` constant presence). However, in some cases you may want to use Anyway Config before Rails initialization (e.g., in `config/puma.rb` when starting a Puma web server).
|
557
|
+
|
558
|
+
By default, Anyway Config sets up a hook (via TracePoint API) and waits for Rails to be loaded to require the Rails extensions (`require "anyway/rails"`). In case you load Rails after Anyway Config, you will see a warning telling you about that. Note that config classes loaded before Rails are not populated from Rails-specific data sources (e.g., credentials).
|
559
|
+
|
560
|
+
You can disable the warning by setting `Anyway::Rails.disable_postponed_load_warning = true` in your application. Also, you can disable the _hook_ completely by calling `Anyway::Rails.tracer.disable`.
|
561
|
+
|
562
|
+
## Using with Ruby
|
563
|
+
|
564
|
+
The default data loading mechanism for non-Rails applications is the following (ordered by priority from low to high):
|
565
|
+
|
566
|
+
1) **YAML configuration files**: `./config/<config-name>.yml`.
|
567
|
+
|
568
|
+
In pure Ruby apps, we also can load data under specific _environments_ (`test`, `development`, `production`, etc.).
|
569
|
+
If you want to enable this feature you must specify `Anyway::Settings.current_environment` variable for load config under specific environment.
|
570
|
+
|
571
|
+
```ruby
|
572
|
+
Anyway::Settings.current_environment = "development"
|
573
|
+
```
|
574
|
+
|
575
|
+
You can also specify the `ANYWAY_ENV=development` environment variable to set the current environment for configuration.
|
576
|
+
|
577
|
+
YAML files should be in this format:
|
578
|
+
|
579
|
+
```yml
|
580
|
+
development:
|
581
|
+
host: localhost
|
582
|
+
port: 3000
|
583
|
+
```
|
584
|
+
|
585
|
+
If `Anyway::Settings.current_environment` is missed we assume that the YAML contains values for a single environment:
|
586
|
+
|
587
|
+
```yml
|
588
|
+
host: localhost
|
589
|
+
port: 3000
|
590
|
+
```
|
591
|
+
|
592
|
+
`ERB` is supported if `erb` is loaded (thus, you need to call `require "erb"` somewhere before loading configuration).
|
593
|
+
|
594
|
+
You can specify the lookup path for YAML files in one of the following ways:
|
595
|
+
|
596
|
+
- By setting `Anyway::Settings.default_config_path` to a target directory path:
|
597
|
+
|
598
|
+
```ruby
|
599
|
+
Anyway::Settings.default_config_path = "/etc/configs"
|
600
|
+
```
|
601
|
+
|
602
|
+
- By setting `Anyway::Settings.default_config_path` to a Proc, which accepts a config name and returns the path:
|
603
|
+
|
604
|
+
```ruby
|
605
|
+
Anyway::Settings.default_config_path = ->(name) { Rails.root.join("data", "configs", "#{name}.yml") }
|
606
|
+
```
|
607
|
+
|
608
|
+
- By overriding a specific config YML file path via the `<NAME>_CONF` env variable, e.g., `MYCOOLGEM_CONF=path/to/cool.yml`
|
609
|
+
|
610
|
+
2) **Environment variables**: `ENV['MYCOOLGEM_*']`.
|
611
|
+
|
612
|
+
See [environment variables](#environment-variables).
|
613
|
+
|
614
|
+
## Environment variables
|
615
|
+
|
616
|
+
Environmental variables for your config should start with your config name, upper-cased.
|
617
|
+
|
618
|
+
For example, if your config name is "mycoolgem", then the env var "MYCOOLGEM_PASSWORD" is used as `config.password`.
|
619
|
+
|
620
|
+
By default, environment variables are automatically type cast (rules are case-insensitive):
|
621
|
+
|
622
|
+
- `"true"`, `"t"`, `"yes"` and `"y"` to `true`;
|
623
|
+
- `"false"`, `"f"`, `"no"` and `"n"` to `false`;
|
624
|
+
- `"nil"` and `"null"` to `nil` (do you really need it?);
|
625
|
+
- `"123"` to `123` and `"3.14"` to `3.14`.
|
626
|
+
|
627
|
+
Type coercion can be [customized or disabled](#type-coercion).
|
628
|
+
|
629
|
+
*Anyway Config* supports nested (_hashed_) env variables—just separate keys with double-underscore.
|
630
|
+
|
631
|
+
For example, "MYCOOLGEM_OPTIONS__VERBOSE" is parsed as `config.options["verbose"]`.
|
632
|
+
|
633
|
+
Array values are also supported:
|
634
|
+
|
635
|
+
```ruby
|
636
|
+
# Suppose ENV["MYCOOLGEM_IDS"] = '1,2,3'
|
637
|
+
config.ids #=> [1,2,3]
|
638
|
+
```
|
639
|
+
|
640
|
+
If you want to provide a text-like env variable which contains commas then wrap it into quotes:
|
641
|
+
|
642
|
+
```ruby
|
643
|
+
MYCOOLGEM = "Nif-Nif, Naf-Naf and Nouf-Nouf"
|
644
|
+
```
|
645
|
+
|
646
|
+
## Type coercion
|
647
|
+
|
648
|
+
> 🆕 v2.2.0
|
649
|
+
|
650
|
+
You can define custom type coercion rules to convert string data to config values. To do that, use `.coerce_types` method:
|
651
|
+
|
652
|
+
```ruby
|
653
|
+
class CoolConfig < Anyway::Config
|
654
|
+
config_name :cool
|
655
|
+
attr_config port: 8080,
|
656
|
+
host: "localhost",
|
657
|
+
user: {name: "admin", password: "admin"}
|
658
|
+
|
659
|
+
coerce_types port: :string, user: {dob: :date}
|
660
|
+
end
|
661
|
+
|
662
|
+
ENV["COOL_USER__DOB"] = "1989-07-01"
|
663
|
+
|
664
|
+
config = CoolConfig.new
|
665
|
+
config.port == "8080" # Even though we defined the default value as int, it's converted into a string
|
666
|
+
config.user["dob"] == Date.new(1989, 7, 1) #=> true
|
667
|
+
```
|
668
|
+
|
669
|
+
Type coercion is especially useful to deal with array values:
|
670
|
+
|
671
|
+
```ruby
|
672
|
+
# To define an array type, provide a hash with two keys:
|
673
|
+
# - type — elements type
|
674
|
+
# - array: true — mark the parameter as array
|
675
|
+
coerce_types list: {type: :string, array: true}
|
676
|
+
```
|
677
|
+
|
678
|
+
You can use `type: nil` in case you don't want to coerce values, just convert a value into an array:
|
679
|
+
|
680
|
+
```ruby
|
681
|
+
# From AnyCable config (sentinels could be represented via strings or hashes)
|
682
|
+
coerce_types redis_sentinels: {type: nil, array: true}
|
683
|
+
```
|
684
|
+
|
685
|
+
It's also could be useful to explicitly define non-array types (to avoid confusion):
|
686
|
+
|
687
|
+
```ruby
|
688
|
+
coerce_types non_list: :string
|
689
|
+
```
|
690
|
+
|
691
|
+
Finally, it's possible to disable auto-casting for a particular config completely:
|
692
|
+
|
693
|
+
```ruby
|
694
|
+
class CoolConfig < Anyway::Config
|
695
|
+
attr_config port: 8080,
|
696
|
+
host: "localhost",
|
697
|
+
user: {name: "admin", password: "admin"}
|
698
|
+
|
699
|
+
disable_auto_cast!
|
700
|
+
end
|
701
|
+
|
702
|
+
ENV["COOL_PORT"] = "443"
|
703
|
+
|
704
|
+
CoolConfig.new.port == "443" #=> true
|
705
|
+
```
|
706
|
+
|
707
|
+
**IMPORTANT**: Values provided explicitly (via attribute writers) are not coerced. Coercion is only happening during the load phase.
|
708
|
+
|
709
|
+
The following types are supported out-of-the-box: `:string`, `:integer`, `:float`, `:date`, `:datetime`, `:uri`, `:boolean`.
|
710
|
+
|
711
|
+
You can use custom deserializers by passing a callable object instead of a type name:
|
712
|
+
|
713
|
+
```ruby
|
714
|
+
COLOR_TO_HEX = lambda do |raw|
|
715
|
+
case raw
|
716
|
+
when "red"
|
717
|
+
"#ff0000"
|
718
|
+
when "green"
|
719
|
+
"#00ff00"
|
720
|
+
when "blue"
|
721
|
+
"#0000ff"
|
722
|
+
end
|
723
|
+
end
|
724
|
+
|
725
|
+
class CoolConfig < Anyway::Config
|
726
|
+
attr_config :color
|
727
|
+
|
728
|
+
coerce_types color: COLOR_TO_HEX
|
729
|
+
end
|
730
|
+
|
731
|
+
CoolConfig.new({color: "red"}).color #=> "#ff0000"
|
732
|
+
```
|
733
|
+
|
734
|
+
## Local files
|
735
|
+
|
736
|
+
It's useful to have a personal, user-specific configuration in development, which extends the project-wide one.
|
737
|
+
|
738
|
+
We support this by looking at _local_ files when loading the configuration data:
|
739
|
+
|
740
|
+
- `<config_name>.local.yml` files (next to\* the _global_ `<config_name>.yml`)
|
741
|
+
- `config/credentials/local.yml.enc` (for Rails >= 6, generate it via `rails credentials:edit --environment local`).
|
742
|
+
|
743
|
+
\* If the YAML config path is not a default one (i.e., set via `<CONFIG_NAME>_CONF`), we look up the local
|
744
|
+
config at this location, too.
|
745
|
+
|
746
|
+
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"`).
|
747
|
+
|
748
|
+
**NOTE:** in Rails apps you can use `Rails.application.configuration.anyway_config.use_local_files`.
|
749
|
+
|
750
|
+
Don't forget to add `*.local.yml` (and `config/credentials/local.*`) to your `.gitignore`.
|
751
|
+
|
752
|
+
**NOTE:** local YAML configs for a Rails app must be environment-free (i.e., you shouldn't have top-level `development:` key).
|
753
|
+
|
754
|
+
## Data loaders
|
755
|
+
|
756
|
+
### Doppler integration
|
757
|
+
|
758
|
+
Anyway Config can pull configuration data from [Doppler](https://www.doppler.com/). All you need is to specify the `DOPPLER_TOKEN` environment variable with the **service token**, associated with the specific content (read more about [service tokens](https://docs.doppler.com/docs/service-tokens)).
|
759
|
+
|
760
|
+
You can also configure Doppler loader manually if needed:
|
761
|
+
|
762
|
+
```ruby
|
763
|
+
# Add loader
|
764
|
+
Anyway.loaders.append :Doppler, Anyway::Loaders::Doppler
|
765
|
+
|
766
|
+
# Configure API URL and token (defaults are shown)
|
767
|
+
Anyway::Loaders::Doppler.download_url = "https://api.doppler.com/v3/configs/config/secrets/download"
|
768
|
+
Anyway::Loaders::Doppler.token = ENV["DOPPLER_TOKEN"]
|
769
|
+
```
|
770
|
+
|
771
|
+
**NOTE:** You can opt-out from Doppler loader by specifying the`ANYWAY_CONFIG_DISABLE_DOPPLER=true` env var (in case you have the `DOPPLER_TOKEN` env var, but don't want to use it with Anyway Config).
|
772
|
+
|
773
|
+
### EJSON support
|
774
|
+
|
775
|
+
Anyway Config allows you to keep your configuration also in encrypted `.ejson` files. More information
|
776
|
+
about EJSON format you can read [here](https://github.com/Shopify/ejson).
|
777
|
+
|
778
|
+
Configuration will be loaded only if you have `ejson` executable in your PATH. Easiest way to do this - install `ejson` as a gem into project:
|
779
|
+
|
780
|
+
```ruby
|
781
|
+
# Gemfile
|
782
|
+
gem "ejson"
|
783
|
+
```
|
784
|
+
|
785
|
+
Loading order of configuration is next:
|
786
|
+
|
787
|
+
- `config/secrets.local.ejson` (see [Local files](#local-files) for more information)
|
788
|
+
- `config/<environment>/secrets.ejson` (if you have any multi-environment setup, e.g Rails environments)
|
789
|
+
- `config/secrets.ejson`
|
790
|
+
|
791
|
+
Example of `config/secrets.ejson` file content for your `MyConfig`:
|
792
|
+
|
793
|
+
```json
|
794
|
+
{
|
795
|
+
"_public_key": "0843d33f0eee994adc66b939fe4ef569e4c97db84e238ff581934ee599e19d1a",
|
796
|
+
"my":
|
797
|
+
{
|
798
|
+
"_username": "root",
|
799
|
+
"password": "EJ[1:IC1d347GkxLXdZ0KrjGaY+ljlsK1BmK7CobFt6iOLgE=:Z55OYS1+On0xEBvxUaIOdv/mE2r6lp44:T7bE5hkAbazBnnH6M8bfVcv8TOQJAgUDQffEgw==]"
|
800
|
+
}
|
801
|
+
}
|
802
|
+
```
|
803
|
+
|
804
|
+
To debug any problems with loading configurations from `.ejson` files you can directly call `ejson decrypt`:
|
805
|
+
|
806
|
+
```sh
|
807
|
+
ejson decrypt config/secrets.ejson
|
808
|
+
```
|
809
|
+
|
810
|
+
You can customize the JSON namespace under which a loader searches for configuration via `loader_options`:
|
811
|
+
|
812
|
+
```ruby
|
813
|
+
class MyConfig < Anyway::Config
|
814
|
+
# To look under the key "foo" instead of the default key of "my"
|
815
|
+
loader_options ejson_namespace: "foo"
|
816
|
+
|
817
|
+
# Or to disable namespacing entirely, and instead search in the root object
|
818
|
+
loader_options ejson_namespace: false
|
819
|
+
end
|
820
|
+
```
|
821
|
+
|
822
|
+
### Custom loaders
|
823
|
+
|
824
|
+
You can provide your own data loaders or change the existing ones using the Loaders API (which is very similar to Rack middleware builder):
|
825
|
+
|
826
|
+
```ruby
|
827
|
+
# remove env loader => do not load params from ENV
|
828
|
+
Anyway.loaders.delete :env
|
829
|
+
|
830
|
+
# add custom loader before :env (it's better to keep the ENV loader the last one)
|
831
|
+
Anyway.loaders.insert_before :env, :my_loader, MyLoader
|
832
|
+
```
|
833
|
+
|
834
|
+
Loader is a _callable_ Ruby object (module/class responding to `.call` or lambda/proc), which `call` method
|
835
|
+
accepts the following keyword arguments:
|
836
|
+
|
837
|
+
```ruby
|
838
|
+
def call(
|
839
|
+
name:, # config name
|
840
|
+
env_prefix:, # prefix for env vars if any
|
841
|
+
config_path:, # path to YML config
|
842
|
+
local:, # true|false, whether to load local configuration
|
843
|
+
**options # custom options can be passed via Anyway::Config.loader_options example: "custom", option: "blah"
|
844
|
+
)
|
845
|
+
#=> must return Hash with configuration data
|
846
|
+
end
|
847
|
+
```
|
848
|
+
|
849
|
+
You can use `Anyway::Loaders::Base` as a base class for your loader and define a `#call` method.
|
850
|
+
For example, the [Chamber](https://github.com/thekompanee/chamber) loader could be written as follows:
|
851
|
+
|
852
|
+
```ruby
|
853
|
+
class ChamberConfigLoader < Base
|
854
|
+
def call(name:, **_opts)
|
855
|
+
Chamber.to_hash[name] || {}
|
856
|
+
rescue Chamber::Errors::DecryptionFailure => e
|
857
|
+
warn "Couldn't decrypt Chamber settings: #{e.message}"
|
858
|
+
{}
|
859
|
+
end
|
860
|
+
end
|
861
|
+
|
862
|
+
# Don't forget to register it
|
863
|
+
Anyway.loaders.insert_before :env, :chamber, ChamberConfigLoader
|
864
|
+
```
|
865
|
+
|
866
|
+
In order to support [source tracing](#tracing), you need to wrap the resulting Hash via the `#trace!` method with metadata:
|
867
|
+
|
868
|
+
```ruby
|
869
|
+
def call(name:, **_opts)
|
870
|
+
trace!(:chamber) do
|
871
|
+
Chamber.to_hash[name] || {}
|
872
|
+
rescue Chamber::Errors::DecryptionFailure => e
|
873
|
+
warn "Couldn't decrypt Chamber settings: #{e.message}"
|
874
|
+
{}
|
875
|
+
end
|
876
|
+
end
|
877
|
+
```
|
878
|
+
|
879
|
+
## Tracing
|
880
|
+
|
881
|
+
Since Anyway Config loads data from multiple source, it could be useful to know where a particular value came from.
|
882
|
+
|
883
|
+
Each `Anyway::Config` instance contains _tracing information_ which you can access via `#to_source_trace` method:
|
884
|
+
|
885
|
+
```ruby
|
886
|
+
conf = ExampleConfig.new
|
887
|
+
conf.to_source_trace
|
888
|
+
|
889
|
+
# returns the following hash
|
890
|
+
{
|
891
|
+
"host" => {value: "test.host", source: {type: :yml, path: "config/example.yml"}},
|
892
|
+
"user" => {
|
893
|
+
"name" => {value: "john", source: {type: :env, key: "EXAMPLE_USER__NAME"}},
|
894
|
+
"password" => {value: "root", source: {type: :credentials, store: "config/credentials/production.enc.yml"}}
|
895
|
+
},
|
896
|
+
"port" => {value: 9292, source: {type: :defaults}}
|
897
|
+
}
|
898
|
+
|
899
|
+
# if you change the value manually in your code,
|
900
|
+
# that would be reflected in the trace
|
901
|
+
|
902
|
+
conf.host = "anyway.host"
|
903
|
+
conf.to_source_trace["host"]
|
904
|
+
#=> {type: :user, called_from: "/path/to/caller.rb:15"}
|
905
|
+
```
|
906
|
+
|
907
|
+
You can disable tracing functionality by setting `Anyway::Settings.tracing_enabled = false` or `config.anyway_config.tracing_enabled = false` in Rails.
|
908
|
+
|
909
|
+
### Pretty print
|
910
|
+
|
911
|
+
You can use `pp` to print a formatted information about the config including the sources trace.
|
912
|
+
|
913
|
+
Example:
|
914
|
+
|
915
|
+
```ruby
|
916
|
+
pp CoolConfig.new
|
917
|
+
|
918
|
+
# #<CoolConfig
|
919
|
+
# config_name="cool"
|
920
|
+
# env_prefix="COOL"
|
921
|
+
# values:
|
922
|
+
# port => 3334 (type=load),
|
923
|
+
# host => "test.host" (type=yml path=./config/cool.yml),
|
924
|
+
# user =>
|
925
|
+
# name => "john" (type=env key=COOL_USER__NAME),
|
926
|
+
# password => "root" (type=yml path=./config/cool.yml)>
|
927
|
+
```
|
928
|
+
|
929
|
+
## Pattern matching
|
930
|
+
|
931
|
+
You can use config instances in Ruby 2.7+ pattern matching:
|
932
|
+
|
933
|
+
```ruby
|
934
|
+
case AWSConfig.new
|
935
|
+
in bucket:, region: "eu-west-1"
|
936
|
+
setup_eu_storage(bucket)
|
937
|
+
in bucket:, region: "us-east-1"
|
938
|
+
setup_us_storage(bucket)
|
939
|
+
end
|
940
|
+
```
|
941
|
+
|
942
|
+
If the attribute wasn't populated, the key won't be returned for pattern matching, i.e. you can do something line:
|
943
|
+
|
944
|
+
```ruby
|
945
|
+
aws_configured =
|
946
|
+
case AWSConfig.new
|
947
|
+
in access_key_id:, secret_access_key:
|
948
|
+
true
|
949
|
+
else
|
950
|
+
false
|
951
|
+
end
|
952
|
+
```
|
953
|
+
|
954
|
+
## Test helpers
|
955
|
+
|
956
|
+
We provide the `with_env` test helper to test code in the context of the specified environment variables values:
|
957
|
+
|
958
|
+
```ruby
|
959
|
+
describe HerokuConfig, type: :config do
|
960
|
+
subject { described_class.new }
|
961
|
+
|
962
|
+
specify do
|
963
|
+
# Ensure that the env vars are set to the specified
|
964
|
+
# values within the block and reset to the previous values
|
965
|
+
# outside of it.
|
966
|
+
with_env(
|
967
|
+
"HEROKU_APP_NAME" => "kin-web-staging",
|
968
|
+
"HEROKU_APP_ID" => "abc123",
|
969
|
+
"HEROKU_DYNO_ID" => "ddyy",
|
970
|
+
"HEROKU_RELEASE_VERSION" => "v0",
|
971
|
+
"HEROKU_SLUG_COMMIT" => "3e4d5a"
|
972
|
+
) do
|
973
|
+
is_expected.to have_attributes(
|
974
|
+
app_name: "kin-web-staging",
|
975
|
+
app_id: "abc123",
|
976
|
+
dyno_id: "ddyy",
|
977
|
+
release_version: "v0",
|
978
|
+
slug_commit: "3e4d5a"
|
979
|
+
)
|
980
|
+
end
|
981
|
+
end
|
982
|
+
end
|
983
|
+
```
|
984
|
+
|
985
|
+
If you want to delete the env var, pass `nil` as the value.
|
986
|
+
|
987
|
+
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/...`.
|
988
|
+
|
989
|
+
You can add it manually by requiring `"anyway/testing/helpers"` and including the `Anyway::Testing::Helpers` module (into RSpec configuration or Minitest test class).
|
990
|
+
|
991
|
+
## OptionParser integration
|
992
|
+
|
993
|
+
It's possible to use config as option parser (e.g., for CLI apps/libraries). It uses
|
994
|
+
[`optparse`](https://ruby-doc.org/stdlib-2.5.1/libdoc/optparse/rdoc/OptionParser.html) under the hood.
|
995
|
+
|
996
|
+
Example usage:
|
997
|
+
|
998
|
+
```ruby
|
999
|
+
class MyConfig < Anyway::Config
|
1000
|
+
attr_config :host, :log_level, :concurrency, :debug, server_args: {}
|
1001
|
+
|
1002
|
+
# specify which options shouldn't be handled by option parser
|
1003
|
+
ignore_options :server_args
|
1004
|
+
|
1005
|
+
# provide description for options
|
1006
|
+
describe_options(
|
1007
|
+
concurrency: "number of threads to use"
|
1008
|
+
)
|
1009
|
+
|
1010
|
+
# mark some options as flag
|
1011
|
+
flag_options :debug
|
1012
|
+
|
1013
|
+
# extend an option parser object (i.e. add banner or version/help handlers)
|
1014
|
+
extend_options do |parser, config|
|
1015
|
+
parser.banner = "mycli [options]"
|
1016
|
+
|
1017
|
+
parser.on("--server-args VALUE") do |value|
|
1018
|
+
config.server_args = JSON.parse(value)
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
parser.on_tail "-h", "--help" do
|
1022
|
+
puts parser
|
1023
|
+
end
|
1024
|
+
end
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
config = MyConfig.new
|
1028
|
+
|
1029
|
+
config.parse_options!(%w[--host localhost --port 3333 --log-level debug])
|
1030
|
+
|
1031
|
+
config.host # => "localhost"
|
1032
|
+
config.port # => 3333
|
1033
|
+
config.log_level # => "debug"
|
1034
|
+
|
1035
|
+
# Get the instance of OptionParser
|
1036
|
+
config.option_parser
|
1037
|
+
```
|
1038
|
+
|
1039
|
+
**NOTE:** values are automatically type cast using the same rules as for [environment variables](#environment-variables).
|
1040
|
+
If you want to specify the type explicitly, you can do that using `describe_options`:
|
1041
|
+
|
1042
|
+
```ruby
|
1043
|
+
describe_options(
|
1044
|
+
# In this case, you should specify a hash with `type`
|
1045
|
+
# and (optionally) `desc` keys
|
1046
|
+
concurrency: {
|
1047
|
+
desc: "number of threads to use",
|
1048
|
+
type: String
|
1049
|
+
}
|
1050
|
+
)
|
1051
|
+
```
|
1052
|
+
|
1053
|
+
## RBS support
|
1054
|
+
|
1055
|
+
Anyway Config comes with Ruby type signatures (RBS).
|
1056
|
+
|
1057
|
+
To use them with Steep, add the following your `Steepfile`:
|
1058
|
+
|
1059
|
+
```ruby
|
1060
|
+
library "pathname"
|
1061
|
+
library "optparse"
|
1062
|
+
library "anyway_config"
|
1063
|
+
```
|
1064
|
+
|
1065
|
+
We also provide an API to generate a type signature for your config class:
|
1066
|
+
|
1067
|
+
```ruby
|
1068
|
+
class MyGem::Config < Anyway::Config
|
1069
|
+
attr_config :host, port: 8080, tags: [], debug: false
|
1070
|
+
|
1071
|
+
coerce_types host: :string, port: :integer,
|
1072
|
+
tags: {type: :string, array: true}
|
1073
|
+
|
1074
|
+
required :host
|
1075
|
+
end
|
1076
|
+
```
|
1077
|
+
|
1078
|
+
Then calling `MyGem::Config.to_rbs` will return the following signature:
|
1079
|
+
|
1080
|
+
```rbs
|
1081
|
+
module MyGem
|
1082
|
+
interface _Config
|
1083
|
+
def host: () -> String
|
1084
|
+
def host=: (String) -> void
|
1085
|
+
def port: () -> String?
|
1086
|
+
def port=: (String) -> void
|
1087
|
+
def tags: () -> Array[String]?
|
1088
|
+
def tags=: (Array[String]) -> void
|
1089
|
+
def debug: () -> bool
|
1090
|
+
def debug?: () -> bool
|
1091
|
+
def debug=: (bool) -> void
|
1092
|
+
end
|
1093
|
+
|
1094
|
+
class Config < Anyway::Config
|
1095
|
+
include _Config
|
1096
|
+
end
|
1097
|
+
end
|
1098
|
+
```
|
1099
|
+
|
1100
|
+
### Handling `on_load`
|
1101
|
+
|
1102
|
+
When we use `on_load` callback with a block, we switch the context (via `instance_eval`), and we need to provide type hints for the type checker. Here is an example:
|
1103
|
+
|
1104
|
+
```ruby
|
1105
|
+
class MyConfig < Anyway::Config
|
1106
|
+
on_load do
|
1107
|
+
# @type self : MyConfig
|
1108
|
+
raise_validation_error("host is invalid") if host.start_with?("localhost")
|
1109
|
+
end
|
1110
|
+
end
|
1111
|
+
```
|
1112
|
+
|
1113
|
+
Yeah, a lot of annotations 😞 Welcome to the type-safe world!
|
1114
|
+
|
1115
|
+
## Contributing
|
1116
|
+
|
1117
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/palkan/anyway_config](https://github.com/palkan/anyway_config).
|
1118
|
+
|
1119
|
+
## License
|
1120
|
+
|
1121
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|