Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
73: YAML Module In Depth
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.
- Create a file named
server_config.yml. In this file:- Define a top-level anchor called
default_settingsthat includesport: 7000,max_players: 50, andregion: "US-East". - Create a
developmentsection that uses the anchor but overrides themax_playersto10. - Create a
productionsection that uses the anchor but overrides theregionto"EU-West".
- Define a top-level anchor called
- Write a Ruby script that:
- Requires the
yamlmodule. - Loads the
server_config.ymlfile usingYAML.safe_load(ensure you handle thepermitted_classesforSymbolif you use symbols in your YAML). - Prints the
max_playersfor thedevelopmentenvironment and theregionfor theproductionenvironment to the console.
- Requires the
There are no comments for now.