Forum: Ruby YAML and constant objects

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Brian B. (Guest)
on 2005-11-15 21:00
(Received via mailing list)
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




-
vjoel (Guest)
on 2005-11-16 06:03
(Received via mailing list)
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">
Brian B. (Guest)
on 2005-11-16 22:31
(Received via mailing list)
>     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.
vjoel (Guest)
on 2005-11-16 22:55
(Received via mailing list)
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 ...".
Brian B. (Guest)
on 2005-11-18 16:42
(Received via mailing list)
> > 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(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
This topic is locked and can not be replied to.