anyway_config 2.8.1 → 2.9.1
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 +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +61 -2
- data/lib/.rbnext/2.7/anyway/loaders/yaml.rb +2 -0
- data/lib/.rbnext/2.7/anyway/settings.rb +12 -0
- data/lib/.rbnext/3.0/anyway/config.rb +51 -3
- data/lib/.rbnext/3.0/anyway/loaders/base.rb +5 -1
- data/lib/.rbnext/3.0/anyway/loaders/ejson.rb +94 -0
- data/lib/.rbnext/3.0/anyway/loaders/yaml.rb +2 -0
- data/lib/.rbnext/3.0/anyway/rails/loaders/credentials.rb +64 -0
- data/lib/.rbnext/3.0/anyway/rails/loaders/secrets.rb +42 -0
- data/lib/.rbnext/3.1/anyway/config.rb +51 -3
- data/lib/.rbnext/3.1/anyway/loaders/base.rb +5 -1
- data/lib/.rbnext/3.2/anyway/config.rb +51 -3
- data/lib/.rbnext/3.2/anyway/loaders/base.rb +5 -1
- data/lib/.rbnext/3.4/anyway/config.rb +51 -3
- data/lib/anyway/config.rb +51 -3
- data/lib/anyway/loaders/base.rb +5 -1
- data/lib/anyway/loaders/ejson.rb +6 -1
- data/lib/anyway/loaders/yaml.rb +2 -0
- data/lib/anyway/rails/loaders/credentials.rb +2 -0
- data/lib/anyway/rails/loaders/secrets.rb +2 -0
- data/lib/anyway/rails.rb +1 -1
- data/lib/anyway/settings.rb +12 -0
- data/lib/anyway/version.rb +1 -1
- data/sig/anyway_config.rbs +12 -1
- data/skills/anyway-config/SKILL.md +223 -0
- data/skills/anyway-config/references/docs.md +30 -0
- data/skills/anyway-config/references/organizing.md +341 -0
- data/skills/anyway-config/references/testing.md +139 -0
- metadata +11 -2
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: anyway-config
|
|
3
|
+
description: Add, organize, refactor and test application configuration in Rails apps with Anyway Config (`Anyway::Config`). Use when a change needs a new setting, secret, API key, token, URL, env variable, feature toggle or third-party integration credentials; when code reads `ENV[...]`, `Rails.application.credentials`, `config_for` or branches on `Rails.env.production?`; when the user asks where configuration should live or how to stub it in tests.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Anyway Config: configuration in Rails apps
|
|
7
|
+
|
|
8
|
+
A config class is the only code that knows where a value comes from.
|
|
9
|
+
Application code depends on the config object. It never reads `ENV`,
|
|
10
|
+
credentials or YAML directly, and it does not branch on `Rails.env`.
|
|
11
|
+
|
|
12
|
+
This file is the decision procedure. Read these files for details:
|
|
13
|
+
|
|
14
|
+
- `references/organizing.md`: class placement, sources, boot integration, examples.
|
|
15
|
+
- `references/testing.md`: test the config class and the code that uses it.
|
|
16
|
+
- `references/docs.md`: the full map of README sections (§10 has the short version). Read the README
|
|
17
|
+
(or the gem source, `bundle info anyway_config --path`) for the API. Do not guess it.
|
|
18
|
+
|
|
19
|
+
## 1. Look at the project first
|
|
20
|
+
|
|
21
|
+
1. Find the existing configs: `config/configs/`, `app/configs/`, and `*/config.rb` next to services.
|
|
22
|
+
2. Read `ApplicationConfig`. Most apps make it a singleton (`AppConfig.host`).
|
|
23
|
+
3. Read the `config.anyway_config.*` lines in `config/application.rb`.
|
|
24
|
+
Note `autoload_static_config_path`, `default_environmental_key` and `enforce_config_schema`.
|
|
25
|
+
4. Read `.gitignore`. Check that `config/*.local.yml` and `config/credentials/local.*` are ignored.
|
|
26
|
+
5. Follow the conventions that you find. This skill gives the defaults for a new project.
|
|
27
|
+
|
|
28
|
+
If the gem is not set up yet, run `bin/rails g anyway:install`.
|
|
29
|
+
|
|
30
|
+
## 2. Decide where the new value goes
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Is the value a config parameter (it changes between deployments or environments)?
|
|
34
|
+
├── no → a constant in the code. Stop.
|
|
35
|
+
└── yes
|
|
36
|
+
├── Is it infrastructure that Rails or a tool reads itself
|
|
37
|
+
│ (PORT, RAILS_MAX_THREADS, DATABASE_URL, RAILS_LOG_LEVEL)?
|
|
38
|
+
│ └── yes → keep it where the tool expects it (puma.rb, database.yml). Stop.
|
|
39
|
+
├── Does an existing config class own this concern (same service, same subsystem)?
|
|
40
|
+
│ └── yes → add an attribute to that class. Go to §4.
|
|
41
|
+
└── no → create a new config class. Go to §3.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Group parameters by concern: one class per integration or subsystem
|
|
45
|
+
(`StripeConfig`, `SMTPConfig`, `FlyDeploy::Config`).
|
|
46
|
+
Do not make one class per parameter. Do not put everything into `AppConfig`.
|
|
47
|
+
`AppConfig` holds app-wide values only: host, port, asset host, global toggles.
|
|
48
|
+
|
|
49
|
+
## 3. Create a config class
|
|
50
|
+
|
|
51
|
+
Choose the location:
|
|
52
|
+
|
|
53
|
+
| The config is used… | Put it in | Notes |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| at boot (`config/application.rb`, initializers, routes, `puma.rb`), or app-wide | `config/configs/<name>_config.rb` | Static: not reloaded in development. |
|
|
56
|
+
| by one module, service or pack only | next to that code, e.g. `app/services/fly_deploy/config.rb` | Set `config_name` explicitly. Not available before autoloading. |
|
|
57
|
+
| at runtime only, and the project keeps such configs in `app/configs/` | `app/configs/<name>_config.rb` | Reloaded in development. |
|
|
58
|
+
|
|
59
|
+
Generate a class with `bin/rails g anyway:config <name> param1 param2`.
|
|
60
|
+
Add `--app` for `app/configs/`. For an in-place class, write it by hand:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
# app/services/fly_deploy/config.rb
|
|
64
|
+
module FlyDeploy
|
|
65
|
+
class Config < ApplicationConfig
|
|
66
|
+
# Without it, the name is "flydeploy" (downcased, not underscored)
|
|
67
|
+
config_name :fly_deploy
|
|
68
|
+
|
|
69
|
+
attr_config :api_token, :org_slug,
|
|
70
|
+
rest_api_url: "https://api.machines.dev/v1",
|
|
71
|
+
region: "iad"
|
|
72
|
+
|
|
73
|
+
required :api_token, env: :production
|
|
74
|
+
|
|
75
|
+
def fake_api? = rest_api_url.include?("fake_fly")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The config name sets all source keys: `config/fly_deploy.yml`,
|
|
81
|
+
`credentials.fly_deploy`, and `FLY_DEPLOY_*` env variables.
|
|
82
|
+
If a gem or tool already uses that name (for example, Litestream reads `LITESTREAM_*`),
|
|
83
|
+
choose a different name: `config_name :litestream_rails`.
|
|
84
|
+
|
|
85
|
+
## 4. Choose the source for each value
|
|
86
|
+
|
|
87
|
+
| Value | Source |
|
|
88
|
+
|---|---|
|
|
89
|
+
| Non-secret default, same everywhere | `attr_config name: default` |
|
|
90
|
+
| Non-secret value that differs per environment | `config/<name>.yml`, per-environment keys |
|
|
91
|
+
| Fake value for development and test (stub API URL, test token) | `development:` / `test:` keys in `config/<name>.yml` |
|
|
92
|
+
| Secret (API key, password, signing secret) | Rails credentials under `<name>:`, or ENV `<NAME>_<ATTR>` from the deployment platform |
|
|
93
|
+
| Personal developer override | `config/<name>.local.yml` or `config/credentials/local.yml.enc` (git-ignored) |
|
|
94
|
+
| Value that an operator changes without a deploy | ENV `<NAME>_<ATTR>` |
|
|
95
|
+
|
|
96
|
+
Load priority, from low to high: attribute defaults → YAML (plus `.local.yml`) →
|
|
97
|
+
credentials → ENV → explicit `Config.new(key: value)`.
|
|
98
|
+
|
|
99
|
+
Do not write `<%= ENV["STRIPE_API_KEY"] %>` in YAML. The ENV loader already maps
|
|
100
|
+
`STRIPE_API_KEY` to `StripeConfig#api_key`.
|
|
101
|
+
|
|
102
|
+
## 5. Write the attributes
|
|
103
|
+
|
|
104
|
+
- Give a default to each attribute that has a safe value. A `true`/`false` default adds a `?` predicate.
|
|
105
|
+
- Mark mandatory values with `required`. Limit it with `env:` (`env: :production`,
|
|
106
|
+
`env: {except: %i[development test]}`) when development can run without the value.
|
|
107
|
+
- Add `coerce_types` when ENV must produce an array (`{type: :string, array: true}`)
|
|
108
|
+
or must stay a string (`oauth_scope: :string`, a numeric-looking token).
|
|
109
|
+
- Put logic on the config, not in callers:
|
|
110
|
+
- Presence predicate: `def configured? = api_key.present? && secret.present?`
|
|
111
|
+
- Derived value: `def asset_host = super || "#{ssl? ? "https" : "http"}://#{host}"`
|
|
112
|
+
- Adapter for a library: `def to_settings = {address:, port:, user_name: username}.compact`
|
|
113
|
+
- Add a comment to an attribute when its purpose or format is not obvious.
|
|
114
|
+
- Do not use reserved names (`values`, `to_h`, `clear`, `config_name`, …).
|
|
115
|
+
With the singleton `ApplicationConfig`, also avoid names of class methods (README, issue #120).
|
|
116
|
+
- If the project type-checks with RBS (`sig/`, `Steepfile`), add a signature for the new config.
|
|
117
|
+
`MyConfig.to_rbs` generates it from `attr_config`, `coerce_types` and `required`.
|
|
118
|
+
|
|
119
|
+
## 6. Use the config
|
|
120
|
+
|
|
121
|
+
- Call the config from application code: `StripeConfig.secret_key`, `AppConfig.analytics_enabled?`.
|
|
122
|
+
- Pass the config as a default argument when a class depends on it. Tests can then inject another one:
|
|
123
|
+
`def initialize(config: FlyDeploy::Config)`.
|
|
124
|
+
- Turn integrations on by the presence of their settings, not by the environment:
|
|
125
|
+
`provider :github, ... if GithubConfig.oauth_configured?`.
|
|
126
|
+
- Configure Rails and gems from configs in `config/application.rb` and initializers.
|
|
127
|
+
See `references/organizing.md` §4.
|
|
128
|
+
- A gem that reads only `ENV`: copy the value once in its initializer,
|
|
129
|
+
`ENV["STRIPE_PRIVATE_KEY"] ||= StripeConfig.secret_key`.
|
|
130
|
+
|
|
131
|
+
## 7. Replace direct reads
|
|
132
|
+
|
|
133
|
+
| You see | Replace with |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `ENV["FOO_API_KEY"]` in app code | `FooConfig.api_key` |
|
|
136
|
+
| `ENV.fetch("FOO_ENABLED", "false") == "true"` | `attr_config enabled: false` → `FooConfig.enabled?` |
|
|
137
|
+
| `Rails.application.credentials.foo[:key]` | `FooConfig.key` (same `foo:` credentials, no change in data) |
|
|
138
|
+
| `Rails.application.config_for(:foo)` | `FooConfig` (same `config/foo.yml`) |
|
|
139
|
+
| `if Rails.env.production?` to select a value | a per-environment YAML value |
|
|
140
|
+
| `if Rails.env.production?` to turn a feature on | a boolean attribute or `configured?` predicate |
|
|
141
|
+
| `ENV["X"] \|\| "default"` | `attr_config x: "default"` |
|
|
142
|
+
|
|
143
|
+
Keep `Rails.env` checks that are about the framework itself (for example, test-only view helpers).
|
|
144
|
+
|
|
145
|
+
## 8. Test
|
|
146
|
+
|
|
147
|
+
Read `references/testing.md`. In short:
|
|
148
|
+
|
|
149
|
+
- Put the test values in the `test:` section of `config/<name>.yml`. CI then needs no secrets.
|
|
150
|
+
- Unit-test the config class with `described_class.new(key: value)` and `with_env`.
|
|
151
|
+
- For code that uses a config, inject a config instance or stub the singleton method.
|
|
152
|
+
- Do not set `ENV` directly in tests.
|
|
153
|
+
|
|
154
|
+
## 9. Debug a wrong value
|
|
155
|
+
|
|
156
|
+
When a config value is not what you expect, do not guess. Ask the config where the value came from:
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
bin/rails runner 'pp StripeConfig.new'
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
#<StripeConfig
|
|
164
|
+
config_name="stripe"
|
|
165
|
+
env_prefix="STRIPE"
|
|
166
|
+
values:
|
|
167
|
+
currency => "eur" (type=env key=STRIPE_CURRENCY),
|
|
168
|
+
api_key => "sk_test_..." (type=credentials store=config/credentials/development.yml.enc),
|
|
169
|
+
webhook_secret => "whsec_..." (type=yml path=./config/stripe.yml)>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`StripeConfig.new.to_source_trace` returns the same data as a hash.
|
|
173
|
+
Use `.new`: the singleton keeps the values it loaded when the process started.
|
|
174
|
+
The output shows secrets in clear text. Do not copy it into commits, issues or reports.
|
|
175
|
+
|
|
176
|
+
If the trace does not explain the value, check these in order:
|
|
177
|
+
|
|
178
|
+
1. Priority: defaults → YAML (+ `.local.yml`) → credentials (+ local) → ENV → explicit values.
|
|
179
|
+
A higher source wins, so a stale ENV variable hides a YAML change.
|
|
180
|
+
2. Names: `env_prefix` and `config_name` in the output. `FlyDeploy::Config` without
|
|
181
|
+
`config_name` reads `FLYDEPLOY_*` and `config/flydeploy.yml`.
|
|
182
|
+
3. Environment: YAML with environment keys needs a section for the current `Rails.env`.
|
|
183
|
+
Local files load in development only.
|
|
184
|
+
4. Typos: a misspelled key in YAML or credentials is ignored silently.
|
|
185
|
+
Turn on `enforce_config_schema` to raise on unknown keys.
|
|
186
|
+
5. Types: ENV values are auto-cast (`"0123"` → `123`). The trace shows the raw string,
|
|
187
|
+
the reader returns the cast value. Fix with `coerce_types key: :string`.
|
|
188
|
+
|
|
189
|
+
## 10. Gem features at a glance
|
|
190
|
+
|
|
191
|
+
The README documents each feature. `references/docs.md` maps tasks to README sections.
|
|
192
|
+
|
|
193
|
+
| Feature | Use it for |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `attr_config`, `required`, `on_load`, `coerce_types` | Schema, validation, types |
|
|
196
|
+
| Local files (`*.local.yml`, local credentials) | Personal development overrides |
|
|
197
|
+
| `enforce_config_schema` (2.9+) | Raise or warn on unknown keys in YAML and credentials |
|
|
198
|
+
| Source tracing (`pp`, `to_source_trace`) | Find where a value came from |
|
|
199
|
+
| Generators (`anyway:install`, `anyway:config`) | New base class and config classes |
|
|
200
|
+
| `with_env` test helper | Set ENV in a block, with the Anyway ENV cache reset |
|
|
201
|
+
| Doppler, EJSON, custom loaders | More sources; ENV stays the last loader |
|
|
202
|
+
| `Anyway::Config.for(:name)` | Read a config without a class (prefer a class) |
|
|
203
|
+
| `to_rbs`, bundled signatures | Type-check configs with RBS and Steep |
|
|
204
|
+
| OptionParser integration | Build CLI options from a config (plain Ruby tools) |
|
|
205
|
+
|
|
206
|
+
## 11. Do not
|
|
207
|
+
|
|
208
|
+
- Do not read `ENV` or credentials outside config classes, except for infrastructure (§2).
|
|
209
|
+
- Do not commit secrets to `config/<name>.yml`. Development and test fakes are fine.
|
|
210
|
+
- Do not add a config class for one value that another config already covers.
|
|
211
|
+
- Do not mutate a config at runtime to change behavior. Pass a new instance instead.
|
|
212
|
+
- Do not reference a config from `app/configs/` or an in-place class during boot.
|
|
213
|
+
Move it to `config/configs/`.
|
|
214
|
+
- Do not add an env-specific `if` to a config method when a YAML value can express the difference.
|
|
215
|
+
|
|
216
|
+
## 12. Report
|
|
217
|
+
|
|
218
|
+
When you finish, list:
|
|
219
|
+
|
|
220
|
+
- each config class you added or changed, and its location;
|
|
221
|
+
- each new parameter with its source (YAML, credentials, ENV) and its ENV variable name;
|
|
222
|
+
- the values that operators must set before a deployment (new required secrets);
|
|
223
|
+
- the direct `ENV` / credentials / `Rails.env` reads that you removed.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Where to find the API
|
|
2
|
+
|
|
3
|
+
The README is the API reference: https://github.com/palkan/anyway_config#readme.
|
|
4
|
+
The installed copy is at `$(bundle info anyway_config --path)/README.md`.
|
|
5
|
+
Read the section that you need. The source is in `lib/anyway/` of the same directory.
|
|
6
|
+
|
|
7
|
+
| Task | README section |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Declare attributes, defaults, boolean predicates, override readers with `super` | [Configuration classes](https://github.com/palkan/anyway_config#configuration-classes) |
|
|
10
|
+
| Change the config name or the ENV prefix (`config_name`, `env_prefix`) | [Config name](https://github.com/palkan/anyway_config#config-name), [Customize env variable names prefix](https://github.com/palkan/anyway_config#customize-env-variable-names-prefix) |
|
|
11
|
+
| Pass explicit values, reload a config | [Explicit values](https://github.com/palkan/anyway_config#explicit-values), [Reload configuration](https://github.com/palkan/anyway_config#reload-configuration) |
|
|
12
|
+
| Load a config without a class (`Anyway::Config.for(:name)`) | [Dynamic configuration](https://github.com/palkan/anyway_config#dynamic-configuration) |
|
|
13
|
+
| `required` (with `env:`), `on_load`, `raise_validation_error`, `ANYWAY_SUPPRESS_VALIDATIONS` | [Validation and callbacks](https://github.com/palkan/anyway_config#validation-and-callbacks) |
|
|
14
|
+
| Detect typos in YAML and credentials (`enforce_config_schema`, `rescue_unknown_attributes_with`) | [Enforcing config schema](https://github.com/palkan/anyway_config#enforcing-config-schema) |
|
|
15
|
+
| Source priority in Rails, YAML lookup path, `<NAME>_CONF`, credentials | [Data population](https://github.com/palkan/anyway_config#data-population) |
|
|
16
|
+
| `default:` key, environment-free YAML, `known_environments` | [Multi-env configuration](https://github.com/palkan/anyway_config#multi-env-configuration) |
|
|
17
|
+
| `config/configs` vs `app/configs`, static configs, inflections | [Organizing configs](https://github.com/palkan/anyway_config#organizing-configs) |
|
|
18
|
+
| `anyway:install`, `anyway:config`, `anyway:app_config` generators | [Generators](https://github.com/palkan/anyway_config#generators) |
|
|
19
|
+
| Use configs in `config/puma.rb` or before Rails loads | [Loading Anyway Config before Rails](https://github.com/palkan/anyway_config#loading-anyway-config-before-rails) |
|
|
20
|
+
| Plain Ruby apps and gems | [Using with Ruby](https://github.com/palkan/anyway_config#using-with-ruby) |
|
|
21
|
+
| ENV names, nested keys (`__`), auto-casting, arrays | [Environment variables](https://github.com/palkan/anyway_config#environment-variables) |
|
|
22
|
+
| `coerce_types`, built-in types, custom deserializers, `disable_auto_cast!` | [Type coercion](https://github.com/palkan/anyway_config#type-coercion) |
|
|
23
|
+
| `*.local.yml`, local credentials | [Local files](https://github.com/palkan/anyway_config#local-files) |
|
|
24
|
+
| Doppler, EJSON, custom loaders, loader order | [Data loaders](https://github.com/palkan/anyway_config#data-loaders) |
|
|
25
|
+
| Find where a value came from (`to_source_trace`, `pp config`) | [Tracing](https://github.com/palkan/anyway_config#tracing) |
|
|
26
|
+
| Pattern matching on configs | [Pattern matching](https://github.com/palkan/anyway_config#pattern-matching) |
|
|
27
|
+
| `with_env` and other test helpers | [Test helpers](https://github.com/palkan/anyway_config#test-helpers) |
|
|
28
|
+
| Build a CLI from a config (`OptionParser`) | [OptionParser integration](https://github.com/palkan/anyway_config#optionparser-integration) |
|
|
29
|
+
| RBS signatures for config classes | [RBS support](https://github.com/palkan/anyway_config#rbs-support) |
|
|
30
|
+
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# Organizing configuration in a Rails app
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
1. Layers
|
|
6
|
+
2. File layout
|
|
7
|
+
3. Base class and access
|
|
8
|
+
4. Boot integration
|
|
9
|
+
5. Sources in practice
|
|
10
|
+
6. In-place config classes
|
|
11
|
+
7. Shared config names
|
|
12
|
+
8. Recommended settings
|
|
13
|
+
|
|
14
|
+
## 1. Layers
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Application code (models, services, controllers, initializers)
|
|
18
|
+
│ depends on
|
|
19
|
+
▼
|
|
20
|
+
Config classes (schema: names, defaults, types, validation, predicates)
|
|
21
|
+
│ loaded from
|
|
22
|
+
▼
|
|
23
|
+
Sources (YAML, credentials, ENV, Doppler, EJSON, custom loaders)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Sources are infrastructure. You can move a value from YAML to credentials to ENV,
|
|
27
|
+
and the application code does not change.
|
|
28
|
+
|
|
29
|
+
## 2. File layout
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
config/
|
|
33
|
+
├── application.rb # assigns configs to Rails.application.config
|
|
34
|
+
├── configs/ # static configs: available at boot
|
|
35
|
+
│ ├── application_config.rb
|
|
36
|
+
│ ├── app_config.rb
|
|
37
|
+
│ ├── smtp_config.rb
|
|
38
|
+
│ └── stripe_config.rb
|
|
39
|
+
├── app.yml # non-secret values, per environment
|
|
40
|
+
├── smtp.yml
|
|
41
|
+
├── app.local.yml # personal overrides (git-ignored)
|
|
42
|
+
└── credentials/
|
|
43
|
+
├── production.yml.enc # secrets
|
|
44
|
+
├── development.yml.enc
|
|
45
|
+
└── local.yml.enc # personal secrets (git-ignored)
|
|
46
|
+
app/services/fly_deploy/
|
|
47
|
+
├── client.rb
|
|
48
|
+
└── config.rb # in-place config: FlyDeploy::Config
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`config/configs/` is the default value of `config.anyway_config.autoload_static_config_path`.
|
|
52
|
+
Anyway Config autoloads it before the initializers run. Rails does not reload it in development.
|
|
53
|
+
|
|
54
|
+
`app/configs/` is a normal autoload path. Its classes reload in development,
|
|
55
|
+
but they are not available in `config/application.rb` or before the app finishes initialization.
|
|
56
|
+
|
|
57
|
+
Set `autoload_static_config_path = "app/configs"` to keep all configs in `app/configs/`.
|
|
58
|
+
|
|
59
|
+
### Custom inflections
|
|
60
|
+
|
|
61
|
+
Static configs load before `config/initializers/inflections.rb`.
|
|
62
|
+
If a class name needs an acronym (`SMTPConfig`), require the inflections first (README, issue #81):
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
# config/application.rb
|
|
66
|
+
require_relative "boot"
|
|
67
|
+
require "rails/all"
|
|
68
|
+
require_relative "initializers/inflections"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 3. Base class and access
|
|
72
|
+
|
|
73
|
+
The generator creates a singleton base class:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
class ApplicationConfig < Anyway::Config
|
|
77
|
+
class << self
|
|
78
|
+
delegate_missing_to :instance
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def instance
|
|
83
|
+
@instance ||= new
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Callers write `StripeConfig.secret_key`. The instance loads once per process.
|
|
90
|
+
|
|
91
|
+
- Use the class methods in application code.
|
|
92
|
+
- Use `StripeConfig.new` when you need a fresh read (a script, a test, an initializer that must not
|
|
93
|
+
keep the singleton).
|
|
94
|
+
- Use `StripeConfig.new(key: value)` to build a config with explicit values. Other sources do not
|
|
95
|
+
override explicit values.
|
|
96
|
+
|
|
97
|
+
## 4. Boot integration
|
|
98
|
+
|
|
99
|
+
Expose the configs that Rails itself needs on `Rails.application.config`.
|
|
100
|
+
Then configure the framework from them in one place:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
# config/application.rb
|
|
104
|
+
module MyApp
|
|
105
|
+
class Application < Rails::Application
|
|
106
|
+
config.app = AppConfig
|
|
107
|
+
config.mailer = MailerConfig
|
|
108
|
+
config.smtp = SMTPConfig
|
|
109
|
+
|
|
110
|
+
if config.app.host
|
|
111
|
+
config.action_controller.default_url_options =
|
|
112
|
+
config.action_mailer.default_url_options = {
|
|
113
|
+
host: config.app.host,
|
|
114
|
+
port: config.app.port,
|
|
115
|
+
protocol: config.app.ssl? ? "https" : "http"
|
|
116
|
+
}
|
|
117
|
+
config.action_controller.asset_host = config.app.asset_host
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
config.action_mailer.default_options = config.mailer.to_default_options
|
|
121
|
+
|
|
122
|
+
if config.smtp.configured?
|
|
123
|
+
config.action_mailer.delivery_method = :smtp
|
|
124
|
+
config.action_mailer.smtp_settings = config.smtp.to_settings
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The config class owns the shape (`to_settings`, `to_default_options`).
|
|
131
|
+
`config/application.rb` only connects it to Rails.
|
|
132
|
+
|
|
133
|
+
### Initializers
|
|
134
|
+
|
|
135
|
+
Turn an integration on when its settings are present. Do not check the environment:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
# config/initializers/omniauth.rb
|
|
139
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
|
140
|
+
if GithubConfig.oauth_configured?
|
|
141
|
+
provider :github, GithubConfig.oauth_key, GithubConfig.oauth_secret,
|
|
142
|
+
scope: GithubConfig.oauth_scope
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
# config/initializers/litestream.rb
|
|
149
|
+
Rails.application.configure do
|
|
150
|
+
litestream = LitestreamConfig.new
|
|
151
|
+
next unless litestream.configured?
|
|
152
|
+
|
|
153
|
+
config.litestream.replica_bucket = litestream.bucket
|
|
154
|
+
config.litestream.replica_key_id = litestream.access_key_id
|
|
155
|
+
config.litestream.replica_access_key = litestream.secret_access_key
|
|
156
|
+
end
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Gems that read only ENV
|
|
160
|
+
|
|
161
|
+
Copy the value from the config once, in the gem's initializer:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
# config/initializers/pay.rb
|
|
165
|
+
# Pay reads Stripe keys from ENV, but we keep them in StripeConfig
|
|
166
|
+
ENV["STRIPE_PRIVATE_KEY"] ||= StripeConfig.secret_key
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Routes
|
|
170
|
+
|
|
171
|
+
Configs can gate routes, too: `mount Litestream::Engine, at: "/litestream" if LitestreamConfig.configured?`.
|
|
172
|
+
|
|
173
|
+
## 5. Sources in practice
|
|
174
|
+
|
|
175
|
+
### YAML: non-secret values per environment
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
# config/app.yml
|
|
179
|
+
default: &default
|
|
180
|
+
host: localhost
|
|
181
|
+
port: 3000
|
|
182
|
+
|
|
183
|
+
development:
|
|
184
|
+
<<: *default
|
|
185
|
+
|
|
186
|
+
test:
|
|
187
|
+
<<: *default
|
|
188
|
+
port: 3002
|
|
189
|
+
|
|
190
|
+
production:
|
|
191
|
+
host: example.com
|
|
192
|
+
port: 443
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
- With `config.anyway_config.default_environmental_key = "default"`, Anyway Config deep-merges
|
|
196
|
+
`default:` into each environment. Then you do not need YAML anchors.
|
|
197
|
+
- With `config.anyway_config.future.use :unwrap_known_environments`, a file without environment keys
|
|
198
|
+
applies to all environments.
|
|
199
|
+
- A section can be absent. For example, `smtp.yml` can define `production:` only.
|
|
200
|
+
- Fake values for development and test belong here: a stub API URL, `api_token: "test-token"`.
|
|
201
|
+
- A production section that omits a secret makes `required` fail fast if the secret is missing
|
|
202
|
+
from credentials and ENV.
|
|
203
|
+
|
|
204
|
+
### Credentials: secrets
|
|
205
|
+
|
|
206
|
+
```yaml
|
|
207
|
+
# bin/rails credentials:edit --environment production
|
|
208
|
+
stripe:
|
|
209
|
+
secret_key: sk_live_...
|
|
210
|
+
signing_secret: whsec_...
|
|
211
|
+
smtp:
|
|
212
|
+
username: ...
|
|
213
|
+
password: ...
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The top-level key is the config name. The `stripe:` keys map to `StripeConfig` attributes.
|
|
217
|
+
|
|
218
|
+
### ENV: deployment-time values
|
|
219
|
+
|
|
220
|
+
`STRIPE_SECRET_KEY` sets `StripeConfig#secret_key`. ENV has the highest priority
|
|
221
|
+
of the standard sources, so an operator can override any value without a deploy.
|
|
222
|
+
|
|
223
|
+
- Nested keys use a double underscore: `APP_SMTP__PORT` → `app.smtp["port"]`.
|
|
224
|
+
- Values are auto-cast: `"true"` → `true`, `"42"` → `42`, `"a,b"` → `["a", "b"]`.
|
|
225
|
+
Use `coerce_types` to change or fix the type.
|
|
226
|
+
|
|
227
|
+
### Local files: personal development overrides
|
|
228
|
+
|
|
229
|
+
`config/app.local.yml` (no environment keys) and `config/credentials/local.yml.enc`
|
|
230
|
+
load in development only. Use them for a personal token, a fork's repository name,
|
|
231
|
+
or a real API in place of the fake one. They must be git-ignored.
|
|
232
|
+
|
|
233
|
+
A local YAML file supports ERB. Use it to switch between a fake and a real service:
|
|
234
|
+
|
|
235
|
+
```yaml
|
|
236
|
+
# config/fly_deploy.local.yml
|
|
237
|
+
<% if ENV["REAL_FLY"] %>
|
|
238
|
+
rest_api_url: https://api.machines.dev/v1
|
|
239
|
+
org_slug: my-org
|
|
240
|
+
<% end %>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Data that stays outside configs
|
|
244
|
+
|
|
245
|
+
- Process and infrastructure settings: `PORT`, `RAILS_MAX_THREADS`, `WEB_CONCURRENCY`,
|
|
246
|
+
`DATABASE_URL`, `RAILS_LOG_LEVEL`. Rails, Puma and the database adapter read them directly.
|
|
247
|
+
- Values that a third-party binary reads from its own ENV variables.
|
|
248
|
+
|
|
249
|
+
## 6. In-place config classes
|
|
250
|
+
|
|
251
|
+
Put a config next to the code that uses it when only one module needs it.
|
|
252
|
+
This keeps a module (or a packwerk pack, or an engine) self-contained.
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
# app/services/fly_deploy/client.rb
|
|
256
|
+
module FlyDeploy
|
|
257
|
+
class Client
|
|
258
|
+
attr_reader :config
|
|
259
|
+
|
|
260
|
+
delegate :api_token, :rest_api_url, to: :config
|
|
261
|
+
|
|
262
|
+
def initialize(config = FlyDeploy::Config)
|
|
263
|
+
@config = config
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
- Set `config_name` explicitly. For `FlyDeploy::Config`, the inferred name is `flydeploy`.
|
|
270
|
+
- The class autoloads with the rest of `app/`. Do not use it in `config/application.rb` outside
|
|
271
|
+
a `config.after_initialize` or `server do` block.
|
|
272
|
+
- If the module grows and boot code needs the config, move the class to `config/configs/`.
|
|
273
|
+
|
|
274
|
+
A config can also be nested inside the model that uses it:
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
class License::GithubAccess
|
|
278
|
+
class Config < ApplicationConfig
|
|
279
|
+
config_name :github
|
|
280
|
+
|
|
281
|
+
attr_config :token
|
|
282
|
+
|
|
283
|
+
def enabled? = token.present?
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def initialize(repo:, config: Config)
|
|
287
|
+
@repo = repo
|
|
288
|
+
@config = config
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def grant(user)
|
|
292
|
+
unless config.enabled?
|
|
293
|
+
Rails.logger.warn("GitHub access is not configured")
|
|
294
|
+
return
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
GithubClient.new(config.token).add_collaborator(@repo, user.github_login)
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## 7. Shared config names
|
|
303
|
+
|
|
304
|
+
Two classes can read the same config name. In the example above, `License::GithubAccess::Config`
|
|
305
|
+
and `GithubConfig` both read `config/github.yml`, `credentials.github` and `GITHUB_*`.
|
|
306
|
+
Each class sees only its own attributes.
|
|
307
|
+
|
|
308
|
+
With `enforce_config_schema` on, each class reports the other class's keys as unknown.
|
|
309
|
+
Allow them with `rescue_unknown_attributes_with`:
|
|
310
|
+
|
|
311
|
+
```ruby
|
|
312
|
+
Anyway::Settings.rescue_unknown_attributes_with = ->(config, _key) do
|
|
313
|
+
config.config_name == "github"
|
|
314
|
+
end
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## 8. Recommended settings
|
|
318
|
+
|
|
319
|
+
Put the settings at the top of the application class, before any config is referenced.
|
|
320
|
+
Settings in `config/environments/*.rb` apply too late for configs that `application.rb` already loaded.
|
|
321
|
+
|
|
322
|
+
```ruby
|
|
323
|
+
# config/application.rb
|
|
324
|
+
module MyApp
|
|
325
|
+
class Application < Rails::Application
|
|
326
|
+
# Deep-merge the `default:` section into each environment
|
|
327
|
+
config.anyway_config.default_environmental_key = "default"
|
|
328
|
+
# Pass all future features in a single `use` call: each call resets the list
|
|
329
|
+
config.anyway_config.future.use :unwrap_known_environments, :reject_unknown_overrides
|
|
330
|
+
# Raise on unknown keys in YAML and credentials (typos). Requires 2.9+. Use :warn to only log
|
|
331
|
+
config.anyway_config.enforce_config_schema = true
|
|
332
|
+
config.anyway_config.known_environments << "staging"
|
|
333
|
+
|
|
334
|
+
config.app = AppConfig
|
|
335
|
+
# ...
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Asset precompilation in CI or Docker runs without secrets. `required` validations skip
|
|
341
|
+
automatically when `SECRET_KEY_BASE_DUMMY` is set. Otherwise, set `ANYWAY_SUPPRESS_VALIDATIONS=1`.
|