Ruby - working of "yield" in Iterator each - ruby

A method can invoke an associated block of code one or more times using the Ruby yield statement. we can also pass values to the block by giving parameters to yield in vertical bars (|). Just like I have done below.
1 def print_name
2 puts "Hello "
3 yield "Vikram"
4 end
5
6 animals = %w( ant bee cat dog elk )
7 animals.each {|animal| puts animal }
8 animals.each
9
10 print_name {|name| puts name}
11 print_name
In my code line number 11 is giving an error that :
`print_name': no block given (yield) (LocalJumpError)
This means we cannot use yield in a method without passing a block of code while calling the method.
My question is that, how in my above code "animals.each" (refer line 8) is working without giving any error, if there is a "yield" statement present inside "each" method of ruby?
If it is not present then
animals.each {|animal| puts animal }
this should not have worked.

Ruby allows you to check whether a block was passed to the current method by using Kernel#block_given?. Array#each, as the documentation says, returns an enumerator if no block is given (which is checked using block_given?).
Unlike each, your print_name method tries to yield regardless of whether a block was given, leading to the error on line 11.

Related

How to test for stdout that is inside a .each iteration block, using rspec?

I have a sample code:
def print_stuff(first_num, second_num)
puts 'hello'
(first_num..second_num).to_a.each do |num|
puts 'The current number is: '
puts "#{num}"
end
end
I and using rspec, I would like to test to see if the output is correct or not. My attempt are as follows:
expect(initialize_program(1, 3)).to output(
"The current number is:
1
The current number is:
2
The current number is:
3").to_stdout
But instead, I get a expected block to output to stdout but not a block error since the initialize_program(1,3) is outputting the texts, but it is done inside a .each block thus the method itself returns the array of range of numbers.
How can I test for the texts inside the block, to see if the outputted texts are correct?
Thanks!
Todd's answer is fine and I'd strongly recommend you read it carefully: refactor your app in a way that UI (CLI in your case) is minimal and easy to test. But when you want full coverage you'd need to test that std output eventually.
The way you're using it now:
expect(initialize_program(1, 3)).to output("whatever").to_stdout
Means that initialize_program(1, 3) is evaluated immediately when the matcher is called, and it's too soon - it has to do it's magic(*) first, and then run your code. Try like this:
expect { initialize_program(1, 3) }.to output("whatever").to_stdout
Now, instead passing results of calling initialize_program(1, 3) into the matcher, you pass a block that "knows how" to call initialize_program(1, 3). So what the matcher does:
saves the block for later
does it magic to capture whatever goes to the STDOUT (see below)
calls the block, calling the initialize_program(1, 3), capturing whatever was supposed to go to STDOUT
compares it with what you've set up in your expectation (the output("whatever") part)
https://relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/output-matcher
The mentioned magic is not that magical anyway:
https://github.com/rspec/rspec-expectations/blob/44d90f46a2654ffeab3ba65eff243039232802ce/lib/rspec/matchers/built_in/output.rb#L49
and
https://github.com/rspec/rspec-expectations/blob/44d90f46a2654ffeab3ba65eff243039232802ce/lib/rspec/matchers/built_in/output.rb#L141
It just creates StringIO, and replaces global var $stdout with it.
Refactor to Inspect a String, Not Standard Output
This type of code is why you should write your tests first. You're essentially testing Kernel#puts, which always returns nil, rather than validating that you've constructed the String you expect. Don't do that. Instead, refactor like so:
def print_stuff(num1, num2)
str =
(num1..num2).map { |num|"The current number is: #{num}" }
.join "\n"
puts str
str
end
print_stuff 1, 3
#=> "The current number is: 1\nThe current number is: 2\nThe current number is: 3"
This will not only print what you expect on standard output:
The current number is: 1
The current number is: 2
The current number is: 3
but will also use the implicit return of the last line of your method to return a value that you can use to compare to the expectations in your spec.
You might also refactor the method to return an Array of String objects, or whatever else you might explicitly want to test. The more your real method reflects what you plan to test, the better.
RSpec Examples
RSpec.describe '#print_stuff' do
it 'prints the expected message' do
expected_string = <<~EOF
The current number is: 1
The current number is: 2
The current number is: 3
EOF
expect(print_stuff 1, 3).to eql(expected_string.chomp)
end
# Even without the collection matchers removed in RSpec 3,
# you can still validate the number of items returned.
it 'returns the expected number of lines' do
lines = print_stuff(1, 3).split("\n").count
expect(lines).to eql(3)
end
end
Testing RSpec Examples in IRB
In irb, you can validate your specs like so:
require 'rspec'
include RSpec::Matchers
expected_string = <<~EOF
The current number is: 1
The current number is: 2
The current number is: 3
EOF
# String#chomp is needed to strip the newline from the
# here-document
expect(print_stuff 1, 3).to eql(expected_string.chomp)
# test the returned object in other ways, if you want
lines = print_stuff(1, 3).split("\n").count
expect(lines).to eql(3)

Having trouble distinguishing where the block is/if this function has a block

I am under the impression that when you write a function, the block is what goes in between do and end.
(1..5).each do |i|
puts 2 ** i
end
So in the function above, would the block part consist of the following?
|i| puts 2 ** i
You are playing with words. It is a matter of definition. Usually, the block includes the do and end. The whole:
do |i|
puts 2 ** i
end
is the block. But since a block is not an object in Ruby (unlike proc), it does not make much difference whichever you call a block.

Can I break a loop using a toplevel break within a Proc in Ruby?

Questions
Why does break within a proc jump out of three loops all the way to puts 8? It's pretty counter-intuitive.
Is there a way to make it break out of the innermost loop, that is, to puts 6?
Code
3.times do
puts "outer loop"
break_proc = proc { break }
puts 1
loop do
puts 2
loop do
puts 3
loop do
puts 4
break_proc.call
puts 5
end
puts 6
end
puts 7
end
puts 8
end
outer loop
1
2
3
4
8
outer loop
1
2
3
4
8
outer loop
1
2
3
4
8
TL;DR
The behavior you're seeing is a result of attempting to treat a Proc object like a snippet of code passed to Kernel#eval, or thinking that a toplevel break inside a Proc is the same as a bare break keyword inside a loop. An explanation for the behavior is provided, but the real solution is to avoid doing what you're doing.
Procs Carry Context
Why does break within a proc jump out of three loops all the way to puts 8?
This happens because a Proc object contains a Binding to the context in which it's created, and the break keyword is exiting the iterator block and returning to its calling context. Specifically, you're creating the Proc in the top-level loop here:
3.times do
puts "outer loop"
break_proc = proc { break }
One could be forgiven for thinking that Ruby's break just exits a loop wherever its called, but its behavior is more complex than that, especially when you're trying to do something odd like a toplevel break inside a Proc. Your use case for break is even covered in The Ruby Programming Language, where it says:
[A break] causes the block to return to its iterator and the iterator to return to the method that invoked it. Because procs work like blocks, we expect break to do the same thing in a proc. We can’t easily test this, however. When we create a proc with Proc.new, Proc.new is the iterator that break would return from. And by the time we can invoke the proc object, the iterator has already returned. So it never makes sense to have a top-level break statement in a proc created with Proc.new[.]
— David Flanagan and Yukihiro Matsumoto. The Ruby Programming Language (Kindle Locations 8185-8192). O'Reilly Media.
When you create deeply nested loops and then complicate that with objects that carry runtime bindings, the results aren't always what you expect. The behavior you're seeing is not a bug, although it may be a misfeature in some cases. You'd have to ask the language designers why it behaves this way if you want a reason for the implementation semantics rather an explanation for the behavior you're seeing.
Breaking Loops
Is there a way to make it break out of the innermost loop, that is, to puts 6?
Yes, but not with break inside a Proc. Replacing the Proc#call with an actual inline break statement does what you expect and is the "simplest thing that could possibly work," but you can also use throw and catch if you want to adjust your nesting level. For example:
3.times do
puts "outer loop"
break_proc = proc { throw :up }
puts 1
loop do
puts 2
loop do
puts 3
catch :up do
loop do
puts 4
break_proc.call
puts 5
end
end
puts 6
end
puts 7
end
puts 8
end
This will yield:
outer loop
1
2
3
4
6
3
4
6
3
4
6
and endlessly loop inside the third loop where you puts 3.
So, this will do what you're asking, but may or may not do what you want. If it helps, great! If not, you may want to ask a separate question with some real data and behavior if you want to find a more elegant data structure or decompose your task into a set of collaborating objects.
Because of context binding break escapes from the loop defined at the same level:
3.times do
puts 1
loop do
break_proc = proc {|b| break }
puts 2
loop do
puts 3
loop do
puts 4
break_proc.call
puts 5
end
puts 6
end
puts 7
raise 'break other loops'
end
puts 8
end
=>
1
2
3
4
7
1.rb:18:in `block (2 levels) in <main>': break other loops (RuntimeError)
Easiest way to break from your construction - return a boolean from the block indicating if loop should be terminated (... = proc{ true }/break if break_proc.call), or use throw:
3.times do
puts "outer loop"
break_proc = proc {|b| throw :breakit }
puts 1
loop do
puts 2
loop do
puts 3
catch :breakit do
loop do
puts 4
break_proc.call
puts 5
end
end
puts 6
raise 'break the other loops...'
end
puts 7
end
puts 8
end
If you want to break till 6 block you could do this
3.times do
puts "outer loop"
break_proc = proc { break }
puts 1
loop do
puts 2
loop do
puts 3
loop do
puts 4
break
puts 5
end
puts 6
end
puts 7
end
puts 8
end
Ruby Folks:
To use proc & lambda in Ruby, kindly consider the following info to use them error-free and use break or return with proper understanding:
Lambda and non-lambda semantics:
-------------------------------
Procs are coming in two flavors: lambda and non-lambda (regular procs). Differences are:
In lambdas, return and break means exit from this lambda;
In non-lambda procs, return means exit from embracing method (and will throw LocalJumpError if invoked outside the method);
In non-lambda procs, break means exit from the method for which the block is given. (and will throw LocalJumpError if invoked after the method returns);
In lambdas, arguments are treated in the same way as in methods: strict, with ArgumentError for mismatching argument number, and no additional argument processing;
Regular procs accept arguments more generously: missing arguments are filled with nil, single Array arguments are deconstructed if the proc has multiple arguments, and there is no error raised on extra arguments.
Ref: https://ruby-doc.org/core-3.0.2/Proc.html

