Strange behavior using CSV class [duplicate] - ruby

I'm working on the EventReporter project to help learn Ruby.
Here's what I've got so far:
require 'CSV'
puts 'Welcome to Event Reporter!'
print 'Enter command: '
command = gets.chomp
def clean(attribute, type)
if (type == 'regdate')
elsif (type == 'first_name')
elsif (type == 'last_name')
elsif (type == 'email_address')
elsif (type == 'homephone')
homephone = attribute
homephone = homephone.to_s.gsub(/\D/, '')
if (homephone.length < 10)
homephone = '0000000000'
elsif (homephone.length == 11)
if (homephone[0] == '1')
homephone[0] = ''
else
homephone = '0000000000'
end
elsif (homephone.length > 11)
homephone = '0000000000'
end
return homephone
elsif (type == 'street')
elsif (type == 'city')
elsif (type == 'state')
elsif (type == 'zipcode')
zipcode = attribute.to_s.rjust(5, "0")[0..4]
return zipcode
end
return attribute
end
queue = []
while (command != 'q') do
command = command.split
if (command[0] == 'load')
command[1] ? filename = command[1] : filename = 'event_attendees.csv'
attendees = CSV.open filename, headers: true, header_converters: :symbol
puts "Loaded #{filename}"
elsif (command[0] == 'find')
attribute = command[1]
criteria = command[2]
# REACHES HERE SECOND TIME AROUND
puts "#{command[0]} #{command[1]} #{command[2]}"
attendees.each do |attendee|
# ISNT REACHING HERE SECOND TIME AROUND
puts 'TEST'
# get cleaned attendee attribute
attendee_attribute = clean(attendee[attribute.to_sym], attribute)
# see if it matches the criteria input
if criteria.to_s.downcase.strip == attendee_attribute.to_s.downcase.strip
# if it does, add the attendee to the queue
puts 'Match!'
queue << attendee
end
end
end
print 'Enter command: '
command = gets.chomp
end
It seems that the attendees.each isn't being executed the second time through the while loop. Why is this?
~/practice/event_manager >> ruby 'lib/event_reporter.rb'
Welcome to Event Reporter!
Enter command: load
Loaded event_attendees.csv
Enter command: find zipcode 11111
find zipcode 11111
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
Enter command: find zipcode 11111
find zipcode 11111
Enter command: q
~/practice/event_manager >>

According to the docs, the CSV object behaves basically like a regular IO object. They keep track of their current position in the file which is advanced by reading through it, generally line by line. So on your first attendees.each, you read through the entire file. Subsequent calls to .each will try to read the next line, but there is not any since we are already at the end of the file hence your loop does not execute anymore.
You can fix this by rewinding the underlying IO instance to the beginning of the file, using #rewind. In your specific case, put it after iterating through the attendees.
attendees.each do |attendee|
# ...
end
attendees.rewind

Related

Ruby - Create a class with a file as a variable - possible?

