Don't understand Ruby ljust/rjust/center methods - ruby

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

Related

How to do user-inputted string templating in Ruby?

I know writing like
a=23
p "the value of a is #{a}"
it will print: the value of a is 23.
but now I am actually receiving this string as a parameter like
def evaluate string
a=23
puts string
end
calling method pass that string as a parameter
evaluate "the value of a is #{a}"
Is there any way to evaluate this string inside the method? puts string has to interpolate the value a=23.
Edit:
I have to read and execute the program from Excel.
At the first line,
Excel entry is,
"id=something" setvalue a
So now corresponding program will read the value from locator id=something and set it into the instance variable #a.
and user's next excel entry would be
"the value of a is 23" compare "the value of a is #{a}"
Now the program will read "the value of a is 23" and this "the value of a is #{a}" for comparison, but before it compares, it has to replace the value a. That's all I want. I hope now my question is very clear.
For ruby you can change how you "format" your strings in Excel, than you can use "classic" formatting
a = 23
s = 'the value of a is %s'
def evaluate(text, value)
puts text % value
end
You can use different formatting keys, for example %d for integers, %f for float numbers
You can use named arguments
dynamic_text = 'the value of the %<product_name>s is %<product_price>0.2f'
def evaluate(text, args)
puts text % args
end
name = "Product"
price = 78.99
evaluate dynamic_text, product_name: name, product_price: price
Without names, use order of the given values
dynamic_text = 'the value of the %s is %0.2f'
def evaluate(text, args)
puts text % args
end
name = "Product"
price = 78.99
evaluate dynamic_text, [name, price]
You can make a block and then evaluate the string:
def evaluate &block
a=23
block.call(a)
end
evaluate { |a| "the value of a is #{a}" } #=> "the value of a is 23"
It's a very odd thing you're attempting to do. When you have some sort of a pattern with placeholders, you do it like:
def evaluate(string)
a=23
format string, a: a
end
evaluate "the value of a is %{a}"
String interpolation with #{..} is not meant for the case you're describing as the value is evaluated at the time of constructing the string, not later. You could do some regexp matching and replace the #{..} with %{..} as a workaround.
There's a few ways:
"Code" Dynamic
lazy evaluation with lambdas:
def evaluate(str_template)
a = 23
str_template.call(a)
end
user_input = gets
my_lambda = lambda do |str|
user_input.size > 10 ? "dynamic 1 #{str}" : "dynamic 2 #{str}"
end
evaluate(my_lambda)
# => "dynamic 1/2 23"
This is "code dynamic", but not "input dynamic", i.e. you can't receive the string template from the user.
"Input" Dynamic 1
ERB templating:
require 'erb'
user_input_erb = gets
puts user_input_erb # "Hello <%= name %>"
name = gets # also user input, e.g. "World"
ERB.new(user_input_erb).result
# => "Hello World"
Note that in general, getting string templates from the user and evaluating them is a potential security vulnerability. If there's any possibility user input can be adversarial, you'll want to see if you can find a "guaranteed to be safe against all user input" string templating library.
"Input" Dynamic 2
user_input_template = gets
puts user_input_template # "Hello %s"
name = gets # also user input, e.g. "World"
user_input_template % name
# => "Hello World"
"Input" Dynamic 3
Really dangerous, but:
user_input_ruby_code = gets
puts user_input_ruby_code # '"Hello #{name}"'
name = gets # also user input, e.g. "World"
eval user_input_ruby_code # DANGER
# => "Hello World"

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.

Passing Ruby Hash into Classes

