I'm trying to write a spec to test how my code will react when a user just presses the "Enter" key i.e. doesn't enter any data just presses "Enter".
The code itself will loop until a valid entry is made but I can't get the spec to test it. The code below is an example of both the class and the spec.
Note that in the spec, I've tried replacing the "asks repeatedly" section with with_input('') but it just seems to hang (or loop)
class Example
def initialize(input: $stdin, output: $stdout)
#input = input
#output = output
end
def ask_for_number
#output.puts "Input an integer 5 or above"
loop do
input = #input.gets.to_i
return true if input >= 5
#output.puts "Invalid. Try again:"
end
end
end
--- And the spec
require 'stringio'
require_relative 'Example'
describe Example do
context 'with input greater than 5' do
it 'asks for input only once' do
output = ask_for_number_with_input(6)
expect(output).to eq "Input an integer 5 or above\n"
end
end
context 'with input equal to 5' do
it 'asks for input only once' do
output = ask_for_number_with_input('5')
expect(output).to eq "Input an integer 5 or above\n"
end
end
context 'with input less than 5' do
it 'asks repeatedly, until a number 5 or greater is provided' do
output = ask_for_number_with_input(2, 3, 6)
expect(output).to eq <<~OUTPUT
Input an integer 5 or above
Invalid. Try again:
Invalid. Try again:
OUTPUT
end
end
def ask_for_number_with_input(*input_numbers)
input = StringIO.new(input_numbers.join("\n"))
output = StringIO.new
example = Example.new(input: input, output: output)
expect(example.ask_for_number).to be true
output.string
end
end
Just mimic the loop:
require "spec_helper"
describe 'Example' do
let(:entered_value) { 6 }
let(:stdin) { double('stdin', gets: entered_value) }
let(:stdout) { double('stdout') }
subject { Example.new(input: stdin, output: stdout) }
describe '#ask_for_number' do
before(:each) do
allow(subject).to receive(:loop).and_yield
end
context 'pressed enter without any input' do
let(:entered_value) { nil }
it 'prints invalid output' do
expect(stdout).to receive(:puts).with("Input an integer 5 or above")
expect(stdout).to receive(:puts).with("Invalid. Try again:")
subject.ask_for_number
end
end
end
end
When you replace it with
output = ask_for_number_with_input("")
it loops forever, because that's what your code tells it do, you wanted it to loop until it receives a number > 6, which will never happen, #input.gets.to_i is just going to keep returning 0 because IO#gets
Returns nil if called at end of file.
To get it to stop hanging, simply give it another value:
it 'asks repeatedly, until a number 5 or greater is provided' do
output = ask_for_number_with_input("", "", 6)
expect(output).to eq <<~OUTPUT
Input an integer 5 or above
Invalid. Try again:
Invalid. Try again:
OUTPUT
end
and now it passes
Related
I want to write a method passing a block, but if a proc and an actual block are given at the same time, it will take only the first one.
I have tried to raise an Exception for SyntaxError, but it keeps prompting an error. This is one of the things that I was trying.
def my_map(&proc)
raise SyntaxError, "using first block given"
rescue
arr = []
proc = proc.call(i) || yield(i)
self.my_each do |i|
arr << proc
end
arr
end
I also tried to add a condition for the raise keyword.
Of course, code works if only one block is given.
I want to write a method passing a block, but if a proc and an actual block are given at the same time, it will take only the first one.
def f(*args)
if args.length == 1
args.first.call
else
yield
end
end
puts 'test 1'
f(->() { puts 'a' }) { puts 'b' }
puts 'test 2'
f { puts 'b' }
Output
test 1
a
test 2
b
I wrote a code where it asks multiple individuals questions, and every individuals' response is put into a hash. This is in a loop and looks something like this
arr:[]
(1..n).each do |i|
hash=Hash.new()
puts "Please input a value for day # #{i}"
hash["day1"]=gets.chomp.to_f
puts "Please input a value for day # #{i}"
hash["day2"]=gets.chomp.to_f
arr << hash
end
So to avoid any incorrect input (i.e. entering string instead of an integer/number), I need to place a conditional statement.
I am super lost with how I would do that though since I am assigning the users' input into a hash at the same time I take their input.
Is it even possible to do that or should I take a different route completely.
thanks
You can get day values like below. When a character other than a number is entered, it asks the value of that day again.
puts 'Enter number of days'
days_hash = {}
number_of_days = gets.chomp.to_i
day = 1
while day <= number_of_days
puts "Please enter a value for day # #{day}"
input = gets.chomp
if input !~ /\D/
days_hash["day#{day}"] = input.to_i
day += 1
else
puts "Please enter only number"
next
end
end
p days_hash
#=> { "day1" => 2, "day2" => 4, "day3" => 8 }
days_hash['day2']
#=> 4
Maybe you can consider this general approach for validating user input, note that this example require 'date'.
So, you can check user input to be an integer or a float or a formatted date or anything you could add...
First define an array of questions containing the question text, validation method and way to convert the input, like this:
questions = [
{text: "Enter an integer:", validate: :must_be_an_integer, convert: :my_to_i},
{text: "Enter a float:", validate: :must_be_a_float, convert: :my_to_f},
{text: "Enter a data as d-m-yyyy", validate: :must_be_a_formatted_date, convert: :to_formatted_date}
]
Then define some methods to be called by :validate key, for user input validation:
def must_be_an_integer(num)
Integer(num).class == Integer rescue false
end
def must_be_a_float(num)
Float(num).class == Float && num.include?('.') rescue false
end
def must_be_a_formatted_date(date_str)
date = Date.strptime(date_str, "%d-%m-%Y") rescue false
return false unless date
date.year <= 9999 && date.year >= 1000
end
Define also the methods required by the key :convert (need to pass an argument, that's why my_to_i and my_to_f):
def my_to_i(num)
num.to_i
end
def my_to_f(num)
num.to_f
end
def to_formatted_date(date_str)
DateTime.strptime(date_str, "%d-%m-%y")
end
Finally iterate over the questions:
res = questions.map do |question|
answer = nil
3.times do
puts question[:text]
u_input = gets.chomp
if send question[:validate], u_input
answer = send question[:convert], u_input
break
end
end
if answer.nil?
puts "C'mon man! Check your keyboard!" # after three input errors!!
break
end
{ question: question[:text], answer: answer }
end
Example of a result:
#=> [
{:question=>"Enter an integer:", :answer=>1},
{:question=>"Enter a float:", :answer=>1.1},
{:question=>"Enter a data as d-m-Y", :answer=>#<DateTime: 2020-10-27T00:00:00+00:00 ((2459150j,0s,0n),+0s,2299161j)>}
]
I've noticed this strange behavior with the begin/rescue block in Ruby, when I define a variable, and an exception occurs and I try to call that variable that the exception occurred on it returns nil.
For example:
begin
print "Enter a number: "
input = Integer(gets.chomp)
sum = input + 5
puts "This is your number plus five: #{sum}"
rescue ArgumentError
puts "#{input}" #This outputs nil
end
Why does the begin/rescue block work like this, and is there a way to print the variable without it returning nil?
I'm not sure this is what you want but I try
input = gets.chomp
begin
number = Integer(input)
puts "your number plus five: #{number + 5}"
rescue ArgumentError
puts "#{input} is not a valid number"
end
Let's say that I have this simple if-elsif-else block of code.
def
# some code...
input = get.chomp
if input == 1
puts "foo"
elsif input == 2
puts "bar"
else
# exit
end
# some more code...
end
How do I tell program to go back and ask for input again for cases 1 and 2 and continue with code within this method if else is triggered? I do not want to go back to start of the method, instead I just want to back to input variable declaration.
def
# some code...
loop do
input = get.chomp
if input == 1
puts "foo"
break
elsif input == 2
puts "bar"
break
end
end
# some more code...
end
Note: Your two if/elsif conditions will never be satisfied.
# main procedure
# defined here so other functions could be declared after
# the main procedure is called at the bottom
def main
loop do
puts "Insert a number"
input = gets.chomp.to_i
if isValidInput input
puts case input
when 1
"foo"
when 2
"bar"
end
break
end
end #loop
puts "Other code would execute here"
end
# Validity Checker
# makes sure your input meets your condition
def isValidInput(input)
if [1,2].include? input
return true
end
return false
end
main
Below is the code for my script.
As you can see, i have an array, and an index. I pass that to the block called 'raise_clean_exception'. The integer part of it does actually raise a Standard Error exception which is great. I have an issue when I use an index that is out of bounds. So if my array only has 4 elements (0-3) and I use an index of 9, it will not raise the exception, and instead it prints out a blank line because nothing is there. Why would it do this?
#!/usr/bin/ruby
puts "I will create a list for you. Enter q to stop creating the list."
array = Array.new
i = 0
input = ''
print "Input a value: " #get the value from the user
input = STDIN.gets.chomp
while input != 'q' do #keep going until user inputs 'q'
array[i] = input #store the value in an array of strings
i += 1 #increment out index where the inputs are stored
print "Input a value: " #get the value from the user
input = STDIN.gets.chomp
end #'q' has been entered, exit the loop or go back through if not == 'q'
def raise_clean_exception(arr, index)
begin
Integer(index)
puts "#{arr[index.to_i]}"
# raise "That is an invalid index!"
rescue StandardError => e # to know why I used this you can google Daniel Fone's article "Why you should never rescue exception in Ruby"
puts "That is an invalid index!"
end
# puts "This is after the rescue block"
end
# now we need to access the array and print out results / error messages based upon the array index value given by the user
# index value of -1 is to quit, so we use this in our while loop
index = 0
arrBound = array.length.to_i - 1
while index != '-1' do
print "Enter an index number between 0 and #{arrBound} or -1 to quit: "
index = STDIN.gets.chomp
if index == '-1'
exit "Have a nice day!"
end
raise_clean_exception(array, index)
end
Consider using a subclass of StandardError, IndexError, which is specific to the problem you are experiencing. Also, using else prevents a blank space from being printed if the index is out of bounds and when raising exceptions within a method, a begin...end block is implied.
def raise_clean_exception(arr, index)
Integer(index)
raise IndexError if index.to_i >= arr.length
rescue StandardError
puts "That is an invalid index!"
else
puts "#{arr[index.to_i]}"
end
Accessing an array element that's outside the range of existing elements returns nil. That's just the way Ruby works.
You could add the following line before the "puts" to trap that condition...
raise StandardError if index.to_i >= arr.size