YAML and constant objects


#1

Hello all,

I have an immutable class Foo (once a foo object is created its state
is fixed). Foo objects have a bunch of attributes so their yaml
strings are fairly long.

There is a small set of standard, frequently used foo objects and I
have defined these as constants right in the class (newed and set
each attribute for Foo::FOO1, Foo::FOO2, … etc.).

When I YAML one of these standard foos to the database and back, it
seems inefficient to create and send a big long yaml string describing
each attribute and then on retrieval to create a whole new Foo object
when all I really want is to look up an already-made constant.

Is there a good solution to making the yaml-ing of constants efficient?

Thanks!

–Brian B.

foo = Foo:FOO1
data = foo.to_yaml # the yaml string here is unnecessarily long
foo = YAML::load(data) # and now after retrieval we have an
additional foo object when we really only need the one original
constant


#2

Brian B. wrote:

When I YAML one of these standard foos to the database and back, it
foo = Foo:FOO1
data = foo.to_yaml # the yaml string here is unnecessarily long
foo = YAML::load(data) # and now after retrieval we have an
additional foo object when we really only need the one original
constant

The only problem with the following (I think) is that you have to pass
the constant name in when you instantiate. Note that the object ID of
the object returned by YAML.load is the same as the original, so it’s
actually the same object.

require ‘yaml’

class Foo
attr_reader :name
def initialize name
@name = name
end

def is_complex_yaml?
false
end
def to_yaml( opts = {} )
YAML::quick_emit( nil, opts ) { |out|
out << "!ruby/foo "
self.name.to_yaml( :Emitter => out )
}
end
end
YAML.add_ruby_type(/^foo/) do |type, val|
subtype, subclass = YAML.read_type_class(type, Foo)
val.split(/::/).inject(Object) { |p, n| p.const_get(n)}
end

foo = FOO1 = Foo.new(“FOO1”)

p foo
data = foo.to_yaml
puts(data)
foo = YAML.load(data)
p foo

END

#<Foo:0xb7bff274 @name=“FOO1”>
— !ruby/foo FOO1
#<Foo:0xb7bff274 @name=“FOO1”>


#3

I don’t yet fully understand your code

I am posting the solution I came up with, seeking critical commentary.
In particular I am unsure whether my use of YAML’s “add_domain_type”
(where I’ve got a separate entry for each constant X for which
“X.yamlize_as_constant” is called) is in the spirit of how YAML was
designed to be used.

Any comments appreciated.

–Brian B.

module YamlizeAsConstant
def yamlize_as_constant(name = nil, domain = “aw.com,2005”)
name = (self.class.name + “/” + constant_name).downcase if !name
yaml_name = “!” + domain + “/” + name
(class << self; self; end).class_eval do
define_method(“to_yaml_type”) {|*args| yaml_name}
define_method(“to_yaml_properties”){|*args| []} #no
properties saved
end
#don’t make an new object, rather return the constant
YAML.add_domain_type(domain, name) {|type, val| self}
end

def constant_name
self.class.constants.each do |c|
return c if self.class.const_get© == self
end
nil
end

end

class Object
include YamlizeAsConstant
end

class Foo
attr_accessor :a, :b # many more
FOO1 = Foo.new
FOO1.a = “test text a”
FOO1.b = “test text b”
FOO1.yamlize_as_constant
end

class TestYamlizeAsConstant < Test::Unit::TestCase
def test_foo
assert_equal “— !aw.com,2005/foo/foo1 {}”, Foo::FOO1.to_yaml
assert_same Foo::FOO1, YAML::load(Foo::FOO1.to_yaml)
end
end


#4
YAML::quick_emit( nil, opts ) { |out|
  out << "!ruby/foo "
  self.name.to_yaml( :Emitter => out )
}

end
end
YAML.add_ruby_type(/^foo/) do |type, val|
subtype, subclass = YAML.read_type_class(type, Foo)
val.split(/::/).inject(Object) { |p, n| p.const_get(n)}
end

Joel, I don’t yet fully understand your code Where can I find
explanations of is_complex_yaml?, add_ruby_type, read_type_class and
quick_emit? (My original class doesn’t actually have a “name”
attribute but maybe once I understand this better I can adapt).

Brian B.


#5

Brian B. wrote:

YAML::quick_emit( nil, opts ) { |out|

Joel, I don’t yet fully understand your code Where can I find
explanations of is_complex_yaml?, add_ruby_type, read_type_class and
quick_emit? (My original class doesn’t actually have a “name”
attribute but maybe once I understand this better I can adapt).

Brian B.

I hope I’m wrong, but I don’t think those are really documented
anywhere.

I got to this point by adapting code in rubytypes.rb in the YAML lib
files. You could replace “name” with anything else that is YAML-able and
persistently identifies your objects.

I think is_complex_yaml? controls whether the object is persisted to a
single line of text or not.

My guess is that add_ruby_type(/^foo/) makes YAML recognize things in
the file that look like “!ruby/foo …”.