So im currently using the Learn.co gem in which I am trying to solve a blackjack lab. https://github.com/learn-co-curriculum/simple-blackjack-cli
The following Rspec code expects a specific output which confuses me since we working with random numbers. Here is the Rspec code:
describe "#runner" do
before(:each) do
def get_user_input
"h"
end
end
it "calls on the #welcome method,
then on the #initial_round method,
then calls #hit? and #display_card_total methods
-until- the card sum is greater than 21,
then calls on the #end_game method" do
expect(self).to receive(:deal_card).at_least(3).times.and_return(10)
expect(self).to receive(:get_user_input).and_return("h")
expect($stdout).to receive(:puts).with("Welcome to the Blackjack Table")
expect($stdout).to receive(:puts).with("Your cards add up to 20")
expect($stdout).to receive(:puts).with("Type 'h' to hit or 's' to stay")
expect($stdout).to receive(:puts).with("Your cards add up to 30")
expect($stdout).to receive(:puts).with("Sorry, you hit 30. Thanks for playing!")
runner
end
end
But since we using random numbers in the following code, how can it be the exact output of Rspec. Here is the Ruby code:
def welcome
# code #welcome here
puts "Welcome to the Blackjack Table"
end
def deal_card
randomNumber = rand(1..11)
end
def display_card_total(total_cards)
puts "Your cards add up to #{total_cards}"
return total_cards
end
def prompt_user
puts "Type 'h' to hit or 's' to stay"
end
def get_user_input
letter = gets.chomp
end
def end_game(card_total)
puts "Sorry, you hit #{card_total}. Thanks for playing!"
end
def initial_round
initOne = deal_card()
initTwo = deal_card()
sumInit = initOne + initTwo
display_card_total(sumInit)
end
def hit?(myNumber)
prompt_user()
result = get_user_input()
card_total = myNumber
if result == 's'
return myNumber
elsif result == 'h'
sumInit = myNumber + deal_card()
return sumInit
else
invalid_command()
end
end
def invalid_command
puts "Please enter a valid command"
end
#####################################################
# get every test to pass before coding runner below #
#####################################################
def runner
welcome()
number = initial_round()
until number > 21
hit?(number)
display_card_total(number)
number += hit?(number)
end
end_game(number)
end
How can the output match the Rspec test if we dealing with random numbers?
The following code in the runner method fixed the problem.
def runner
welcome()
number = initial_round()
until number > 21
number = hit?(number)
display_card_total(number)
end
end_game(number)
end
Related
I've got User model method which calculate a number of user membership. I want to it test by MiniTest. Here is what I have:
def member_for
time_diff = Time.current - created_at
result = ActiveSupport::Duration.build(time_diff.to_i).parts
return "Member for #{result[:years]}y, #{result[:months]}m" if result.key?(:years) && result.key?(:months)
if result.key?(:years) && result.key?(:days)
"Member for #{result[:years]}y, #{result[:days]}d"
elsif result.key?(:months)
"Member for #{result[:months]}m"
elsif result.key?(:days)
"Member for #{result[:days]}d"
end
end
I was trying to write some MiniTest:
test 'member for' do
user.created_at = 2.years.ago + 3.months + 2.days
user.member_for
end
private
def user
#user ||= users(:one)
end
But to be honest, I don't know how to compare if it returns the correct string.
You can decrease the cyclic complexity as well as removing several bugs which give the wrong duration (by omitting weeks and months) by simply iterating across the parts:
def member_for
time_diff = Time.current - created_at
parts = ActiveSupport::Duration.build(time_diff.to_i).parts
formatted = parts.except(:seconds).map do |unit, value|
"#{value} #{unit}"
end
"Member for #{formatted.to_sentance}"
end
You can use a simple lookup table or the I18n API if you want to have abbreviations instead of the full unit names. Array#to_sentance is from ActiveSupport.
You would then test this by:
class MemberTest < ActiveSupport::TestCase
test 'member_for without weeks' do
user.created_at = 2.years.ago + 3.months + 2.days
assert_equal(user.member_for, 'Member for 1 year, 3 months and 2 days')
end
test 'member_for with weeks' do
user.created_at = 2.years.ago + 2.weeks
assert_equal(user.member_for, 'Member for 1 year and 2 weeks')
end
private
def user
#user ||= users(:one)
end
end
However its questionable if this code really belongs in a model in the first place since its presentation and not buisness logic. I would say a helper or decorator/presenter is more suitible then cramming more logic into your models.
I get NoMethodError when I run test for the code below
csv_importer.rb
require 'csv_importer/engine'
class WebImport
def initialize(url)
#url = url
end
def call
url = 'http://example.com/people.csv'
csv_string = open(url).read.force_encoding('UTF-8')
string_to_users(csv_string)
end
def string_to_users(csv_string)
counter = 0
duplicate_counter = 0
user = []
CSV.parse(csv_string, headers: true, header_converters: :symbol) do |row|
next unless row[:name].present? && row[:email_address].present?
user = CsvImporter::User.create row.to_h
if user.persisted?
counter += 1
else
duplicate_counter += 1
end
end
p "Email duplicate record: #{user.email_address} - #{user.errors.full_messages.join(',')}" if user.errors.any?
p "Imported #{counter} users, #{duplicate_counter} duplicate rows ain't added in total"
end
end
csv_importer_test.rb
require 'csv_importer/engine'
require 'test_helper'
require 'rake'
class CsvImporterTest < ActiveSupport::TestCase
test 'truth' do
assert_kind_of Module, CsvImporter
end
test 'should override_application and import data' do
a = WebImport.new(url: 'http://example.com/people.csv')
a.string_to_users('Olaoluwa Afolabi')# <-- I still get error even I put a comma separated list of attributes that is imported into the db here.
assert_equal User.count, 7
end
end
csv format in the url in the code:
This saves into DB once I run the Rake Task
Name,Email Address,Telephone Number,Website
Coy Kunde,stone#stone.com,0800 382630,mills.net
What I have done to debug:
I use byebug and I figured out the in csv_importer_test.rb, the line where I have a.string_to_users('Olaoluwa Afolabi') is throwing error. See byebug error below:
So, I when I run rails test, I get the error below:
So, how do I solve this error, I have no clue what am doing wrong??
If you don't have any row in your csv_string, this line:
user = CsvImporter::User.create row.to_h
isn't executed, so user variable holds previous value, which is []:
user = []
As we know, there's no method errors defined for Array, yet you try to call it in this line:
p "Email duplicate record: #{user.email_address} - #{user.errors.full_messages.join(',')}" if user.errors.any?
and that's why you get an error.
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
The following code works fine in IRB (Interactive Ruby Shell):
require 'prometheus/client'
prometheus = Prometheus::Client.registry
begin
#requests = prometheus.gauge(:demo, 'Random number selected for this users turn.')
rescue Prometheus::Client::Registry::AlreadyRegisteredError => e
end
#requests.set({name: "test"}, 123)
test = #requests.get name: "test"
puts 'output: ' + test.to_s
2.4.0 :018 > load 'test.rb'
output: 123.0
=> true
2.4.0 :019 >
However, when I put the same code into my Ruby on Rails controller, the second time the user uses the application, the following error is returned:
undefined method `set' for nil:NilClass
Can someone tell me when I'm doing wrong? Thank you.
require 'prometheus/client'
class RandomnumbersController < ApplicationController
def index
#randomnumbers = Randomnumber.order('number DESC').limit(8)
#counter = 0
end
def show
#randomnumber = Randomnumber.find(params[:id])
end
def new
end
def create
#randomnumber = Randomnumber.new(randomnumber_params)
prometheus = Prometheus::Client.registry
begin
#requests = prometheus.gauge(:demo, 'Random number selected for this users turn.')
rescue Prometheus::Client::Registry::AlreadyRegisteredError => e
end
#requests.set({name: "test"}, 123)
test = #requests.get name: "test"
#randomnumber.save
redirect_to #randomnumber
end
private
def randomnumber_params
params.require(:randomnumber).permit(:name, :number)
end
end
Because there is no #requests for :demo argument.
When ORM cannot find any info in db it returns nil (NilClass)
and You're trying to do:
#requests.set({name: "test"}, 123)
it's interpreted like:
nil.set({name: "test"}, 123)
why it's causes this issue in second time?
cuz Your code changes #requests name attribute to be test and seems like :demo is not test or maybe in another part of Your app You're replacing/deleting data in database that makes: #requests = prometheus.gauge(:demo, 'Random number selected for this users turn.') to return nil
Solution:
in code level add this fixes to avoid such unpredictable situations (check for nil) :
unless #requests.nil?
#requests.set({name: "test"}, 123)
test = #requests.get name: "test"
end
I have this test:
describe 'check_account_status' do
it 'should send the correct reminder email one week prior to account disablement' do
# Three weeks since initial email
reverification = create(:reverification)
initial_notification = reverification.twitter_reverification_sent_at.to_datetime
ActionMailer::Base.deliveries.clear
Timecop.freeze(initial_notification + 21) do
Reverification.check_account_status
ActionMailer::Base.deliveries.size.must_equal 1
ActionMailer::Base.deliveries.first.subject.must_equal I18n.t('.account_mailer.one_week_left.subject')
reverification.reminder_sent_at.class.must_equal ActiveSupport::TimeWithZone
reverification.notification_counter.must_equal 1
must_render_template 'reverification.html.haml'
end
end
This test produces this error:
check_account_status#test_0001_should send the correct reminder email one week prior to account disablement [/Users/drubio/vms/ohloh-ui/test/models/reverification_test.rb:67]:
Expected: ActiveSupport::TimeWithZone
Actual: NilClass
Here is my code:
class Reverification < ActiveRecord::Base
belongs_to :account
FIRST_NOTIFICATION_ERROR = []
class << self
def check_account_status
Reverification.where(twitter_reverified: false).each do |reverification|
calculate_status(reverification)
one_week_left(reverification)
end
end
private
def calculate_status(reverification)
#right_now = Time.now.utc.to_datetime
#initial_email_date = reverification.twitter_reverification_sent_at.to_datetime
#notification_counter = reverification.notification_counter
end
def one_week_left(reverification)
# Check to see if three weeks have passed since the initial email
# and check to see if its before the one day notification before
# marking an account as spam.
if (#right_now.to_i >= (#initial_email_date + 21).to_i) && (#right_now.to_i < (#initial_email_date + 29).to_i)
begin
AccountMailer.one_week_left(reverification.account).deliver_now
rescue
FIRST_NOTIFICATION_FAILURE << account.id
return
end
update_reverification_fields(reverification)
end
end
def update_reverification_fields(reverification)
reverification.notification_counter += 1
reverification.reminder_sent_at = Time.now.utc
reverification.save!
reverification.reload
end
end
Forgive the indentation, but what seems to be the problem, is that my reverification object doesn't update when it leaves the check_account_status method. I've placed puts statements through out the code and I can see without a doubt that the reverification object is indeed updating. However after it leaves the update_reverification_fields and returns to the test block, the fields are not updated. Why is that? Has anyone encountered this?
I believe you have a scope issue, the methods you call from check_account_status certainly don't return the updated object back to your method and Ruby only passes parameters by value.
Try something like this instead:
def check_account_status
Reverification.where(twitter_reverified: false).each do |reverification|
reverification = calculate_status(reverification)
reverification = one_week_left(reverification)
end
end
private
def calculate_status(reverification)
# ...
reverification
end
def one_week_left(reverification)
# ...
reverification = update_reverification_fields(reverification)
reverification
end
def update_reverification_fields(reverification)
# ...
reverification
end
The problem is that reverification object in your test and objects inside of check_account_status are different instances of the same model.
def update_reverification_fields(reverification)
reverification.notification_counter += 1
reverification.reminder_sent_at = Time.now.utc
reverification.save!
reverification.reload
end
This reload here, it's doing nothing. Let's walk through your test.
# check_account_status runs a DB query, finds some objects and does things to them
Reverification.check_account_status
# this expectation succeeds because you're accessing `deliveries` for the
# first time and you don't have it cached. So you get the actual value
ActionMailer::Base.deliveries.size.must_equal 1
# this object, on the other hand, was instantiated before
# `check_account_status` was called and, naturally, it doesn't see
# the database changes that completely bypassed it.
reverification.reminder_sent_at.class.must_equal ActiveSupport::TimeWithZone
So, before making expectations on reverification, reload it, so that it pulls latest data from the DB.
reverification.reload # here
reverification.reminder_sent_at.class.must_equal ActiveSupport::TimeWithZone