Ruby: My code doesn't pass if I use `.downcase!` - ruby

I'm trying to complete the "A Night at the Movies" Ruby course on CodeCademy, and encountered a little issue: My code doesn't pass if I use downcase! on choice and the input is lower-case - if it contains an upper-case letter or is entirely upper-case, it works fine.
Here's my code:
movies = {
Foo: "Bar",
}
puts "Commands:"
puts "Add - Add a movie and its rating."
puts "Update - Update a movie's rating."
puts "Display - Display all movies and their ratings."
puts "Delete - Delete a movie and its rating."
choice = gets.chomp.downcase!
case choice
when 'add'
puts "Added!"
when 'update'
puts "Updated!"
when 'display'
puts "Movies!"
when 'delete'
puts "Deleted!"
else
puts "Error!"
end
Help?

Try using #downcase instead of #downcase!.
The reason your code is not passing is because when using #downcase! on an already lowercased string, the result returned is nil, which is what then gets assigned to your choice variable.
Using #downcase would return the string itself, even if the provided string is already lowercased.
Hope it helps!

Related

How can I make script work in Ruby?

I am new to Ruby.
I need to make this script work:
puts "Do you like cats?"
ask = gets
def ask(n)
if ask == yes
return "I do too"
end
if ask == no
return "Dogs are better"
end
end
puts "#{ask(n)}"
Error message is :
pracif.rb:15:in <main>': undefined local variable or methodn' for
main: Object (NameError)
Here's a script that would work for you :
puts "Do you like cats?"
answer = gets
def ask(n)
if n == 'yes'
return "I do too"
end
if n == 'no'
return "Dogs are better"
end
end
puts ask(answer.downcase.chomp)
Explaination
As the error said you were trying to pass in a variable n which was not defined
Secondly you have a method name ask same as variable name. I've renamed the variable to answer instead
Thirdly, enclose yes and no in quotes
And finally, since you are using gets a \n gets appended like yes\n so none of your conditions would match. So i've used chomp to remove \n. And also used downcase to make input case insensitive.
EDIT
As mentioned by #Jordan in the comments, there is no reason to use string interpolation for the puts statement. So it's enough to call the method directly.
There are a bunch of issues with your code. Try something more like:
def reply(response)
return 'I do too' if response == 'yes'
return 'Dogs are better' if response == 'no'
'Invalid response!'
end
puts 'Do you like cats?'
response = gets().chomp()
puts reply(response)
Pay attention to the variable names. If you keep them descriptive, it is easier to spot mistakes.
Your script has no n local variable defined that you are passing to your ask(n) method at the end.
Rename your ask variable that your script gets from user to answer for example and pass it to your ask method at the end like so:
Updated code to fix other problem I did not see in the first run.
puts "Do you like cats?"
answer = gets.chomp
def ask(n)
(n == 'yes') ? "I do too" : "Dogs are better"
end
puts "#{ask(answer)}"

Codecademy - a night at movies 4/10: Prompting: Redux

I can't find the error, it keeps telling me: Oops, try again. It looks like you didn't add to the movies hash!
Thank you.
movies = { 'himym' => 5,
'oitnb' => 4 }
puts "Which one do you like better?"
choice = gets.chomp
case choice
when "add"
puts "What is your favorite movie?"
title = gets.chomp
puts "What would you rate it?"
rating = gets.chomp
movies = {}
movies[title] = rating
puts "#{title} with rating #{rating} has been added!"
when "update"
puts "Updated!"
when "display"
puts "Movies!"
when "delete"
puts "Deleted!"
else
puts "Error!"
end
I tested your code - I got stuck in the same part. Instructions at CodeAcademy are not always clear. Your code, according to what CA is asking is right, you just need to write "add" as an answer for "Which one do you like better?" when testing code at CA's virtual machine. So:
choice = "add"
That way you will be running your case/when filter. Got it?

Trying to reference earlier 'case' in a case statement

When someone tries to update a value that is not currently stored in my hash, I would like to immediately refer back to when 'add' without restarting the entire case statement since I already know they want to add and don't want to prompt them again.
Is there a way to refer back to the case choice -> when "add" section of my code without restarting the entire case statement?
I know I could use nested case statements but I would rather not copy/paste identical code if I don't have to.
hash = {}
puts "Would you like to add or update this hash?"
choice = gets.chomp
case choice
when "add"
puts "What key you like to add?"
key = gets.chomp
puts "With what value?"
value = gets.chomp
hash[key] = value
when "update"
puts "Which key would you like to update?"
key = gets.chomp
if hash[key].nil?
puts "Key not present, would you like to add it?"
#here I would like the code that references back to "when 'add'" if the user types 'yes'
Sorry for the abrupt ending of the code. I didn't want to put in anything unnecessary to the solution.
Create a method/function that wraps the functionality inside that case. Then you can call that function from both places
hash = {}
def add_key
puts "What key you like to add?"
key = gets.chomp
puts "With what value?"
value = gets.chomp
hash[key] = value
end
puts "Would you like to add or update this hash?"
choice = gets.chomp
case choice
when "add"
add_key
when "update"
puts "Which key would you like to update?"
key = gets.chomp
if hash[key].nil?
puts "Key not present, would you like to add it?"
add_key

How to restart or reuse a case statement in Ruby?

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?)
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
Put a loop around it.
loop do
choice = input
case choice
.
.
.
when "exit", "quit"
break
else
puts "Invalid choice"
end
end

Unexpected boolean result using .nil? on a hash

I am working through exercises on codecademy and I came across a solution that I do not completely understand involving .nil? Here is my code :
movies = { GIS: 10.0, Phantasm: 1.5, Bourne: 4.0}
puts "Whats your movie brah?"
title = gets.chomp
puts "What's your rating brah?"
rating = gets.chomp
movies[title.to_sym] = rating.to_i
puts "Your info was saved brah!"
case movies
when 'add'
puts "What movie do you want to add son?"
title = gets.chomp
if movies[title.to_sym].nil?
puts "What's your new rating brah?"
rating = gets.chomp
movies[title.to_sym] = rating.to_i
puts "#{title} has been added with a rating of #{rating}."
else
puts "That movie already exists! Its rating is #{movies[title.to_sym]}."
end
when "update"
if movies[title.to_sym].nil?
when "display"
puts "Movies!"
when "delete"
puts "Deleted!"
else puts "Error!"
end
I am only referring to the add method. the rest of the script is a work in progress. I don't like not understanding things though and this has me in a bit of a quandry.
My question is does Ruby know not to add a title that already exists because two symbols cannot have the same name? I am curious how it determines when the hash has no value. Can anyone clarify this for me? I would really appreciate it!
The answer is a bit more complicated than that.
From the RubyDoc: "Two objects refer to the same hash key when their hash value is identical and the two objects are eql? to each other."
An object's hash value is a calculated numerical result based on the data the object contains. And the eql? method tests if two objects are equal, and this is usually aliased to == in ruby (i.e my_string1 == my_string2 is the same as my_string1.eql? my_string2).
When you say movies[title.to_sym], Ruby is saying "In the movies hash, are there any pairs currently stored where the key.eql? title.to_sym and key.hash == title.to_sym.hash? If so, return that value of the pairing, and if not return nil.
The reason Ruby doesn't add the title if it already exists is because of your if movies[title.to_sym].nil? line, which in English translates to "only do what follows if no pairing for the key title.to_sym exists."
If you had title = "GIS", and you were to just say movies[title.to_sym] = 1, Ruby would gladly over write the 10.0 you currently have stored there so that movies[:GIS] returned 1.

Resources