This is lives within a method "play" that is called once. After you enter the while loop, you stay there until you exit the process. Right now, I'm trying to use the case statement to turn user-defined strings into the variable that is passed at the end to call the next method, all within the while loop.
def play
next_action = #start # comes from an initialize function earlier in script
while true
case next_action
when beginning
next_action = beginning
when "instruct"
next_action = instructions
when "display"
next_action = display_users
else
puts "Unknown command."
next_action = display_users
end
puts "\n----------"
next_action = method(next_action).call
end
end
First problem: the case statement fails to recognize any choice but the last.
Second problem: this leads to the loop ending, jumping to the last method called, and then exiting the process.
Any help or advice is appreciated.
See if changing
next_action = #start
to:
next_action = #start.chomp
gets you any further.
you should use a state-machine instead.
See: http://railscasts.com/episodes/392-a-tour-of-state-machines
Related
I am encountering the following unexpected behavior while running the following loop:
outside_var = 'myString'
loop do
inside_var ||= outside_var
result = SomeCalculation.do_something(inside_var)
inside_var = result[:new_inside_var_value]
end
Now, on the first iteration inside_var gets set to outside_var, which is the expected behavior. Just before the next iteration I set inside_var to something else (depending on the result I got from the calculation inside the loop). This assignment works (printing inside_var at the very bottom of the loop confirms that). On the next iteration, however, inside var goes back to the original state, which is something I didn't anticipate. Why is it doing that and how can I set this variable inside this loop?
I am running Ruby 2.6.5 with Rails 6.
This is a scoping issue. inside_var is scoped to the block. One might check the binding, it changes.
outside_var = 'myString'
2.times do
puts "INSIDE 1: #{defined?(inside_var).nil?} → #{binding}"
inside_var ||= outside_var
puts "INSIDE 2: #{inside_var}"
end
#⇒ INSIDE 1: true → #<Binding:0x000055a3936ee0b0>
# INSIDE 2: myString
# INSIDE 1: true → #<Binding:0x000055a3936edc50>
# INSIDE 2: myString
That said, every time the execution enters the block, the binding is reset, that’s why one should not expect the variables from another scope (with another binding) to exist.
When you do a new iteration inside the loop, you are going to reset everything. I suggest you to modify the var outside the loop to preserve the value inside. Something like this:
result_var = 'myString' # value on the first iteration
loop do
result = SomeCalculation.do_something(result_var)
result_var = result[:new_inside_var_value] # at the end of the first iteration you are already overriding this value
end
Basically in my search for code which will loop, and terminate upon user input, i managed to find code here, and after some alteration, produced this:
#desired destination method, however loop persists!!
def desired_method
print "method entered"
end
Thread.new do
while line = STDIN.gets
break if line.chomp == "" # code detects user input
end
desired_method
end
# program will loop here until user presses enter
loop do
puts "foo"
sleep 1
end
This code is brilliant, and will enter the method 'desired_method' when i hit enter, however the loop persists!! printing 'foo' perpetually after "method entered"!!. I have done some research prior to posting this question on how to kill threads, which i believe may hold the answer. My attempts included naming the thread and using the 'thread.exit' function to kill it, however these techniques have remained unsuccessful.
Can anyone illustrate how i might enter the 'desired_method' method without the persisting "foo" print?
Thanks in advance, and greatly appreciated.
An easy solution here is to use semaphore, signalling between threads with a variable access to both places:
# This will be out stop flag, for signalling between threads.
#time_to_stop = false
def desired_method
print "method entered"
# Here we want the loop in the other thread to stop.
#time_to_stop = true
end
Thread.new do
while line = STDIN.gets
break if line.chomp == "" # code detects user input
end
desired_method
end
# program will loop here until user presses enter
loop do
puts "foo"
sleep 1
# only continue if the stop flag is not set.
break if #time_to_stop
end
Hope this helps.
I have the following code
# colours a random cell with a correct colour
def colour_random!
while true do
col, row = rand(columns), rand(rows)
cell = self[row,col]
if cell.empty? then
cell.should_be_filled? ? cell.colour!(1) : cell.colour!(0)
break
end
end
end
it's not that important what's doing, although it should pretty obvious. The point is that Rubocop gives me a warning
Never use 'do' with multi-line 'while
Why should I not do that? How should I do it then?
while is a keyword,so you don't need to pass a block. Without do..end it will work fine. The below is fine
def colour_random!
while true
col, row = rand(columns), rand(rows)
cell = self[row,col]
if cell.empty? then
cell.should_be_filled? ? cell.colour!(1) : cell.colour!(0)
break
end
end
end
while is a keyword, and if you pass a block to it, like do..end, it still works as you asked it to do, by not throwing any error, rather just a warning. But it could be dangerous if you try to pass a Proc or Method object to it, and dynamically try to convert it to a block using & keyword, as we do generally. That means
# below code will work as expected just throwing an warning.
x = 2
while x < 2 do
#code
end
But if you try to do by mistake like below
while &block # booom!! error
The reason is while is a keyword, which don't support any to_proc method to satisfy your need. So it can be dangerous.
Ruby style guide also suggested that Never use while/until condition do for multi-line while/until
I think the reason is as Nobuyoshi Nakada said in the mailing list
loop is a kernel method which takes a block. A block introduces new local variable scope.
loop do
a = 1
break
end
p a #=> causes NameError
while doesn't.
while 1
a = 1
break
end
p a #=> 1
Ruby actually has a shortcut for while true: the loop statement.
def colour_random!
loop do
col, row = rand(columns), rand(rows)
cell = self[row,col]
if cell.empty? then
cell.should_be_filled? ? cell.colour!(1) : cell.colour!(0)
break
end
end
end
The code below works completely fine as long as users enter in the method name. I'd like to avoid requiring users to enter the name of the method at the various gets.chomp prompts.
I thought that using a case statement to translate the user input into method calls would work, but I keep getting a .include? NoMethodDefined error.
class Foo
def initialize(start_action)
#start = start_action
end
def play
next_action = #start
while true
case next_action.include?
when beginning
next_action = beginning
when "instruct"
next_action = instructions # returns instructions as
# the method that's called below
when "users"
next_action = users # returns users as the
# method that's called below
else
puts "Unknown command."
next_action = # some placeholder method call that gets the user
# back to being able to make another choice
end
puts "\n----------"
next_action = method(next_action).call
end
def beginning
puts "This is the beginning."
next_action = gets.chomp
end
def instructions
puts "These are the instructions"
# code to display instructions omitted
next_action = gets.chomp
end
def users
puts "Here are your users"
# code to display users omitted
next_action = gets.chomp
end
end
start = Foo.new(:beginning)
start.play
Any advice or help is appreciated.
On the first pass through your loop, next_action is the symbol :beginning and symbols don't have an include? method.
In addition I think you've misunderstood how case statements work - even removing the first error your code will then complain that you're passing 0 arguments to include? (instead of 1)
I think you instead mean something like
case next_action
when /instruct/
..
when /users
..
else
..
end
Which will test next action against each regular repression in turn
cool_words = []
while true
cool_words.push gets
break if gets.chomp == ''
end
puts cool_words
It is only pushing the first entry then the third and then the fifth. I think it is the way I have it breaking out of the loop because without the break method it doesn't happen.
I need it to break out of the loop when I hit enter on an empty line.
Thanks in advance!
You are calling gets twice in the loop. The first time it is being pushed into the array. The second time it is comparing against an empty string for loop breaking. But each time it is asking for a new line.
You only want to call gets one time per loop. So you can save it in a variable, and then use that variable multiple times later in the code.
cool_words = []
while true
line = gets
cool_words.push line
break if line.chomp == ''
end
puts cool_words
UPDATE: #MicahelKohl in the comments points out that you can accomplish the above task more elegantly like this:
cool_words = []
until (line = gets).to_s.chomp.empty?
cool_words << line
end
puts cool_words