Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby yaml custom domain type does not keep class

Tags:

ruby

yaml

I'm trying to dump duration objects (from the ruby-duration gem) to yaml with a custom type, so they are represented in the form hh:mm:ss. I've tried to modify the answer from this question, but when parsing the yaml with YAML.load, a Fixnum is returned instead of a Duration. Interestingly, the Fixnum is the total number of seconds in the duration, so the parsing seems to work, but convert to Fixnum after that.

My code so far:

class Duration
  def to_yaml_type
    "!example.com,2012-06-28/duration"
  end

  def to_yaml(opts = {})
    YAML.quick_emit( nil, opts ) { |out|
      out.scalar( to_yaml_type, to_string_representation, :plain )
    }
  end

  def to_string_representation
    format("%h:%m:%s")
  end

  def Duration.from_string_representation(string_representation)
    split = string_representation.split(":")
    Duration.new(:hours => split[0], :minutes => split[1], :seconds => split[2])
  end
end

YAML::add_domain_type("example.com,2012-06-28", "duration") do |type, val|
  Duration.from_string_representation(val)
end

To clarify, what results I get:

irb> Duration.new(27500).to_yaml
=> "--- !example.com,2012-06-28/duration 7:38:20\n...\n"
irb> YAML.load(Duration.new(27500).to_yaml)
=> 27500
# should be <Duration:0xxxxxxx @seconds=20, @total=27500, @weeks=0, @days=0, @hours=7, @minutes=38>
like image 867
crater2150 Avatar asked Jun 28 '12 07:06

crater2150


1 Answers

It look like you’re using the older Syck interface, rather that the newer Psych. Rather than using to_yaml and YAML.quick_emit, you can use encode_with, and instead of add_domain_type use add_tag and init_with. (The documentation for this is pretty poor, the best I can offer is a link to the source).

class Duration
  def to_yaml_type
    "tag:example.com,2012-06-28/duration"
  end

  def encode_with coder
    coder.represent_scalar to_yaml_type, to_string_representation
  end

  def init_with coder
    split = coder.scalar.split ":"
    initialize(:hours => split[0], :minutes => split[1], :seconds => split[2])
  end

  def to_string_representation
    format("%h:%m:%s")
  end

  def Duration.from_string_representation(string_representation)
    split = string_representation.split(":")
    Duration.new(:hours => split[0], :minutes => split[1], :seconds => split[2])
  end
end

YAML.add_tag "tag:example.com,2012-06-28/duration", Duration

p s = YAML.dump(Duration.new(27500))
p YAML.load s

The output from this is:

"--- !<tag:example.com,2012-06-28/duration> 7:38:20\n...\n"
#<Duration:0x00000100e0e0d8 @seconds=20, @total=27500, @weeks=0, @days=0, @hours=7, @minutes=38>

(The reason the result you’re seeing is the total number of seconds in the Duration is because it is being parsed as sexagesimal integer.)

like image 105
matt Avatar answered Sep 20 '22 01:09

matt