Range is printed at the end of the "each" iteration

What I'll show is a fulish function, but it's only to explain my problem in a simple way. If I solve the problem in this function, I solved the problem in the original code too.
The problem is simple, and probably the solution too, but I'm new in Ruby and have this doubt. I want to print the values in a range:
def test
(0...5).each do |i|
puts i
end
end
When I call the function, the result that I want is
0
1
2
3
4
but the result that I have is
0
1
2
3
4
0...5
Why this 0...5 is printed together? How can I avoid that?
i don't think the 0..5 is being produced as part of the puts call. Rather, when you call this in your REPL (irb, pry, rails console, etc), you're seeing because it's the last returned value in your code.
Let me show you an example.
Say I save a file called test.rb with the following content:
1.upto(5).each { |i| puts i }
If I call ruby test.rb, I see the expected output,
0
1
2
3
4
If I open irb and run require("./test.rb"), I see the same output.
It's only when I paste the code into irb that I see the additional output (=> 0...5). So I would just ignore this.
In addition to #Max's answer
Whenever any expression is executed in IRB sessions, it will also print the value returned by every expression executed.
In case of method definition it returns the method_name just defined.
> def my_method
?> puts 'this is my method'
?> end
=> :my_method
You see, the :my_method is printed
When the method is invoked, it should print the value returned by the method execution i.e. response of the last expression in the method i.e. puts
> my_method
this is my method
=> nil
but it printed nil because puts always returns nil. I mentioned this because normally developers are astonished when they see their methods returning nil unexpectedly.

