Harry Tucker Harry Tucker - 1 month ago 5
Ruby Question

Find method is returning wrong object from array?

I'm working on a Ruby project for a text based adventure game and have encountered an issue when trying to use the 'find' method in Ruby.

I've got a location class that all locations are instantiated from. Each location has a set of two coordinates, ([x, y] = [1, 1]) and the player also has their own set of coordinates to store where they are actually located.

The class method & initialise method

def self.findLocation(coord, attributeToPrint)
puts coord.inspect
puts @@finder.find { coord }.inspect
end

def initialize(newTitle, newDescription, newCoord)
@title = newTitle
@description = newDescription
@coordinate = newCoord

@@finder << self
end


What I'm trying to do is store all of the locations in an array and have a class method for printing out the title and description of a location by using the find method to select the location with the matching coordinates to the player. The method I currently have passes the player's coordinate in
coord
parameter and uses the find method to check the array (which has all location objects within it) for the coordinate.

I've found many questions relating to this method but have had no success with any of the solutions found on these questions and no luck with any solution of my own. If I try and use a comparison statement such as
@coordinate == coord
the method will simply return
nil
and my current version returns an object, but only the object which is first in the array and does not return the location with the matching
@coordinate
attribute.

I would greatly appreciate any help with this issue as it is the main roadblock to making some progress on the text adventure game and allowing some interactivity. I am sure that I am using this method incorrectly and don't understand how it functions but the enumerator documentation hasn't helped me very much after looking at it and there is possibly a much better way of implementing this over a class method.

Reproducing the Issue



Location class (necessary parts)



class Location
@@finder = Array.new

def self.findLocation(coord, attributeToPrint)
puts coord.inspect
puts @@finder.find { coord }.inspect
end

#Initialise locations here
def initialize(newTitle, newDescription, newCoord)
@title = newTitle
@description = newDescription
@coordinate = newCoord

@@finder << self
end


Player class (necessary parts)



class Player
def initialize(playerHealth, playerLocation, playerInventory)
@health = playerHealth
@location = playerLocation
@inventory = playerInventory
end


Main script (necessary parts)



require_relative '../lib/player'
require_relative '../lib/location'

start = Location.new('Test 1', 'This is test 1.', [0, 0])
start2 = Location.new('Test 2', 'This is test 2.', [1,1])
start3= Location.new('Test 3', 'This is test 3.', [2, 2])
player = Player.new(100, [1,1], ['sword'])

#Input loop
loop do

Location.findLocation(player.getLocation, 'example')
end

Answer

You have to specify how find will match the stored records against the provided value. Specifically, you need to compare coord to the record's coord. To access the record's coord you need a getter method.

class Location

  def self.findLocation(coord, attributeToPrint)
    puts coord.inspect
    puts @@finder.find { |location| location.coord == coord }.inspect
  end

  def coord
    @coord
  end

end  

The way that find works is that it executes the block for every instance in the array, returning the first array element where the result is 'truthy' (i.e. not nil and not false). When you do { coord } the block returns the coord value immediately. coord is not nil and not false, so the first record is selected. When you did @coord == coord the @coord is undefined at the class level (it's nil) and so for all records the comparison was false so no record was selected, hence your nil result.

To print a specific attribute (say, title) you can also access the attribute with the getter method. and then send that method to the object.

class Location

  def self.findLocation(coord, attributeToPrint)
    puts coord.inspect
    found_location =  @@finder.find { |location| location.coord == coord }
    puts found_location.send(attributeToPrint) if found_location
  end

  def coord
    @coord
  end

  def title
    @title
  end

end 

So now you can do...

Location.findLocation([1,1], 'title')

However...

It's much more flexible to have findLocation only be responsible for returning the object and then output the attribute outside the method... single responsibility principle...

class Location

  def self.findLocation(coord)
    @@finder.find { |location| location.coord == coord }
  end

  def coord
    @coord
  end

  def title
    @title
  end

end 

So now you can do...

player_location = Location.findLocation([1,1])
puts player_location.title if player_location
Comments