Why does this variable disappear in a struct? - ruby

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}").

Related

Ruby validation of name, email, and phone number

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.

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)}"

How can i get the sentence before special character (like :) by Ruby?

I need to get the sentence before ":" character from the full sentence.
ex. I have "This message will self destruct: Ruby is fun "
I only need "This message will self destruct"
this is my method
def destroy_message(sentence)
check_list = /[^:]/
sentence_list = /^([a-zA-z\s]+) \:/
if sentence =~ check_list
puts "have :"
firstConsonant = sentence.match(sentence_list)
puts firstConsonant
else
puts "Not have :"
end
end
destroy_message("This message will self destruct: Ruby is fun ")
But i got nothing from puts firstConsonant.
How can i fix it?
Thanks!
I would just split the string at the : and use the first part. That is probably faster that using the complex regexp engine.
string = "This message will self destruct: Ruby is fun "
string.split(':').first
#=> "This message will self destruct"
# ruby.rb
def destroy_message(sentence)
result, * = sentence.split(':')
unless result == "" # in case ":sentence_case"
puts "#{result}"
else
puts "Not have :"
end
end
destroy_message("This message will self destruct: Ruby is fun ")
spickermann's answer will work, but besides that, there is a method String#partition specifically for doing this kind of thing (and also might be slightly faster).
s, * = "This message will self destruct: Ruby is fun ".partition(":")
s # => "This message will self destruct"
I got nothing from puts firstConsonant. How can I fix it?
You could use #Scan Methods
def destroy_message(sentence)
if sentence.scan(/^[^:]+/)
puts "have :"
firstConsonant = sentence.scan(/^[^:]+/)
puts firstConsonant[0]
else
puts "Not have :"
end
end
destroy_message("This message will self destruct: Ruby is fun")
Output
=> have :
This message will self destruct
Explanation about the Regex That used
^ asserts that we are at the start of a line.
[^...] Negated character class which matches any character but not the one present inside that particular character class.
+ after the char class would repeat the char class one or more times.

.include? doesn't raise when I add identical strings

I want to be able to downcase name before include is ran. Calling it on push has caused some weird behavior: It will add the second Jack to the list if it starts with a capitol, but will not add it if the second Jack is lowercase. How can I downcase name before the include check?
$list = []
def hand_out_gift(name)
if $list.include?name
raise "u can't get more than one present!"
else
$list.push(name.downcase)
puts "Here you go #{name} :)"
end
end
hand_out_gift("Jack")
hand_out_gift("Mary")
hand_out_gift("Jill")
hand_out_gift("Jack")
Downcase it early in your function:
def hand_out_gift(name)
key = name.downcase
if $list.include? key
raise "u can't get more than one present!"
else
$list.push key
puts "Here you go #{name} :)"
end
end
Aside: avoid using global variables like that.

Empty array messing up default argument values

I'm following through the Hungry Academy curriculum using a post here: http://jumpstartlab.com/news/archives/2013/09/03/scheduling-six-months-of-classes
And I'm up to the EventReporter project found here: http://tutorials.jumpstartlab.com/projects/event_reporter.html
So far I've built a simple CLI that asks for a valid command and accepts additional arguments with the command. I'm working ONLY on the load functionality right now and I'm having some trouble getting a default listfile variable set in AttendeeLists initialize method. Here's the code so far:
require 'csv'
class Reporter
def initialize()
#command = ''
loop()
end
#Main reporter loop
def loop
while #command != 'quit' do
printf "Enter a valid command:"
user_command_input = gets.chomp
user_input_args = []
#command = user_command_input.split(" ").first.downcase
user_input_args = user_command_input.split(" ").drop(1)
#DEBUG - puts #command
#DEBUG - puts user_input_args.count
case #command
when "load"
attendee_list = AttendeeList.new(user_input_args[0])
when "help"
puts "I would print some help here."
when "queue"
puts "I will do queue operations here."
when "find"
puts "I would find something for you and queue it here."
when "quit"
puts "Quitting Now."
break
else
puts "The command is not recognized, sorry. Try load, help, queue, or find."
end
end
end
end
class AttendeeList
def initialize(listfile = "event_attendees.csv")
puts "Loaded listfile #{listfile}"
end
end
reporter = Reporter.new
I'm testing running the load command with no arguments and is see that when I initialize the AttendeeList that user_input_args[0] is an empty array [] which, to my understanding is not nil, so I think that's the problem. I'm a little lost on how to continue though when I want the args to be passed through to my new instance of AttendeeList. I'd also prefer not to include the default logic in my Reporter class since that kind of defeats the purpose of encapsulating within the list.
EDIT: I forgot to mention that listfile default for AttendeeList initialize method is argument I'm talking about.
You need to make this change:
def initialize(listfile = nil)
listfile ||= "event_attendees.csv"
puts "Loaded listfile #{listfile}"
end
Explanation
In fact, user_input_args[0] is nil, but nil has no special meaning for default argument values. Default values are used only if the arguments are omitted when calling a function.
In your case:
AttendeeList.new
would work as you expected, but
AttendeeList.new(user_input_args[0])
is effectively
AttendeeList.new(nil)
and parameter listfile becomes nil.

Resources