I ran into a study drill problem, and I couldn't figure it out.
Here's the link to the exercise. https://learnrubythehardway.org/book/ex40.html
Below are my work. On Study Drill 2, I passed in variables and it worked.
However, at study drill 3, I broke my code. I realized I wasn't passing in variable, but a hash. And because my class takes in 2 arguments, I couldn't figure out how to pass a dictionary as 2 arguments.
class Song
def initialize(lyrics, singer)
#lyrics = lyrics
#singer = singer
end
def sing_along()
#lyrics.each {|line| puts line}
end
def singer_name()
puts "The song is composed by #{#singer}"
end
def line_reader(lineNum)
line = #lyrics[lineNum-1]
puts "The lyrics line #{lineNum} is \"#{line}\"."
end
end
# The lyrics are arrays, so they have [] brackets
practiceSing = Song.new(["This is line 1",
"This is line 2",
"This is line 3"],"PracticeBand")
practiceSing.sing_along()
practiceSing.singer_name()
practiceSing.line_reader(3)
puts "." * 20
puts "\n"
# Variable for passing. Working on dictionary to pass the singer value.
lovingThis = {["Don't know if I'm right",
"but let's see if this works",
"I hope it does"] => 'TestingBand'}
# Everything after this line is somewhat bugged
# Because I was using a variable as an argument
# I couldn't figure out how to use dictionary or function to work with
this
practiceVariable = Song.new(lovingThis,lovingThis)
practiceVariable.sing_along()
practiceVariable.singer_name()
practiceVariable.line_reader(3)
Here's the Output. What it should do is return the singer/band, and return requested lyrics line.
I'm new to coding, please advise how to pass hashes into classes?
How to pass lovingThis hash into Song.new() and read as 2 arguments?
you can pass hash to constructor of class in the same way as we pass any other variable, But for that you need to change your constructor definition to take variable number of arguments i.e def initialize(*args)
class Song
def initialize(*args)
if args[0].instance_of? Hash
#lyrics = args[0].keys.first
#singer = args[0].values.first
else
#lyrics = args[0]
#singer = args[1]
end
end
def sing_along()
#lyrics.each {|line| puts line}
end
def singer_name()
puts "The song is composed by #{#singer}"
end
def line_reader(lineNum)
line = #lyrics[lineNum-1]
puts "The lyrics line #{lineNum} is \"#{line}\"."
end
end
# The lyrics are arrays, so they have [] brackets
practiceSing = Song.new(["This is line 1",
"This is line 2",
"This is line 3"],"PracticeBand")
practiceSing.sing_along()
practiceSing.singer_name()
practiceSing.line_reader(3)
puts "." * 20
puts "\n"
# Variable for passing. Working on dictionary to pass the singer value.
lovingThis = {["Don't know if I'm right",
"but let's see if this works",
"I hope it does"] => 'TestingBand'}
practiceVariable = Song.new(lovingThis)
practiceVariable.sing_along()
practiceVariable.singer_name()
practiceVariable.line_reader(3)

Function calls in hash come up empty in Ruby

I've been sifting through the prior questions and answers on stackoverflow, and I have gotten most of my question figured out. I figured out that I can't place a function call within a hash, without placing it within a proc, or a similar container.
What I'm ultimately trying to do is have a menu displayed, grab user input, and then iterate through the hash, and run the specified function:
def Main()
menu_titles = {"Answer1" => Proc.new{Choice1()}}
Menu(menu_titles)
end
def Choice1()
puts "Response answer"
end
def Menu(menu_titles)
menu_titles.each_with_index do |(key, value),index|
puts "#{index+1}. #{key}"
end
user_input = 0
menu_titles.each_with_index do |(key, value), index|
if index.eql?(user_input)
menu_titles[value]
break
end
end
end
Main()
The issue I'm having right now is that I'm not entering the functions that my hash calls for. Whether I use a return or a "puts", I either get a blank line or nothing at all. If anyone has other recommendations about my code, I'm all ears also. To be honest, I don't like using procs, but that's mostly because I don't entirely know how they work and where to use them.
Right now for my menus I have:
user_input = 1
if user_input == 1
Choice1()
...
end
Here's how I would refactor this:
class Menu
attr_reader :titles
# initialize sets up a hard-coded titles instance variable,
# but it could easily take an argument.
def initialize
#titles = {
"Answer1" => Proc.new{ puts "choice 1" },
"Answer2" => Proc.new{ puts "choice 2" }
}
end
# This is the only public instance method in your class,
# which should give some idea about what the class is for
# to whoever reads your code
def choose
proc_for_index(display_for_choice)
end
private
# returns the index of the proc.
def display_for_choice
titles.each_with_index { |(key,value), index| puts "#{index + 1}. #{key}" }
gets.chomp.to_i - 1 # gets will return the string value of user input (try it in IRB)
end
# first finds the key for the selected index, then
# performs the hash lookup.
def proc_for_index(index)
titles[titles.keys[index]]
end
end
If you're serious about Ruby (or object-oriented programming in general), I would highly recommend learning about the advantages of packaging your code into behavior-specific classes. This example allows you to do this:
menu = Menu.new
proc = menu.choose
#=> 1. Answer1
#=> 2. Answer2
2 #(user input)
proc.call
#=> choice 2
And you could actually run it on one line:
Menu.new.choose.call

How could one block detect that its inside another block?

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

Resources