Need Help with Nested Loops


#1

Want to make this code loop back to the original inputs, 'What would you like to do' after completing an action. New to Ruby, could use some guidance/ an example. This isn't part of the tutorial but something I want to do for myself.

movies = { Hackers: 4,
The_Shawshank_Redemption: 4,
Lucky_Number_Slevin: 4
}

puts "What would you like to do?"
puts "--Add:"
puts "--Update:"
puts "--Display:"
puts "--Delete:"
puts "--Exit:"
choice = gets.chomp.downcase
case choice

when "add" then print "Please provide the name of the movie: "
title = String(gets.chomp)
if movies[title.to_sym].nil?
print "Please provide a rating for #{title}: "
rating = Integer(gets.chomp)
movies[title.to_sym] = rating.to_i
puts "Added!"

else
puts "That movie is already in the database!"
end
when "update" then print "Type the name of the movie you wish to update: "
title = String(gets.chomp)
if movies[title.capitalize.to_sym].nil?
puts "That movie is not yet in the database!"

else
print "Please provide a rating for #{title}: "
rating = Integer(gets.chomp.capitalize)
movies[title.to_sym.capitalize] = rating.to_i
puts "Updated!"
end
when "display" then
movies.each do |name, rating|
name = name.to_s.split("_").collect(&:capitalize).join(" ")
puts "#{name}: #{rating}"
end

when "delete" then
print "Type the name of the movie you wish to delete: "
title = String(gets.chomp)
if movies[title.capitalize.to_sym].nil?
puts "That movie is not in the database and therefore cannot be deleted."

else
movies.delete(title)
end
when "exit" then
puts "Goodbye."

else
puts "That was not a valid entry."
end


#2

The easiest way to achieve this is via recursion. Just wrap the whole thing in a method and then call it from inside the method.

def my_method
  puts "What would you like to do?"
  choice = gets.chomp.downcase
  #then all of the code is here
  #and then you call the method from the inside
  my_method
end # <- here the method's closed

When dealing with recursion you'll be trapped inside that loop infinitely, that's why you have to make sure you have "a way out" of that method, for example the next code:

def my_method
  puts "What would you like to do?"
  choice = gets.chomp.downcase
  if choice == "end"
    return # <- this will cause the method to exit, e.g it will stop executing at this line
  end
  #then all of the code is here
  #and then you call the method from the inside
  my_method
end # <- here the method's closed

I hope this was helpful


#3

Processing the response now but I like where your head is at!


#4

Okay the logic of what you're suggesting makes sense to me, but I can't seem to get it to work properly. Following the schema of the 2nd block of code you posted, the code passes but doesn't ask the user to enter a command. Instead it just outputs nil.


#5

Could you paste the code you're using and the output you see?


#6

def movie_db

movies = { Hackers: 4,
The_Shawshank_Redemption: 4,
Lucky_Number_Slevin: 4
}

puts "What would you like to do?"
puts "--Add:"
puts "--Update:"
puts "--Display:"
puts "--Delete:"
puts "--Exit:"
choice = gets.chomp.downcase

if choice == "Exit"
return
end

case choice
when "add" then print "Please provide the name of the movie: "
title = String(gets.chomp)
if movies[title.capitalize.to_sym].nil?
print "Please provide a rating for #{title}: "
rating = Integer(gets.chomp)
movies[title.to_sym] = rating.to_i
puts "Added!"

else
    puts "That movie is already in the database!"
end

when "update" then print "Type the name of the movie you wish to update: "
title = String(gets.chomp)
if movies[title.capitalize.to_sym].nil?
puts "That movie is not yet in the database!"

else
    print "Please provide a rating for #{title}: "
    rating = Integer(gets.chomp.capitalize)
    movies[title.to_sym.capitalize] = rating.to_i
    puts "Updated!"
end

when "display" then
movies.each do |name, rating|
name = name.to_s.split("_").collect(&:capitalize).join(" ")
puts "#{name}: #{rating}"
end

when "delete" then
print "Type the name of the movie you wish to delete: "
title = String(gets.chomp)
if movies[title.capitalize.to_sym].nil?
puts "That movie is not in the database and therefore cannot be deleted."
else
movies.delete(title)
end

