YAML Troubles

Posted by Charlie Tue, 21 Mar 2006 06:04:00 GMT

So for MapBuzz we have some machines running on Ruby 1.8.2 and 1.8.4. And we use GEOS, a C++ library, for manipulating geometries. These geometries are part of Rails test fixtures, and thus must support custom YAML marshalling. Turns out there were some big changes in YAML between 1.8.2 and 1.8.4.After struggling most of today with this, partially due to lack of documentation, here is what we found.


We’ll use an example of a simple coordinate class that has an x and a y value. What we want to end up with is this:

!ruby/Geos::Geometry
  x: 7
  y: 4

Ruby 1.8.2

For Ruby 1.8.2, the to_yaml_type controls the type that is output in the YAML document (the !ruby/Geos::Geometry bit). You also need to define a method to output YAML (to_yaml naturally enough) and one to read YAML (add_ruby_type strangely).

require 'geos'

class Coordinate  
  def to_yaml_type
    "!ruby/#{self.class}"
  end

  def to_yaml( opts = {} )
    YAML::quick_emit( self.object_id, opts ) do |out|
      out.map(to_yaml_type) do |map|
        ['x','y'].each do |m|
          map.add( m, self.send(m) )
        end
      end
    end
  end
  YAML.add_ruby_type( /^Geos::Coordinate/ ) do |type, val|
    result = Geos::Coordinate.new()
    val.each_pair do |k,v|
      result.send("#{k}=", v)
    end
    result
  end
end

Ruby 1.8.4

Things are significantly different with Ruby 1.8.4. First, the add_ruby_type method has been deprecated in favor of a class method called yaml_new. In fact, as far as I can see the add_ruby_type no longer works. Another major change is the use of taguris, such as "tag:ruby.yaml.org,2002." In fact, the 1.8.2 form !ruby is a shortcut for a YAML tag.

require 'geos'

class Coordinate  
  yaml_as "tag:ruby.yaml.org,2002:#{self}"

  def to_yaml( opts = {} )
    YAML::quick_emit( self.object_id, opts ) do |out|
      out.map(taguri) do |map|
        ['x','y'].each do |m|
          map.add( m, self.send(m) )
        end
      end
    end
  end

  def self.yaml_new(klass, tag, val)
    result = Geos::Coordinate.new()
    val.each_pair do |k,v|
      result.send("#{k}=", v)
    end
    result
  end
end

Of course, writing code that works on both platforms is kind of a pain. The approach we are using is:

require 'geos'

class Coordinate  
  yaml_as "tag:ruby.yaml.org,2002:#{self}"

  if not self.respond_to?(:taguri)
    def taguri
      "!ruby/#{self.class}"
    end
    YAML.add_ruby_type( /^Geos::Coordinate/ ) do |type, val|
      Coordinate.yaml_new(Geos::Coordinate, type, val)
    end
  end
    
  def to_yaml( opts = {} )
    YAML::quick_emit( self.object_id, opts ) do |out|
      out.map(taguri) do |map|
        ['x','y'].each do |m|
          map.add( m, self.send(m) )
        end
      end
    end
  end

  def self.yaml_new(klass, tag, val)
    result = Geos::Coordinate.new()
    val.each_pair do |k,v|
      result.send("#{k}=", v)
    end
    result
  end
end

This requires putting this code somewhere:

if not Module.methods.include?('yaml_as')
  # We're on a Ruby version before 1.8.4, stub out yaml_as
  class Module
    def yaml_as( tag, sc = true )
    end
  end
end

Hope this helps!

Posted in ,  | no comments | no trackbacks

Hello World

Posted by Charlie Sat, 18 Mar 2006 20:52:00 GMT

Well, I figured it was time to start a blog before being passed by seemingly the entire world. So let’s see how this goes. Hopefully I’ll say at least one or two things that are interesting to you- but its probably best not to get your expectations too high! If you want to know more about me check out the About Me link.

Well, at a minimum I’ve notched another “Hello World” program. Not sure how many that makes, but its somewhere between ten and twenty. Unsurprisingly, there are websites about these sorts of things - Wikipedia is a good one as well as the ACM hello world project (yes, some of us having nothing better to do!).

4 comments | no trackbacks

Older posts: 1 ... 11 12 13