Tony D Tony D - 14 days ago 7
YAML Question

How to inject a line to a YAML file using Ruby

I'm using

to_yaml
to write an array to a YAML file in Ruby. After the file is written I need to add another line or word under the YAML directive line (---) or the three dashes.

Is there a way to do this after I write the YAML file? Or is there just a better way in general to do this?

I'm reading in from one YAML file to write to another:

items = YAML::load(File.open(yaml_file)

items.each do |key, value|
item = items["#{key}"]
f = File.open("/tmp/blah.yaml", 'w')
f.puts item.to_yaml
f.close
end


After it writes the YAML file correctly I realized that I need to add another line under the YAML directive line. I need to add the word classes below the three dashes (
---
):

---
- blah::words
- blah::words

Answer

YAML is a serialized version of an object, either a hash or an array. Because of the way the serializer does it, according to the specification, we can't stick a line into the output any old place, it has to be syntactically correct. And, the easiest way to do that is to let the YAML parser and serializer handle it for you.

For instance:

require 'yaml'

foo = {'a' => 1}
puts foo.to_yaml

Which outputs:

---
a: 1

and is a simple hash in YAML format.

We can do a round-trip showing that's correct:

bar = foo.to_yaml
YAML.load(bar) # => {"a"=>1}

A more complex object shows how it can get tricky:

foo = {'a' => [1,2], 'b' => {'c' => [3, 4]}}
puts foo.to_yaml

which results in:

---
a:
- 1
- 2
b:
  c:
  - 3
  - 4

There are other ways to designate an array, but that's the default for the serializer. If you added a line, depending on what you're adding it'd have to be before a: or b:, which would be a pain when writing code or appended to the file after - 4.

Instead, we can load and parse the file, munge the resulting object however we want, then rewrite the YAML file, knowing the syntax will be right.

In the following code, imagine that bar is the result of using YAML's load_file, which reads and parses a YAML file, instead of my use of load which only parses the serialized object:

require 'yaml'

bar = YAML.load("---\na: [1]\n") # => {"a"=>[1]}

I can modify bar:

bar['b'] = {'c' => [2,3,4]}

Here's the modified object:

bar # => {"a"=>[1], "b"=>{"c"=>[2, 3, 4]}}

and serializing using to_yaml will write the correct YAML:

bar.to_yaml # => "---\na:\n- 1\nb:\n  c:\n  - 2\n  - 3\n  - 4\n"

If that was:

File.write('foo.yaml', bar.to_yaml)

you'd have accomplished the change without any real hassle.

Instead of simply overwriting the file I'd recommend following safe file overwriting practices by writing to a new file, renaming the old, renaming the new to the name of the old, then deleting the renamed old file. This helps to make sure the file doesn't get clobbered if the code or machine dies causing you to lose all your data.