require 'yaml' class String def wordwrap( len ) gsub( /\n/, "\n\n" ).gsub( /.{#{len},}?\s+/, "\\0\n" ) end end puts "** Ordering YAML mapping keys **" puts # # Robert Feldt wrote: # # > ...when I use Yaml for ini files I'd like to be able to # > spec an order for map pairs to be serialized in. Obviously Ruby hashes has # > no garantuee for this but are there any plans on supporting this? If not # > I'll whip something up myself; I really wanna make sure the most important # > stuff comes out on top... # # I had three suggestions: # # 1. When using objects, you can specify the ordering of your properties # explicitly by defining a `to_yaml_properties' method. # class Instrument attr_accessor :name, :key, :is_woodwind def initialize( n, k, ww ) @name, @key, @is_woodwind = n, k, ww end def to_yaml_properties [ '@name', '@key', '@is_woodwind' ] end end puts "-- 1. Instrument object --" test1 = Instrument.new( 'Alto Saxophone', 'Eb', true ) puts test1.inspect.wordwrap( 30 ).gsub( /^/, ' ' ) puts puts "** With ordered properties **" puts test1.to_yaml.gsub( /^/, ' ' ) puts # # 2. The same can't be done for Hashes because the key set isn't # predictable. But if the :SortKeys method is called, then # the Hash will be sorted with Hash#sort. You could define a # singleton `sort' method to sort certain Hashes. # test2 = { 'name' => 'Alto Saxophone', 'key' => 'Eb', 'is_woodwind' => true } def test2.sort order = [ 'name', 'key', 'is_woodwind' ] super { |a, b| order.index( a[0] ) <=> order.index( b[0] ) } end puts "-- 2. Instrument hash --" puts test2.inspect.wordwrap( 30 ).gsub( /^/, ' ' ) puts puts "** With ordered keys **" puts test2.to_yaml( :SortKeys => true ).gsub( /^/, ' ' ) puts # # Alternatively, you could define a singleton `to_a' to sort # correctly and skip the :SortKeys option. # # 3. Finally, the YAML spec now defines an !omap type. This type is # an ordered mapping with unique keys. YAML.rb will load this # into a Hash-like class. # test3 = YAML::Omap[ 'name', 'Alto Saxophone', 'key', 'Eb', 'is_woodwind', true ] puts "-- 3. Instrument Omap --" puts test3.inspect.wordwrap( 30 ).gsub( /^/, ' ' ) puts puts "** With ordered keys **" puts test3.to_yaml.gsub( /^/, ' ' ) puts # # Robert's answer was great. He used a mixin to add a singleton `to_a' # method, which allows him to easily sort many hashes. # module SortableToAKeys def key_order=( keys ) @__key_order = keys end def to_a ks, out = self.keys, [] (@__key_order + (ks - @__key_order)).each do |key| out << [key, self[key]] if ks.include?(key) end out end end test4 = { 'name' => 'Alto Saxophone', 'key' => 'Eb', 'is_woodwind' => true } puts "-- 4. Instrument SortableToAKeys --" puts test4.inspect.wordwrap( 30 ).gsub( /^/, ' ' ) puts test4.extend SortableToAKeys test4.key_order = [ 'name', 'key', 'is_woodwind' ] puts "** With ordered keys **" puts test4.to_yaml.gsub( /^/, ' ' ) puts