rubyyamldocuments

Ruby YAML Multiple Documents


Here is my YAML file, 'test.yml':

---
alpha: 100.0
beta: 200.0
gama: 300.0
--- 3
...

The first document is a hash.

The second document is an integer.

I am trying to load these to a Ruby program as a hash and an integer.

Here is my current attempt:

require 'yaml'

variables = YAML.load_file('test.yml')
puts variables.inspect

Solution

  • Don't use multiple documents; They're not a substitute for defining individual elements in your data:

    require 'yaml'
    
    yaml = <<EOT
    ---
    hash:
      alpha: 100.0
      beta: 200.0
      gama: 300.0
    int: 3
    EOT
    
    YAML.load(yaml)   
    # => {"hash"=>{"alpha"=>100.0, "beta"=>200.0, "gama"=>300.0}, "int"=>3}
    

    You can access the contents by assigning YAML.load(yaml) to a variable:

    data = YAML.load(yaml)
    data['hash'] # => {"alpha"=>100.0, "beta"=>200.0, "gama"=>300.0}
    data['int'] # => 3
    

    Think of it this way, you're asking for an object of some sort from YAML after it parses the YAML file. You need to be able to extract specific values from it, so make it easy on yourself and define an array or a hash that contains what you want, in a way that works the way your brain does, within YAML's limitations and specs.

    If I'm going to be creating a complicated structure, I do it in Ruby first and have YAML dump the format for me:

    require 'yaml'
    
    data = {
      "hash" => {
        "alpha" => 100.0,
        "beta" => 200.0,
        "gama" => 300.0
      },
      "int" => 3
    }
    
    puts data.to_yaml
    
    # >> ---
    # >> hash:
    # >>   alpha: 100.0
    # >>   beta: 200.0
    # >>   gama: 300.0
    # >> int: 3
    

    I can put the Ruby code into a script and run it, redirecting it to a YAML file:

    ruby test.rb > test.yaml
    

    Then I can expand the structure:

    require 'yaml'
    
    data = {
      "hash" => {
        "alpha" => 100.0,
        "beta" => 200.0,
        "gama" => 300.0
      },
      "int" => 3,
      "array" => %w[a b c]
    }
    
    puts data.to_yaml
    
    # >> ---
    # >> hash:
    # >>   alpha: 100.0
    # >>   beta: 200.0
    # >>   gama: 300.0
    # >> int: 3
    # >> array:
    # >> - a
    # >> - b
    # >> - c
    

    Testing it round-trip:

    require 'yaml'
    
    yaml = <<EOT
    ---
    hash:
      alpha: 100.0
      beta: 200.0
      gama: 300.0
    int: 3
    array:
    - a
    - b
    - c
    EOT
    
    YAML.load(yaml)
    # => {"hash"=>{"alpha"=>100.0, "beta"=>200.0, "gama"=>300.0}, "int"=>3, "array"=>["a", "b", "c"]}
    

    Iteratively do that until you're comfortable with YAML syntax, then you can build/tweak your YAML file by hand.

    Now, here's how smart it is. The YAML spec supports aliases, which let us define a variable, then reuse it multiple times using & and * respectively. Creating those by hand is a pain when your document gets large, but the YAML driver is smart and will output them for you:

    require 'yaml'
    
    FOO = ['foo']
    BAR = ['bar']
    
    foobar = [FOO, BAR]
    
    data = {
      "foo" => FOO,
      'bar' => BAR,
      'foobar' => foobar,
    }
    
    puts data.to_yaml
    
    
    # >> ---
    # >> foo: &1
    # >> - foo
    # >> bar: &2
    # >> - bar
    # >> foobar:
    # >> - *1
    # >> - *2
    

    foo: &1 defines ["foo"], and gets reused at the bottom as *1.

    "Yaml Cookbook at the YamlForRuby site" is a great reference for working with YAML.