Empty array messing up default argument values - ruby

I'm following through the Hungry Academy curriculum using a post here: http://jumpstartlab.com/news/archives/2013/09/03/scheduling-six-months-of-classes
And I'm up to the EventReporter project found here: http://tutorials.jumpstartlab.com/projects/event_reporter.html
So far I've built a simple CLI that asks for a valid command and accepts additional arguments with the command. I'm working ONLY on the load functionality right now and I'm having some trouble getting a default listfile variable set in AttendeeLists initialize method. Here's the code so far:
require 'csv'
class Reporter
def initialize()
#command = ''
loop()
end
#Main reporter loop
def loop
while #command != 'quit' do
printf "Enter a valid command:"
user_command_input = gets.chomp
user_input_args = []
#command = user_command_input.split(" ").first.downcase
user_input_args = user_command_input.split(" ").drop(1)
#DEBUG - puts #command
#DEBUG - puts user_input_args.count
case #command
when "load"
attendee_list = AttendeeList.new(user_input_args[0])
when "help"
puts "I would print some help here."
when "queue"
puts "I will do queue operations here."
when "find"
puts "I would find something for you and queue it here."
when "quit"
puts "Quitting Now."
break
else
puts "The command is not recognized, sorry. Try load, help, queue, or find."
end
end
end
end
class AttendeeList
def initialize(listfile = "event_attendees.csv")
puts "Loaded listfile #{listfile}"
end
end
reporter = Reporter.new
I'm testing running the load command with no arguments and is see that when I initialize the AttendeeList that user_input_args[0] is an empty array [] which, to my understanding is not nil, so I think that's the problem. I'm a little lost on how to continue though when I want the args to be passed through to my new instance of AttendeeList. I'd also prefer not to include the default logic in my Reporter class since that kind of defeats the purpose of encapsulating within the list.
EDIT: I forgot to mention that listfile default for AttendeeList initialize method is argument I'm talking about.

You need to make this change:
def initialize(listfile = nil)
listfile ||= "event_attendees.csv"
puts "Loaded listfile #{listfile}"
end
Explanation
In fact, user_input_args[0] is nil, but nil has no special meaning for default argument values. Default values are used only if the arguments are omitted when calling a function.
In your case:
AttendeeList.new
would work as you expected, but
AttendeeList.new(user_input_args[0])
is effectively
AttendeeList.new(nil)
and parameter listfile becomes nil.

Related

Ruby - Testing a method that calls itself in Minitest

