YAML question (hi _why!)

H

Hal Fulton

_why,

Just saw you post and was reminded to ask: How are we coming on a
"good" solution to my default values problem?

For those curious, what I want to do is establish defaults for
fields in an object, such that:

1. When I dump an object, fields that still have the default
values will be left unspecified.
2. When I load an object, unspecified fields will be given their
default values.

Right now I'm using an initialize_copy hack and some other stuff.

Something like this can greatly compress the YAML, I've found.
Very convenient when you read or hand-edit the file.


Cheers,
Hal
 
L

leon breedt

For those curious, what I want to do is establish defaults for
fields in an object, such that:

1. When I dump an object, fields that still have the default
values will be left unspecified.
2. When I load an object, unspecified fields will be given their
default values.
+1 for this, I've wanted something very similar.

Extra points if it can trickle down to the deepest nested node :)

Leon
 
N

Nicholas Van Weerdenburg

Hal,

Would this be appropriate for the YAML equivalent of DTDs?

In XML this can be handled by DTDs (and I assume schemas and Relax NG,
but I'm not familiar with those). Oddly, I don't think it's a required
part of the spec, so different parsers would or would not display the
default attributes, say when rendering to a web page. I think IE showed
them, but Mozilla didn't which bummed me out.

It was very nice for hand-editing, saving a lot of typing, but sometimes
awkward when I hadn't looked at the XML for a while, since it was hidden
in the DTD, and not explicit in the document I was looking at. An
editor that showed defaults in a ghost-like overlay would have been
helpful there. But overally, the saving of typing vastly outweighed the
lack of explicitness, since I was authoring a lot of XML by hand. Plus
the resulting common-case files were much simpler and easy to read.

If it specifically about Ruby serialization, it may make more sense to
have a ruby library that hacks yaml, like you do. I'd hate to have to
manage a dtd just for defaults- why not add it to the code? Also, then
it would possible provide a common mechanism for other persistence
mechanisms- XML, etc. (though maybe that's over generalizing). I suppose
the downside of this is that yaml is not self-documenting then, which is
an important/nice aspect, especially if you want to use the YAML in
downsteam applications (other languages, style-sheet renderings, etc.).
On the other hand, the code should be self-documenting, ideally. But a
dtd-type doc provides the documentation outside of the ruby context,
such as for handediting. I guess that's the problem with multiple
demands- it stresses the design :).

One thought...

require "yaml"
require "persistence/defaults" # hacks YAML to insert/remove defaults

class Document
default_value :author => "Anonymous Coward" # explicit initial
value doesn't easily equal a wanted default.
....
end

Nick
 
W

why the lucky stiff

Hal said:
For those curious, what I want to do is establish defaults for
fields in an object, such that:

1. When I dump an object, fields that still have the default
values will be left unspecified.
2. When I load an object, unspecified fields will be given their
default values.

Hi, Hal. Good for you, bringing this up again. We didn't finish this
discussion at all and the clock has struck loudly here: time to continue.

In Hobix, I am currently using a mixin for classes which need the above.
This mixin relies upon the presence of a property_map method within
those classes, which defines the full set of instance variables which I
want to output in YAML, along with whether those fields are optional or not.

The mixin also requires default_#{ivar} methods for every instance
variable you plan on having a default for. The mixin will use nil as
the default, if no such method is found. You could include defaults in
the property_map, but I sometimes base the default value of a field on
the value of other fields (in the case of file paths), so I use a method
for each instead.

So, for example, Hobix blog entries only require @title, @content and
@author. Other fields are optional and aren't output to YAML if they
are blank.

The Hobix::Entry#property_map basically looks like this:

class Hobix::Entry
include ToYamlExtras
def property_map
[
['@title', :req],
['@author', :req],
['@contributors', :eek:pt],
['@created', :eek:pt],
['@tagline', :eek:pt],
['@summary', :eek:pt],
['@content', :req]
]
end
end

I'm using an Array to ensure ordering of the elements upon output.

Here would be the ToYamlExtras mixin:

module ToYamlExtras
def to_yaml_properties
property_map.find_all do |prop, req|
case req
when :eek:pt
val = nil
if respond_to?( "default_#{ prop[1..-1] }" )
val = method( "default_#{ prop[1..-1] }" ).call
end
val != instance_variable_get( prop )
when :req
true
end
end.
collect { |prop, req| prop }
end
def initialize
apply_defaults
yield self if block_given?
end
def apply_defaults
property_map.each do |prop, req|
name = prop[1..-1]
if instance_variable_get( prop ).nil? and
respond_to?( "default_#{ name }" )
instance_variable_set( prop,
method( "default_#{ name }" ).call )
end
end
self
end
end

As you can see, the mixin simply defines a to_yaml_properties method,
which is a method understood by the YAML library. The
to_yaml_properties method should return an Array of instance variable
names, in the order they are output.

As you can see, the mixin also defines 'initialize', since you'll
probably want to apply the defaults even if the object is created
programmatically.

You'll also need to hook yourself a YAML type for each class. In the
case of the Hobix::Entry class, I'm hooking !hobix.com,2004/entry.

class Hobix::Entry
def to_yaml_type
"!hobix.com,2004/entry"
end
end

YAML::add_domain_type( 'hobix.com,2004', 'entry' ) do |type, val|
YAML::eek:bject_maker( Hobix::Entry, val ).apply_defaults
end

A script including this mixin and a few tests of the above can be found
at: http://whytheluckystiff.net/ruby/yaml-defaults-mixin.rb

_why
 
H

Hal Fulton

why said:
Hi, Hal. Good for you, bringing this up again. We didn't finish this
discussion at all and the clock has struck loudly here: time to continue.

Bonjour and thanks for the reply!

Are you near an irc client, btw, since we seem to be talking in near-
realtime?
In Hobix, I am currently using a mixin for classes which need the above.
This mixin relies upon the presence of a property_map method within
those classes, which defines the full set of instance variables which I
want to output in YAML, along with whether those fields are optional or
not.

This property_map isn't clear to me yet. Why not class-level data instead
of instance?

I hadn't thought about the issue of required vs. optional. I would have
just said: If it doesn't have a default, and it's not there, default it
to nil.

Ordering is also interesting; I hadn't thought of that either.
As you can see, the mixin simply defines a to_yaml_properties method,
which is a method understood by the YAML library. The
to_yaml_properties method should return an Array of instance variable
names, in the order they are output.

Fine, clear enough.
As you can see, the mixin also defines 'initialize', since you'll
probably want to apply the defaults even if the object is created
programmatically.

This confuses me greatly. What if a class already has an #initialize (as
most do?).
You'll also need to hook yourself a YAML type for each class. In the
case of the Hobix::Entry class, I'm hooking !hobix.com,2004/entry.

