validation on user input ruby - ruby

I created a gem that looks for a recipe with user input. I have almost 1000 recipes available to search for. How can I validate the user input when it does not match the names of my recipes?
as an example when the user type nabucodonosor or vocka the method load_recipe_by_ingridients return empty and I wish I could fix that. I'm using Ruby vanilla.. no rails
def start
puts "Hey there! you hungry? lets find some recipes ideas for you."
list_recipe_by_ingredients
show_summary
while #input != "exit"
if #input == "back"
list_recipe_by_ingredients
elsif valid_input?
puts Recipe.find_by_number(#input).summary
else
puts "Ops! not a valid number. try again."
end
ask_for_choices
end
end
def load_recipe_by_ingredients
puts "Search for recipes with the main ingredient, example: milk, pizza, eggs flour, ect."
#input = gets.strip.downcase
puts " "
Recipe.search_for_recipes(#input).each.with_index(1) do |recipe, index|
puts "#{index}. #{recipe.title}"
end
end

A pretty simple solution is to loop until a recipe is found, and once one or more recipes are found you break out of the loop and then render them:
def load_recipe_by_ingredients
recipes = []
loop do
puts 'Search for recipes with the main ingredient, example: milk, pizza, eggs flour, etc.'
input = gets.strip.downcase
puts
recipes = Recipe.search_for_recipes(input)
break if recipes.any?
puts 'No recipes found, please try again'
end
recipes.each.with_index(1) do |recipe, index|
puts "#{index}. #{recipe.title}"
end
end

Related

List in Ruby gem cli

I am making a ruby cli that outputs a list of game deals scraped from a site.
The list prints out promptly using
def games_sales
Deal.all.each_with_index do |deal, index|
puts "#{index + 1}. #{deal.title}"
end
puts "What game do you want to see?"
input = gets.strip
game_selection(input.to_i)
end
My problem comes when asking the user to select an item from the list.
def game_selection(input)
deal = Deal.find_by_index(input)
#binding.pry
deal.each do |deal|
puts "#{deal.index}"
puts " Name: #{deal.title}"
puts " Price: #{deal.price}"
puts " Store: #{deal.store}"
puts " Expiration: #{deal.expiration}"
end
deal
end
It returns the int input but only the first item on the list every time.
I forgot my find_by_index method:
def self.find_by_index(input)
all.select do |deal|
end
end
which is incomplete
Not 100% sure if I got your question right and if you're using Rails, but Deals.all let me think of this.
I had to replace Deals.all with DEALS for testing as I haven't got a rails app running. So I used an Array of OpenStructs to fake your Model result.
# this fakes Deals.all
require 'ostruct'
DEALS = [
# add any more properties the same way as title, separated by comma
OpenStruct.new(title: 123),
OpenStruct.new(title: 456)
]
def games_sales
DEALS.each_with_index do |deal, index|
puts "#{index + 1}. #{deal.title}"
end
puts "What game do you want to see?"
input = gets.strip
game_selection(input.to_i)
end
def game_selection(input)
deal = DEALS.at(input-1)
p deal[:title]
end
def self.find_by_index(input)
all.select do |deal|
deal.index == input
end
end
games_sales
Result when choosing 1 is 123, choosing 2 you'll get 456, due to p deal[:title] above in the code.
I think your find_by_index need to get the right index and in my example I had to use at(index) as at(input-1) in order to get the right result.
I really hope this helps somehow and I suggest that you add the expected result to your question, in case my answer does not help you.

Asking a user multiple questions using Ruby gets.chomp method

I'm very new to Ruby and practicing with user input. I have coded the following which allows the user to input names of students continuously until they hit return twice. After each input the program returns how many students are in the school, and when they are finished inputting for good, it prints out a list of the students and the cohort they are in.
At the moment, the cohort is hardcoded, and I want to modify this so that I can ask for both the name and the cohort, with the program continuing to ask for this info until the user hits return twice. Any help would really be appreciated - thanks :)
puts "Please enter the names of the students"
puts "To finish, just hit return twice"
students = []
name = gets.chomp
while !name.empty? do
students << {name: name, cohort: cohort}
puts "Now we have #{students.count} students"
name = gets.chomp
end
students
end
def print_header
puts "The students of this Academy".center(50)
puts "-----------".center(50)
end
def print(students)
students.each do |student, index|
puts "#{student[:name]} #{student[:cohort]} cohort"
end
end
end
def print_footer(names)
puts "Overall, we have #{names.count} great students".center(50)
end
students = input_students
print_header
print(students)
print_footer(students)
Instead of a while loop, I'd suggest to use loop do and break in case the name is empty (or also cohort):
loop do
puts "Please enter the names of the students"
name = gets.chomp
break if name.empty?
puts "Please enter the cohort"
cohort = gets.chomp
# break if cohort.empty?
students << {name: name, cohort: cohort}
end

Sentence rotate program not working ruby

I'm trying to write a program that takes a user's input (sentence), a word the user wants to rotate around, and outputs a rotated sentence around the word that has been chosen by user.
eg. Sentence: This is a book
Word to rotate around: book
Output: book This is a
I can't seem to exit this loop of entering data (the program keeps asking for input, not doing anything more.)
Please help. Here's my code:
class SentenceRotator
def get_sentence
puts "Please enter your sentence: "
sentence = gets.chomp
get_word
end
def get_word
puts "Please enter the word you want to rotate around: "
word = gets.chomp
if converts_sentence_to_array.include?(word)
rotate_sentence_around_word
else
puts "Your word isn't in the sentence. Please enter another word."
word = gets.chomp
end
rotate_sentence_around_word
end
def converts_sentence_to_array()
get_sentence.split(" ")
end
def rotate_sentence_around_word()
new_array = converts_sentence_to_array.each_with_index {|word,index| converts_sentence_to_array.rotate(index)}
new_array
end
end
new_app = SentenceRotator.new
new_app.rotate_sentence_around_word
new_app = SentenceRotator.new
new_app.rotate_sentence_around_word
So, calling methods: rotate_sentence_around_word => converts_sentence_to_array => get_sentence => get_word => converts_sentence_to_array => get_sentence => ...
Try something like this:
class SentenceRotator
def gets_user_data
puts "Please enter your sentence: "
#sentence = get_sentence.split(" ")
puts "Please enter the word you want to rotate around: "
#word = get_word_to_rotate_on
end
def get_sentence
gets.chomp
end
def get_word_to_rotate_on
word = gets.chomp
return word if #sentence.include?(word)
puts "Your word isn't in the sentence. Please enter another word."
get_word_to_rotate_on
end
def rotate_sentence_around_word()
gets_user_data
#sentence.rotate(#sentence.index(#word)).join(' ')
end
end
new_app = SentenceRotator.new
new_app.rotate_sentence_around_word
Here's the logic you want, split up into separate lines:
# input
sentence = 'This is a book'
word = 'book'
# processing
words = sentence.split
pos = words.index(word)
rotated = words.rotate(pos)
back_together = rotated.join(' ')
# output
p back_together
I'd advise you to separate out your processing code as much as possible. Then you can focus on the terminal input and output logic, which is what actually seems to be your issue.
I can't seem to exit this loop of entering data (the program keeps asking for input, not doing anything more.)
It looks like your foremost problem is to take the input from user properly and enter the rotation logic. You could make use of attr_reader to access the input across methods. I have made some changes to your class to accept the input in multiple steps:
class SentenceRotator
attr_reader :sentence, :rotate_on_word
def get_sentence
puts "Please enter your sentence: "
#sentence = gets.chomp
end
def get_word_to_rotate_on
puts "Please enter the word you want to rotate around: "
#rotate_on_word = gets.chomp
unless sentence.include?(rotate_on_word)
puts "Your word isn't in the sentence. Please enter another word."
get_word_to_rotate_on
end
end
def rotate
puts sentence
puts rotate_on_word
puts 'You have all the info. Add the logic to rotate.'
end
end
> new_app = SentenceRotator.new
> new_app.get_sentence
Please enter your sentence:
This is a very funny book
> new_app
=> #<SentenceRotator:0x00007fee44178c40 #sentence="This is a very funny book">
> new_app.get_word_to_rotate_on
Please enter the word you want to rotate around:
a
> new_app
=> #<SentenceRotator:0x00007fee44178c40 #sentence="This is a very funny book", #rotate_on_word="a">
> new_app.rotate
This is a very funny book
a
You have all the info. Add the logic to rotate.

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

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

Resources