3

After going through the codecademy ruby section "A Night at the Movies", I wanted to extend the case-statement to allow input again. By the end my code was:

movies = {
  living_torah: 5,
  ushpizin: 5
}

def input #method for gets.chomp
    gets.chomp.downcase
end

puts "To exit please type 'Quit' or 'Exit'"
puts 'Please type "add", "display", "update" or "delete".'
choice = input

case choice
    when "add"
        puts "Movie Title please:"
        title = input.to_sym
        puts "How would you rate it?"
        rating = input.to_i
        if movies[title].nil?
            movies[title] = rating
            puts "Movie: '#{title.to_s.capitalize}' added with a Rating of # {rating}."
        else
            puts "That Movie already exists. Try updating it."
        end
    when "update"
        puts "Movie Title please:"
        title = input.to_sym
        if movies[title].nil?
            puts "That Title doesn't exist. Please 'add' it."
        else
            puts "Your Movie was found. How would you rate it?"
            rating = input.to_i
            movies[title] = rating
            puts "Movie: '#{title.to_s.capitalize}' updated with a Rating of #{rating}."
        end
    when "display"
        movies.each { |movie, rating| puts "#{movie}: #{rating}" }
    when "delete"
        puts "Which Movie would you like to delete?"
        title = input.to_sym
        if movies[title].nil?
            puts "That Title doesn't exist. Please 'add' it."
        else
            movies.delete(title)
            puts "The Movie '#{title.to_s.capitalize}' has been deleted."
        end
    when "exit", "quit"
        exit
    else
        puts "Invalid choice."
end

I added the "exit" case independently of the exercise hoping to C.R.U.D. until explicitly exiting the program. How would I change the code to be able to restart/reuse the case-statement indefinitely? (Also, is there a simpler/shorter way to produce the same results as this case-statement?)

Yu Hao
  • 119,891
  • 44
  • 235
  • 294
  • I assume that this is working code. If so, it's not suitable for SO. For advice on how to improve working code you should post it at SO's sister-site, [Code Review](http://codereview.stackexchange.com/questions/tagged/ruby). If you look over some of the Q&A's at CR I think you will be impressed by the quality of the answers that are given there. – Cary Swoveland Feb 04 '15 at 07:10
  • Thank you @YuHao for cleaning the oversights in the text. – moshiachman Feb 04 '15 at 15:13
  • @CarySwoveland The presented code may work independently of the core question yet a working solution for the requested help was not readily apparent. Thank you for your concern & I'll certainly check CR for future research. – moshiachman Feb 04 '15 at 15:20

2 Answers2

1

Put a loop around it.

loop do
    choice = input

    case choice
        .
        .
        .
        when "exit", "quit"
            break
        else
            puts "Invalid choice"
    end
end
ganbustein
  • 1,593
  • 11
  • 10
1

Well, you can put the entire case statement inside of a loop. Something like:

loop do

  puts "To exit please type 'Quit' or 'Exit'"
  puts 'Please type "add", "display", "update" or "delete".'
  choice = input

  case choice
    # ...
    when 'exit', 'quit'
      break
  end
end

However, large case statements like this are not idiomatic Ruby. You might consider more dynamic options, such as using object.send(method_name, args...).

Additionally, its also best to place your code inside of a class or module. This makes it easier to understand and keeps things organized. This is called encapsulation.

In the example below, you can see that a single method is responsible for a single piece of functionality, and the class as a whole is responsible for managing the delegation of its tasks. This is called the single responsibility principle.

class MyCode
  # store the current state for this object in an accessor.
  # `attr_accessor` defines a read-write property.
  attr_accessor :running

  def add_choice
    # your "add" code here
  end

  def update_choice
    # "update" code
  end

  def exit_choice
    # change the state of this class by marking `running` as false
    self.running = false
  end

  # `alias_method` defines a method called `quit_choice` that
  # runs the same code as `exit_choice`.
  alias_method :quit_choice, :exit_choice

  # reads a single input from the user and returns it,
  # in a normalized form. 
  # "Add" -> "add", "Do Something" -> "do_something"
  def read_choice
    STDIN.gets.chomp.downcase.strip.gsub(/\s+/, '_')
  end

  # Process a single command from the user.
  def process_choice
    choice = read_choice

    # the methods that correspond to user input are named
    # following the same pattern. "add" -> "add_choice"
    method_name = [choice, 'choice'].join('_').to_sym

    # check if the method actually exists.
    if self.respond_to? method_name
      # call the method named by `method_name`
      self.send(method_name)

    else
      # the method doesn't exist. 
      # that means the input was unrecognized.
      puts "Invalid choice #{choice}"
    end
  end

  # this method acts as a "run loop" that continues execution
  # until the `running` state changes.
  def choose
    # define the initial state.
    self.running = true
    # process a single input as long as the state hasn't changed.
    process_choice while self.running
  end
end
slushie
  • 480
  • 2
  • 10
  • 1
    Thank you for the thorough response. The first answer `loop...do` satisfies the core question while the second expands my options for further research. Thank you for touching upon the "refactoring" part. I'd appreciate if the second option would be explained step-by-step if possible. – moshiachman Feb 04 '15 at 15:10
  • @moshiachman I've edited my answer to include much more comments. Hope it helps! – slushie Feb 04 '15 at 18:24