How is the value of a begin block determined? - ruby

According to The Ruby Programming Language p.164.
If a begin statement doesn't propagate an exception, then the value
of the statement is the value of the last expression evaluated in
the begin, rescue or else clauses.
But I found this behavior consistent with the begin block together with else clause and ensure clause.
Here is the example code:
def fact (n)
raise "bad argument" if n.to_i < 1
end
value = begin
fact (1)
rescue RuntimeError => e
p e.message
else
p "I am in the else statement"
ensure
p "I will be always executed"
p "The END of begin block"
end
p value
The output is:
"I am in the else statement"
"I will be always executed"
"The END of begin block"
"I am in the else statement"
[Finished]
The value is evaluated to the else clause. This is inconsistent behavior as the ensure clause is the last statement executed.
Could someone explain what's happening within the begin block?

I'd interpret the goal of the begin/rescue/else/end block as:
Execute the code in the begin section, and then the code in the else section.
If something goes wrong in the begin section, execute the rescue section instead of the else section.
So either the rescue section or the else section will be executed after trying the begin section; so it makes sense that one of them will be used as the whole block's value.
It's simply a side effect that the ensure section will always be executed.
val = begin
p "first"; "first"
rescue => e
p "fail"; "fail"
else
p "else"; "else"
ensure
p "ensure"; "ensure"
end
val # => "else"
# >> "first"
# >> "else"
# >> "ensure"
But:
val = begin
p "first"; "first"
raise
rescue => e
p "fail"; "fail"
else
p "else"; "else"
ensure
p "ensure"; "ensure"
end
val # => "fail"
# >> "first"
# >> "fail"
# >> "ensure"

I'm just guessing here, but as the purpose of a ensure block is to finalize any resources that may remain open (cleanup in other words), and so it makes sense that the logical value should be the result of the else statement. It makes sense to me that it is by design.

In this case the begin block is just a way of defining a section for which you may want to do exception handling.
Remember that else in this case runs if no exceptions occur, and ensure will run regardless of exceptions or a lack thereof.

Related

Implement 'rescue' without 'next'

I have this example to handle errors and continue code execution:
begin
p '-' * 100
request_builder.new(env: tested_env).submit!
rescue => error
error_logs << "#{error}\n#{error.backtrace.first(5).join("\n")}"
next
end
How can I rewrite the code without next?
I found this example:
%w(1 2).each do |x|
p x
begin
raise 'something'
rescue => error
p error
end
end
=>
"1"
#<RuntimeError: something>
"2"
#<RuntimeError: something>
How can it be used to implement the code?
The next in you example has nothing to do, with a normal rescue block. It just tells to go to the next iteration. So remove your next statement and you are find.
begin
p '-' * 100
request_builder.new(env: tested_env).submit!
rescue => error
error_logs << "#{error}\n#{error.backtrace.first(5).join("\n")}"
# next # removing next, removes you error,
# you just put whatever you want in your rescue block,
# the application continues in this block
end

include works with if but not else

I doing a Ruby botcamp. I'm supposed to write code that replaces all user input of 's' with 'th' so it reads like Daffy Duck is speaking. If I enter an s it will be replaced with th. That works! But If I don't enter an 's' it's supposed to print that none were included in my elsif statemnt. Instead I'm getting the error 'undefined method `include?' for nil:NilClass'. Other than that error, the interpretor is telling me the code is good.
print "Input a string: "
user_input=gets.chomp.downcase!
if user_input.include?"s"
user_input.gsub!(/s/, "th")
puts "Your string is #{user_input}!"
elsif
puts "There are no s's in your string!"
end
Any ideas on what I need to change?
You need to be careful with built-in ruby methods that end with an exclamation point (!). A lot of them will return nil if no changes were made:
'test'.downcase! # => nil
'Test'.downcase! # => "test"
Since you are assigning the result to a variable, there's no need to use the exclamation point method, since those modify in-place, you can just use the normal downcase method.
'test'.downcase # => "test"
You also later on have an elsif with no condition, that should probably just be an else. It's actually executing the first line of the "body" of the elsif as the conditional:
if false
puts "a"
elsif
puts "b" # recall that `puts` returns `nil`
puts "c"
else
puts "d"
end
This results in
b
d
being output

if-elsif without conditions not branching correctly in Ruby

When I run the following code:
if
puts "A"
elsif
puts "B"
end
I get the output:
A
B
Why does it not warn or raise any errors? And why does it execute both branches?
an if-elsif without conditions
Here's where you're wrong. The puts are the conditions. There are no bodies in that snippet, only the conditions.
Here's your code, properly formatted.
if puts "A"
elsif puts "B"
end
And why it executes both branches?
puts returns nil, a falsey value. That's why it tries both branches. If this code had an else, it'd be executed too.
In other words :
if # this is the condition :
puts "A" # an expression which prints A and returns nil
# hence it's like "if false", try elsif ...
then
puts 'passes in then'
elsif # this is another condition :
puts "B" # puts prints B and returns nil
else # no condition satisfied, passes in else :
puts 'in else'
end
Execution :
$ ruby -w t.rb
A
B
in else

ruby: finish loop iteration before raising Interrupt

