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

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.

Related

I'm trying to design a simple Ruby calculator and I'm getting an error

So I've been messing around with Ruby for the first time after finishing the codecademy course up to "Object Oriented Programming, Part I" and I decided to start making a calculator. For some reason though, I get this error:
calc.rb:13:in `addition': undefined local variable or method `user_input' for main:Object (NameError)
from calc.rb:21:in `<main>'
I'm confused why it doesn't see my "user_input" array. Is it out of the scope of the method? Did I initialize it wrong?
Here's the code so you can see for yourself, it's obviously nothing sophisticated and it's not finished. I'm just trying to test for addition right now.
#!/usr/bin/env ruby
user_input = Array.new
puts "Would you like to [a]dd, [s]ubtract, [m]ultiply, or [d]ivide? "
type_of_math = gets.chomp
def addition
operator = :+
puts "Please enter the numbers you want to add (enter \"=\" to stop adding numbers): "
until gets.chomp == "="
user_input << gets.chomp.to_i
end
sum = user_input.inject(operator)
return sum
end
case type_of_math
when "a"
addition
when "s"
puts "Test for subtraction"
when "m"
puts "Test for multiplication"
when "d"
puts "Test for division"
else
puts "Wrong"
end
Consider this untested variation on your code. It's more idiomatic:
def addition
user_input = []
puts 'Please enter the numbers you want to add (enter "=" to stop adding numbers): '
loop do
input = gets.chomp
break if input == '='
user_input << input
end
user_input.map(&:to_i).inject(:+)
end
Notice that it puts user_input into the method. It also uses the normal [] direct assignment of an empty array to initialize it. Rather than chomp.to_i each value as it's entered it waits to do that until after the loop exits.
Instead of while loops, consider using loop do. They tend to be more easily seen when scanning code.
Also notice there's no return at the end of the method. Ruby automatically returns the last value seen.

Catch and throw not working in ruby

I am trying to make a number guessing game in Ruby but the program exits after I type in yes when I want to play again. I tried using the catch and throw but it would not work. Could I please get some help.
Here is my code.
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
until $g==$a
puts "Guess the number between 0-10."
$g=gets.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets()
if $s=="yes"
$c=true
end
if $c==true
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
Game.new.Play
Edit: Here's my new code after trying suggestions:
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
while $s=="yes"
until $g==$a
puts "Guess the number between 0-10."
$g=gets.chomp.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets.chomp
if $s=="yes"
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
end
Game.new.Play
Your first problem is here:
$s=gets()
if $s=="yes"
$c=true
end
The gets method will read the next line including the new line character '\n', and you compare it to only "yes":
> gets
=> "yes\n"
The idiomatic way to fix this in Ruby is the chomp method:
> gets.chomp
=> "yes"
That said, your code has two other deficiencies.
You may come from a language such as PHP, Perl, or even just Bash scripting, but Ruby doesn't require the dollar sign before variables. Using a $ gives a variable global scope, which is likely not what you want. In fact, you almost never want a variable to have global scope.
Ruby uses three types of symbol prefixes to indicate scope - # for instance, ## for class, and $ for global. However the most common type of variable is just local which doesn't need any prefix, and what I would suggest for your code.
I have always been told that it is very bad practice to use exceptions for control structure. Your code would be better served with a while/break structure.
When you do gets(), it retrieves the full line with a '\n' in the end. You need to trim the new line character by using:
$g=gets.chomp.to_i
Same for other gets
Based on your updated code (where you fixed the newline problem shown by others), your new problem is that you have wrapped all your game inside while $s=="true". The very first time your code is run, $s is nil (it has never been set), and so you never get to play. If you used local variables instead of global variables (s instead of $s) this would have become more obvious, because the code would not even have run.
Here's one working way that I would re-write your game.
class Game
def play
keep_playing = true
while keep_playing
answer = rand(11) # Make a new answer each time
puts answer if $DEBUG # we don't normally let the user cheat
loop do # keep going until I break from the loop
puts "Guess the number between 0-10."
guess = gets.to_i # no need for chomp here
if guess>answer
puts "The number you guessed is too high."
elsif guess<answer
puts "The number you guessed is too low."
else
puts "Correct you won!!!",
"Would you like to play again?"
keep_playing = gets.chomp.downcase=="yes"
break
end
end
end
end
end
Game.new.play
I know this doesn't really answer your question about why your code isn't working, but after seeing the code you posted I just had to refactor it. Here you go:
class Game
def initialize
#answer = rand(11)
end
def play
loop do
guess = get_guess
display_feedback guess
break if guess == #answer
end
end
def self.play_loop
loop do
Game.new.play
break unless play_again?
end
end
private
def get_guess
puts "Guess the number between 0-10."
return gets.chomp.to_i
end
def display_feedback(guess)
if guess > #answer
puts "The number you guessed is too high."
elsif guess < #answer
puts "The number you guessed is too low."
elsif guess == #answer
puts "Correct you won!!!"
end
end
def self.play_again?
puts "Would you like to play again?"
return gets.chomp == "yes"
end
end
Game.play_loop

Re-open a script in ruby

I would like to re-open my script at the end of its execution. I tried with load but it didn't work. Is what I want to do possible? If yes, what should I try?
This is the structure of my script:
usr_choice = gets.chomp.to_i
case usr_choice
when 1
# Do something
when 2
# Do something else
else
puts "Choice not recognised"
end
Basically I would like for example to go back to the user input (usr_choice) after going through the case statement. Without a GoTo.
So you want a loop? Well, use loop:
loop do
usr_choice = gets.chomp.to_i
case usr_choice
when 1
puts 'one'
when 2
puts 'two'
else
break # exit the loop (not the case construct as in other languages)
end
end
Example:
$ ruby filename.rb
1
one
2
two
1
one
kthxbye
$

Why is include? throwing an argument error?

This is a snippet from a larger chunk of code:
print "> "
$next_move = gets.chomp
case $next_move.include?
when "instructions"
puts "$next_move is instructions"
else
puts "$next_move is NOT instructions"
end
Everytime I run it in the terminal, whether I'm using ruby 1.8.7, 1.9.3, or 2.0.0, I get the following error:
test.rb:4:in `include?': wrong number of arguments (0 for 1) (ArgumentError)
from test.rb:4
This code worked last night on a different computer.
Isn't include? checking the contents of that global variable? What other argument should I be passing to it?
I'm kinda stumped here, especially since all I did was move the code from one computer to another.
http://www.ruby-doc.org/core-1.9.3/String.html#method-i-include-3F
Returns true if str contains the given string or character.
That means it requires exactly 1 argument, so no wonder it throws ArgumentError when called without arguments.
So the code should be:
if $next_move.include? 'instructions'
puts '$next_move is instructions'
else
puts '$next move is NOT instructions'
end
Something had to have changed between the two computers you tested this on. If you are wanting to use this as a case statement, you probably had something along the lines of:
next_move = 'instructions'
case next_move
when "instructions"
puts "$next_move is instructions"
else
puts "$next_move is NOT instructions"
end
This specifically tests if next_move IS instructions. As an if/else statement:
if next_move.include? 'instructions'
puts "$next_move is instructions"
else
puts "$next_move is NOT instructions"
end
See eval.in for more information.

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