anyway_config 2.9.0 → 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 +7 -0
- data/README.md +13 -0
- data/lib/.rbnext/3.0/anyway/config.rb +1 -1
- data/lib/.rbnext/3.1/anyway/config.rb +1 -1
- data/lib/.rbnext/3.2/anyway/config.rb +1 -1
- data/lib/.rbnext/3.4/anyway/config.rb +1 -1
- data/lib/anyway/config.rb +1 -1
- data/lib/anyway/version.rb +1 -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 +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5b4393c06a6078956ade1727f37879c8a38003806b59c8c5a7e9c330708a42fd
|
|
4
|
+
data.tar.gz: 98e3e7d9c0a50e2a18f98a917818ab4fa088314471102d6d6f9e67c08b059670
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c9a771b5fd116be880f4aa3271a5389c6023500452eb8ea98ff8cefc5452ad0f24e3eab97bc02d39daab5231a070e4441efed4a285331d64f7f329073bfbab38
|
|
7
|
+
data.tar.gz: 8af2c0fdc71913929d78ef07f84db3b440cfa0a324834958f3bce0b77dd90e5a9c6995a61c3ba5637008171f6e02d7c7a429f46470b2d5a15984bcc524698737
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## master
|
|
4
4
|
|
|
5
|
+
## 2.9.0 (2026-10-21)
|
|
6
|
+
|
|
7
|
+
- Add an agent skill (`skills/anyway-config`) with guidance on organizing configuration in Rails apps. ([@palkan][])
|
|
8
|
+
|
|
9
|
+
The skill follows the [skills.sh](https://www.skills.sh) layout and installs automatically with [Rails Hyperdrive](https://github.com/rails-hyperdrive/rails-hyperdrive).
|
|
10
|
+
|
|
5
11
|
## 2.9.0 (2026-10-20)
|
|
6
12
|
|
|
7
13
|
- Add a new `enforce_config_schema` to warn or raise on unknown attributes. ([@palkan][])
|
|
@@ -24,6 +30,7 @@ config.anyway_config.enforce_config_schema = true
|
|
|
24
30
|
- Guard against a missing `PATH` environment variable in `Anyway::Utils.which`. ([@SAY-5][])
|
|
25
31
|
|
|
26
32
|
- Do not require optional loader dependencies to speed up loading. ([@ardecvz][])
|
|
33
|
+
- Use `to_s` on values that support it during validation for required attributes. This provides more consistent behaviour for the `:uri` coercer, which will otherwise produce a `#<URI::Generic >` rather than a validation failure. ([@phil-janeapp][])
|
|
27
34
|
|
|
28
35
|
## 2.8.0 (2026-06-24)
|
|
29
36
|
|
data/README.md
CHANGED
|
@@ -34,6 +34,7 @@ For version 1.x see the [1-4-stable branch](https://github.com/palkan/anyway_con
|
|
|
34
34
|
|
|
35
35
|
- [Main concepts](#main-concepts)
|
|
36
36
|
- [Installation](#installation)
|
|
37
|
+
- [Adding agent skills](#adding-agent-skills)
|
|
37
38
|
- [Usage](#usage)
|
|
38
39
|
- [Configuration classes](#configuration-classes)
|
|
39
40
|
- [Dynamic configuration](#dynamic-configuration)
|
|
@@ -105,6 +106,18 @@ Or adding to your project:
|
|
|
105
106
|
gem "anyway_config", "~> 2.0"
|
|
106
107
|
```
|
|
107
108
|
|
|
109
|
+
### Adding agent skills
|
|
110
|
+
|
|
111
|
+
The gem ships a skill for coding agents in `skills/anyway-config`. It guides an agent when it adds or changes configuration in a Rails app: where to put a config class, which source to use for a value, how to replace direct `ENV` reads, and how to test configs. It follows the [skills.sh](https://www.skills.sh) layout, so it works with any agent that reads `SKILL.md`.
|
|
112
|
+
|
|
113
|
+
With [Rails Hyperdrive](https://github.com/rails-hyperdrive/rails-hyperdrive), the skill installs automatically: run `bin/rails hyperdrive:init` the first time, then `bin/rails hyperdrive:sync`.
|
|
114
|
+
|
|
115
|
+
Without it, install the skill from the repository with the [skills.sh](https://www.skills.sh) CLI, or copy `skills/anyway-config` into your agent's skills directory:
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
npx skills add palkan/anyway_config
|
|
119
|
+
```
|
|
120
|
+
|
|
108
121
|
### Supported Ruby versions
|
|
109
122
|
|
|
110
123
|
- Ruby (MRI) >= 2.7.0
|
|
@@ -471,7 +471,7 @@ module Anyway # :nodoc:
|
|
|
471
471
|
|
|
472
472
|
self.class.required_attributes.select do |name|
|
|
473
473
|
val = values.dig(*name.to_s.split(".").map(&:to_sym))
|
|
474
|
-
val.nil? || (val.
|
|
474
|
+
val.nil? || (val.respond_to?(:to_s) && val.to_s.empty?)
|
|
475
475
|
end.then do |missing|
|
|
476
476
|
next if missing.empty?
|
|
477
477
|
raise_validation_error "The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(", ")}"
|
|
@@ -471,7 +471,7 @@ module Anyway # :nodoc:
|
|
|
471
471
|
|
|
472
472
|
self.class.required_attributes.select do |name|
|
|
473
473
|
val = values.dig(*name.to_s.split(".").map(&:to_sym))
|
|
474
|
-
val.nil? || (val.
|
|
474
|
+
val.nil? || (val.respond_to?(:to_s) && val.to_s.empty?)
|
|
475
475
|
end.then do |missing|
|
|
476
476
|
next if missing.empty?
|
|
477
477
|
raise_validation_error "The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(", ")}"
|
|
@@ -471,7 +471,7 @@ module Anyway # :nodoc:
|
|
|
471
471
|
|
|
472
472
|
self.class.required_attributes.select do |name|
|
|
473
473
|
val = values.dig(*name.to_s.split(".").map(&:to_sym))
|
|
474
|
-
val.nil? || (val.
|
|
474
|
+
val.nil? || (val.respond_to?(:to_s) && val.to_s.empty?)
|
|
475
475
|
end.then do |missing|
|
|
476
476
|
next if missing.empty?
|
|
477
477
|
raise_validation_error "The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(", ")}"
|
|
@@ -471,7 +471,7 @@ module Anyway # :nodoc:
|
|
|
471
471
|
|
|
472
472
|
self.class.required_attributes.select do |name|
|
|
473
473
|
val = values.dig(*name.to_s.split(".").map(&:to_sym))
|
|
474
|
-
val.nil? || (val.
|
|
474
|
+
val.nil? || (val.respond_to?(:to_s) && val.to_s.empty?)
|
|
475
475
|
end.then do |missing|
|
|
476
476
|
next if missing.empty?
|
|
477
477
|
raise_validation_error "The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(", ")}"
|
data/lib/anyway/config.rb
CHANGED
|
@@ -471,7 +471,7 @@ module Anyway # :nodoc:
|
|
|
471
471
|
|
|
472
472
|
self.class.required_attributes.select do |name|
|
|
473
473
|
val = values.dig(*name.to_s.split(".").map(&:to_sym))
|
|
474
|
-
val.nil? || (val.
|
|
474
|
+
val.nil? || (val.respond_to?(:to_s) && val.to_s.empty?)
|
|
475
475
|
end.then do |missing|
|
|
476
476
|
next if missing.empty?
|
|
477
477
|
raise_validation_error "The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(", ")}"
|
data/lib/anyway/version.rb
CHANGED
|
@@ -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`.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Testing configuration
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
1. Test values live in YAML
|
|
6
|
+
2. Test the config class
|
|
7
|
+
3. Test code that uses a config
|
|
8
|
+
4. ENV in tests
|
|
9
|
+
5. CI and asset precompilation
|
|
10
|
+
|
|
11
|
+
## 1. Test values live in YAML
|
|
12
|
+
|
|
13
|
+
Put deterministic values in the `test:` section of `config/<name>.yml`:
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
# config/fly_deploy.yml
|
|
17
|
+
test:
|
|
18
|
+
rest_api_url: http://localhost:3002/fake_fly
|
|
19
|
+
api_token: test-token
|
|
20
|
+
org_slug: test-org
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- The test suite then needs no secrets and no credentials key.
|
|
24
|
+
- Integrations that use a `configured?` predicate stay off in test when their secrets are absent.
|
|
25
|
+
- Use obviously fake values (`test-token`, `sk_test_fake`). Never copy real secrets.
|
|
26
|
+
|
|
27
|
+
## 2. Test the config class
|
|
28
|
+
|
|
29
|
+
Test the logic that you added: predicates, derived values, adapters, validations.
|
|
30
|
+
Do not test that Anyway Config reads YAML.
|
|
31
|
+
|
|
32
|
+
Build an instance with explicit values. Other sources do not override them:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
# spec/configs/smtp_config_spec.rb
|
|
36
|
+
RSpec.describe SMTPConfig do
|
|
37
|
+
subject(:config) { described_class.new(address: "smtp.example.com", port: 587, username: "u") }
|
|
38
|
+
|
|
39
|
+
it { is_expected.to be_configured }
|
|
40
|
+
|
|
41
|
+
specify do
|
|
42
|
+
expect(config.to_settings).to include(address: "smtp.example.com", user_name: "u")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context "without address" do
|
|
46
|
+
subject(:config) { described_class.new(address: nil) }
|
|
47
|
+
|
|
48
|
+
it { is_expected.not_to be_configured }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Check the ENV mapping and type coercion with `with_env`.
|
|
54
|
+
The helper is included automatically for specs in `spec/configs/` or with `type: :config`:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
RSpec.describe AnycableProConfig, type: :config do
|
|
58
|
+
it "parses a comma-separated safelist" do
|
|
59
|
+
with_env("ANYCABLE_PRO_GITHUB_SAFELIST" => "alice,bob") do
|
|
60
|
+
expect(described_class.new.github_safelist).to eq(%w[alice bob])
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`with_env` sets the variables, clears the Anyway ENV cache and restores both after the block.
|
|
67
|
+
Pass `nil` to delete a variable.
|
|
68
|
+
Elsewhere, require `"anyway/testing/helpers"` and include `Anyway::Testing::Helpers`
|
|
69
|
+
(this also works for Minitest).
|
|
70
|
+
|
|
71
|
+
Check `required` validations:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
it "requires the magic link key" do
|
|
75
|
+
expect { described_class.new(magic_link_key: "") }
|
|
76
|
+
.to raise_error(Anyway::Config::ValidationError, /magic_link_key/)
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`required ..., env:` checks the environment once, when the class loads.
|
|
81
|
+
A stub in a test cannot change it, so do not test environment-scoped `required` in unit tests.
|
|
82
|
+
|
|
83
|
+
Check an environment-dependent default:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
it "enables analytics in production by default" do
|
|
87
|
+
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("production"))
|
|
88
|
+
|
|
89
|
+
expect(described_class.new(analytics_enabled: nil)).to be_analytics_enabled
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 3. Test code that uses a config
|
|
94
|
+
|
|
95
|
+
### Inject a config
|
|
96
|
+
|
|
97
|
+
If the class takes the config as an argument, pass an instance with explicit values.
|
|
98
|
+
This is the preferred way:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
let(:config) { FlyDeploy::Config.new(api_token: "t", rest_api_url: "http://fake") }
|
|
102
|
+
let(:client) { FlyDeploy::Client.new(config) }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Stub the singleton
|
|
106
|
+
|
|
107
|
+
If the code calls the singleton (`AppConfig.cables_quota`), stub the class method.
|
|
108
|
+
`delegate_missing_to` defines `respond_to_missing?`, so verified partial doubles accept the stub:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
before { allow(AppConfig).to receive(:analytics_enabled?).and_return(true) }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Read expected values from the config instead of hard-coding them:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
expect(account.cables_quota).to eq(AppConfig.cables_quota)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Do not mutate the singleton
|
|
121
|
+
|
|
122
|
+
Do not assign attributes on the shared instance (`AppConfig.cables_quota = 10`).
|
|
123
|
+
The value leaks into other tests. Stub the method or inject an instance.
|
|
124
|
+
|
|
125
|
+
## 4. ENV in tests
|
|
126
|
+
|
|
127
|
+
- The singleton loads once per process. `with_env` does not change it.
|
|
128
|
+
Use `with_env` together with `described_class.new`, not with `AppConfig.some_value`.
|
|
129
|
+
- Do not assign `ENV[...]` directly. Anyway Config caches parsed ENV,
|
|
130
|
+
and a missed restore leaks into other tests. Use `with_env`.
|
|
131
|
+
- A gem that reads `ENV` itself (for example, Pay) is outside Anyway Config.
|
|
132
|
+
Set and restore its variable in an `around` hook.
|
|
133
|
+
|
|
134
|
+
## 5. CI and asset precompilation
|
|
135
|
+
|
|
136
|
+
- `bin/rails assets:precompile` with `SECRET_KEY_BASE_DUMMY=1` skips `required` validations.
|
|
137
|
+
- In other jobs without secrets, set `ANYWAY_SUPPRESS_VALIDATIONS=1`.
|
|
138
|
+
- Turn on `config.anyway_config.enforce_config_schema = true` so that the test suite
|
|
139
|
+
fails on a mistyped key in YAML or credentials.
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: anyway_config
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.9.
|
|
4
|
+
version: 2.9.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vladimir Dementyev
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby-next-core
|
|
@@ -197,6 +197,10 @@ files:
|
|
|
197
197
|
- lib/rails/commands/local_credentials/local_credentials_command.rb
|
|
198
198
|
- sig/anyway_config.rbs
|
|
199
199
|
- sig/manifest.yaml
|
|
200
|
+
- skills/anyway-config/SKILL.md
|
|
201
|
+
- skills/anyway-config/references/docs.md
|
|
202
|
+
- skills/anyway-config/references/organizing.md
|
|
203
|
+
- skills/anyway-config/references/testing.md
|
|
200
204
|
homepage: http://github.com/palkan/anyway_config
|
|
201
205
|
licenses:
|
|
202
206
|
- MIT
|
|
@@ -207,6 +211,8 @@ metadata:
|
|
|
207
211
|
homepage_uri: http://github.com/palkan/anyway_config
|
|
208
212
|
source_code_uri: http://github.com/palkan/anyway_config
|
|
209
213
|
funding_uri: https://github.com/sponsors/palkan
|
|
214
|
+
hyperdrive_targets: railties
|
|
215
|
+
hyperdrive_artifacts: skill
|
|
210
216
|
post_install_message:
|
|
211
217
|
rdoc_options: []
|
|
212
218
|
require_paths:
|