Argh. If I had to do this stuff every time, I'd almost rather keep using
the hack I'm using.

But I'll look at your examples. Maybe I just haven't seen the light yet.


Thanks much,
Hal
 
W

why the lucky stiff

Hal said:
This property_map isn't clear to me yet. Why not class-level data instead
of instance?

You could certainly use a class method.

In Hobix, I like to encourage the user to override my classes and to
create singletons to exhibit custom behavior. Instance methods are
simpler in this regard.
I hadn't thought about the issue of required vs. optional. I would have
just said: If it doesn't have a default, and it's not there, default it
to nil.

Having required fields is nice when I am supplying the user with a blank
object to fill in.
This confuses me greatly. What if a class already has an #initialize (as
most do?).

I'm providing this default #initialize as an example of using
#apply_defaults in the constructor. It's okay if it's not used.
Argh. If I had to do this stuff every time, I'd almost rather keep using
the hack I'm using.

If it's any reassurance, this will be simpler in Syck 0.50. You'll
still have to set up the property_map and use the mixin to get this
auto-defaults behavior. But assigning types will be as simple as:

class Hobix::Entry
tag_as "tag:hobix.com,2004:entry"
end

For now, though, you'll need to do this for each class with a type:

class Hobix::Entry
def to_yaml_type
"!hobix.com,2004/entry"
end
end

YAML::add_domain_type( 'hobix.com,2004', 'entry' ) do |type, val|
YAML::eek:bject_maker( Hobix::Entry, val ).apply_defaults
end

_why
 
T

trans. (T. Onoma)

On Friday 26 November 2004 11:36 am, why the lucky stiff wrote:
| If it's any reassurance, this will be simpler in Syck 0.50. You'll
| still have to set up the property_map and use the mixin to get this
| auto-defaults behavior. But assigning types will be as simple as:
|
| class Hobix::Entry
| tag_as "tag:hobix.com,2004:entry"
| end

:)~ Drooling

Can't wait!
T.

P.S. the method name may be a bit too generic, though ?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top