I need to create a program in which the user can take different tests. As i dont want to copy paste my code all over for every test, i have tried to setup a class for that purpose - but i have problems with this class.
Error message = undefined variables or method in 'display_test'
I have predefined som test as a .txt file
I want to choose the file in the class depending on what the user answer - is that possible?
Class code:
class Test
#correct_answers = 0
def display_question( question, options, answer )
puts question
options.each_with_index { |option, idx| puts "#{ idx + 1 }: #{ option
}" }
print 'Answer: '
reply = gets.to_i
if answer == reply
puts 'Correct!'
#correct_answers += 1
puts "#{#correct_answers}"
else
puts 'Wrong. The correct answer was: ' + answer.to_s
end
end
def display_test()
f = File.new(userinput, 'r')
while ! (f.eof?) #logikken til at splitte
line = f.gets()
question = line.split("|")
question[1] = question[1].split(";")
display_question question[0], question[1], question[2].to_i
end
end
display_test
puts "________________________________________________________"
puts "Total score:"
puts "You've got" + " #{#correct_answers}" + " correct answers!"
Before hand i have used ("geografitest.txt") instead of username in the File.new so it looked like this:
f = File.new('geografitest.txt','r')
But now i am trying to let the user decide what test to take.
I am very new to ruby, so please bear with me.
I have tried to do it this way, which obviously is not working.
puts "Which test do you want to take?"
select = 0
while (select != 3)
puts "Press 1 to take Geografi test."
puts "Press 2 to take Math test."
puts "Press 3 to take Religion test."
puts "Press 3 to exit"
select = gets.chomp.to_i
if (select == 1)
gets.chomp = userinput
userinput =`geografitest.txt`
echo $userinput
end
if (select == 2)
gets.chomp = userinput
userinput =`matematiktest.txt`
echo $userinput
end
if (select == 3)
gets.chomp = userinput
userinput =`religionstest.txt`
echo $userinput
end
if (select > 4)
puts "Not a correct selection"
elsif (select == 4)
puts "Goodbye"
end
end
abort
So my questions is now;
How can i make the user choose what test to take? Can i make a variable instead of the textfile as i have tried, but in a different way? Or is there a smarter way?
And in what way is my class wrong and how do i fix it? I know its not the way to make it, but i simple cant get my head around how to make it right.
Please help a rookie out.
Cheers!
You can pass file as dependency to you Test class based on user input with object constructor. Something like this
class Test
attr_reader :correct_answers_count
def initialize(file)
#file = file
#correct_answers_count = 0
end
#other code goes here
end
loop do
case user_input = gets.chomp
when '1'
file_name = 'some_file1'
when '2'
file_name = 'some_file1'
when '3'
break
else
puts 'wrong variant'
end
test = Test.new(File.new(file_name, 'r'))
test.display
end

formal argument cannot be a class variable in ruby