Ruby's `find` method won't work on code block that returns an array. Gets a NilClass error

My question involves Ruby's find method and why it works in one situation but not in another. NOTE: I'm using this method in a Cucumber step definition written in Ruby.
This code block works the way I want it to:
Then(/^I expect the bolt service children for CrossrefDepositsDaf to include only: IngestUpdateWorkflow$/) do
#boltworkflowservice ||= BoltWorkflowService.new
Watir::Wait.until(60) do
#job_family = #boltworkflowservice.get_workflows_job_family(#job_id).json
#job_family.find { |job| job['type'] == "IngestUpdateWorkflow" }
end
expect(#job_family.find{|job| job['type'] == "IngestUpdateWorkflow"}.nil?).to eq(false), "IngestUpdateWorkflow child not found"
end
But I wanted to rewrite this code block to get rid of the instance variable and reduce the block by one line. I tried the following:
Then(/^I expect the bolt service children for CrossrefDepositsDaf to include only: IngestUpdateWorkflow$/) do
#boltworkflowservice ||= BoltWorkflowService.new
Watir::Wait.until(60) do
puts #boltworkflowservice.get_workflows_job_family(#job_id).json
#boltworkflowservice.get_workflows_job_family(#job_id).json.find {|job| job['type'] == "IngestUpdateWorkflow"}
end
expect(#job_family.find{|job| job['type'] == "IngestUpdateWorkflow"}.nil?).to eq(false), "IngestUpdateWorkflow child not found"
end
I just added the puts to see what the code would return. According to the puts, it returns an array that contains three hash objects but I still get an error. Here is what I get when I try to run the second block:
[{"id"=>914295, "type"=>"CrossrefDepositsDaf", "subType"=>"", "parentId"=>0, "state"=>"SUCCESS", "subState"=>"SUCCESS", "dataPointer"=>"s3://sequoia-install/app-resources/test.cirrostratus.org/workflows/CrossrefDepositsDaf/2015/7/29/148c7ebc-3da4-4650-a22e-2e8962d448fc", "created"=>"2015-07-29 01:58:14", "metadata"=>[]}, {"id"=>914296, "type"=>"CrossRefDeposits", "subType"=>"BoltCatDaf", "parentId"=>914295, "state"=>"SUCCESS", "subState"=>"SUCCESS", "dataPointer"=>"s3://sequoia-install/app-resources/test.cirrostratus.org/workflows/CrossRefDeposits/2015/7/29/3a703736-d4a2-456e-baa9-7bf2c0f4a0e2", "created"=>"2015-07-29 01:58:17", "metadata"=>[]}, {"id"=>914297, "type"=>"IngestUpdateWorkflow", "subType"=>"CrossRefDeposits", "parentId"=>914296, "state"=>"SUCCESS", "subState"=>"CREATED", "dataPointer"=>"s3://sequoia-install/app-resources/test.cirrostratus.org/workflows/IngestUpdateWorkflow/2015/7/29/9282591b-f93c-49d6-b656-4afbd14156ef", "created"=>"2015-07-29 01:58:23", "metadata"=>[]}]
NoMethodError: undefined method `find' for nil:NilClass
./features/step_definitions/catdaf_integration_crossref_deposits_steps.rb:42:in `/^I expect the bolt service children for CrossrefDepositsDaf to include only: IngestUpdateWorkflow$/'
C:\Users\Dugan23\workspace\cloudy_clouds\cucumber\features\content_management\daf\integration\crossref_deposits.feature:11:in `And I expect the bolt service children for CrossrefDepositsDaf to include only: IngestUpdateWorkflow'
1 scenario (1 failed)
6 steps (1 failed, 5 passed)
9m30.221s
Process finished with exit code 1
Can somebody please explain to me why the second block of code doesn't work? The puts shows that I should be feeding the find method an array so why does it say that I"m trying to run the find method on a nil:NilClass. Thank you in advance for any and all of your help.
NOTE: When I added a puts statement to the first block of code to print out #job_family it returned the same exact array as the second block of code. Does this mean that in the second block of code Ruby is trying to run the find method before evaluating the code before it? I tried to remedy this by adding parentheses as follows, but to no avail:
(#boltworkflowservice.get_workflows_job_family(#job_id).json).find {|job| job['type'] == "IngestUpdateWorkflow"}
In the second version, you have avoided using #job_family inside the block, but still you are referring to it on the expect, which obviously would be nil unless you have defined it elsewhere

Resources