Accessing instance variable array using IRB - ruby

I'm new to Ruby and am working on an exercise where a sports team (of up to 10 ppl) have to have at least 2 men and 2 women. I've created a Player class where the player's gender is determined and a Team class, where I add these players into an instance variable array of #team (which is created upon initialization of Team).
I have placed my full code towards the bottom of this request.
I'm hoping someone can please help me on the following:
(1) What do I type in IRB to be able to specifically recall/maniuplate the #team instance variable array (which stores all the players). I wish to later iterate over the array or extract the first item in the array (#team.first does not work)
(2) I'm having difficulty writing the code to determine if at least 2 male and female players are in the #team instance variable. The best code I came up in the Team class was the following - but it is reporting that #team is nilclass.
def gender_balance
#team.select{|player| player.male == true }
end
I have researched the internet and tried various combinations for an answer - with no success.
Directly below are the IRB commands that I have typed to create and add players to teams. Later below is my code for Team (which contains the method to assess whether it has the right gender mix) and Players.
irb(main):001:0> team = Team.new
=> #<Team:0x007fd8f21df858 #team=[]>
irb(main):002:0> p1 = Player.new
=> #<Player:0x007fd8f21d7b08 #male=true>
irb(main):003:0> p1.female_player
=> false
irb(main):004:0> p2 = Player.new
=> #<Player:0x007fd8f21bff58 #male=true>
irb(main):005:0> team.add_player(p1)
=> [#<Player:0x007fd8f21d7b08 #male=false>]
irb(main):006:0> team.add_player(p2)
=> [#<Player:0x007fd8f21d7b08 #male=false>, #<Player:0x007fd8f21bff58 #male=true>]
These two IRB lines are me trying unsucessfully to recall the contents of #team
irb(main):007:0> team
=> #<Team:0x007fd8f21df858 #team=[#<Player:0x007fd8f21d7b08 #male=false>, #<Player:0x007fd8f21bff58 #male=true>]>
irb(main):013:0> #team
=> nil
The code for both classes is below:
class Player
def initialize
#male = true
end
def female_player
#male = false
end
def male_player
#male
end
end
class Team
def initialize
#team = []
end
def add_player player
#team << player
end
def player_count
#team.count
end
def valid_team?
player_number_check
gender_balance
end
private
def player_number_check
player_count > 6 && player_count < 11
end
def gender_balance
#team.select{|player| player.male == true }
end
end
My github reference for this code is: https://github.com/elinnet/object-calisthenics-beach-volleyball-edition.git
Thank you.

Your Team class does not have an attribute for getting the #team instance variable.† So the only way you can extract its value is by using instance_variable_get:
irb(main):029:0> team = Team.new
=> #<Team:0x007fff4323fd58 #team=[]>
irb(main):030:0> team.instance_variable_get(:#team)
=> []
Please don't use instance_variable_get for actual production code though; it's a code smell. But for the purposes of inspecting instance variables in IRB, it's okay.
† You'd normally define one using either attr_accessor :team (read/write) or attr_reader :team (read-only) inside the class definition.

Related

Isolate dependencies while building object relationships in ruby

I'm practicing OOP for the first time by modeling my application domain (public high school) as objects, and I'm stuck on how to create relationships between the classes without introducing lots of external dependencies.
I have lots of relationships I want to construct, so in hopes of learning the general principle I'm giving two classes and sample objects here to illustrate the difficulty I'm having.
I have two classes Gradeand Transcript. Every instance of Transcript has an instance variable #mark, which right now is a string. I collected all the instances of each class a grades hash and a transcripts hash.
Question: How can I modify these classes so that #mark references the corresponding Grade instance?
(or, is that the wrong approach entirely?)
Grade has an instance for every possible final grade students can receive
class Grade
attr_accessor :mark, :alpha_equivalent, :numeric_range_low, :numeric_range_high, :numeric_qquivalent, :pass_fail_equivalent, :description
def initialize(args)
#mark = args["Mark"]
#alpha_equivalent = args["AlphaEquivalent"]
#numeric_range_low = args["NumericRangeLow"]
#numeric_range_high = args["NumericRangeHigh"]
#numeric_equivalent = args["NumericEquivalent"]
#pass_fail_equivalent = args["PassFailEquivalent"]
#description = args["Description"]
end
end
Sample object from the grades hash:
grades["100"] =>
#<Grade:0x007f9fcb077d68
#alpha_equivalent="100",
#description="100 out of 100",
#mark="100",
#numeric_equivalent="100",
#numeric_range_high="100",
#numeric_range_low="100",
#pass_fail_equivalent="P">
Transcript has instances for every final grade the student has ever received for all the courses they've studied
class Transcript
attr_accessor :student_id, :last_name, :first_name, :grade, :official_class, :school, :year, :term, :course, :course_title, :mark, :pass_fail, :credits
def initialize(args)
#student_id = args["StudentID"]
#last_name = args["LastName"]
#first_name = args["FirstName"]
#grade = args["Grade"]
#official_class = args["OffClass"]
#school = args["school"]
#year = args["Year"]
#term = args["Term"]
#course = args["Course"]
#course_title = args["Course Title"]
#mark = args["Mark"]
#credits = args["Credits"]
#grade_entry_cohort = args["GEC"]
end
end
Sample object from the transcripts hash:
transcripts["foobar-COURSE1-100"] =>
#<Transcript:0x007f9fce8786b8
#course="COURSE1",
#course_title="Example Course",
#credits="5",
#first_name="FOO",
#grade="100",
#grade_entry_cohort="V",
#last_name="BAR",
#mark="100",
#official_class="000",
#school="1",
#student_id="0123",
#term="1",
#year="2000">
I'm instantiating all the objects from CSV source files and then collecting them into a hash because I wanted to be able to address them directly.
Sounds like you need to want Transcript#grade to return a Grade instance. So let's make a method for that:
class Grade
def self.all
#all ||= {}
end
def self.find(mark)
all[mark]
end
end
Now, Grade.all needs to be populated. This could be achieved like this from your CSV:
grade_args = %w[alpha_equivalent description mark numeric_equivalent numeric_range_high numeric_range_low pass_fail_equivalent]
CSV.parse { |row| Grade.all.merge(csv['mark'] => Grade.new(row.slice(*grade_args)}
Now, we can modify Transcript like this:
class Transcript
def initialize(args)
#args = args
end
def grade
#grade ||= Grade.find(args['mark'])
end
private
attr_reader :args
end
Assuming that you've created the grades hash earlier:
# read args from csv
# id built from first, last, course, and grade
transcripts[id] = Transcript.
new(args.merge('Mark' => grades[args['Mark']])
It uses Hash#merge to extend args with an instance of Grade that was built earlier.

Ruby: many related objects with similiar method naming pattern. How to map them to standardized methods

I'm working on a program that receives responses from an API that represent 'songs' from a database. Those responses arrive in my program as Struct objects, and they are structured slightly differently depending on which table they were pulled from.
For instance, the song object pulled from the 'track' table looks like:
song_1 = <struct Song track_artist="Michael Jackson", track_title="Billie Jean">
And the song object returned from the 'license' table looks like:
song_2 = <struct Song license_artist="Michael Jackson", license_title="Billie Jean">
If I want to get the 'artist' from song_1, I'd call song_1.track_artist, and with song_2, song_2.license_artist. But this is problematic when running loops. I want to be able to call song.title on any of them and receive the title.
Right now, I'm putting each Struct through a 'Normalizer' object when I receive it. It uses a hash mapping to change the method name of each Struct; the mapping more or less looks like:
{ track_artist: artist,
track_title: title,
license_artist: artist,
license_title: title }
This seems like it might be overkill. What's the best way to go about this?
You could use method_missing for this
module Unifier
def method_missing(name, *args, &block)
meth = public_methods.find { |m| m[/_#{name}/] }
meth ? send(meth, *args, *block) : super
end
def respond_to_missing?(method_name, include_private = false)
public_methods.any? { |m| m[/_#{method_name}/] } || super
end
end
class A
include Unifier
attr_reader :artist_name
def initialize
#artist_name = 123
end
end
a = A.new
a.respond_to?(:name) # => true
a.name # => 123
a.respond_to?(:title) # => false
a.title # => undefined method `title' for #<A:0x007fb3f4054330 #artist_name=123> (NoMethodError)
Update
For you case it will be more complex and tricky.
If you can make changes to place, where this Struct objects are created, then just patch classes, generated from Struct
song_1_class = Struct.new(:track_artist, :track_title) do
include Unifier
end
song_1 = song_1_class.new('Michael Jackson', 'Billie Jean')
puts "#{song_1.artist} - #{song_1.title}"
# => Michael Jackson - Billie Jean
If you can work only with objects of that classes - you could patch it dynamically
# We get objects of licence_struct class
licence_struct = Struct.new(:license_artist, :license_title)
song_2 = licence_struct.new('Michael Jackson', 'Billie Jean')
song_3 = licence_struct.new('Michael Jackson', 'Black of White')
def process_song(song)
puts "Song #{song} patched - #{song.respond_to?(:artist)}"
"#{song.artist} - #{song.title}"
rescue NoMethodError => err
# If we don't have methods on our struct - patch it
# If after patching object still dont respond to our method - throw exception
patch_object_from_error(err) ? retry : raise(err)
end
def patch_object_from_error(error)
receiver = error.receiver
receiver.class.class_exec { include Unifier }
meth = error.message.match(/undefined method `(\S+)'/)[1].to_sym
receiver.respond_to?(meth)
end
puts process_song(song_2)
# => Song #<struct license_artist="Michael Jackson", license_title="Billie Jean"> patched - false
# after retry
# => Song #<struct license_artist="Michael Jackson", license_title="Billie Jean"> patched - true
# => Michael Jackson - Billie Jean
puts process_song(song_3)
# dont need retry - class already patched
# => Song #<struct license_artist="Michael Jackson", license_title="Black of White"> patched - true
# => Michael Jackson - Black of White

Tracking score and rounds played using Sinatra

I have spent the last two weeks building a Sinatra app, and I have hit my first major roadblock. I have spent the last 3 days trying to find a solution, but need to ask for help.
I am building a small game using Sinatra, and I am having trouble understanding a concept.
The game tracks rounds and scores.
Here is some of my code:
#Player Class
class Player
attr_accessor :name, :guess, :round_score, :total_score
def initialize
#guess = 0
#round_score = 0
#total_score = 0
end
end
#Tracks Round
class RoundCount
attr_accessor :round
def initialize
#round = 0
end
def count
#round += 1
end
end
helpers do
#New game function. Makes set number of player classes in ##player array.
def new_game(players)
##i = RoundCount.new
##player = []
players.times do |x|
##player[x] = Player.new
##player[x].name = "Player#{x}"
end
end
#Calculates score
def player_score()
params.each do |x, y|
##player.each do |z|
if z.name == x
z.guess = y.to_i
z.round_score = (y.to_i - ##movie[##i.round].ratings['critics_score'].to_i).abs
z.total_score += (y.to_i - ##movie[##i.round].ratings['critics_score'].to_i).abs
end
end
end
end
get '/' do
slim :home
end
get '/players' do
slim :players
end
post '/numplayers' do
new_game(params[:numplayers].to_i)
slim :names
end
post '/names' do
slim :titles
end
Currently the program gives me the error "warning: class variable access from topelevel" and is affected by other users who play at the same time on seperate computers.
I need help understanding how to track players scores and the round of the game in each view, in a way that isn't affected by other players running a different game.
Any suggestions on how to avoid using class variables for round and score, while retaining a way to access them in each route and view?
Use Sessions or Cookies
Generally, keeping server-side state through class variables will make your life harder. There's also usually more than one way to do anything, but for your use case I'd recommend that you store your state in a session variable or a cookie.
You could then store your state in any serializable object, such as a Hash, Array, Struct, or OpenSruct. This will allow for concurrent users, and do away with the need for the class variables you're currently using.

Assert_equal undefined local variable LRTHW ex52

Hi I made it to the lase exercise os Learn Ruby The Hard Way, and I come at the wall...
Here is the test code:
def test_gothon_map()
assert_equal(START.go('shoot!'), generic_death)
assert_equal(START.go('dodge!'), generic_death)
room = START.go("tell a joke")
assert_equal(room, laser_weapon_armory)
end
And here is the code of the file it should test:
class Room
attr_accessor :name, :description, :paths
def initialize(name, description)
#name = name
#description = description
#paths = {}
end
def ==(other)
self.name==other.name&&self.description==other.description&&self.paths==other.paths
end
def go(direction)
#paths[direction]
end
def add_paths(paths)
#paths.update(paths)
end
end
generic_death = Room.new("death", "You died.")
And when I try to launch the test file I get an error:
generic_death = Room.new("death", "You died.")
I tried to set the "generic_death = Room.new("death", "You died.")" in test_gothon_map method and it worked but the problem is that description of the next object is extremely long, so my questions are:
why assertion doesn't not respond to defined object?
can it be done different way then by putting whole object to testing method, since description of the next object is extremely long...
The nature of local variable is that they are, well, local. This means that they are not available outside the scope they were defined.
That's why ruby does not know what generic_death means in your test.
You can solve this in a couple of ways:
define rooms as constants in the Room class:
class Room
# ...
GENERIC_DEATH = Room.new("death", "You died.")
LASER_WEAPON_ARMORY = Room.new(...)
end
def test_gothon_map()
assert_equal(Room::START.go('shoot!'), Room::GENERIC_DEATH)
assert_equal(Room::START.go('dodge!'), Room::GENERIC_DEATH)
room = Room::START.go("tell a joke")
assert_equal(room, Room::LASER_WEAPON_ARMORY)
end
assert the room by its name, or some other identifier:
def test_gothon_map()
assert_equal(START.go('shoot!').name, "death")
assert_equal(START.go('dodge!').name, "death")
room = START.go("tell a joke")
assert_equal(room.name, "laser weapon armory")
end

Ruby - Method call to object in array

I'm working with a Ruby project for school, and have sadly not been able to find an answer to this question in my literature.
I have an array of camping lots, each containing a guest. I initialize the lots like this:
lots = Array.new
for i in (1..36)
lots[i] = Lot.new(i)
end
Further down I create a Guest object, initialize it, and now I want to add the Guest to my Lot. The method in the class Lot looks like this:
def AddGuest(guest)
#guest = guest
end
The problem comes when I want to call the method, as the Lot is in an Array.
lots[lotnumber].AddGuest(guest)
This call gives me the error:
undefined method `+#' for #<Guest:0x2c1ff14> (NoMethodError)
I have used require, so the classes know about each other. I've had quite a hard time understanding Ruby, could my error be that I try to access the AddGuest method in the Array class? I'm used to doing things like this in C++.
Below is the full source (the relevant parts at least).
Entire Lot class:
class Lot
def initialize(number)
#gauge = rand(2000) + 2000
#number = number
#guest = false
end
def Occupied()
return #guest
end
def AddGuest(guest)
#guest = guest
end
def RemoveGuest()
#guest = false
end
end
Parts of main.rb
#includes
require 'guest'
require 'lot'
#initiate comparison variables
userInput = "0"
numberOfGuests = 0
foundLot = false
guests = Array.new
lots = Array.new
#initialize lot list
for i in (1..36)
lots[i] = Lot.new(i)
end
Player input omitted
#make sure lot is not taken
while foundLot == false do
lotnumber = rand(35)+1
if lots[lotnumber].Occupied() == false then
foundLot = "true"
end
end
foundLot = false
guest = Guest.new(firstName, lastName, adress, phone, arrival, lotnumber)
guests.insert(numberOfGuests, guest)
numberOfGuests++
lots[lotnumber].AddGuest(guest) #this is where error hits
end
end
end
The error appears to be related to your use of the ++ operator, which is, quite naturally, supported in C++, but is not supported in Ruby.
The equivalent is:
numberOfGuests += 1
A couple little tips...
[1]
A slightly more idiomatic way to write this...
for i in (1..36)
lots[i] = Lot.new(i)
end
would be...
(1..36).each { |i| lots[i] << Lot.new(i) }
[2]
To remove a Guest from a Lot, you might want to set it to nil rather than false. This would be my suggestion...
class Lot
def initialize(number)
#gauge = rand(2000) + 2000
#number = number
# Don't need to set #guest -- it's nil by default.
end
# In Ruby, methods that return a boolean often have a "?".
# Makes it "read better" when you call the method. (See
# usage sample.)
def occupied?
! #guest.nil?
end
# There's a more commonplace way to do this. See below...
def add_guest(guest)
#guest = guest
end
def remove_guest()
#guest = nil
end
end
Example of usage:
>> lot = Lot.new(2)
=> #<Lot:0x1300920 #number=2, #gauge=3444>
>> lot.occupied
=> false
>> lot.add_guest('A guest')
=> "A guest"
>> lot.occupied?
=> true
>> lot.remove_guest
=> nil
>> lot.occupied?
=> false
Take two...
It's conventional to use attr_accessor methods in your class definition. They automatically add getter and setter methods to your class. You could do that instead of add_guest and remove_guest if you wanted to follow the common Ruby pattern...
class Lot
attr_accessor :number, :gauge, :guest
def initialize(number)
#gauge = rand(2000) + 2000
#number = number
end
def occupied?
! #guest.nil?
end
end
Usage...
irb(main):017:0> lot = Lot.new(3)
=> #<Lot:0xb7f7fca8 #gauge=3186, #number=3>
Set the Guest of a Lot (like add_guest)...
irb(main):019:0> lot.guest = 'A guest'
=> "A guest"
irb(main):020:0> lot.occupied?
=> true
Get the Guest for a Lot...
irb(main):025:0> lot.guest
=> "A guest"
Remove the Guest...
irb(main):021:0> lot.guest = nil
=> nil
irb(main):023:0> lot.occupied?
=> false
Generally Ruby method names are not capitalized. The convention are simply: ClassName, CONSTANT, method_name.
Since you have an Array of Lot objects, the following should be true:
lots.class # => Array
lots[1].class # => Lot
The method called should be defined for Lot.

Resources