I've never used Ruby before, and am attempting to run a program written
long ago. I've installed Ruby 2.4.1 and the gem package [test-unit
3.4.3] OK, but when I try to run it, I get an error:
tcreporter.rb:156: formal argument cannot be a class variable
##tc_array.each do |##tc|
^
Is there something in particular I'm doing wrong?
Below is the code snippet :
class TCReporter
##m = nil; ##c = nil; ##tc = nil; ##status = 'p'; ##notes = nil; ##tlArr = []
##resultMapping = {'p'=>'PASS', 'f'=>'FAIL', 'b'=>'BLOCKED', 's'=>'SKIP','pr'=>'PREQFAIL', 'con'=>'CONERR', 'h'=>'HWSKIP', 'cor'=>'CORE'}
def self.report_result(currentTest, status, notes=nil)
if $trInit
##m = currentTest.split('(')[0] ###m is the test METHOD currently being executed in the framework.
##c = currentTest.split('(')[1].split(')')[0] ###c is the test CLASS currently being executed in the framework
if ##c =~ /(?:#{TESTRAIL_TEST_PREFIXES.join('|')})_\d*$/i #If there's a mapping on the test class then report a test class result.
##tc = ##c.scan(/(?:#{TESTRAIL_TEST_PREFIXES.join('|')})_\d*$/i)[0].upcase.sub('_', '-') #Get the TR class mapping
#When reporting at the test class level, the status always starts out as 'p' (PASS). If there's any
#non-passing status for any test method within the test class (blocked or failed) then use that result
#for reporting. Once the global status '##status' has been updated once then no more updating occurs.
if ##status == 'p' && status != 'p'
##status = status
##notes = notes
end
if eval("#{##c}.public_instance_methods.grep(/^test_/).sort.first") == ##m && eval("#{##c}.public_instance_methods.grep(/^test_/).sort.last") == ##m #The first test method is the last test method. All done, do a TestLink update.
begin
result = TR.report_tc_result(##tc, TESTRAIL_PROJECT, TESTRAIL_MILESTONE, TESTRAIL_PLAN, TESTRAIL_RUN, TESTRAIL_BUILD, ##status, (##notes ? ##notes : notes))
ensure
result.case_id = ##tc
result.class = ##c
result.method = ##m
if !result.success #success means a successful communication with testLink and test case was found and updated.
$trReport = ReportFile.new('tr_report.txt') if !$trReport
$trReport.puts "#{##tc}, #{TEST_ARGS.project}, #{TEST_ARGS.plan}, #{TEST_ARGS.build}, #{TEST_ARGS.platform}, #{##c}, #{##m}, #{status} 'class', #{result.message ||= result.exception}"
end
end
elsif eval("#{##c}.public_instance_methods.grep(/^test_/).sort.first") == ##m #A new test class is being evaluated. Set everything to default except status (use whatever the first test class returned).
##m = nil; ##c = nil; ##tc = nil; ##status = status
elsif eval("#{##c}.public_instance_methods.grep(/^test_/).sort.last") == ##m #Done with the test class. Time to report the test result.
begin
result = TR.report_tc_result(##tc, TESTRAIL_PROJECT, TESTRAIL_MILESTONE, TESTRAIL_PLAN, TESTRAIL_RUN, TESTRAIL_BUILD, ##status, (##notes ? ##notes : notes))
ensure
result.case_id = ##tc
result.class = ##c
result.method = ##m
if !result.success #success means a successful communication with testLink and test case was found and updated.
$trReport = ReportFile.new('tr_report.txt') if !$trReport
$trReport.puts "#{##tc}, #{TEST_ARGS.project}, #{"#{TEST_ARGS.milestone}, " if TEST_ARGS.milestone}#{TEST_ARGS.plan}, #{TEST_ARGS.build}, #{TEST_ARGS.platform}, #{##c}, #{##m}, #{status} 'class', #{result.message ||= result.exception}"
end
end
else #The test class is still being executed. Don't update TestLink yet, just check for a non-passing result.
if ##status == 'p' && status != 'p' #Update the test status if it's a non-pass result. Otherwise, use the earlier failed or blocked status.
##status = status
end
end
end
#If there's a mapping on a test method then report a test method result.
if ##m =~ /(?:#{TESTRAIL_TEST_PREFIXES.join('|')})_\d?[\d_]+$/i
##tc_array = ##m.scan(/(?:#{TESTRAIL_TEST_PREFIXES.join('|')})_\d?[\d_]+$/i)[0].upcase.sub('_', '-').split("_")
if ##tc_array.size > 1
tmp_prefix = ##tc_array[0].split("-").first
tmp_array = []
##tc_array.each do|tmp|
tmp_array << tmp_prefix + "-" + tmp.split("-").last
end
##tc_array = tmp_array
end
##tc_array.each do |##tc|
begin
result = TR.report_tc_result(##tc, TESTRAIL_PROJECT, TESTRAIL_MILESTONE, TESTRAIL_PLAN, TESTRAIL_RUN, TESTRAIL_BUILD, status, notes)
puts status
rescue => e
puts e
ensure
if result && !result.success
$trReport = ReportFile.new('tr_report.txt') if !$trReport
$trReport.puts "#{##tc}, #{TEST_ARGS.project}, #{"#{TEST_ARGS.milestone}, " if TEST_ARGS.milestone}#{TEST_ARGS.plan}, #{TEST_ARGS.build}, #{TEST_ARGS.platform}, #{##c}, #{##m}, #{status} 'class', #{result.message ||= result.exception}"
end
end
end
end
end
end
Thanks in advance
This is fixed now after making "tc" as local variable.

Ruby: file output blank

So I'm trying to build a program which tells me how many days are left till someone's birthday. I have a text file that I'm drawing data from. The problem is the save method is not working for some reason, and nothing is being printed to the output file. Thank you so much in advance!
require 'date'
Person = Struct.new(:name, :bday)
module Family
Member = Hash.new
File.readlines("bday_info.txt").each do |line|
name, bday = line.split(',')
person = Person.new(name, bday)
Member[name] = bday
end
def self.next_bday(name)
birthday = Date.parse(Family::Member[name])
this_year = Date.new(Date.today.year, birthday.month, birthday.day)
next_year = Date.new(Date.today.year + 1, birthday.month, birthday.day)
if this_year > Date.today
puts "\n#{(this_year - Date.today).to_i} days to #{name}'s birthday"
else
puts "\n#{(next_year - Date.today).to_i} days to #{name}'s birthday"
end
end
def self.save(name)
File.open("days_left.txt", "w") do |file|
file.puts "#{next_bday(name)}"
end
end
end
loop do
puts "\nName:"
response = gets.chomp.capitalize
if response.downcase == "quit"
break
elsif Family::Member.has_key?("#{response}") == false
puts "\nYou ain't on the list"
else
Family.next_bday(response)
Family.save(response) #WHY IS THIS LINE NOT WORKING???
end
end
Your Family.next_bday does not return anything (to be more precise - it's returning nil that last puts returns), thus nothing is written to the file.
Other notes(not directly related):
No reason in calling Family.next_bday(response) twice, just save return value from the first call
Family::Member constant looks more like a variable, more clean design will be to make the whole thing a class that takes input file name, reads data in initializer and then stores into a instance variable

Ruby CSV.each in while loop not executing second time through

I'm working on the EventReporter project to help learn Ruby.
Here's what I've got so far:
require 'CSV'
puts 'Welcome to Event Reporter!'
print 'Enter command: '
command = gets.chomp
def clean(attribute, type)
if (type == 'regdate')
elsif (type == 'first_name')
elsif (type == 'last_name')
elsif (type == 'email_address')
elsif (type == 'homephone')
homephone = attribute
homephone = homephone.to_s.gsub(/\D/, '')
if (homephone.length < 10)
homephone = '0000000000'
elsif (homephone.length == 11)
if (homephone[0] == '1')
homephone[0] = ''
else
homephone = '0000000000'
end
elsif (homephone.length > 11)
homephone = '0000000000'
end
return homephone
elsif (type == 'street')
elsif (type == 'city')
elsif (type == 'state')
elsif (type == 'zipcode')
zipcode = attribute.to_s.rjust(5, "0")[0..4]
return zipcode
end
return attribute
end
queue = []
while (command != 'q') do
command = command.split
if (command[0] == 'load')
command[1] ? filename = command[1] : filename = 'event_attendees.csv'
attendees = CSV.open filename, headers: true, header_converters: :symbol
puts "Loaded #{filename}"
elsif (command[0] == 'find')
attribute = command[1]
criteria = command[2]
# REACHES HERE SECOND TIME AROUND
puts "#{command[0]} #{command[1]} #{command[2]}"
attendees.each do |attendee|
# ISNT REACHING HERE SECOND TIME AROUND
puts 'TEST'
# get cleaned attendee attribute
attendee_attribute = clean(attendee[attribute.to_sym], attribute)
# see if it matches the criteria input
if criteria.to_s.downcase.strip == attendee_attribute.to_s.downcase.strip
# if it does, add the attendee to the queue
puts 'Match!'
queue << attendee
end
end
end
print 'Enter command: '
command = gets.chomp
end
It seems that the attendees.each isn't being executed the second time through the while loop. Why is this?
~/practice/event_manager >> ruby 'lib/event_reporter.rb'
Welcome to Event Reporter!
Enter command: load
Loaded event_attendees.csv
Enter command: find zipcode 11111
find zipcode 11111
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
Enter command: find zipcode 11111
find zipcode 11111
Enter command: q
~/practice/event_manager >>
According to the docs, the CSV object behaves basically like a regular IO object. They keep track of their current position in the file which is advanced by reading through it, generally line by line. So on your first attendees.each, you read through the entire file. Subsequent calls to .each will try to read the next line, but there is not any since we are already at the end of the file hence your loop does not execute anymore.
You can fix this by rewinding the underlying IO instance to the beginning of the file, using #rewind. In your specific case, put it after iterating through the attendees.
attendees.each do |attendee|
# ...
end
attendees.rewind

Calling multiple methods on a CSV object

I have constructed an Event Manager class that performs parsing actions on a CSV file, and produces html letters using erb. It is part of a jumpstart labs tutorial
The program works fine, but I am unable to call multiple methods on an object without the earlier methods interfering with the later methods. As a result, I have opted to create multiple objects to call instance methods on, which seems like a clunky inelegant solution. Is there a better way to do this, where I can create a single new object and call methods on it?
Like so:
eventmg = EventManager.new("event_attendees.csv")
eventmg.print_valid_phone_numbers
eventmg_2 = EventManager.new("event_attendees.csv")
eventmg_2.print_zipcodes
eventmg_3 = EventManager.new("event_attendees.csv")
eventmg_3.time_targeter
eventmg_4 = EventManager.new("event_attendees.csv")
eventmg_4.day_of_week
eventmg_5 = EventManager.new("event_attendees.csv")
eventmg_5.create_thank_you_letters
The complete code is as follows
require 'csv'
require 'sunlight/congress'
require 'erb'
class EventManager
INVALID_PHONE_NUMBER = "0000000000"
Sunlight::Congress.api_key = "e179a6973728c4dd3fb1204283aaccb5"
def initialize(file_name, list_selections = [])
puts "EventManager Initialized."
#file = CSV.open(file_name, {:headers => true,
:header_converters => :symbol} )
#list_selections = list_selections
end
def clean_zipcode(zipcode)
zipcode.to_s.rjust(5,"0")[0..4]
end
def print_zipcodes
puts "Valid Participant Zipcodes"
#file.each do |line|
zipcode = clean_zipcode(line[:zipcode])
puts zipcode
end
end
def clean_phone(phone_number)
converted = phone_number.scan(/\d/).join('').split('')
if converted.count == 10
phone_number
elsif phone_number.to_s.length < 10
INVALID_PHONE_NUMBER
elsif phone_number.to_s.length == 11 && converted[0] == 1
phone_number.shift
phone_number.join('')
elsif phone_number.to_s.length == 11 && converted[0] != 1
INVALID_PHONE_NUMBER
else
phone_number.to_s.length > 11
INVALID_PHONE_NUMBER
end
end
def print_valid_phone_numbers
puts "Valid Participant Phone Numbers"
#file.each do |line|
clean_number = clean_phone(line[:homephone])
puts clean_number
end
end
def time_targeter
busy_times = Array.new(24) {0}
#file.each do |line|
registration = line[:regdate]
prepped_time = DateTime.strptime(registration, "%m/%d/%Y %H:%M")
prepped_time = prepped_time.hour.to_i
# inserts filtered hour into the array 'list_selections'
#list_selections << prepped_time
end
# tallies number of registrations for each hour
i = 0
while i < #list_selections.count
busy_times[#list_selections[i]] += 1
i+=1
end
# delivers a result showing the hour and the number of registrations
puts "Number of Registered Participants by Hour:"
busy_times.each_with_index {|counter, hours| puts "#{hours}\t#{counter}"}
end
def day_of_week
busy_day = Array.new(7) {0}
d_of_w = ["Monday:", "Tuesday:", "Wednesday:", "Thursday:", "Friday:", "Saturday:", "Sunday:"]
#file.each do |line|
registration = line[:regdate]
# you have to reformat date because of parser format
prepped_date = Date.strptime(registration, "%m/%d/%y")
prepped_date = prepped_date.wday
# adds filtered day of week into array 'list selections'
#list_selections << prepped_date
end
i = 0
while i < #list_selections.count
# i is minus one since days of week begin at '1' and arrays begin at '0'
busy_day[#list_selections[i-1]] += 1
i+=1
end
#busy_day.each_with_index {|counter, day| puts "#{day}\t#{counter}"}
prepared = d_of_w.zip(busy_day)
puts "Number of Registered Participants by Day of Week"
prepared.each{|date| puts date.join(" ")}
end
def legislators_by_zipcode(zipcode)
Sunlight::Congress::Legislator.by_zipcode(zipcode)
end
def save_thank_you_letters(id,form_letter)
Dir.mkdir("output") unless Dir.exists?("output")
filename = "output/thanks_#{id}.html"
File.open(filename,'w') do |file|
file.puts form_letter
end
end
def create_thank_you_letters
puts "Thank You Letters Available in Output Folder"
template_letter = File.read "form_letter.erb"
erb_template = ERB.new template_letter
#file.each do |line|
id = line[0]
name = line[:first_name]
zipcode = clean_zipcode(line[:zipcode])
legislators = legislators_by_zipcode(zipcode)
form_letter = erb_template.result(binding)
save_thank_you_letters(id,form_letter)
end
end
end
The reason you're experiencing this problem is because when you apply each to the result of CSV.open you're moving the file pointer each time. When you get to the end of the file with one of your methods, there is nothing for anyone else to read.
An alternative is to read the contents of the file into an instance variable at initialization with readlines. You'll get an array of arrays which you can operate on with each just as easily.
"Is there a better way to do this, where I can create a single new object and call methods on it?"
Probably. If your methods are interfering with one another, it means you're changing state within the manager, instead of working on local variables.
Sometimes, it's the right thing to do (e.g. Array#<<); sometimes not (e.g. Fixnum#+)... Seeing your method names, it probably isn't.
Nail the offenders down and adjust the code accordingly. (I only scanned your code, but those Array#<< calls on an instance variable, in particular, look fishy.)

Resources