I'm new to Ruby and am just learning RSpec. I've written a test for a certain method and keep getting the failing test error:
Failure/Error: if #board[5][#move - 1] == EMPTY_CIRCLE
NoMethodError:
undefined method `-' for nil:NilClass
I know that the code in the Class file does actually work as it should but I just can't seem to get the test to pass.
Here is the Class file code:
class ConnectFour
attr_accessor :player_one, :player_two, :move, :current_player, :board
EMPTY_CIRCLE = "\e[37m\u25cb".freeze
YELLOW_CIRCLE = "\u001b[33m\u25cf".freeze
RED_CIRCLE = "\u001b[31m\u25cf".freeze
def initialize
#board = Array.new(6){Array.new(7,EMPTY_CIRCLE)}
#player_one = nil
#player_two = nil
end
def play_round
print_board
prompt_player
#move = prompt_move
place_marker
end
def prompt_move
loop do
#move = gets.chomp.to_i
return #move if valid_move?(#move)
puts "Invalid input. Enter a column number between 1 and 7"
end
end
def valid_move?(move)
#move.is_a?(Integer) && #move.between?(1, 7)
end
def place_marker
if #board[5][#move - 1] == EMPTY_CIRCLE
#board[5][#move - 1] = #current_player.marker
end
end
end
And here is the RSpec test code:
describe ConnectFour do
subject(:game) { described_class.new }
let(:player){ double(Player) }
describe '#place_marker' do
context 'when column is empty' do
before do
move = 4
allow(game).to receive(:move).and_return(4)
end
it 'places marker on bottom row' do
game.place_marker
expect{game.place_marker}.to change{#board[5][3]}
end
end
end
end
I've tried multiple things but just can't get the test method to recognize the value for #move i'm trying to send it. Any suggestions would be greatly appreciated!.
#move is nil. You're stubbing out a move method (that doesn't seem to exist) to return 4, but this has nothing to do with the #move variable.
Either make sure #move is set, or modify your code so that it actually has a move method and that all direct reads to #move instead use this method, so your stub can work.
Here's how you can stub #move attr_accessor to return a value:
allow(game).to receive(:move=).with(move: 2)
Related
I am trying to access class methods within a define_singleton_method block, but it doesn't seem to be working.
Here is an example.
class Test
attr_accessor :tags
def initialize
#tags = []
#tags.define_singleton_method(:<<) do |val|
val = tosymbol(val)
push(val)
end
end
def tosymbol(value)
value = value.to_s
value = value.gsub!(/\s+/,'_') || value
value = value.downcase! || value
return value.to_sym
end
end
But when I use it I get an error.
test = Test.new
test.tags<<"Hello World"
NoMethodError: undefined method `tosymbol' for []:Array
from /home/joebloggs/GitHub/repo/file.rb:183:in `block in initialize'
from (irb):9
from /home/joebloggs/.rvm/rubies/ruby-2.3.0/bin/irb:11:in `<main>'
I tried changing val = tosymbol(val) to val = Test::tosymbol(val) but that didn't work either, I get undefined method 'tosymbol' for Test:Class
I could re-write what tosymbol is doing, but it wouldn't be very DRY. Where am I going wrong? Thanks.
Where am I going wrong?
You're (re)defining a << method for instance of Array class, not Test class.
While doing so you are trying to access tosymbol method, that is not defined in Array class, but in Test class.
What you want, probably (read judging by your code sample), is to define << method for instances of Test class:
def initialize
#tags = []
end
def <<(val)
tags << tosymbol(val)
end
test = Test.new
test << "Hello World"
#=> [:hello_world]
EDIT
To make your example to work you just need to assign the instance to a variable and call the tosymbol method with correct receiver:
def initialize
#tags = []
test = self # <============
#tags.define_singleton_method(:<<) do |val|
val = test.tosymbol(val)
push(val)
end
end
Now:
test.tags << 'Hello World'
#=> [:hello_world]
I am building a Tic-Tac-Toe game to be played on the command line.
module TicTacToe
class Player
attr_accessor :symbol
def initialize(symbol)
#symbol = symbol
end
end
class Board
attr_reader :spaces
def initialize
#spaces = Array.new(9)
end
def to_s
output = ""
0.upto(8) do |position|
output << "#{#spaces[position] || position}"
case position % 3
when 0, 1 then output << " | "
when 2 then output << "\n-----------\n" unless position == 8
end
end
output
end
def space_available(cell, sym)
if spaces[cell].nil?
spaces[cell] = sym
else
puts "Space unavailable"
end
end
end
class Game < Board
attr_reader :player1, :player2
def initialize
play_game
end
def play_game
#player1 = Player.new("X")
#player2 = Player.new("O")
puts Board.new
#current_turn = 1
turn
end
def move(player)
while victory != true
puts "Where would you like to move?"
choice = gets.chomp.to_i
space_available(choice, player.symbol)
puts Board
#current_turn += 1
turn
end
end
def turn
#current_turn.even? ? move(#player2) : move(#player1)
end
def victory
#still working on this
end
end
end
puts TicTacToe::Game.new
The method that is to take a user's cell choice (space_available) and alter the array with their piece ('X' or 'O') is giving me an error. I can't find why my code is throwing this particular error.
The problem is that you don't call the parent constructor in your Game class, therefore #spaces is not initialized.
Your hierarchy decision is questionable, but to make it work, you can simply change the Game constructor to:
def initialize
super
play_game
end
You are calling spaces[cell]. The error is telling you that you are calling [] on nil, which means that spaces must be nil.
Perhaps you mean #spaces? Otherwise - you need to tell the program how spaces is defined and how it is initialized. A simple spaces = {} unless spaces would work
Another way of initialising your spaces variable would be to call super when you initialize the Game:
class Game < Board
attr_reader :player1, :player2
def initialize
super
play_game
end
...
I want to create a special settings class Settings. The class should be able to handle cases when a user types something like Settings.new.method_1.method_2.method_3 and it's translated to something like:
result = nil
if ConfigurationSettings['method_1'].present?
result = ConfigurationSettings['method_1']
if result['method_2'].present?
result = result['method_2']
...
end
return result
Of course, I'll make it more flexible later so it can have more than 2/3 "methods".
I guess this is the issue you are facing:
class Settings
def abc
puts "abc"
end
def xyz
puts "xyz"
end
end
s = Settings.new
s.abc
#abc
# => nil
s.xyz
#xyz
# => nil
s.abc.xyz
#abc
#NoMethodError: undefined method `xyz' for nil:NilClass
The issue here is s.abc is returning nil and xyz is called over nil. What you are trying to achieve is called Method Chaining. Now, xyz needs an Settings object. Simplest thing to do here is:
class Settings2
def abc
puts "abc"
self
end
def xyz
puts "xyz"
self
end
end
s2 = Settings2.new
s2.abc.xyz
#abc
#xyz
method_missing is available for your use and can be used to help you solve this problem. Coupling this with method chaining and you're good to go. For example:
class Settings
def method_missing(meth)
puts "Missing #{meth}"
self
end
def test
puts "Test"
self
end
end
a = Settings.new
a.test
a.test.b
a.b.test
The trouble with the other answers is all the methods return "self" so if you want to access a nested value...
final_value = Settings.new.method_1.method_2.method_3
You're just going to get the whole settings hash instead.
Try this instead...
class Settings
class SubSettings
def initialize(sub_setting)
#sub_setting = sub_setting
end
def method_missing(method, *arguments, &block)
if #sub_setting[method].is_a?(Hash)
SubSettings.new #sub_setting[method]
else
#sub_setting[method]
end
end
def answer
#sub_setting
end
end
def initialize
#settings = ConfigurationSettings
end
def method_missing(method, *arguments, &block)
SubSettings.new #settings[method]
end
end
ConfigurationSettings = {level1a: {level2a: {level3a: "hello", level3b: "goodbye"}, level2b: {level3b: "howdy"}}}
result = Settings.new.level1a.level2a.level3b
p result
=> "goodbye"
What this does is take the initial method and takes the associated sub-hash of the ConfigurationSettings hash and stored it into a new object of class SubSettings. It applies the next method and if the result is another sub-hash it iterates to create another SubSettings, etc. It only returns the actual result when it no longer sees hashes.
The following Ruby code raises the confusing error "no id given" shown at the end. How do I avoid this problem?
class Asset; end
class Proxy < Asset
def initialize(asset)
#asset
end
def method_missing(property,*args)
property = property.to_s
property.sub!(/=$/,'') if property.end_with?('=')
if #asset.respond_to?(property)
# irrelevant code here
else
super
end
end
end
Proxy.new(42).foobar
#=> /Users/phrogz/test.rb:13:in `method_missing': no id given (ArgumentError)
#=> from /Users/phrogz/test.rb:13:in `method_missing'
#=> from /Users/phrogz/test.rb:19:in `<main>'
The core of this problem can be shown with this simple test:
def method_missing(a,*b)
a = 17
super
end
foobar #=> `method_missing': no id given (ArgumentError)
This error arises when you call super inside method_missing after changing the value of the first parameter to something other than a symbol. The fix? Don't do that. For example, the method from the original question can be rewritten as:
def method_missing(property,*args)
name = property.to_s
name.sub!(/=$/,'') if name.end_with?('=')
if #asset.respond_to?(name)
# irrelevant code here
else
super
end
end
Alternatively, be sure to explicitly pass a symbol as the first parameter to super:
def method_missing(property,*args)
property = property.to_s
# ...
if #asset.respond_to?(property)
# ...
else
super( property.to_sym, *args )
end
end
Working in Ruby, I'm getting an error saying
'add': undefined local variable or method 'food' for #<FoodDB:...
This is the code I'm trying to run
require_relative 'FoodDB.rb'
class Manager
def initialize
food = FoodDB.new
self.create_foodDB(food)
end
def create_foodDB(food)
counter = 1
word = []
file = File.new("FoodDB.txt","r")
while (line = file.gets)
food.addFood(line)
counter = counter + 1
end
file.close
end
end
manager = Manager.new
input_stream = $stdin
input_stream.each_line do |line|
line = line.chomp
if line == "quit"
input_stream.close
end
end
This is FoodDB.rb's code
class FoodDB
def initialize
food = []
end
def addFood(str)
food.push(str)
end
end
I'm not sure what's the problem since it seems like I'm definitely calling the correct method from the FoodDB class. All help is appreciated, thank you!
You need to change food in the FoodDB class to an instance variable:
class FoodDB
def initialize
#food = []
end
def addFood(str)
#food.push(str)
end
end
An instance variable will be available throughout all methods of an instance, while the food variable you used was local to its lexical scope, i.e. only available within the initialize method.