Skip to Content
Course content

73: YAML Module In Depth

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've spent a lot of time in my career moving configuration settings out of hardcoded Ruby hashes and into external files. For a long time, I just used JSON, but it's too rigid for complex configurations. That's where YAML comes in. It's basically a superset of JSON that's designed to be human-readable, but in Ruby, the YAML module does a lot more than just read text—it actually serializes Ruby objects.

Actually putting data into a file

Let's start with a scenario. I'm building a small game server, and I've got a settings hash that's getting bloated. I want to dump this to a file so I can edit it without touching the code.

require 'yaml'

server_config = {
  "port" => 8080,
  "timeout" => 30,
  "plugins" => ["Logger", "Auth", "Metrics"],
  "db" => {
    "adapter" => "postgresql",
    "pool" => 5
  }
}

File.write("config.yml", YAML.dump(server_config))

If I open config.yml, it looks exactly like what you'd expect. Clean, indented, and easy to read. YAML.dump took my Ruby hash and translated it into the YAML format. Simple enough, right? Now let's try to get it back into the program.

The "Safe Load" Wall

Here is where I usually hit a snag with newer versions of Ruby (specifically since the Psych 4.0 update). I'll try to load that file back in:

config = YAML.load_file("config.yml")
# Or YAML.load(File.read("config.yml"))

For a basic hash of strings and integers, this works. But watch what happens when I introduce Symbols. I prefer symbols for keys because they're more idiomatic in Ruby. Let's change the hash to use :port instead of "port", dump it, and load it again.

Suddenly, I get a Psych::DisallowedClass error. This caught me off guard the first time it happened. The reason is that YAML.load in modern Ruby defaults to safe_load. Since symbols are actual Ruby objects (not just primitive strings), the YAML parser refuses to instantiate them for security reasons. It doesn't want to accidentally execute malicious code embedded in a YAML file.

To fix this, I have to explicitly tell the parser that symbols are allowed:

# This tells YAML it's okay to create Symbol objects
config = YAML.safe_load(File.read("config.yml"), permitted_classes: [Symbol])

I've found that if you're dealing with a trusted config file, this is the way to go. If you're loading user-uploaded YAML, keep those permitted_classes as tight as possible.

Round-tripping Custom Objects

Now, let's push the module further. One of the "magic" parts of the YAML module is that it can serialize entire Ruby objects, not just hashes. I'll define a simple class for my server's network settings.

class NetworkSettings
  attr_accessor :ip, :dns
  def initialize(ip, dns)
    @ip = ip
    @dns = dns
  end
end

net = NetworkSettings.new("192.168.1.1", "8.8.8.8")
yaml_string = YAML.dump(net)
puts yaml_string

The output looks weird: !ruby/object:NetworkSettings. That ! tag is YAML's way of saying, "This isn't just a hash; this is a specific Ruby class." If I try to YAML.load this string, it will actually recreate the NetworkSettings object with the instance variables intact.

Again, this will trigger the DisallowedClass error unless I add NetworkSettings to the permitted_classes list. It's a powerful feature for caching complex object states, but it's also why you should never YAML.load a string from an untrusted API response.

Staying DRY with Anchors and Aliases

Finally, there's a feature I use constantly in production environments: Anchors and Aliases. Imagine I have a 'staging' and 'production' config. They share 90% of the same settings. I don't want to duplicate that data in my YAML file.

I can't do this inside a Ruby hash easily, but I can do it in the YAML file itself using & (anchor) and * (alias).

# I'll write this manually into the file
File.write("env.yml", <<~YAML)
  common: &common_settings
    adapter: postgresql
    pool: 5
    timeout: 5000

  staging:
    <<: *common_settings
    database: staging_db

  production:
    <<: *common_settings
    database: prod_db
YAML

When I load this via YAML.load_file("env.yml"), Ruby merges those anchors. The <<: *common_settings line tells Ruby to "splat" the anchored common settings into the current hash. It's a clean way to manage environment-specific overrides without creating a maintenance nightmare.




📋 Practical Task

Exercise: Building a Multi-Environment Game Server Config Loader

You are tasked with creating a configuration manager for a game server. You need to handle shared settings across different environments and ensure the data is loaded safely.

  1. Create a file named server_config.yml. In this file:
    • Define a top-level anchor called default_settings that includes port: 7000, max_players: 50, and region: "US-East".
    • Create a development section that uses the anchor but overrides the max_players to 10.
    • Create a production section that uses the anchor but overrides the region to "EU-West".
  2. Write a Ruby script that:
    • Requires the yaml module.
    • Loads the server_config.yml file using YAML.safe_load (ensure you handle the permitted_classes for Symbol if you use symbols in your YAML).
    • Prints the max_players for the development environment and the region for the production environment to the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.