Ruby validation of name, email, and phone number - ruby

After creating the loop to check that the phone number is 10 characters I believe the phone issue is now resolved. Now I'm working with checking the email address, name, and making sure it outputs correctly, and making sure 2 names are entered by the user. Having issues getting the email address to validate and output in the correct format.
NAME_PATTERN = /([\w\-\']{2,})([\s]+)([\w\-\']{2,})/
EMAIL_PATTERN = /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
PHONE_PATTERN = /^(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})$/
def valid_name?(name)
!!name.match(NAME_PATTERN)
end
puts "Enter your first and last name (John Doe): "
while (name=gets)
names = name.split(" ", 2)
if valid_name?(name)
puts "Great, that looks like it could work."
break
else
puts "Please enter first and last name."
end
end
def valid_email?(email)
!!email.match(EMAIL_PATTERN)
end
puts "Enter your email address (joe#info.com): "
while (email = gets)
if valid_email?(email)
puts "Great, that looks like it could work."
break
else
puts "Invalid email address entered. Please try again. "
end
end
def valid_phone?(number)
!!number.match(PHONE_PATTERN)
end
puts "Enter your phone number including area code (numbers only): "
while (number=gets)
if valid_phone?(number)
puts "Great, that looks like it could work."
break
else
puts "Invalid phone number entered. Please try again."
end
end
puts names
puts email
puts number

I suspect you didn't intend to use the assignment operator here:
if (email = /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i)
Try this:
if email =~ /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
You have a similar error in the phone_number method:
if number = 10
I'm not sure what you intended here. Perhaps this?
if number.size == 10
You have more problems, however. Take a look at this loop:
loop do
if number.size == 10
break
else
puts "Invalid phone number entered. Please try again."
end
end
How will the user ever exit this loop if the number is invalid? The value of number never changes.

Here's a Ruby flavoured approach to what you're trying to do:
# Define constants for things that are special and get re-used.
EMAIL_PATTERN = /\A\S+#\S+\z/
# Methods that test something and return true or false often end with
# a question mark (?) to indicate this.
def valid_email?(email)
!!email.match(EMAIL_PATTERN)
end
# Try and keep your support methods separate from the main body of
# your program.
puts "Enter your email address (joe#info.com): "
# This sets up a loop that waits until you get a valid response.
while (email = gets)
email = gets
if valid_email?(email)
puts "Great, that looks like it could work"
break
else
# Note that the message is rendered here, not in the validation
# method, so there's no assumptions about how this method is used.
puts "Invalid email address entered. Please try again. "
end
end
If you try and structure your code this way you'll find it's a lot easier to keep things organized. This is one of the big challenges when learning programming so as not to get overwhelmed.
Regular expressions are great for validating things that conform to a very specific pattern, but try not to get overly confident in the pattern of everyday things. Even the humble IPv4 address comes in a multitude of forms.

Related

Can I recall the "case" in case?

I want to recall the case until user writes a or b. I do not want to use "case"
particularly.
I just want to get input from user but not geting something else. If he writes something else, he should need to write until he writes a or b.
str = gets.chomp.to_s
case str
when "a"
print "nice a"
when "b"
puts "nice b"
else
puts "please do it again"
end
class person
attr_accessor :name , :surname #and other attributes
end
#There will be a method here and it will run when the program is opened.
#The method will create the first object as soon as the program is opened.
#The new object that the user will enter will actually be the 2nd object.
puts "What do you want to do?
add
list
out"
process = gets.chomp.to_s
case process
when "add"
#in here user will add new objects of my class
when "list"
#in here user will show my objects
when "out"
puts "Have a nice day"
else
puts "please do it again"
end
In fact, if you look at it, many actions will be taken as a result of the user entering the correct input. what I want to tell is more detailed in this example. According to the input of the user, there will be actions such as calling methods, adding objects, etc.
I wrote most of the code on my computer. But still I couldn't solve my first problem.
Use Kernel#loop
There are a lot of ways to solve this problem, but let's start with a simple Kernel#loop wrapper around your existing code, as that's probably the easiest path forward for you.
loop do
str = gets.chomp.to_s
case str
when "a"
print "nice a"
when "b"
puts "nice b"
else
puts "please do it again"
# restart your loop when not "a" or "b"
next
end
# exit the loop if else clause wasn't triggered
break
end
Use until Control Expression
The loop construct above is pretty straightforward, but it requires you to think about where you need next and break statements for flow control. My own instinct would be to simply call a block until it's truthy. For example, the core logic could be shortened to:
str = nil; until str =~ /a|b/i do str = gets.chomp end; p str
This is a lot shorter, but it's not particularly user-friendly. To leverage this approach while making the solution more communicative and error-resistant, I'd refactor the original code this way:
# enable single-character input from console
require 'io/console'
# make sure you don't already have a value,
# especially in a REPL like irb
str = nil
until str =~ /a|b/ do
printf "\nLetter (a, b): "
str = STDIN.getch.downcase
end
puts "\nYou entered: #{str}"
While not much shorter than your original code, it handles more edge cases and avoids branching. It also seems less cluttered to me, but that's more a question of style. This approach and its semantic intent also seem more readable to me, but your mileage may legitimately vary.
See Also
IO::Console
Control Expressions
"I just want to do something until something else happens" is when you use some sort of while loop.
You can do this:
while true
str = gets.chomp
break unless str == 'a' || str == 'b'
puts "please do it again"
end
You can also use loop do:
loop do
str = gets.chomp
break unless ['a', 'b'].include?(str)
puts "please do it again"
end
puts "Nice #{str}."
Rubyists tend to prefer loop do over while true. They do pretty much the same thing.
One more thing. There's a simpler way to write out arrays of strings:
loop do
str = gets.chomp
break unless %w(a b).include?(str)
puts "please do it again"
end
puts "Nice #{str}."
It doesn't look a whole lot simpler, but if you have, say, 10 strings, it's definitely quicker to type in when you don't have to use all those quotation marks.
As your intuition was telling you, you don't need to use the case statement at all. Like trying to kill a flea with a sledgehammer. The most concise way to do your check is to check whether the input character is included in an array of the desired characters.

Different Messages For Different User Inputs

How do I put a message (string) for a specific answer (user input) and another message for another answer? For e.g.
puts "Did You Like My Program?"
feedback = gets
if feedback = "Yes"
puts "We're Glad!"
elsif feedback = "No"
puts "We Will Try To Improve!"
end
What should I change, add, or modify?
Your problem is that, when you compare, you have to use ==, not =.
When you input on command line, you always use Enter. It produces \n at the end of the string. So you need to remove it with chomp.
Also, to filter user input, I suggest this variant:
feedback = nil
until %w[y n].include?(feedback)
puts 'Did You Like My Program? Y/N'
feedback = gets.chomp.downcase
end
if feedback == 'y'
puts "We're Glad!"
else
puts "We Will Try To Improve!"
end
Brief explanation:
The code uses Array#include? and String#downcase.
%w[y n] is equal to ["y", "n"].
The until-loop executes the code while the condition is false.

How do I return an error message when the user inputs wrong info?

I have a program that displays a numbered list and asks the user to input either a number or name from the list, and loops a block until the user enters "exit", after which it ends.
I want to add a line or two that puts an error message like, "Sorry, I don't seem to understand your request" if the user inputs something that is not on the list (name/number) and is not the word "exit".
I can't seem to figure it out. Any advice? My current code is below.
def start
display_books
input = nil
while input != "exit"
puts ""
puts "What book would you more information on, by name or number?"
puts ""
puts "Enter list to see the books again."
puts "Enter exit to end the program."
puts ""
input = gets.strip
if input == "list"
display_books
elsif input.to_i == 0
if book = Book.find_by_name(input)
book_info(book)
end
elsif input.to_i > 0
if book = Book.find(input.to_i)
book_info(book)
end
end
end
puts "Goodbye!!!"
end
Seems that you should add an elsif statement in this if:
if book = Book.find_by_name(input)
book_info(book)
elsif input != 'exit'
puts "Sorry, I don't seem to understand your request"
end
A good template for an interpreter is to build around Ruby's very capable case statement:
loop do
case (gets.chomp.downcase)
when 'list'
display_books
when /\Afind\s+(\d+)/
if book = Book.find($1.to_i)
book_info(book)
end
when /\Afind\s+(.*)/
if book = Book.find_by_name($1)
book_info(book)
end
when 'exit'
break
else
puts "Not sure what you're saying."
end
end
Although this involves regular expressions, which can be a bit scary, it does give you a lot of flexibility. \A represents "beginning of string" as an anchor, and \s+ means "one or more spaces". This means you can type in find 99 and it will still work.
You can create a whole command-line interface with it if you take the time to specify the commands clearly. Things like show book 17 and delete book 17 are all possible with a bit of tinkering.

How do I loop a request for user input until the user enters the correct info?

I am a beginner who is trying to learn Ruby. I have learned some of the easier stuff so far, but I seem to be stuck in trying to combine a couple of things I've learned.
What I am trying to do is to ask the user a question and tell them to enter either 1 or 2. A simple if statement would let me respond with one option if they enter 1, and another option if they enter 2. However, if they enter something entirely different like a different number, a string, etc., how can I prompt them to try again and have it loop back to the original question?
What I have so far looks something like this.
prompt = "> "
puts "Question asking for 1 or 2."
print prompt
user_input = gets.chomp.to_i
if user_input == 1
puts "One response."
elsif user_input == 2
puts "Second response."
else
puts "Please enter a 1 or a 2."
end
This is where I'm stuck. How do I make it go back to the "Question asking for 1 or 2." until the user enters a 1 or 2? I know it's probably a loop of some kind, but I can't seem to figure out which kind to use and how to incorporate asking for user input repeatedly (if necessary) until getting the desired input. Any help would be greatly appreciated. Thanks.
You're right that you need to run your code in a loop. Using a while loop with gets.chomp as a condition, you can carry on asking for user input until you decide you've got what you want.
In this case, you want to validate the answer to the question and ask again if it's invalid. You don't need to change a great deal, except making sure you break out of the loop when the answer is correct. If the answer is wrong, print the prompt again.
This is a slightly refactored version that uses case instead, but it shows what you need to do. There is no doubt a cleaner way to do this...
prompt = "> "
puts "Question asking for 1 or 2."
print prompt
while user_input = gets.chomp # loop while getting user input
case user_input
when "1"
puts "First response"
break # make sure to break so you don't ask again
when "2"
puts "Second response"
break # and again
else
puts "Please select either 1 or 2"
print prompt # print the prompt, so the user knows to re-enter input
end
end
Try using the until method like this:
prompt = "> "
print prompt
user_input = nil
until (user_input == 1 or user_input == 2)
puts "Please enter a 1 or 2."
user_input = gets.chomp.to_i
end
if user_input == 1
puts "One response."
elsif user_input == 2
puts "Second response."
else
puts "Please enter a 1 or a 2."
end
user_input = 0
until [1,2].include? user_input do
puts "Please enter a 1 or a 2.>"
user_input = gets.chomp.to_i
end
if user_input == 1
puts "One response."
else
puts "Second response."
end
You can try this to make your code clean.
While the title of this question is somewhat unrelated, please see the trick that is used here: Ruby Retry and ensure block is not working
The use of error detection and unique retry keyword available in Ruby allows you to easily do a retry-loop compacted together with nice an error handling.
However, mind that the example I pointed is not really the best. There are some minor issues. For example, you should not catch Exception, rather simple rescue => e would be enough. But the overall idea should be rather clear.

Why does this variable disappear in a struct?

Maybe I just don't know enough about structs and have been using them blindly but the result below seems irrational to me.
class VarTest < Struct.new(:email)
def perform
puts "Start: #{email}"
if email == "nothing"
email = "bad email"
end
puts "End: #{email}"
end
end
VarTest.new("test#example.com").perform
Unexpected Output:
Start: test#example.com
End:
If I change the code to:
class VarTest < Struct.new(:email)
def perform
e = email
puts "Start: #{e}"
if e == "nothing"
e = "bad email"
end
puts "End: #{e}"
end
end
VarTest.new("test#example.com").perform
We get the expected output:
Start: test#example.com
End: test#example.com
Can someone please explain what is going on with this?
Thanks.
If you replace email = "bad email" with self.email = "bad email" it will work as expected. This is always true when using setters.
The reason is simple: when Ruby encounters a bareword, it tries to resolve it as a local var. If there is none, it will try to call a method by that name. Inside the class body self is the implicit receiver, so readers just work. Now for the writer there's a problem. If you write something like foo = "bar", Ruby will create a new local variable, hence you need to make the receiver explicit.
This case is a bit trickier: if email == "nothing" uses the getter. However, email = "bad email" is still seen by the parser and a local variable email will be set to nil. This always happens when the parser sees a bareword as the LHS of an assignment. This local nil value is what makes it seem like the value of email disappears (which you can verify by only changing the last puts to puts "End: #{self.email}").

Resources