Ruby: 'gets' method called inside a method saves Nil instead of Input - ruby-on-rails

Ok guys, this in a Dungeon Text Adventure project that runs on terminal.
When user says he wants to "go north" it splits the string: first word checks for method and second word for parameter. That happens someplace else, i copy pasted the 2 methods that are giving me the following problem:
When it calls go(north) and north isn't a valid connection, it shows the options and asks user for direction again, the input the user enters at that moment is stored as Nil for some reason.
Why?? I also tried STDIN.gets.chomp.downcase! and had same Nil results
Here's the code:
def find_room_in_direction(direction)
##-> if room connects with other on that direction, returns connection
if find_room_in_dungeon(#player.location).connections.include?(direction.to_sym)
return find_room_in_dungeon(#player.location).connections[direction.to_sym]
##-> if direction connection is not found,
##-> show possible connections & return trigger to ask again inside go()
elsif !find_room_in_dungeon(#player.location).connections.include?(direction.to_sym)
puts "I don't see any #{direction}..."
puts "This room only connects #{(find_room_in_dungeon(#player.location)).connections.keys.join(', ')}"
puts "Where should we go?"
return :redo
end
end
def go(direction)
current_direction = #player.location
new_direction = find_room_in_direction(direction)
##-> if REDO trigger received, ask for new direction & try again
if new_direction == :redo
puts "REDOING DIRECTION"
##-> HERE IS THE PROBLEM:
##-> this does ask for input
new_direction = gets.chomp.downcase!
##-> but it saves Nil instead of the input, so that puts shows ''
puts "#{new_direction}"
##-> so this call trows an error: cant call method to Nil
new_direction = find_room_in_direction(new_direction)
##-> if user entered valid direction from start, the following would run
elsif new_direction != :redo && current_direction != new_direction
#player.location = new_direction
puts "You go #{direction},"
puts "and enter #{show_current_description}"
end
end
Any suggestions?
Thanks!

You are using downcase! instead of downcase. downcase! changes a string in-place and returns nil if there were no changes (which is what is happening here).
str = "teSt"
puts str.downcase # test
puts str # teSt
str.downcase!
puts str # test
See the documentation for downcase!

Just bang chomp. As for downcase there are two variants - chomp and chomp!

Related

How to get my API to return more than one value in Ruby

My CLI project is working almost correctly, however, is only returning one value. I would like my program to return multiple values.
Here's the code:
def display_info
puts "You'll love the following spots!"
puts "********************************"
#objects.each.with_index(1) {|brewery, index| puts "#{index}. #{brewery.name}"}
puts "Please make a selection by index number for more information:"
puts "****************************************************"
puts "Type Quit to end. Type Menu to try another location."
input = gets.strip.downcase
if(input.to_i > 0)
#brewery = #objects[input.to_i - 1]
puts "name: #{#brewery.name}"
puts "street: #{#brewery.street}"
puts "city: #{#brewery.city}"
puts "phone: #{#brewery.phone}"
puts "website_url: #{#brewery.website_url}"
display_info
elsif (input == "quit")
quit
elsif (input == "menu")
start
else
puts "Ooops, please try again to get more info:"
display_info
end
end
Here's the input result if that helps.
Austin
You'll love the following spots!
********************************
1. Oasis Texas Brewing Company
Please make a selection by index number for more information:
****************************************************
Type Quit to end. Type Menu to try another location.
quit
Goodbye. Drink responsibly and enjoy.
What can I do to return more than one value?
Here's the source of this code. This is the content.
class Breweries::API
def self.get_breweries(input)
#breweries_hash = HTTParty.get("https://api.openbrewerydb.org/breweries?by_city=#{input}")
return if #breweries_hash.empty? #empty array handle
breweries_obj = {
name: #breweries_hash[1]["name"],
street: #breweries_hash[3]["street"],
city: #breweries_hash[4]["city"],
phone: #breweries_hash[10]["phone"],
website_url: #breweries_hash[11]["website_url"]
}
Breweries::HoppyCode.new(breweries_obj)
end
end
end
You need to split the string you capture using gets with a character. You should ask for it in your message, normally that would be a comma, possibly surrounded by spaces.
I extracted the problem in a simplified script so that you can test it separately, which is always a good idea when solving a problem.
Mind the use of regular expressions between the // delimiters, and followed by the i flag to indicate that the comparison should be case-insensitive.
# what you would receive when you type this in the console following your message
# and captured by the gets, in this case brewerie 1 and 3
input = "1, 3"
#split by a comma preceded or followed or not by a space
breweries = input.split(/ *, */)
breweries.each do |brewerie|
if(brewerie.to_i > 0)
# #brewery = #objects[input.to_i - 1]
puts "displaying info about #{brewerie}"
elsif brewerie[/quit/i]
# quit
elsif brewerie[/menu/i]
# start
else
puts "Ooops, please try again to get more info:"
# display_info
end
end
Which returns:
displaying info about 1
displaying info about 3
From what I understand, You are asking the user to pick a brewery from a list of breweries. When they pick one, you show its info to them. What you could do instead is display a list of cities and let them pick a city and select all elements of the #objects where the city matches
def display_info
arr_of_cities = []
#objects.each{|element| arr_of_cities.push(element.city)}
arr_of_cities.each.with_index(1) {|city, index| puts "#{index}.#{city}"}
puts "Please make a selection by index number for more information:"
puts "****************************************************"
puts "Type Quit to end. Type Menu to try another location."
input = gets.strip.downcase
arr_of_breweries = #objects.where(city:arr_of_cities[input.to_i- 1])
if(input.to_i > 0)
arr_of_breweries.each.with_index(1) do |brewery,index|
puts "#{index}"
puts "name: #{#brewery.name}"
puts "street: #{#brewery.street}"
puts "city: #{#brewery.city}"
puts "phone: #{#brewery.phone}"
puts "website_url: #{#brewery.website_url}"
end
display_info
elsif (input == "quit")
quit
elsif (input == "menu")
start
else
puts "Ooops, please try again to get more info:"
display_info
end
end
Hopefully that helps. I haven't used ruby in a while so my syntax might be off a bit.

I trying to make a code that gives the user a personal number after they have made an user

Here is my ruby code. When you run it and you press 1 it will ask you for name and birth of date. I want to give the user a personal number after he is finished typing name and birth date. Futhermore I would be great to search for the users number to find them in the file. Hope someone can help!
I have written #HELP HERE were i need help. The code works fine, but I dont know how to code my problem...
file = File.new("Capgemini.txt", "a") #load information on startup, and create the file
class Customer # Making a class for the whole code to call it up later in the code
def new_custom
er # The costumer method
prompt = "> " #creating a class for prompt here, since I use it multiple times
puts"Full name of the person?"
print prompt
#name = gets.chomp.upcase #A global variabel which i can use outside the class
if File.read("Capgemini.txt").include?(#name) #so you don't register the same name, twice
puts"This name is already stored. Returning you to the main menu."
puts "_____________________________________________"
else
#puts"What is your employee number?"
#print prompt
##number = gets.chomp #Global
puts"Date of birth? (DD/MM/YYYY)"
print prompt
#birth = gets.chomp #Global
puts"Thanks for the input."
puts "_____________________________________________"
puts"Is this information correct? " #Giving the user all the information back to check for mistakes, etc.
puts ("Name: #{#name} Number: #{#number} Date of birth: #{#birth}")
puts "_____________________________________________"
puts "Yes or No?"
print prompt
while user_input = gets.chomp.upcase #loop while getting user input
case user_input
when "YES"
file = File.new("Capgemini.txt", "a")
file.puts("#{#name}, Number: #{#number}, Date of birth: #{#birth}") #puts the information into the textfile, separeted by commas
file.close
#NEED HELP HERE
number = File.readlines('Capgemini.txt')
puts "_____________________________________________"
puts
puts "Your employee number: "
puts "_____________________________________________"
#NEED HELP OVER HERE^
puts
puts "The information has now been stored in the Capgemini.txt file."
puts "_____________________________________________"
break # make sure to break so you don't ask again
when "NO"
puts "The information has not been stored. Returning you to the main menu."
puts "_____________________________________________"
break # and again
else
puts "Please either write 'Yes' or 'No'"
print prompt # print the prompt, so the user knows to re-enter input
end
end
end
end
def search_customer(search)
keyword = File.readlines('Capgemini.txt') #converting all the lines into indexes in an Array
matches = keyword.select { |name| name[/#{search}/] } #
if File.read("Capgemini.txt").include?(search) #An if statement that will print the results if the textfile matches the keyword
puts "_____________________________________________"
puts ("Search results including the word/number/birth " + search + ":")
puts "_____________________________________________"
puts matches
puts "_____________________________________________"
else #If not it will give the user feedback that its not there
puts "_____________________________________________"
puts ("Sorry, we couldnt find #{search} in the textfile.")
puts "_____________________________________________"
end
end
def all_customers
f = File.new("Capgemini.txt","r")
while !(f.eof?)
line = f.gets()
puts line
end
end
def delete_customer
puts("What customer do you want to delete?")
print("> ")
keyword = gets.chomp.upcase
txt_file = File.readlines('Capgemini.txt')
matches = txt_file.select { |name| name[/#{keyword}/] }
search_results = matches.length
if search_results > 1
puts "_____________________________________________"
puts "The name you entered gave these outputs:"
puts ""
puts matches
puts ""
puts "Please specify the name better, as we only allow one person to be deleted at the time. \nReturning you to the main menu."
puts "_____________________________________________"
else
if File.read("Capgemini.txt").include?(keyword) #An if statement that will print the results if the textfile matches the person
puts "_____________________________________________"
puts ("Is this the person you want to delete?")
puts matches
puts "_____________________________________________"
puts "Yes or No?"
print "> "
while user_input = gets.chomp.upcase # loop while getting user input
case user_input
when "YES"
no_matches = txt_file.reject { |name| name[/#{keyword}/] }
File.open('Capgemini.txt','w+'){|out| out.puts no_matches}
puts"User has been deleted. Returning you to the main menu."
puts "_____________________________________________"
break # make sure to break so you don't ask again
when "NO"
puts "User will not be deleted. Returning you to the main menu."
puts "_____________________________________________"
break # and again
else
puts "Please either write 'Yes' or 'No'"
print "> " # print the prompt, so the user knows to re-enter input
end
end
puts "_____________________________________________"
else #If not it will give the user feedback that its not there
puts "_____________________________________________"
puts ("Sorry, we couldnt find #{keyword} in the textfile.")
puts "_____________________________________________"
end
end
end
end
customer = Customer.new
require 'io/console'
select = 0
prompt = "> "
puts
puts
puts "Welcome to Capgemini Sogeti Denmark"
puts "_____________________________________________"
loop do (select != 7)
puts
puts("Press 1 to register a new user.\nPress 2 to search for a employee or keyword within the textfile.\nPress 3 to show all customers.\nPress 4 to delete a customer.\nPress 5 to exit.")
puts "_____________________________________________"
select = STDIN.getch.to_i
if(select == 1)
customer.new_customer
elsif(select == 2)
puts("What customer/keyword do you want to search for?") #You can search for a keyword, like forexample 'Manzur' which will prompt you back with every user names Manzur
print prompt
customer.search_customer(gets.chomp.upcase)
elsif(select == 3)
customer.all_customers
puts "_____________________________________________"
elsif(select == 4)
customer.delete_customer
elsif(select == 5)
puts
puts "The application will now exit."
puts "_____________________________________________"
break
else
puts"Invalid input. Please try again."
puts "_____________________________________________"
end
end
I am not sure how you want the numbers generated, but something like this could work.
This will always give you the highest number from your text file, and add 1 to it.
Keep in mind this method will be slow with a rather large amount of employees.
if File.exist?('Capgemini.txt')
number = File.readlines('Capgemini.txt')
#number = 1
number.each do |x|
customer = x.split(',')
customer_number = customer[1].gsub('Number: ', '').to_i
if customer_number >= #number
#number = customer_number + 1
end
end
else
#number = 1
end
Output:
BOB ROSS, Number: 1, Date of birth: 07/07/2007
WILL SMITH, Number: 2, Date of birth: 08/08/2008
JIM BOB, Number: 3, Date of birth: 09/09/2009
You can also use a similar method for searching through an array:
number = File.readlines('Capgemini.txt')
number.each do |x|
customer = x.split(',')
customer_name = customer[0]
customer_number = customer[1].gsub('Number: ', '').to_i
customer_bday = customer[2].gsub('Date of birth: ', '')
if customer_name == some_variable
puts x
end
end
There is a lot to say about this code (yours and mine). Run Ruby with the -w option to display unused variables, it points to possible mistakes like :
$ ruby -w t_op.rb
t_op.rb:180: warning: mismatched indentations at 'end' with 'class' at 3
t_op.rb:191: warning: possibly useless use of != in void context
t_op.rb:1: warning: assigned but unused variable - file
There are a lot of File.read, I have replaced them by an IO.readlines which creates an array of lines (OK for files which are not Gigabytes big). It also allows to store user numbers.
As always in Ruby, there are many ways to do the same thing.
require 'io/console'
class Customer
attr_reader :file
def initialize(p_file_name)
#file_name = p_file_name
refresh
#next_number = #numbers.max + 1
end
def refresh
#lines = IO.readlines(#file_name) # load information on startup
#numbers = []
#names = #lines.collect do | line |
# the line has the format : <name>, Number: <number>, Date of birth: <birth>
idxn = line.index(', Number')
idxd = line.index(', Date')
#numbers << line[idxn + 10...idxd].to_i
line[0...idxn]
end
end
def new_customer
prompt = "> " # creating a local variable for prompt, since I use it multiple times
puts 'Full name of the person ?'
print prompt
name = gets.chomp.upcase
if #names.include?(name) #so you don't register the same name, twice
then
puts_underlined 'This name is already stored. Returning you to the main menu.'
else
puts 'Date of birth? (DD/MM/YYYY)'
print prompt
birth = gets.chomp # TODO check validity
puts_underlined 'Thanks for the input.'
puts 'Is this information correct ?' # Giving the user all the information back to check for mistakes, etc.
puts_underlined "Name: #{name} Number: #{#next_number} Date of birth: #{birth}"
puts 'Y(es) or N(o)'
print prompt
while user_input = gets.chomp.upcase #loop while getting user input
case user_input[0]
when 'Y'
line = "#{name}, Number: #{#next_number}, Date of birth: #{birth}"
#next_number +=1
#lines << line
File.open(#file_name, 'a') do | file | # open the file for append, ensure it will be closed by the end of the block
file.puts line # puts the information into the textfile, separeted by commas
end
puts
puts_underlined "The information has now been stored in the #{#file_name} file."
break # make sure to break so you don't ask again
when 'N'
puts_underlined 'The information has not been stored. Returning you to the main menu.'
break # and again
else
puts 'Please either write Y(es) or N(o)'
print prompt # print the prompt, so the user knows to re-enter input
end
end
end
end # new_customer
def search_customer(search)
matches = #lines.grep(/#{search}/)
unless matches.empty? # An if statement that will print the results if the textfile matches the keyword
puts_underlined()
puts_underlined "Search results including the word/number/birth #{search} :"
puts matches
puts_underlined()
else # If not it will give the user feedback that it's not there
puts_underlined()
puts_underlined "Sorry, we couldnt find #{search} in the text file."
end
end
def search_customer_number(search)
index = #numbers.index(search.to_i)
if index
then # found, print the user
puts_underlined()
puts_underlined "This is the user number #{search} :"
puts #lines[index]
puts_underlined()
else # not found, it will give the user feedback that it's not there
puts_underlined()
puts_underlined "Sorry, we couldnt find the user #{search}."
end
end
def all_customers
puts #lines
end
def delete_customer
puts 'Which customer do you want to delete ?'
print '> '
keyword = gets.chomp.upcase
matches = #lines.grep(/#{keyword}/)
case matches.size
when 0 # not found, give the user feedback that it's not there
puts_underlined()
puts_underlined "Sorry, we couldnt find #{keyword} in the textfile."
when 1 # print the results if the textfile matches the person
puts_underlined()
puts 'Is this the person you want to delete ?'
puts_underlined matches
puts 'Yes or No?'
print '> '
while user_input = gets.chomp.upcase # loop while getting user input
case user_input
when 'YES'
no_matches = #lines.reject { | line | line[/#{keyword}/] }
File.open(#file_name, 'w+') { | out | out.puts no_matches }
refresh
puts_underlined 'User has been deleted. Returning you to the main menu.'
break # make sure to break so you don't ask again
when 'NO'
puts_underlined 'User will not be deleted. Returning you to the main menu.'
break # and again
else
puts "Please either write 'Yes' or 'No'"
print '> ' # print the prompt, so the user knows to re-enter input
end
end
else
puts_underlined()
puts 'The name you entered gave these outputs:'
puts
puts matches
puts
puts_underlined "Please specify the name better, as we only allow one person to be deleted at the time. \nReturning you to the main menu."
end
end # delete_customer
end # class Customer
def puts_underlined(p_text = nil)
puts p_text if p_text
puts '_____________________________________________'
end
file_name = 'Capgemini.txt'
customer = Customer.new(file_name)
prompt = "> "
puts
puts_underlined 'Welcome to Capgemini Sogeti Denmark'
loop do
puts
puts_underlined "Press 1 to register a new user.\nPress 2 to search for a employee.\nPress 3 to search for a keyword within the textfile.\nPress 4 to show all customers.\nPress 5 to delete a customer.\nPress 6 to exit."
select = STDIN.getch.to_i
case select
when 1
customer.new_customer
when 2
puts 'Which customer number do you want to search for ?'
print prompt
customer.search_customer_number(gets.chomp.upcase)
when 3
puts 'What keyword do you want to search for ?' # You can search for a keyword, like for example 'Manzur' which will prompt you back with every user names Manzur
print prompt
customer.search_customer(gets.chomp.upcase)
when 4
customer.all_customers
puts_underlined()
when 5
customer.delete_customer
when 6
puts
puts_underlined 'The application will now exit.'
break
else
puts_underlined 'Invalid input. Please try again.'
end
end

Increment field within validator

I have a custom validator that checks if the user has entered the correct SMS code. When the user enters the wrong code I need to log the failed attempt and limit their retries to 3 per code.
I have created the following validator that works however the field is not being incremented.
def token_match
if token != User.find(user_id).verification_token
User.find(user_id).increment!(:verification_fails)
errors.add(:sms_code, "does not match")
end
end
The problem is as soon as I add the error the previous statement is rolled back. If I comment out the errors.add line then the increment works however there is no higher level validation performed.
Change your custom validator to be:
def token_match
if token != User.find(user_id).verification_token
errors.add(:sms_code, "does not match")
end
end
and add in your model after_validation callback to be like this:
after_validation: increase_fails_count
def increase_fails_count
unless self.errors[:sms_code].empty?
user = User.find_by(:id => user_id)
user.increment!(:verification_fails)
user.save
end
end
You can use #update_columns in your validator. It writes directly to db.
u = User.find(user_id)
u.update_columns(verification_fails: u.verification_fails + 1)
This worked for me. But if for some reason it doesn't work for you, maybe you can try running it in a new thread,which creates a new db connection:
Thread.new do
num = User.find(user_id).verification_fails
ActiveRecord::Base.connection_pool.with_connection { |con| con.exec_query("UPDATE users SET verification_fails = #{num} WHERE id = #{user_id}") }
end.join

Why not interpreted code can affect "messages" behavior in Ruby?

I've got a validator in ActiveRecord model, in which I faced with some realy strange behavior.
Example:
if status_changed?
p status # output on line below
# <= "my_status_1"
p my_conditions_1 # output on line below
# <= false
if my_conditions_1
errors.add(:status, 'Error1')
status = status_was
end
p status # output on line below
# <= nil
# my_conditions_2 depends on "status variable"
if my_conditions_2
errors.add(:status, 'Error2')
status = 2
end
end
Second condition always failed, because status somehow was setted to nil. But when I changed status to self.status everything started working as expected.
UPDATED
I've got the rule, that in case of assigning attribute I have to use self, thanks everyone who explained it. But part of the code's behavior still doesn't obvious to me
More general example:
class Detector
def status
"Everything ok"
end
def check
p status
# <= "Everything ok"
if false
status = "Danger!"
end
p status
# <= nil
end
end
detector = Detector.new
detector.check
Can someone explain it? How not interpreted code can "redirect" message from method to a variable? Is it ok?
To access object's attribute it's fine to do it with attribute.
While updating this attribute one should be using self.attribute, because otherwise how should Rails know you mean to set its attribute, not define local variable?
Rule of thumb: use self for assigning attribute, don't use it for reading the attribute.
EDIT
Regarding your update:
As #Jörg W Mittag said (who would say better?):
Well, status is un-initialized, und un-initialized local variables
evaluate to nil, just like instance variables.
To make your code sample behave as you expect you would want to call status as a method. Look:
class Detector
def status
"Everything ok"
end
def check
p status
# <= "Everything ok"
status = "Danger!" if false
status() # or method(:status).call
# <= "Everything ok"
end
end
First p status works because Ruby looks for local variable status. When it does not find it, it looks for a method called status (by method lookup). So it prints it "Everything ok".
Then in parses the if statement and sees, that there's un-initialized local variable status. Thus, when you reference it, it is legitimately nil.
So in other words, make Ruby know exactly, what you mean.
If you are updating the attribute then you must use self
self.status = 'something'
otherwise rails will assume status as a local variable so
puts self.status
#=> "something"
status = 'abc'
puts self.status
#=> "something"
self.status = 'something else'
puts self.status
#=> "something else"
But you can access the attribute with just status.
why status was set to nil?
Maybe because of this line
status = status_was
before status_changed? maybe the self.status was nil

How do I track changes of an instance variable outside of the class in Ruby?

I am fairly new to Ruby and I am building a program that basically works as an interactive orchard, where the user will input what type of tree they want to grow and then give commands to water, prune, pick and harvest the tree.
The problem I am having is when I try to have the program ask for commands until the tree dies which occurs at a certain height. The height is defined in an instance variable inside a class, and I can't seem to figure out how to have the program track that variable outside of the class, so that it keeps prompting for a command until a certain value is achieved.
The below code is the start and end of the code, but not the middle parts which seem to be working fine. Each of the commands at the bottom work once, but then the program ends.
Any help would be appreciated.
class Orangetree
def initialize name
#name = name
#height = 0
#branches = 0
#winter = false
#orangesontree = 0
#orangesinbasket = 0
#timeOfyear = 0
puts #name + 'Just Sprouted! What would you like to do with him?'
end
puts 'Welcome to the Orchard! What would you like to grow today?'
reply = gets.chomp
while reply != 'oranges'
puts 'I am sorry, we do not have that kind of tree, try again'
gets.chomp
end
oranges = Orangetree.new 'Woody '
while Orangetree |#height| <= 61
command = gets.chomp
if command == 'water'
puts oranges.rain
end
if command == 'pick'
puts oranges.pick
end
if command == 'prune'
puts oranges.prune
end
if command == 'harvest'
puts oranges.harvest
end
end
You cannot access an object's instance field outside of its class directly. Use a getter method.
Adding attr_writer :height to your class will give this to you.
Then you can reference the height outside the class with
while oranges.height <= 61

Resources