Home > Mobile >  How do I write a Ruby DSL that can produce nested hashes?
How do I write a Ruby DSL that can produce nested hashes?

Time:11-08

I'm trying to write a Ruby DSL module that can be used to construct a hash with nested keys, with syntax like:

Tree.new do
  node :building do
    string :name, 'Museum'
    node :address do
      string :street, 'Wallaby Way'
      string :country, 'USA'
    end
  end
end.to_h

The intended output of that invocation would be this hash:

{
  "building": {
    "name": "Museum",
    "address": {
      "street": "Wallaby Way",
      "country": "USA"
    }
  }
}

I may need to support several levels of depth for the nodes, but can't quite work out how to model that.

I've got this so far, initializing the tree class with an empty hash, and evaluating the block passed to it:

class Tree
  def initialize(&block)
    @tree = {}
    instance_eval &block
  end

  def to_h
    @tree
  end

  def node(name, &block)
    @tree[name] = block.call
  end

  def string(name, value)
    @tree[name] = value
  end
end

But it gives me this flat structure:

=> {:name=>"Museum", :street=>"Wallaby Way", :country=>"USA", :address=>"USA", :building=>"USA"}

I think I'm sort of on the right lines of trying to call the block within node, but can't quite work out how to have each node apply its contents to its part of the hash, rather than @tree[name], which is obviously that key on the top level hash?

CodePudding user response:

You need to initialize a new tree when you call node.

class Tree
  def initialize(&block)
    @tree = {}
    instance_eval &block
  end

  def to_h
    @tree
  end

  def node(name, &block)
    @tree[name] = (Tree.new &block).to_h
  end

  def string(name, value)
    @tree[name] = value
  end
end
  •  Tags:  
  • ruby
  • Related