Ruby’s current yaml support is good, but under-documented. Syck (from 1.8.7
and 1.9.2 IIRC) had some easily understood and well-documented features for
de/serialising. But Psych … well it works, obviously. But I’ve bashed my
head on it a couple of times trying to do the stuff that took 5 minutes with
Syck.
You can in fact deserialise very nicely and get back a structure of objects
instead of a nested hash. Yeah, yeah. I know. functions + property-structs are
all the rage. But as the man
said, whenever uptake exceeds
understanding you end up with a pop culture
(Yes, that was an Appeal to Authority)
.
Let’s say you want to have a nicely human-readable file (why else would you
want yaml? It’s slow, old, and unfashionable…) and you want to import it into
ruby:
The yaml version is pleasant without the “ and ‘ and {} and % characters
(unless they provide you with a sense of security and comfort…) With a nod to
the fashionistas, json can’t come close in readability.
Even EDN
and s-expressions
can’t be much more concise. They are however the way to go if you hate : and , .
Sometimes I like to think of objects as convenience wrappers for domain data,
aka key-value pairs, aka hashes.
week_days.rb
12345678910111213141516171819202122
classWeekDaysdefinitialize(*days)self.days=*daysendattr_reader:days# Handle# days = 'Mon'# days = %w[Mon Tue]# days = 'Mon,Tue'## protected so it's effectively a value class.protecteddefdays=(str_or_ary)@days=ifstr_or_ary.size==1str_or_ary.first.split/,\s*/elsestr_or_aryendendend
And with that class, the extraction code looks like this:
Which is not terrible. But still. There should be a nicer way to do
it. After all, the yaml spec has tags like !!str and !!float to
specify types when it’s not obvious from the context.
The obvious approach is to use a tag like !ruby/object:WeekDays
But I can’t exactly complain about the , and “ and ‘ and {} and [] and % characters
and accept !ruby/object:WeekDays, can I now?
Well, there is a way using ruby and Psych and standard yaml tags. It’s not
even hard. Just undocumented by Psych:
The !days tags are the clue. Following is the Psych interfacing.
123
classWeekDaysyaml_tag'!days'end
But since I wanted to handle constructing from both strings and arrays,
it’s a bit more complex. It’s not hard though, really.
Go ahead and read the comments. The ones in the code. Below. They’re
important.
classWeekDays# yes, you MUST have the leading ! otherwise Psych adds <> around your# tags, and the tags that it recognizes, and nothing works properly.# These end up in Psych.load_tags, which is the first place to check# if your classes are not deserialising.yaml_tag'!ds'# You can actually have several tags here. Psych will use the last one# it finds as the one for serialising. They're in Psych.dump_tagsyaml_tag'!days'# For encode_with and init_with, coder will be a Psych::Coder instance.## Psych::Coder#methods:# [] []= add implicit implicit= map map=# object object=# represent_map represent_object represent_scalar represent_seq# scalar scalar= seq seq= style style= tag tag=# type# serialise to yamldefencode_with(coder)# This doesn't actually have an effect. Apparently it should.# I guess that's a bug.coder.style=Psych::Nodes::Mapping::FLOWifdays.andand.size==1# don't set tag explicitly, let Psych figure it out from yaml_tag# coder.represent_scalar '!days', days.firstcoder.scalar=days.firstelse# don't set tag explicitly, let Psych figure it out from yaml_tag# coder.represent_seq '!days', days.to_acoder.seq=days.to_aendend# deserialise from yamldefinit_with(coder)casecoder.typewhen:scalarself.days=[coder.scalar]when:seqself.days=coder.seqelseraise"Dunno how to handle #{coder.type} for #{coder.inspect}"endendend
Now when you say
extraction revisited
1
YAML.load'calendar.yml'
you get the following pry dump (‘#’ removed cos they break the syntax
highlighting)
And as an extra bonus, if you just parse that without the relevant classes and
Psych tags defined, you get back the good ole nested hash of strings ‘n’
things.
Whaddya know – optionally self-describing data.
And if you really must have a schema (implemented in ruby, naturally), check out
Kwalify
and
Yes
.