0

I am trying to create a 'foo' item with a child item called 'bar'. The expected output is:

foo_item = Item @name="foo", @children=[<...>]
foo_item children = [Item @name="bar", @children=[]]

I am using blocks, binding and eval. This is my code:

class Item
  attr_accessor :name, :children
  def initialize name
    @name = name
    @children = []
  end
end

def item(item_name)
  @item = Item.new(item_name)
  if @context
    @context.eval('@item.children') << @item
  end

  if block_given? 
    old_context = @context if @context
    @context = binding
    yield
    if old_context
      @context = old_context 
    else
      @context = nil
    end
  end
  @item
end

foo_item = item('foo') do 
  item('bar')
end

puts "foo_item = #{foo_item.inspect}"
puts "foo_item children = #{foo_item.children.inspect}"

In the actual output below, the foo_item contains the bar item, whose child is also the bar item:

foo_item = Item @name="bar", @children=[<...>]
foo_item children = [Item @name="bar", @children=[<...>]]

Given the same input:

foo_item = item('foo') do 
  item('bar')
end

How do I get the expected output above?

Anand
  • 3,690
  • 4
  • 33
  • 64

1 Answers1

2

instance_eval solution

Here's one way to achieve what you want.

instance_eval with block is usually a better idea than eval.

item method isn't too complicated :

  • It first creates an Item with item_name
  • If there's a block, it executes it withing the context of item. It means that the code executed in this block will know about @name and @children.
  • If children is defined, it means the current item method has been called inside the block of another item. The current item should be added to children of the parent item.

class Item
  attr_accessor :name, :children
  def initialize(name)
    @name = name
    @children = []
  end

  def inspect
    "#{name} #{children}"
  end
end

def item(item_name, &block)
  item = Item.new(item_name)
  item.instance_eval(&block) if block
  children << item if defined?(children)
  item
end

foo_item = item('foo') do
  item('bar') do
    item('biz')
    item('boz')
  end
  item('baz')
end

p foo_item
#=> foo [bar [biz [], boz []], baz []

Debug mode

Here's the same code with debug information :

class Item
  attr_accessor :name, :children
  def initialize(name, indent = "")
    @name = name
    @children = []
    @indent = indent
  end

  def inspect
    "#{name} #{children}"
  end
end

@indent = ""
def item(name, &block)
  puts "#{@indent}Creating item #{name}"
  item = Item.new(name, @indent + "  ")
  item.instance_eval do
    puts "#{@indent}Inside item #{@name}"
  end
  if block
    puts "#{@indent}  Block is here. Executing it in item #{item.name}"
    item.instance_eval(&block)
  end
  if defined?(children)
    puts "#{@indent}Inside item #{@name}! Adding item #{item.name} to #{@children}"
    children << item 
  end
  item
end

It outputs :

Creating item foo
  Inside item foo
  Block is here. Executing it in item foo
  Creating item bar
    Inside item bar
    Block is here. Executing it in item bar
    Creating item biz
      Inside item biz
    Inside item bar! Adding item biz to []
    Creating item boz
      Inside item boz
    Inside item bar! Adding item boz to [biz []]
  Inside item foo! Adding item bar to []
  Creating item baz
    Inside item baz
  Inside item foo! Adding item baz to [bar [biz [], boz []]]
Community
  • 1
  • 1
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124