ruby 55 lines · 8 steps

A typed config loader in Ruby

How AppConfig loads YAML, selects the current environment, and reads nested keys with typed accessors.

Explained by highlit
1require "yaml"
2require "pathname"
3 
4class AppConfig
5 class MissingKeyError < KeyError; end
6 
7 def self.load(path, env: ENV.fetch("RACK_ENV", "development"))
8 raw = YAML.safe_load(Pathname(path).read, aliases: true) || {}
9 new(raw.fetch(env, raw))
10 end
11 
12 def initialize(data)
13 @data = deep_symbolize(data)
14 end
15 
16 def fetch(*keys)
17 keys.reduce(@data) do |node, key|
18 node.fetch(key)
19 rescue KeyError, NoMethodError
20 raise MissingKeyError, "missing config key: #{keys.join(".")}"
21 end
22 end
23 
24 def string(*keys)
25 fetch(*keys).to_s
26 end
27 
28 def integer(*keys)
29 Integer(fetch(*keys))
30 end
31 
32 def boolean(*keys)
33 value = fetch(*keys)
34 return value if [true, false].include?(value)
35 
36 %w[true 1 yes on].include?(value.to_s.strip.downcase)
37 end
38 
39 def list(*keys)
40 Array(fetch(*keys))
41 end
42 
43 private
44 
45 def deep_symbolize(value)
46 case value
47 when Hash
48 value.each_with_object({}) { |(k, v), acc| acc[k.to_sym] = deep_symbolize(v) }
49 when Array
50 value.map { |item| deep_symbolize(item) }
51 else
52 value
53 end
54 end
55end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A custom error subclass lets callers distinguish a missing config key from any other failure.
  2. 2Symbolizing keys recursively gives the whole config tree one consistent access convention.
  3. 3Typed accessors that wrap a single fetch keep coercion logic in one place instead of at every call site.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A typed config loader in Ruby — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code