Transforming YAML config files into Ruby objects with helpers
Have you ever found yourself juggling configuration data in your Ruby or Rails applications? YAML files are great for storing configuration, but accessing that data through plain hashes can get messy. What if you could transform that static YAML data into proper Ruby objects with custom methods? Today I'll show you exactly how to do that!
Why YAML for configuration?
YAML (YAML Ain't Markup Language) has become a standard for configuration files in Ruby applications, especially in Rails. Its human-readable format makes it perfect for storing structured data without the verbosity of XML or the strictness of JSON.
But the default way we use YAML in Ruby - parsing it into nested hashes - often leads to code like this:
config = YAML.load_file('config/admins.yml')
puts config['production']['john']['email']
This approach has several drawbacks:
- No validation for your configuration structure
- No methods for transforming or combining configuration values
- No type checking
- String keys instead of symbols (or vice versa) causing subtle bugs
Wouldn't it be nicer to write Admin.find(:john).email
instead?
Creating a model to represent your configuration
Let's start by examining a sample YAML configuration file for admins:
# config/admins.yml
production:
john:
name: John Smith
email: [email protected]
role: admin
jane:
name: Jane Doe
email: [email protected]
role: editor
development:
john:
name: John Smith
email: [email protected]
role: admin
jane:
name: Jane Doe
email: [email protected]
role: developer
Now, we'll create a Ruby class that will represent each admin entry:
# app/models/admin.rb
class Admin
include ActiveModel::Model
include ActiveModel::Attributes
attribute :id, :string
attribute :name, :string
attribute :email, :string
attribute :role, :string
class << self
def all
@all ||=
Rails.application.config_for(:admins).map do |key, attrs|
new(attrs.merge(id: key.to_s))
end
end
def find(key)
all.find { |admin| admin.id == key.to_s }
end
end
end
Let's break down what's happening here:
- We're using
ActiveModel::Model
which gives us validations and other model-like behaviors without a database ActiveModel::Attributes
gives us typed attributes with potential for custom types- We define the attributes we expect for each admin
- The class methods (
self.all
andself.find
) provide an interface to access our configuration data
How it works
The magic happens through Rails' config_for
method, which loads the YAML file corresponding to the current environment. So in production, it loads the production
section, and in development, it loads the development
section.
For each admin entry in the YAML, we create a new Admin
object, merging in the id
(the key from the YAML) with the other attributes.
The result is cached in the @all
class variable, so we only load and parse the YAML once.
Using your configuration objects
Now, instead of accessing your configuration through nested hashes, you can do:
# Get all configured admins
Admin.all
# Find a specific admin
john = Admin.find(:john) # => #<Admin ...>
puts john.name # => "John Smith"
puts john.email # => "[email protected]" (in development)
# What if the admin doesn't exist?
Admin.find(:guest) # => nil
Extending with custom methods
The real power comes when you add domain-specific methods to your model:
class Admin
# ... our current existing code ...
def admin?
role == 'admin'
end
def display_name
"#{name} <#{email}>"
end
def domain
email.split('@').last
end
def gravatar_url
email_digest = Digest::MD5.hexdigest(email.downcase)
"https://www.gravatar.com/avatar/#{email_digest}"
end
end
Now you can use these methods in your application:
admin = Admin.find(:john)
puts admin.display_name # => "John Smith <[email protected]>"
puts admin.domain # => "example.com"
puts admin.admin? # => true
puts admin.gravatar_url # => "https://www.gravatar.com/avatar/b28fab587c86a2e20a97b29da731121d"
Summary
Converting YAML configuration files into Ruby objects gives you:
- A more intuitive API for accessing configuration
- Type checking and validation for configuration values
- The ability to add domain-specific methods to transform or combine configuration values
- Better organization of your configuration code
This pattern is especially useful for configurations that are referenced in multiple places in your application, as it centralizes the logic for accessing and manipulating the configuration data.
Happy YAML-ing!