I am working on an application that takes input from a YAML file, parses them into objects, and let\'s them do their thing. The only problem I\'m having now, is that the YAML pa
If you only want this behavior with pure ruby classes that use @
-style instance variables (not those from compiled extensions and not Struct
-style), the following should work. YAML seems to call the allocate
class method when loading an instance of that class, even if the instance is nested as a member of another object. So we can redefine allocate
. Example:
class Foo
attr_accessor :yaml_flag
def self.allocate
super.tap {|o| o.instance_variables.include?(:@yaml_flag) or o.yaml_flag = true }
end
end
class Bar
attr_accessor :foo, :yaml_flag
def self.allocate
super.tap {|o| o.instance_variables.include?(:@yaml_flag) or o.yaml_flag = true }
end
end
>> bar = Bar.new
=> #
>> bar.foo = Foo.new
=> #
>> [bar.yaml_flag, bar.foo.yaml_flag]
=> [nil, nil]
>> bar_reloaded = YAML.load YAML.dump bar
=> #, @yaml_flag=true>
>> [bar_reloaded.yaml_flag, bar_reloaded.foo.yaml_flag]
=> [true, true]
# won't overwrite false
>> bar.foo.yaml_flag = false
=> false
>> bar_reloaded = YAML.load YAML.dump bar
=> #, @yaml_flag=true>
>> [bar_reloaded.yaml_flag, bar_reloaded.foo.yaml_flag]
=> [true, false]
# won't overwrite nil
>> bar.foo.yaml_flag = nil
=> nil
>> bar_reloaded = YAML.load YAML.dump bar
=> #, @yaml_flag=true>
>> [bar_reloaded.yaml_flag, bar_reloaded.foo.yaml_flag]
=> [true, nil]
I intentionally avoided a o.nil?
check in the tap
blocks because nil
may actually be a meaningful value that you don't want to overwrite.
One last caveat: allocate
may be used by third party libraries (or by your own code), and you may not want to set the members in those cases. If you want to restrict allocation, to just yaml loading, you'll have to do something more fragile and complex like check the caller
stack in the allocate method to see if yaml is calling it.
I'm on ruby 1.9.3 (with psych) and the top of the stack looks like this (path prefix removed):
psych/visitors/to_ruby.rb:274:in `revive'",
psych/visitors/to_ruby.rb:219:in `visit_Psych_Nodes_Mapping'",
psych/visitors/visitor.rb:15:in `visit'",
psych/visitors/visitor.rb:5:in `accept'",
psych/visitors/to_ruby.rb:20:in `accept'",
psych/visitors/to_ruby.rb:231:in `visit_Psych_Nodes_Document'",
psych/visitors/visitor.rb:15:in `visit'",
psych/visitors/visitor.rb:5:in `accept'",
psych/visitors/to_ruby.rb:20:in `accept'",
psych/nodes/node.rb:35:in `to_ruby'",
psych.rb:128:in `load'",