when "exit" then
puts "Goodbye."

else
puts "That was not a valid entry."

end
movie_db

end

The output is below:


#7

Currently all you do is define one giant function. In order to start it you have to actually call that function (or method). So you just have to add after the final end movie_db to call it.


#8

end
movie_db

end
movie_db

when the code looks like this it works, but it does not stop looping when I type exit. I changed nothing else from what was above. Thoughts?


#9

Read through your code :wink: Very carefully. You downcase the user's input but you exit only if it's capitalised e.g. Exit. Also you have exit clause in your case statement.


#10

I was just testing it out myself, and I came to the same solution! I didn't remove the exit clause from my case statement, would you say that is worth doing?


#11

It would clarify your code a bit, I mean it's not really obvious when you read it which clause would get executed when you write "exit". Currently the case "exit" clause is redundant because you'll never actually reach it. I could also recommend you splitting that giant method into a lot smaller ones. And use a global variable for your movies variable that's actually outside of all of the methods. (Global variables in ruby have dollar sign in front of them)

$movies = { Something: 59 }

def methods
...

#12

Yeah these are all good bits of advice. I've only just started learning Ruby, finished the tutorial yesterday.

I figured out which 'exit' to remove. I'm still getting used to the idea of adding global and instance variables, and breaking things down into methods.

If you're willing to do a little re-factoring, I'd love to see how this program might look if it were broken down efficiently as you've described.


#13

There you go. It's not the most efficient nor the best refactor but it does the job. As you can see, I've added 2 global variables for the movies and then for the choice (that could've been local variable easily but whatever)

I've extracted every case into it's own additional method. That way you have nice little blocks of code you can work with and the case statement is actually readable now. At a glance you can see what paths the user can take without caring about the implementation of each method. I've left the exit clause where the loop exit happens.

I've also removed the recursion and gone with a classic endless loop while true. Inside both methods (for the inital screen and action_methods) get called. The exit in the case "exit" clause actually brakes the outer loop (the while true loop) and stops the program.

$movies = {
  Hackers: 4,
  The_Shawshank_Redemption: 4,
  Lucky_Number_Slevin: 4
}
$choice = "none"

def initial_screen
  puts "What would you like to do?"
  puts "--Add:"
  puts "--Update:"
  puts "--Display:"
  puts "--Delete:"
  puts "--Exit:"

  $choice = gets.chomp.downcase
end

def add_movie
  print "Please provide the name of the movie: "
  title = String(gets.chomp)
  if $movies[title.capitalize.to_sym].nil?
    print "Please provide a rating for #{title}: "
    rating = Integer(gets.chomp)
    $movies[title.to_sym] = rating.to_i
    puts "Added!"
  else
    puts "That movie is already in the database!"
  end
end

def update_movie
  print "Type the name of the movie you wish to update: "
  title = String(gets.chomp)
  if $movies[title.capitalize.to_sym].nil?
    puts "That movie is not yet in the database!"

  else
    print "Please provide a rating for #{title}: "
    rating = Integer(gets.chomp.capitalize)
    $movies[title.to_sym.capitalize] = rating.to_i
    puts "Updated!"
  end
end

def display_movies
  $movies.each do |name, rating|
    name = name.to_s.split("_").collect(&:capitalize).join(" ")
    puts "#{name}: #{rating}"
  end
end

def delete_movie
  print "Type the name of the movie you wish to delete: "
  title = String(gets.chomp)
  if $movies[title.capitalize.to_sym].nil?
    puts "That movie is not in the database and therefore cannot be deleted."
  else
    $movies.delete(title)
  end
end

def movie_actions
  case $choice
  when "add" then
    add_movie
  when "update" then
    update_movie
  when "display" then
    display_movies
  when "delete" then
    delete_movie
  when "exit" then
    puts "Goodbye."
    exit
  else
    puts "That was not a valid entry."
  end
end

while true
  initial_screen
  movie_actions
end

#14

This is really fantastic work and I appreciate you sharing it with me! I feel as though now I'm at the point that I can look at the code you provided and have a pretty good understanding of how the parts interact with one another and why.

Thanks so much for your help!