user1832897
user1832897

Reputation: 97

Block/Proc Returning Array within an Array Explanation

The prompt: Extend the Array class to include a method named my_each that takes a block, calls the block on every element of the array, and then returns the original array.

class Array
  def my_each(&prc)
    if block_given?
      proc.call(self)
    else
      for i in (0..self.length-1)
        puts self[i]
      end
    end
    self
  end
end

This is what I put together and I don't have a good understanding of how Blocks/Procs work within this context, but somehow I magically wrote the code that passed 3 of the 4 RSPEC tests.

  describe "#my_each" do
    it "calls the block passed to it" do
      expect do |block|
        ["test array"].my_each(&block)
      end.to yield_control.once
    end

    it "yields each element to the block" do
      expect do |block|
        ["el1", "el2"].my_each(&block)
      end.to yield_successive_args("el1", "el2")
    end

    it "does NOT call the built-in #each method" do
      original_array = ["original array"]
      expect(original_array).not_to receive(:each)
      original_array.my_each {}
    end

    it "is chainable and returns the original array" do
      original_array = ["original array"]
      expect(original_array.my_each {}).to eq(original_array)
    end
  end

All of the above RSPEC tests passes with the exception of the second one, where my code returns [["el1", "el2"]] when ["el1", "el2"] is expected. Can someone please give me an explanation of how or why I am receiving a nested array here?

Can someone also give me an explanation of how the code is running as a block is passing through this method? I'm not sure if my "else" condition is actually even necessary in the context of the RSPEC tests. I'm generally confused by the concept of passing blocks through self-written methods and how they interact with the method itself.

Thanks in advance!

Upvotes: 0

Views: 172

Answers (2)

mrzasa
mrzasa

Reputation: 23327

In the first part of your condition, you pass the whole array to the block:

if block_given?
  proc.call(self)
else
# ...

E.g. for an array of ["el1", "el2"] you do proc.call(["el1", "el2"]). What you expect in the test are two consecutive calls:

proc.call("el1")
proc.call("el2")

To do that you need to use a loop also in the first part of the condition and pass there an array element, not the whole array:

if block_given?
  for i in (0..self.length-1)
     proc.call(self[i])
  end
else
  for i in (0..self.length-1)
    puts self[i]
  end
end

Upvotes: 3

steenslag
steenslag

Reputation: 80065

  proc.call(self)

is the culprit. self is the whole array.

Extend the Array class to include a method named my_each

class Array
  def my_each
  end
end

that takes a block,

#every method in ruby accepts a block, it is just ignored when not used (yielded to). Do nothing.

calls the block on every element of the array,

class Array
  def my_each
    for element in self
      yield element
    end
  end
end

and then returns the original array.

# "for" loops do this. Do nothing. It should pass.

Upvotes: 0

Related Questions