I'm having trouble developing unit tests for a method that calls itself (a game loop) in Ruby using minitest. What I've attempted has been stubbing the method I'm trying to call in said game loop with my input. Here's the game loop:
#main game loop
def playRound
#draw board
#board.printBoard
#get input
playerInput = gets.chomp #returns user input without ending newline
#interpret input, quitting or beginning set selection for a player
case playerInput
when "q"
quit
when "a", "l"
set = getPlayerSet()
if(playerInput == "a")
player = 1
else
player = 2
end
when "h"
if #hintsEnabled
giveHint
playRound
else
puts "Hints are disabled"
playRound
end
else
puts "Input not recognized."
end
if(set != nil)
#have board test set
checkSet(set, player)
end
#check if player has quitted or there are no more valid sets
unless #quitted || #board.boardComplete
playRound
end
end
Much of it is ultimately irrelevant, all I'm trying to test is that this switch statement is calling the correct methods. Currently I'm trying to circumvent the loop by stubbing the called method to raise an error (which my test assers_raise's):
def test_playRound_a_input_triggers_getPlayerSet
#game.stub :getPlayerSet, raise(StandardError) do
assert_raises(StandardError) do
simulate_stdin("") {
#game.playRound
}
end
end
end
This approach does not seem to work, however, as Minitest is recording the results of the above test as an error with the message
E
Error:
TestGame#test_playRound_a_input_triggers_getPlayerSet:
StandardError: StandardError
test_game.rb:136:in `test_playRound_a_input_triggers_getPlayerSet'
If anyone has any advice or direction for me it would be massively appreciated as I can't tell what's going wrong
I'm not very familiar with minitest, but I expect you need to wrap the raise(exception) in a block, otherwise your test code is raising the exception immediately in your test (not as a result of the stubbed method being called).
Something like:
class CustomTestError < RuntimeError; end
def test_playRound_a_input_triggers_getPlayerSet
raise_error = -> { raise(CustomTestError) }
#game.stub(:getPlayerSet, raise_error) do
assert_raises(CustomTestError) do
simulate_stdin("") {
#game.playRound
}
end
end
end
-- EDIT --
Sometimes when i'm having difficulty testing a method it's a sign that I should refactor things to be easier to test (and thus have a cleaner, simpler interface, possibly be easier to understand later).
I don't code games and don't know what's typical for a game loop, but that method looks very difficult to test. I'd try to break it into a couple steps where each step/command can be easily tested in isolation. One option for this would be to define a method for each command and use send. This would allow you to test that each command works separately from your input parsing and separately from the game loop itself.
COMMANDS = {
q: :quit,
# etc..
}.stringify_keys.freeze
def play_round # Ruby methods should be snake_case rather than camelCase
#board.print_board
run_command(gets.chomp)
play_round unless #quitted || #board.board_complete
end
def run_command(input)
command = parse_input_to_command(input)
run_command(command)
end
def parse_input_to_command(input)
COMMANDS[input] || :bad_command
end
def run_command(command)
send("run_#{command}")
end
# Then a method for each command, e.g.
def run_bad_input
puts "Input not recognized"
end
However, for this type of problem I really like a functional approach, where each command is just a stateless function that you pass state into and get new state back. These could either mutate their input state (eww) or return a new copy of the board with updated state (yay!). Something like:
COMMANDS = {
# All state change must be done on board. To be a functional pattern, you should not mutate the board but return a new one. For this I invent a `.copy()` method that takes attributes to update as input.
q: -> {|board| board.copy(quitted: true) },
h: -> HintGiver.new, # If these commands were complex, they could live in a separate class entirely.
bad_command: -> {|board| puts "Unrecognized command"; board },
#
}.stringify_keys.freeze
def play_round
#board.print_board
command = parse_input_to_command(gets.chomp)
#board = command.call(#board)
play_round unless #board.quitted || #board.board_complete
end
def parse_input_to_command(input)
COMMANDS[input] || COMMANDS[:bad_command]
end

How to restart or reuse a case statement in Ruby?

After going through the codecademy ruby section "A Night at the Movies", I wanted to extend the case-statement to allow input again. By the end my code was:
movies = {
living_torah: 5,
ushpizin: 5
}
def input #method for gets.chomp
gets.chomp.downcase
end
puts "To exit please type 'Quit' or 'Exit'"
puts 'Please type "add", "display", "update" or "delete".'
choice = input
case choice
when "add"
puts "Movie Title please:"
title = input.to_sym
puts "How would you rate it?"
rating = input.to_i
if movies[title].nil?
movies[title] = rating
puts "Movie: '#{title.to_s.capitalize}' added with a Rating of # {rating}."
else
puts "That Movie already exists. Try updating it."
end
when "update"
puts "Movie Title please:"
title = input.to_sym
if movies[title].nil?
puts "That Title doesn't exist. Please 'add' it."
else
puts "Your Movie was found. How would you rate it?"
rating = input.to_i
movies[title] = rating
puts "Movie: '#{title.to_s.capitalize}' updated with a Rating of #{rating}."
end
when "display"
movies.each { |movie, rating| puts "#{movie}: #{rating}" }
when "delete"
puts "Which Movie would you like to delete?"
title = input.to_sym
if movies[title].nil?
puts "That Title doesn't exist. Please 'add' it."
else
movies.delete(title)
puts "The Movie '#{title.to_s.capitalize}' has been deleted."
end
when "exit", "quit"
exit
else
puts "Invalid choice."
end
I added the "exit" case independently of the exercise hoping to C.R.U.D. until explicitly exiting the program. How would I change the code to be able to restart/reuse the case-statement indefinitely?
(Also, is there a simpler/shorter way to produce the same results as this case-statement?)
Well, you can put the entire case statement inside of a loop. Something like:
loop do
puts "To exit please type 'Quit' or 'Exit'"
puts 'Please type "add", "display", "update" or "delete".'
choice = input
case choice
# ...
when 'exit', 'quit'
break
end
end
However, large case statements like this are not idiomatic Ruby. You might consider more dynamic options, such as using object.send(method_name, args...).
Additionally, its also best to place your code inside of a class or module. This makes it easier to understand and keeps things organized. This is called encapsulation.
In the example below, you can see that a single method is responsible for a single piece of functionality, and the class as a whole is responsible for managing the delegation of its tasks. This is called the single responsibility principle.
class MyCode
# store the current state for this object in an accessor.
# `attr_accessor` defines a read-write property.
attr_accessor :running
def add_choice
# your "add" code here
end
def update_choice
# "update" code
end
def exit_choice
# change the state of this class by marking `running` as false
self.running = false
end
# `alias_method` defines a method called `quit_choice` that
# runs the same code as `exit_choice`.
alias_method :quit_choice, :exit_choice
# reads a single input from the user and returns it,
# in a normalized form.
# "Add" -> "add", "Do Something" -> "do_something"
def read_choice
STDIN.gets.chomp.downcase.strip.gsub(/\s+/, '_')
end
# Process a single command from the user.
def process_choice
choice = read_choice
# the methods that correspond to user input are named
# following the same pattern. "add" -> "add_choice"
method_name = [choice, 'choice'].join('_').to_sym
# check if the method actually exists.
if self.respond_to? method_name
# call the method named by `method_name`
self.send(method_name)
else
# the method doesn't exist.
# that means the input was unrecognized.
puts "Invalid choice #{choice}"
end
end
# this method acts as a "run loop" that continues execution
# until the `running` state changes.
def choose
# define the initial state.
self.running = true
# process a single input as long as the state hasn't changed.
process_choice while self.running
end
end
Put a loop around it.
loop do
choice = input
case choice
.
.
.
when "exit", "quit"
break
else
puts "Invalid choice"
end
end

Do something if no arguments present

This is a small test script I wrote:
#!/usr/bin/env ruby
require 'packetfu'
def mac(host)
if host
rmac = PacketFu::Utils.arp(host, :iface => 'wlan0')
puts rmac
else
whoami = PacketFu::Utils.whoami?(:iface => 'wlan0')
puts whoami
end
end
mac(ARGV[0])
What I want to do is have it print the second variable if no argument is specified. Instead I get an ArgumentError. There's obviously an easier way that I'm just missing.
If you want to be able to call the function without any arguments, you need to change its definition to such that it does not require an argument. One way is to give the argument a default value. Then you can check for that, e.g.,
def mac(host = nil)
if host
puts "host: #{host}"
else
puts 'no host'
end
end
If you want to distinguish between no argument given and argument given with the default value, you could use a variable number of arguments:
def mac2(*args)
if args.empty?
puts "no arguments"
else
host = args[0]
end
end
On the other hand, if your problem is detecting whether ARGV was empty (i.e., no command-line argument given), you can check that higher up. For example, only call mac if an argument was given:
if ARGV.empty?
puts "Usage: …"
exit 1
end
mac(ARGV[0])

Ruby, Convert String Queries Into Method Calls

The code below works completely fine as long as users enter in the method name. I'd like to avoid requiring users to enter the name of the method at the various gets.chomp prompts.
I thought that using a case statement to translate the user input into method calls would work, but I keep getting a .include? NoMethodDefined error.
class Foo
def initialize(start_action)
#start = start_action
end
def play
next_action = #start
while true
case next_action.include?
when beginning
next_action = beginning
when "instruct"
next_action = instructions # returns instructions as
# the method that's called below
when "users"
next_action = users # returns users as the
# method that's called below
else
puts "Unknown command."
next_action = # some placeholder method call that gets the user
# back to being able to make another choice
end
puts "\n----------"
next_action = method(next_action).call
end
def beginning
puts "This is the beginning."
next_action = gets.chomp
end
def instructions
puts "These are the instructions"
# code to display instructions omitted
next_action = gets.chomp
end
def users
puts "Here are your users"
# code to display users omitted
next_action = gets.chomp
end
end
start = Foo.new(:beginning)
start.play
Any advice or help is appreciated.
On the first pass through your loop, next_action is the symbol :beginning and symbols don't have an include? method.
In addition I think you've misunderstood how case statements work - even removing the first error your code will then complain that you're passing 0 arguments to include? (instead of 1)
I think you instead mean something like
case next_action
when /instruct/
..
when /users
..
else
..
end
Which will test next action against each regular repression in turn

Function calls in hash come up empty in Ruby

I've been sifting through the prior questions and answers on stackoverflow, and I have gotten most of my question figured out. I figured out that I can't place a function call within a hash, without placing it within a proc, or a similar container.
What I'm ultimately trying to do is have a menu displayed, grab user input, and then iterate through the hash, and run the specified function:
def Main()
menu_titles = {"Answer1" => Proc.new{Choice1()}}
Menu(menu_titles)
end
def Choice1()
puts "Response answer"
end
def Menu(menu_titles)
menu_titles.each_with_index do |(key, value),index|
puts "#{index+1}. #{key}"
end
user_input = 0
menu_titles.each_with_index do |(key, value), index|
if index.eql?(user_input)
menu_titles[value]
break
end
end
end
Main()
The issue I'm having right now is that I'm not entering the functions that my hash calls for. Whether I use a return or a "puts", I either get a blank line or nothing at all. If anyone has other recommendations about my code, I'm all ears also. To be honest, I don't like using procs, but that's mostly because I don't entirely know how they work and where to use them.
Right now for my menus I have:
user_input = 1
if user_input == 1
Choice1()
...
end
Here's how I would refactor this:
class Menu
attr_reader :titles
# initialize sets up a hard-coded titles instance variable,
# but it could easily take an argument.
def initialize
#titles = {
"Answer1" => Proc.new{ puts "choice 1" },
"Answer2" => Proc.new{ puts "choice 2" }
}
end
# This is the only public instance method in your class,
# which should give some idea about what the class is for
# to whoever reads your code
def choose
proc_for_index(display_for_choice)
end
private
# returns the index of the proc.
def display_for_choice
titles.each_with_index { |(key,value), index| puts "#{index + 1}. #{key}" }
gets.chomp.to_i - 1 # gets will return the string value of user input (try it in IRB)
end
# first finds the key for the selected index, then
# performs the hash lookup.
def proc_for_index(index)
titles[titles.keys[index]]
end
end
If you're serious about Ruby (or object-oriented programming in general), I would highly recommend learning about the advantages of packaging your code into behavior-specific classes. This example allows you to do this:
menu = Menu.new
proc = menu.choose
#=> 1. Answer1
#=> 2. Answer2
2 #(user input)
proc.call
#=> choice 2
And you could actually run it on one line:
Menu.new.choose.call

Resources