I'm looping through a lot of items and I want to periodically interrupt the loop to save and continue at a later time like this:
begin
big_list.each do |i|
# sensitive stuff
sensitive_method(i)
# other sensitive stuff
end
rescue Interrupt
# finish the current iteration
# then do something else (save)
# don't raise (print done)
end
By sensitive I mean that, if Interrupt is raised in the middle of an iteration, data will be corrupted so I need to guarantee that the iteration finishes before exiting.
Also, if another exception is raised, it should still finish the loop but raise it afterwards
EDIT:
Using the answer by mudasobwa in a test scenario:
while true
result = begin
puts "start"
sleep 1
puts "halfway"
sleep 1
puts "done\n\n"
nil
rescue Exception => e
e
end
case result
when Interrupt
puts "STOPPED"
break
when Exception then raise result
end
end
I get:
start
halfway
done
start
^C: /...
STOPPED
which is my exact problem, I need it to finish the loop (sleep, print halfway, sleep, print done) and only then break out (wrapping the puts, sleep... in a method does not help)
TL;DR: There is no way to continue the execution of the method from inside the middle of it.
big_list.each do |i|
# sensitive stuff
result = begin
sensitive_method(i)
nil
rescue Exception => e
e
end
# other sensitive stuff
case result
when Interrupt
puts "done"
break "done"
when Exception then raise result
end
end
Sidenote: you probably don’t want to rescue the topmost Exception, but some subclass that makes sense to rescue.
To make it possible to finish the chunk of operations:
operations = [
-> { puts "start" },
-> { sleep 1 },
-> { puts "halfway" },
-> { sleep 1 },
-> { puts "done\n\n" }
]
def safe_chunk(operations, index = 0)
result = operations[index..-1].each_with_index(index) do |op, idx|
begin
op.()
rescue Exception => e
safe_chunk(operations, idx) # or idx + 1
break e
end
end
result.is_a?(Array) ? nil : result
end
The Interrupt exception is raised in the main thread. If you use a worker thread to process the list it will never be interrupted. You will need a way to tell the worker thread to terminate though. Rescuing Interrupt in the main thread and setting a flag that's checked by the child can accomplish this.
BigList = (1..100)
def sensitive_method(item)
puts "start #{item}"
sleep 1
puts "halfway #{item}"
sleep 1
puts "done #{item}"
puts
end
#done = false
thread = Thread.new do
begin
BigList.each do |item|
break if #done
sensitive_method item
end
end
end
begin
thread.join
rescue Interrupt
#done = true
thread.join
end
The keyword ensure, used in rescue clauses, is available for situation such as this one, where code must be executed after an exception occurs.
[-1, 0, 1].each do |i|
begin
puts "i=#{i} before exception"
# <additional code>
n = 1/i
rescue ZeroDivisionError => e
puts "Exception: #{e}"
exit
ensure
puts "Just executed 1/#{i}"
# <additional code>
end
end
i=-1 before exception
Just executed 1/-1
i=0 before exception
Exception: divided by 0
Just executed 1/0
Notice that begin/rescue/ensure/end must be inside the loop and that the code after ensure is executed for each i regardless of whether a zero-divide exception occurs.

Ruby skips items from list tasks

I am trying to make an app which if give the option to type, it types false then it skips the certain element from the list and it jumps to the next executing the same task.
That is the basic idea of the following code:
string["items"].each do |item|
p continue.to_s + "<- item"
begin
Anemone.crawl("http://" + item["displayLink"] + "/") do |anemone|
anemone.on_every_page do |page|
if continue.chomp.to_bool == false
raise "no more please"
end
request = Typhoeus::Request.new(page.url, followlocation: true)
response = request.run
email = /[-0-9a-zA-Z.+_]+#[-0-9a-zA-Z.+_]+\.[a-zA-Z]{2,4}/.match(response.body)
if email.nil?
else
p email
begin
continue = Timeout::timeout(2) do
p "insert now false/nothing"
gets
end
rescue Timeout::Error
continue = "true"
end
end
end
end
rescue
continue = true
next
end
p "---------------------------------------------------------"
end
As the code shows, if the user types false when prompted the app should skip the item and go to the next one. However what it does is: when the user types false the app skips the current item and then doesn't execute any of the code that should be executed for all of the other items except the printing ( the second line of code );
Here is how the output looks like:
$ruby main.rb
"1"
"true<- item"
#<MatchData "support#keycreative.com">
"insert now false/nothing"
false
"true<- item"
"true<- item"
"true<- item"
As I'm doing my best to show after false is entered the code does skip the certain item from the list but it also never ever executes code for the other items as it should since it is an each loop
First I thought that maybe the continue is false however as you can see from the output the continue is true which makes me wonder why does ruby skip my code?
UPDATE
Here is where the to_bool method comes from:
class String
def to_bool()
return true if self == "true"
return false if self == "false"
return nil
end
end
In your last rescue statement add:
rescue => e
puts e.message
continue = true
next
end
and inspect the output. Most likely your code is throwing an exception other than "no more please" (I expect undefined method to_bool for true:TrueClass). Note that using exception for skipping the loop element is a terrible idea. Why can't you just get rid of this rescue and do:
if continue.chomp.to_bool == false
continue = true
next
end
There are a lot of things in this code which makes it very un-ruby-like. If you want to improve it please paste it to StackExchange CodeReview page. (link in the comment).
UPDATE:
My bad, you are in nested loop, so the if statement won't work. You might look at sth similar to raise/rescue bit, namely throw/catch, see example here: How to break from nested loops in Ruby?. I still think you should post it to codereview though for refactoring advises.
As to your actual code (without refactoring). You are calling to_bool method on continue, and in your rescue block you assign true instead of 'true'. Hence your to_bool method raises exception which is then rescued same way as 'no more please' exception.

Resources