0

I have the following class:

class AEnumerableObject
  include Enumerable

  def initialize
    @original_list = []
  end

  def [] index
    @original_list[index]
  end

  def []= index, value
    @original_list[index] = value
  end

  def each &block
    @original_list.each_with_index do |item, i|
      yield item, i
    end if block_given?
  end
end

If I run inline commands

$ object = AEnumerableObject.new
$ object[0] = 1
$ object[1] = 2
$ object[2] = 3
$ p object

should show

[1, 2, 3]

but it actually shows

#<AEnumerableObject:... @original_list=[1, 2, 3]>

while if I run

$ p object.class

it shows

=> AEnumerableObject

How do I implement an Array like print method?

UPDATED

I implemented the methods #[], #[]= and #each (so now it’s iterable). But how to show it like an Array in line command?

rplaurindo
  • 1,277
  • 14
  • 23

2 Answers2

2

As you can see on the left column (with all the defined methods in the module) of the documentation, the Enumerate module does not define the methods #[] and #[]=, which must be provided by the class.

Also, the class that includes Enumerate must provide an #each method.

class Test
  include Enumerate

  def [](i)
    # do your stuff
  end

  def []=(i, v)
    # do your stuff
  end

  def each
    # required!
  end
end

Implementing the required methods does not create any (let's say) "relation" with the Array class. The only way to create such a "relation" is through inheritance.

Let's make a silly example with some sort of linked list just to see how it works the idea...It is not robust nor well implemented... It is only an idea:

class Node
  include Enumerable

  attr_reader :data

  def initialize(payload, n = nil)
    @node = n if n.is_a? Node
    @data = payload
  end

  def [](n)
    if n == 0
      self
    else
      @node[n - 1]
    end
  end

  def []=(n, v)
    if n == 0
      @data = v
    else
      @node[n - 1] = v 
    end
  end

  def each(&block)
    block.call(self)
    @node.each(&block) if @node
  end

  def <=>(o)
    @data <=> o.data
  end

  def to_s
    "[" + self.map { |e| "#{e.data}" }.join(", ") + "]"
  end
end

a = Node.new("Node 0")
b = Node.new("Node 1", a)
c = Node.new("Node 2", b)
d = Node.new("Node 3", c)

d[2] = "Edited"

d.each do |n|
  puts n.data
end

# Let's use an enumerable method
s = d.select { |n| n.data == "Node 0" }[0]
puts "#{a.inspect} = #{s.inspect}"

puts a, b, c, d

# => Output:
# 
# Node 3
# Node 2
# Edited
# Node 0
# #<Node:0x0000562e121e2440 @data="Node 0"> = #<Node:0x0000562e121e2440 @data="Node 0">
# [Node 0]
# [Edited, Node 0]
# [Node 2, Edited, Node 0]
# [Node 3, Node 2, Edited, Node 0]

As alternatives, you may consider:

# 1. Inheritance
def Test < Array
  # do your stuff
end

# 2. Reopen the Array class
def Array
  # do your stuff
end

The methods #[] and #[]= for the Array class are defined in the C source of the interpreter.

Matteo Ragni
  • 2,837
  • 1
  • 20
  • 34
  • It’s true, but I don’t want to inherit from Array. My class has no relationship "is a" with Array. – rplaurindo Oct 24 '17 at 23:38
  • 1
    then follow the first example. That one has no relation with `Array` – Matteo Ragni Oct 24 '17 at 23:39
  • 1
    So take care of `[]` and `each` yourself, per Mr. Ragni's first example. Of course, the easiest way to do so internally is with an array, but you don't have to expose it outside your class except through interfaces that you design and control. – Michael Chaney Oct 24 '17 at 23:40
  • Ok, I updated the post. Now I would like to show as an array when executing the variable that holds the instance reference in line command. – rplaurindo Oct 25 '17 at 00:00
  • Ok, now `Node` prints as an `Array`. Check out the `to_s` method. I'm using a method from enumerable – Matteo Ragni Oct 25 '17 at 00:11
  • @MatteoRagni In this way I have to explicitly call the ```to_s``` method. – rplaurindo Oct 25 '17 at 01:39
2

Your problem is that you have not implemented the inspect method. At the moment, the default inspect definition is used in your class. You may want to define it like:

class AEnumerableObject
  def inspect
    @original_list.inspect
  end
end
sawa
  • 165,429
  • 45
  • 277
  • 381
  • 2
    When @sawa refers to the "default definition of `inspect` he is referring to [Kernel#inspect](http://ruby-doc.org/core-2.4.0/Object.html#method-i-inspect). We know that because `class A; end; A.instance_method(:inspect).owner #=> Kernel`. Note from the second sentence of the doc for the module [Kernel](http://ruby-doc.org/core-2.4.0/Kernel.html) that `Kernel`'s instance methods are documented in the class [Object](http://ruby-doc.org/core-2.4.0/Object.html). – Cary Swoveland Oct 25 '17 at 04:30
  • Thanks for ```A.instance_method(:inspect).owner```. Very useful. – rplaurindo Oct 25 '17 at 12:26