RubyでYAMLファイルを読み込んでエイリアスを展開し直す方法

この記事は、Notion AIに対して

  • 問題と解決策の概要
  • 例示用のコード

を渡して生成した実験的な記事になります。

TL;DR

  • YAMLファイルを読み込む際にエイリアスを利用すると同じオブジェクトが参照されるため、YAMLファイルを作成するとエイリアスが展開されないことがある
  • 一度JSON文字列にしてから再度Hash化することで別オブジェクトとしてオブジェクトが作成されるため、エイリアスを展開したYAMLファイルを作成できる

背景

YAMLは、人間が読み書きしやすいマークアップ言語の一つであり、設定ファイルなどに利用されることが多いです。 エイリアスとは、同一のオブジェクトを複数の場所で参照するための機能です。 設定ファイルで共通する設定がある場合、エイリアスを使うことで重複を避けることができます。

RubyYAMLファイルを読み込んで再度YAMLとして書き込む場合、同じオブジェクトが参照されるため、エイリアスが展開されないことがあります。 以下は、その問題が発生するコードの例です。

require 'yaml'

content = <<~YAML
aliases:
  - &auth
    auth:
      username: test
      password: test

staging:
  <<: *auth

production:
  <<: *auth
YAML

config = YAML.safe_load content, aliases: true
File.write 'config.yaml', YAML.dump(config)

同じオブジェクトが参照されるため、エイリアスが展開されていません。 以下は、その出力結果の例です。

# config.yaml
---
aliases:
- auth: &1
    username: test
    password: test
staging: *1
production: *1

エイリアスが展開されていないことがわかります。 オブジェクトIDをコードで確認してみると、どれも同じIDが返ってくることがわかります。

puts "aliases.auth: #{config['aliases'][0]['auth'].object_id}"
puts "staging.auth: #{config['staging']['auth'].object_id}"
puts "production.auth: #{config['production']['auth'].object_id}"
# => aliases.auth: 2378771560
# => staging.auth: 2378771560
# => production.auth: 2378771560

解決方法

この問題を解決するためには、Hashを一度JSON文字列にし、JSON.parseで再度Hash化したものを、YAML.dumpすることで、エイリアスを展開したYAMLファイルを作成することができます。

以下は、その方法を示した例です。

require 'yaml'
require 'json'

content = <<~YAML
aliases:
  - &auth
    auth:
      username: test
      password: test

staging:
  <<: *auth

production:
  <<: *auth
YAML

yaml_config = YAML.safe_load content, aliases: true
config = JSON.parse yaml_config.to_json
File.write 'expand_config.yaml', YAML.dump(config)

以下は、その出力結果の例です。

# expand_config.yaml
---
aliases:
- auth:
    username: test
    password: test
production:
  auth:
    username: test
    password: test
staging:
  auth:
    username: test
    password: test

この方法により、同じオブジェクトが参照される問題を回避することができます。