How could one block detect that its inside another block? - ruby

This is my code:
def block
puts "from block"
yield
end
block do
puts "from command line"
block do
end
end
Here is the output:
from block
from command line
from block
I wonder how the second block could detect that its inside another block (of the same method).
So that the output will be this instead:
from block 1
from command line
from block 2
Is this possible? Because I want the nested block to be aware of this and run some additional code.
Thanks!

You could keep track of the block level with an instance variable, increment it whenever you enter a block, and decrement it whenever you leave a block:
def block
#block_level ||= 0
#block_level += 1
puts "from block ##block_level"
yield
#block_level -= 1
end

This answer is mostly just for fun, I don't suggest you use it.
Ruby lets you inspect the call stack in the form of a backtrace, but only when an exception is raised. So let's raise an exception and then stick out our arm and catch it before it goes to anyone else, and then: the backtrace is all ours!!
Then all you need to do is search the backtrace (an array) for any method calls to our method named "block", and count them.
class InspectBacktrace < Exception
end
def block
raise InspectBacktrace
rescue InspectBacktrace => e
level = e.backtrace.count { |x| x =~ /in `block'/ }
puts "from block #{level}"
yield
end
block do
puts "from command line"
block do
puts "from command line"
block do
puts "from command line"
end
end
end
Output:
from block 1
from command line
from block 2
from command line
from block 3
from command line
Edit: I've since come across the Kernel#caller method which just gives you the current execution stack with no hassles. So the following code might be acceptable, as long as you don't have two methods named "block" in the same file that call each other:
def block
level = caller.count { |x| x =~ /^#{ Regexp.escape(__FILE__) }:\d+:in `block'$/ } + 1
puts "from block #{level}"
yield
end

What yjerem says, just use ensure to avoid troubles with exceptions, and it sounds like a global variable, not instance variable.
def block
begin
$block_level ||= 0
$block_level += 1
puts "from block #{$block_level}"
yield
ensure
$block_level -= 1
end
end

Related

Is there any way to handling an Exception for Ruby's SyntaxError "both arg and actual block given"?

I want to write a method passing a block, but if a proc and an actual block are given at the same time, it will take only the first one.
I have tried to raise an Exception for SyntaxError, but it keeps prompting an error. This is one of the things that I was trying.
def my_map(&proc)
raise SyntaxError, "using first block given"
rescue
arr = []
proc = proc.call(i) || yield(i)
self.my_each do |i|
arr << proc
end
arr
end
I also tried to add a condition for the raise keyword.
Of course, code works if only one block is given.
I want to write a method passing a block, but if a proc and an actual block are given at the same time, it will take only the first one.
def f(*args)
if args.length == 1
args.first.call
else
yield
end
end
puts 'test 1'
f(->() { puts 'a' }) { puts 'b' }
puts 'test 2'
f { puts 'b' }
Output
test 1
a
test 2
b

Why the global variable not works in .rb file but works in irb?

I was trying to add indentation based on how deep the block goes. I used a global variable to record the depth of block.
$depth = 0
def log(des, &block)
indentation = " " * $depth
$depth += 1
puts "#{indentation}Begginning the #{des} block"
puts "#{indentation}Finished #{des} and returned: #{block.call}"
$depth -= 1
end
log "outer block" do
log "second level block" do
log "third level block" do
"I am number 3"
end
"I am number 2"
end
"I am out most!"
end
In terminal I tried several times ruby file_name.rb, it showed no indentation, it even won't puts the global variable out. After then I copy the code to irb and it worked.
Why this happened?
What's the difference when running ruby code in between these two places?
I suspect you have a different definition of log() in your environment. Changing the name of your routine to something else (say 'mylog') will test this hypothesis.

Move to next iteration of a loop through a function inside the loop in ruby

I am trying to write a piece of code where i can move to the next iteration of a loop while inside a method called in the loop.
In sample code, this is what i am trying to do
def my a
if a > 3
next
end
end
x = [1,2,3,4,5,6,7]
for i in x
my i
print i
end
This gives a syntax error.
One way to achieve this is by raising an error and catching it.
def my a
if a > 3
raise "Test"
end
end
x = [1,2,3,4,5,6,7]
for i in x
begin
my i
print i
rescue Exception => e
#do nothing
end
end
But exceptions are expensive. I dont want to return anything from the function or set flag variables in the function because i want to keep the code clean of these flags.
Any ideas?
A Ruby way of having a function affect the caller's flow of control is for the caller to pass the function a block, which the function can then execute (or not):
def my a
yield unless a > 3
end
x = [1,2,3,4,5,6,7]
for i in x
my i do
print i
end
end
# => 123
See also: Blocks and yields in Ruby

Don't understand Ruby ljust/rjust/center methods

I'm learning the nesting and my task is to make each new line start with an indent. Here's my code, but it doesn't work
$nestingDepth = 0
def logger description, &block
puts "Beginning #{description}".rjust($nestingDepth)
puts $nestingDepth
$nestingDepth = $nestingDepth + 10
result = block.call
$nestingDepth = $nestingDepth - 10
puts $nestingDepth
puts "End of #{description} block that returned #{result}".rjust($nestingDepth)
end
logger "first block" do
logger "second block" do
logger "third block" do
puts "third block part"
end
puts "second block part"
end
puts "first block part"
end
Your code has several problems:
you are using global variables which is generally a bad idea, pass it down as an argument instead. To do this you can use a DSL class that defines the logger and log methods.
you are calling puts inside the blocks, but you never changed the definition of it, I don't see how you were expecting it to print an indented string, it will just print the string normally without indentation. For this to work you need to define a special method that prints with indentation, e.g. log
you are calling rjust with the expectation that it will indent the string. This method has a different purpose - justifying a string to the right (i.e. left-padding it) with a specified length. If the string is longer than the specified length, the original string is returned. To actually indent a string you should do puts ' ' * nestingDepth + string. Looks magic at first, but the * operator just repeats the string, e.g. 'abc' * 3 #=> 'abcabcabc'
All taken together I would do it like this:
class DSL
def initialize
#depth = 0
end
def logger(description, &block)
log "Beginning #{description}"
#depth += 1
result = instance_eval(&block)
#depth -= 1
log "End of #{description} that returned #{result}"
end
def log(string)
puts indent + string
end
private
def indent
' ' * (10 * #depth)
end
end
def logger(*args, &block)
DSL.new.logger(*args, &block)
end
Example:
logger "first block" do
logger "second block" do
logger "third block" do
log "third block part"
end
log "second block part"
end
log "first block part"
end
This prints:
Beginning first block
Beginning second block
Beginning third block
third block part
End of third block that returned
second block part
End of second block that returned
first block part
End of first block that returned
Your issue is that rjust requires an integer greater than the length of the string it's applied on. Your string is:
"Beginning #{description}"
Which turns into:
Beginning first block
Beginning second block
On most passes this is either a length of 21 or 22. The largest you ever make $nestingdepth is 20. When the integer is less than the length of the string it just returns the string with no padding. If I start the script with a nesting depth of 25 you see it unfold.
Beginning first block
25
Beginning second block
35
Beginning third block
45

Does begin . . . end while denote a 'block'?

temp = 98.3
begin
print "Your temperature is " + temp.to_s + " Fahrenheit. "
puts "I think you're okay."
temp += 0.1
end while temp < 98.6
In the above example, is everything between begin and end a block?
I'm still confused what a block is.
If you can't call it a block, what would you call that chunk of code between begin and end? Is it ok to call it a chunk?
Block has a special meaning in Ruby. According to Matz, Ruby's creator, you can look at a block as a nameless function - typically something that can be yielded into, and which may also take parameters.
You may see the following kind of disamiguation when describing Ruby syntax:
begin...end (what is called block in other languages) may sometimes be referred to simply as what it is, i.e. an expression (which may in turn contain other expressions - an expression is simply something that has a return value) in Ruby. Some references will still call it a begin/end block, or a code block, adding somewhat to the confusion
do...end or {...} will always be referred to as a block in Ruby
For examples, peruse the the Ruby syntax man page, e.g.
begin expression end
expression while expression
loop block
For further reading, see:
Programming Ruby
Ruby (from other languages)
Much, much more documentation
begin/end are strictly control flow, not blocks.
begin
puts "hi"
end
# => "hi"
The code runs immediately. If it was a block, it would have to been called somehow in order for the code in it to run, as in this example:
def a_method; end
a_method { puts "hi" }
# nothing..
def a_method
yield
end
a_method { puts "Hi!" }
# => "Hi!"

Resources