YAML and constant objects

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 Buckley

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

···

-

Brian Buckley wrote:

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 Buckley

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">

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

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

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 Buckley

Brian Buckley wrote:

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

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 Buckley

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 ...".

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

> 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 Buckley

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(c) == 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