Ruby YAML parser by passing constructor

后端 未结 3 906
花落未央
花落未央 2021-02-08 18:11

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

3条回答
  •  一整个雨季
    2021-02-08 18:37

    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'",
    

提交回复
热议问题