I am working on an assignment in Ruby.. I had to write RSPEC tests for a method that removes an entry from my address_book app.. and also had to write a test and a method for importing 5 entries from a CSV. In my remove_entry method when I run the Specs it says that I have an undefined method of delete. I have already asked a couple fellow ruby devs and they could not figure it out at first glance. The next error is that when I run the tests for my CSV imports.. The data is not being imported in the proper order. I have spent several hours walking through my code over and over and researching in attempts to fix this.. I am at my wits end.. Any help would be appreciated!
address_book.rb
require_relative 'entry'
require "csv"
class AddressBook
attr_accessor :entries
def initialize
#entries = []
end
def add_entry(name,phone_number,email)
index = 0
#entries.each do |entry|
if name < entry.name
break
end
index += 1
end
#entries.insert(index, Entry.new(name, phone_number, email))
end
def import_from_csv(file_name)
csv_text = File.read(file_name)
csv = CSV.parse(csv_text, headers: true, skip_blanks: true)
csv.each do |row|
row_hash = row.to_hash
add_entry(row_hash["name"], row_hash["phone_number"], row_hash["email"])
#can you clarify what the above is doing
#is the format of row_hash["name"] because it is iterating over a hash or because it is an array?
end
end
def remove_entry(name,phone_number,email)
#entries.each do |entry|
if (name == entry.name) && (email == entry.email) && (phone_number = entry.phone_number)
entry.delete #this line returns an error in my RSPEC test
else
p "Entry does not exist \n Please try again."
end
end
end
end
address_book_spec.rb
require_relative "../models/address_book"
RSpec.describe AddressBook do
let(:book) {AddressBook.new} # => lets us use the book variable in every test
describe "attributes" do
it "should respond to entries" do
# book = AddressBook.new # => Replaced by line 4
expect(book).to respond_to(:entries)
end
it "should initialize entries as an array" do
# book = AddressBook.new # => Replaced by line 4
expect(book.entries).to be_a(Array)
end
it "should initialize entries as an empty array" do
# book = AddressBook.new # => Replaced by line 4
expect(book.entries.size).to eq(0)
end
end
describe "#add_entry" do
it "adds only a single entry to the Address Book" do
# book = AddressBook.new # => Replaced by line 4
book.add_entry('Ada Lovelace', '010.012.1815', 'augusta.king#lovelace.com')
expect(book.entries.size).to eq(1)
end
it "adds the correct information to entries" do
# book = AddressBook.new # => Replaced by line 4
book.add_entry('Ada Lovelace', '010.012.1815', 'augusta.king#lovelace.com')
new_entry = book.entries[0]
expect(new_entry.name).to eq('Ada Lovelace')
expect(new_entry.phone_number).to eq('010.012.1815')
expect(new_entry.email).to eq('augusta.king#lovelace.com')
end
end
# added remove entry test
describe "#remove_entry" do
it "should remove a single entry" do
# book = AddressBook.new # => Replaced by line 4
book.add_entry('Austin Thesing', '800.445.8833','austin#thesing.xyz')
expect(book.entries.size).to eq(1)
book.remove_entry('Austin Thesing', '800.445.8833','austin#thesing.xyz')
expect(book.entries.size).to eq(0)
end
end
def check_entry(entry,expected_name,expected_phone_number, expected_email)
expect(entry.name).to eql(expected_name)
expect(entry.phone_number).to eql(expected_phone_number)
expect(entry.email).to eql(expected_email)
end
describe "#import_from_csv" do
it "import an entry from a CSV file" do
book.import_from_csv("entries.csv")
book_size = book.entries.size
expect(book_size).to eq 5 #checks the size of the book
end
it "adds the first entry" do
book.import_from_csv("entries.csv")
entry_one = book.entries[0]
check_entry(entry_one,"Mark Griffo","123456789","mark#bloc.com")
end
it "adds the second entry" do
book.import_from_csv("entries.csv")
entry_two = book.entries[1]
check_entry(entry_two,"Natalie Griffo","123456789","natalie#bloc.com")
end
it "adds the third entry" do
book.import_from_csv("entries.csv")
entry_three = book.entries[2]
check_entry(entry_three, "Steve Thesing", "8583878899", "steve#steve.com")
end
it "adds the fourth entry" do
book.import_from_csv("entries.csv")
entry_four = book.entries[3]
check_entry(entry_four, "Haidee Thesing", "8584458833", "h#thesing.com")
end
it "adds the fifth entry" do
book.import_from_csv("entries.csv")
entry_five = book.entries[4]
check_entry(entry_five, "Olivia Meers", "0987654321", "olivia#meers.com")
end
end
end
Terminal Output/Spec Failures
Austins-MacBook-Pro:address-bloc austinthesing$ rspec spec/address_book_spec.rb
.....F.FFFFF
Failures:
1) AddressBook#remove_entry should remove a single entry
Failure/Error: book.remove_entry('Austin Thesing', '800.445.8833','austin#thesing.xyz')
NoMethodError:
undefined method `delete' for #<Entry:0x007f8e8c1dea08>
# ./models/address_book.rb:37:in `block in remove_entry'
# ./models/address_book.rb:35:in `each'
# ./models/address_book.rb:35:in `remove_entry'
# ./spec/address_book_spec.rb:45:in `block (3 levels) in <top (required)>'
2) AddressBook#import_from_csv adds the first entry
Failure/Error: expect(entry.name).to eql(expected_name)
expected: "Mark Griffo"
got: "Haidee Thesing"
(compared using eql?)
# ./spec/address_book_spec.rb:50:in `check_entry'
# ./spec/address_book_spec.rb:64:in `block (3 levels) in <top (required)>'
3) AddressBook#import_from_csv adds the second entry
Failure/Error: expect(entry.name).to eql(expected_name)
expected: "Natalie Griffo"
got: "Mark Griffo"
(compared using eql?)
# ./spec/address_book_spec.rb:50:in `check_entry'
# ./spec/address_book_spec.rb:69:in `block (3 levels) in <top (required)>'
4) AddressBook#import_from_csv adds the third entry
Failure/Error: expect(entry.name).to eql(expected_name)
expected: "Steve Thesing"
got: "Natalie Griffo"
(compared using eql?)
# ./spec/address_book_spec.rb:50:in `check_entry'
# ./spec/address_book_spec.rb:74:in `block (3 levels) in <top (required)>'
5) AddressBook#import_from_csv adds the fourth entry
Failure/Error: expect(entry.name).to eql(expected_name)
expected: "Haidee Thesing"
got: "Olivia Meers"
(compared using eql?)
# ./spec/address_book_spec.rb:50:in `check_entry'
# ./spec/address_book_spec.rb:79:in `block (3 levels) in <top (required)>'
6) AddressBook#import_from_csv adds the fifth entry
Failure/Error: expect(entry.name).to eql(expected_name)
expected: "Olivia Meers"
got: "Steve Thesing"
(compared using eql?)
# ./spec/address_book_spec.rb:50:in `check_entry'
# ./spec/address_book_spec.rb:84:in `block (3 levels) in <top (required)>'
Finished in 0.0176 seconds (files took 0.08714 seconds to load)
12 examples, 6 failures
Failed examples:
rspec ./spec/address_book_spec.rb:40 # AddressBook#remove_entry should remove a single entry
rspec ./spec/address_book_spec.rb:61 # AddressBook#import_from_csv adds the first entry
rspec ./spec/address_book_spec.rb:66 # AddressBook#import_from_csv adds the second entry
rspec ./spec/address_book_spec.rb:71 # AddressBook#import_from_csv adds the third entry
rspec ./spec/address_book_spec.rb:76 # AddressBook#import_from_csv adds the fourth entry
rspec ./spec/address_book_spec.rb:81 # AddressBook#import_from_csv adds the fifth entry
First test is failing because the delete method is being used incorrectly:
entry.delete #this line returns an error in my RSPEC test
needs to be
#entries.delete(entry)
Entries are being inserted in a scrambled fashion because you are using break where you probably want
next
(I suppose that loop is supposed to insert in alphabetical order.)
break command terminates the full each block, so no more iterations are executed if there is one name of higher alphabetical order, next just skips to the next iteration.
That should be it
Related
I am writing a connect-four game for more OO and rspec practice. As part of my program, I would like to prompt the user to choose a column that they would like to put their game piece in. Here's that method:
def get_col_choice(input, output)
output.print "Player #{#player}, choose a column from 0-6: "
input_string = input.gets.chomp
begin
col_choice = Integer(input_string)
raise("The column you chose is out of bounds.") if out_of_bounds?(col_choice)
raise("The column you chose is fully occupied.") if unavailable?(col_choice)
return col_choice
rescue TypeError, ArgumentError
output.puts "Your choice of column is invalid. Try again."
rescue RuntimeError => err
puts err.message
end
get_col_choice(input, output)
end
In IRB, everything works as I planned. My hangup is in rspec where I am faced with a NoMethodError, which I think is coming from my recursive call to get_col_choice.
Can anyone help me understand what I can do to either improve get_col_choice or write the correct tests in Rspec? This is what my console output from my rspec file looks like:
Failures:
1) ConnectFourGame#get_col_choice notifies users which player gets to choose a column
Failure/Error: $connect_four.get_col_choice(input, output)
NoMethodError:
undefined method `chomp' for nil:NilClass
# /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:31:in `get_col_choice'
# /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:42:in `get_col_choice'
# ./connect_four_game_spec.rb:52:in `block (3 levels) in <top (required)>'
2) ConnectFourGame#get_col_choice returns the player's column choice
Failure/Error: expect($connect_four.get_col_choice(input, output)).to eq(0)
NoMethodError:
undefined method `chomp' for nil:NilClass
# /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:31:in `get_col_choice'
# /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:42:in `get_col_choice'
# ./connect_four_game_spec.rb:57:in `block (3 levels) in <top (required)>'
3) ConnectFourGame#get_col_choice notifies the user if the column they chose is already full of pieces
Failure/Error: expect(output.string).to include("The column you chose is fully occupied.")
expected "" to include "The column you chose is fully occupied."
# ./connect_four_game_spec.rb:62:in `block (3 levels) in <top (required)>'
4) ConnectFourGame#get_col_choice notifies the user their input is invalid when a non-numeric string is entered
Failure/Error: $connect_four.get_col_choice(input, output)
NoMethodError:
undefined method `chomp' for nil:NilClass
# /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:31:in `get_col_choice'
# /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:42:in `get_col_choice'
# ./connect_four_game_spec.rb:67:in `block (3 levels) in <top (required)>'
5) ConnectFourGame#get_col_choice column choice is out of bounds notifies the user their column choice is out of bounds when col is greater than 6
Failure/Error: $connect_four.get_col_choice(input, output)
NoMethodError:
undefined method `chomp' for nil:NilClass
# /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:31:in `get_col_choice'
# /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:42:in `get_col_choice'
# ./connect_four_game_spec.rb:74:in `block (4 levels) in <top (required)>'
6) ConnectFourGame#get_col_choice column choice is out of bounds notifies the user their column choice is out of bounds when col is less than 0
Failure/Error: $connect_four.get_col_choice(input, output)
NoMethodError:
undefined method `chomp' for nil:NilClass
# /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:31:in `get_col_choice'
# /home/learnsometing/Desktop/the_Odin_Project/ruby/connect-four/lib/connect_four_game.rb:42:in `get_col_choice'
# ./connect_four_game_spec.rb:80:in `block (4 levels) in <top (required)>'
Finished in 0.02399 seconds (files took 0.1108 seconds to load)
12 examples, 6 failures
Failed examples:
rspec ./connect_four_game_spec.rb:51 # ConnectFourGame#get_col_choice notifies users which player gets to choose a column
rspec ./connect_four_game_spec.rb:56 # ConnectFourGame#get_col_choice returns the player's column choice
rspec ./connect_four_game_spec.rb:60 # ConnectFourGame#get_col_choice notifies the user if the column they chose is already full of pieces
rspec ./connect_four_game_spec.rb:66 # ConnectFourGame#get_col_choice notifies the user their input is invalid when a non-numeric string is entered
rspec ./connect_four_game_spec.rb:73 # ConnectFourGame#get_col_choice column choice is out of bounds notifies the user their column choice is out of bounds when col is greater than 6
rspec ./connect_four_game_spec.rb:79 # ConnectFourGame#get_col_choice column choice is out of bounds notifies the user their column choice is out of bounds when col is less than 0
Here are the tests I wrote for get_col_choice:
describe '#get_col_choice' do
let(:output) { StringIO.new }
let(:input) { StringIO.new("0\n") }
it 'notifies users which player gets to choose a column' do
$connect_four.get_col_choice(input, output)
expect(output.string).to include("Player 2, choose a column from 0-6: ")
end
it "returns the player's column choice" do
expect($connect_four.get_col_choice(input, output)).to eq(0)
end
it 'notifies the user if the column they chose is already full of pieces' do
6.times { $connect_four.board.add_game_piece_to(0, "\u2468") }
expect(output.string).to include("The column you chose is fully occupied.")
end
let(:input) { StringIO.new("!\n") }
it 'notifies the user their input is invalid when a non-numeric string is entered' do
$connect_four.get_col_choice(input, output)
expect(output.string).to include("Your choice of column is invalid. Try again.")
end
context 'column choice is out of bounds' do
let(:input) { StringIO.new("7\n") }
it 'notifies the user their column choice is out of bounds when col is greater than 6' do
$connect_four.get_col_choice(input, output)
expect(output.string).to include("The column you chose is out of bounds.")
end
let(:input) { StringIO.new("-1\n") }
it 'notifies the user their column choice is out of bounds when col is less than 0' do
$connect_four.get_col_choice(input, output)
expect(output.string).to include("The column number you chose is out of bounds.")
end
end
end
I was able to solve my problem by removing the input and output from get_col_choice:
def get_col_choice
print "Player #{#player}, choose a column from 0-6: "
input_string = gets.chomp
begin
col_choice = Integer(input_string)
raise("The column you chose is out of bounds.") if out_of_bounds?(col_choice)
raise("The column you chose is fully occupied.") if #board.unavailable?(col_choice)
return col_choice
rescue TypeError, ArgumentError
puts "Your choice of column is invalid. Try again."
rescue RuntimeError => err
puts err.message
end
get_col_choice
end
In my rspec file, instead of using StringIO to inject the behavior I expected into get_col_choice, I stubbed gets to return the values I needed to trigger expected behaviors:
#code left out for brevity
before(:each) do
#game = ConnectFourGame.new
end
describe '#get_col_choice' do
before(:each) do
allow($stdout).to receive(:write)
#game.player = 1
end
context 'valid input' do
before(:each) do
allow(#game).to receive(:gets) {"0\n"}
end
it 'notifies users which player gets to choose a column' do
expect{ #game.get_col_choice }.to output('Player 1, choose a column from 0-6: ').to_stdout
end
it "returns the user's column choice as an integer" do
expect(#game.get_col_choice).to eq(0)
end
end
context 'invalid input' do
output_expectation = Proc.new{ expect { #game.get_col_choice }.to output(output).to_stdout }
it 'notifies the user if the column they chose is already full of pieces' do
allow(#game).to receive(:gets).and_return("0\n", "1\n")
6.times { #game.board.add_game_piece_to(0, "\u2648") }
output = "Player 1, choose a column from 0-6: The column you chose is fully occupied.\nPlayer 1, choose a column from 0-6: "
output_expectation
end
it 'notifies the user their input is invalid when a non-numeric string is entered' do
allow(#game).to receive(:gets).and_return("foo\n", "0\n")
output = "Player 1, choose a column from 0-6: Your choice of column is invalid. Try again.\nPlayer 1, choose a column from 0-6: "
output_expectation
end
it 'notifies the user their column choice is out of bounds when col is greater than 6' do
allow(#game).to receive(:gets).and_return("7\n", "0\n")
output = "Player 1, choose a column from 0-6: The column you chose is out of bounds.\nPlayer 1, choose a column from 0-6: "
output_expectation
end
it 'notifies the user their column choice is out of bounds when col is less than 0' do
allow(#game).to receive(:gets).and_return("-1\n", "0\n")
output = "Player 1, choose a column from 0-6: The column you chose is out of bounds.\nPlayer 1, choose a column from 0-6: "
output_expectation
end
end
end
The reason I received errors in my previous implementation of get_col_choice was because after StringIO received my first input string, it closed, resulting in a value of nil that I tried to call gets on.
I have the following table TodoList :
class CreateTodoLists < ActiveRecord::Migration
def change
create_table :todo_lists do |t|
t.string :list_name
t.date :list_due_date
t.timestamps null: false
end
end
end
I create crud methods:
def create_todolist(params)
todolist = TodoList.create(params)
end
And i have the followging tests:
context "the code has to create_todolist method" do
it { is_expected.to respond_to(:create_todolist) }
it "should create_todolist with provided parameters" do
expect(TodoList.find_by list_name: "mylist").to be_nil
due_date=Date.today
assignment.create_todolist(:name=> 'mylist', :due_date=>due_date)
testList = TodoList.find_by list_name: 'mylist'
expect(testList.id).not_to be_nil
expect(testList.list_name).to eq "mylist"
expect(testList.list_due_date).to eq due_date
expect(testList.created_at).not_to be_nil
expect(testList.updated_at).not_to be_nil
end
end
When i launch the test give me the following errors:
assignment code has create_todolist method should create_todolist with provided parameters
Failure/Error: assignment.create_todolist(:name=> 'mylist', :due_date=>due_date)
ActiveRecord::UnknownAttributeError:
unknown attribute 'name' for TodoList.
# ./assignment/assignment.rb:25:in `create_todolist'
# ./spec/assignment_spec.rb:171:in `block (4 levels) in <top (required)>'
# ./spec/assignment_spec.rb:14:in `block (2 levels) in <top (required)>'
# ------------------
# --- Caused by: ---
# NoMethodError:
# undefined method `name=' for #<TodoList:0x007f96dd0d13f0>
# ./assignment/assignment.rb:25:in `create_todolist'
Finished in 0.14136 seconds (files took 1.66 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/assignment_spec.rb:168 # Assignment rq03 rq03.2 assignment code has create_todolist method should create_todolist with provided parameters
I think it's because params attributes does not match exactly the same TodoList attributes. How to modify my create_todolist to change keys values ?
Your field is called list_name, but you're passing :name => 'myList'.
The same for due_date and list_due_date.
Should be
assignment.create_todolist(list_name:'mylist', list_due_date: due_date)
Given this code:
class Game
def self.game_board
return [[][][]]
end
def self.empty_output
'_|_|_'+
'_|_|_'+
' | |'
end
end
and these tests:
describe 'It should display a grid' do
it 'should have empty output' do
Game.empty_output.should ==
'_|_|_'+
'_|_|_'+
' | |'
end
it 'should have an empty array for the game ' do
Game.game_board.should ==
[[][][]]
end
end
why does the test fail that tries to return the array of arrays with the error
.F
Failures:
1) "It should display a grid should be empty
Failure/Error: return [[][][]]
ArgumentError:
wrong number of arguments (0 for 1..2)
# ./checkers_rspec.rb:4:in `[]'
# ./checkers_rspec.rb:4:in `game_board'
# ./checkers_rspec.rb:24:in `block (2 levels) in <top (required)>'
Finished in 0.00121 seconds
2 examples, 1 failure
[[][][]] should hae been [[],[],[]] in both cases.
I'm trying to create a classifier using the cardmagic classifier gem. This is my code:
require 'classifier'
classifications = '1007.09', '1006.03'
traindata = Hash["1007.09" => "ADAPTER- SCREENING FOR VALVES VBS", "1006.03" => "ACTUATOR- LINEAR"]
b = Classifier::Bayes.new classifications
traindata.each do |key, value|
b.train(key, value)
end
But when I run this I get the following error:
Notice: for 10x faster LSI support, please install http://rb-gsl.rubyforge.org/
c:/Ruby192/lib/ruby/gems/1.9.1/gems/classifier-1.3.3/lib/classifier/bayes.rb:27:in `block in train': undefined method `[]' for nil:NilClass (NoMethodError)
from c:/Ruby192/lib/ruby/gems/1.9.1/gems/classifier-1.3.3/lib/classifier/bayes.rb:26:in `each'
from c:/Ruby192/lib/ruby/gems/1.9.1/gems/classifier-1.3.3/lib/classifier/bayes.rb:26:in `train'
from C:/_Chris/Code/classifier/smdclasser.rb:13:in `block in <main>'
from C:/_Chris/Code/classifier/smdclasser.rb:11:in `each'
from C:/_Chris/Code/classifier/smdclasser.rb:11:in `<main>'
This is the source from the gem code:
# Provides a general training method for all categories specified in Bayes#new
# For example:
# b = Classifier::Bayes.new 'This', 'That', 'the_other'
# b.train :this, "This text"
# b.train "that", "That text"
# b.train "The other", "The other text"
def train(category, text)
category = category.prepare_category_name
text.word_hash.each do |word, count|
#categories[category][word] ||= 0
#categories[category][word] += count
#total_words += count
end
end
I am lost where to go to troubleshoot this error, what is the next step I should take?
Classifier::Bayes.new expects an exploded array of values, rather than a single parameter. For example, notice that the sample code uses:
b = Classifier::Bayes.new 'This', 'That', 'the_other'
rather than:
b = Classifier::Bayes.new ['This', 'That', 'the_other']
Pass in the splat version of your classifications array and it should work:
b = Classifier::Bayes.new *classifications
Well, I think the code speaks out for itself :)
# book_in_stock.rb
class BookinStock
attr_reader :isbn, :price
def initialize(isbn, price)
#isbn = isbn
#price = Float(price)
end
end
# csv_reader.rb
require 'csv'
class CsvReader
def initialize
#book_in_stock = []
end
def read_in_csv_data(csv_file_name)
CSV.foreach(csv_file_name, headers: true) do |row|
#books_in_stock << BookinStock.new(row["ISBN"], row["Amount"])
end
end
# later we'll see how to use inject to sum a collection
def total_value_in_stock
sum = 0.0
#books_in_stock.each { |book| sum += book.price }
sum
end
def number_of_each_isbn
# ...
end
end
# stock_stats.rb
reader = CsvReader.new()
ARGV.each do |csv_file_name|
STDERR.puts "[+] Processing #{csv_file_name}"
reader.read_in_csv_data(csv_file_name)
end
puts "[+] Total value = #{reader.total_value_in_stock}"
When running I get:
# +search/pickaxe/csv $ ruby1.9.1 test.rb data.csv
# [+] Processing data.csv
# test.rb:23:in `block in read_in_csv_data': undefined method `<<' for nil:NilCla
# ss (NoMethodError)
# from /usr/lib/ruby/1.9.1/csv.rb:1760:in `each'
# from /usr/lib/ruby/1.9.1/csv.rb:1196:in `block in foreach'
# from /usr/lib/ruby/1.9.1/csv.rb:1334:in `open'
# from /usr/lib/ruby/1.9.1/csv.rb:1195:in `foreach'
# from test.rb:22:in `read_in_csv_data'
# from test.rb:46:in `block in <main>'
# from test.rb:44:in `each'
# from test.rb:44:in `<main>'
What've I done wrong?
'#books_in_stock' != '#book_in_stock' # !
(typo)
You have a typo in the initialize function (#book_in_store insteadn of #books_in_store). Is that it?
By the way, the sum of the prices can be done with inject in one line
#books_in_stock.inject(0) { |sum, book| sum + book.price }
If you run the command with ruby -w, it'll turn on warnings, and the interpreter will explain what went wrong:
$ ruby -w book_in_stock.rb
book_in_stock.rb:28: warning: instance variable #books_in_stock not initialized
book_in_stock.rb:28:in `total_value_in_stock': undefined method `each' for nil:NilClass (NoMethodError)
from book_in_stock.rb:47:in `<main>'