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
Related
I have 3 classes: Invoice, Address and Customer (but for this problem, only the Invoice and Address class are relevant)
This is my Invoice class:
class Invoice
attr_reader :billing_address, :shipping_address, :order
def initialize(attributes = {})
#billing_address = attributes.values_at(:billing_address)
#shipping_address = attributes.values_at(:shipping_address)
#order = attributes.values_at(:order)
end
end
and this is my Address class:
class Address
attr_reader :zipcode, :full_address
def initialize(zipcode:)
#zipcode = zipcode
url = 'https://viacep.com.br/ws/' + zipcode.to_s + '/json/'
uri = URI(url)
status = Net::HTTP.get_response(uri)
if (status.code == "200")
response = Net::HTTP.get(uri)
full_address = JSON.parse(response)
#full_address = full_address
else
p "Houve um erro. API indisponível. Favor tentar novamente mais tarde."
#full_adress = nil
end
end
end
And this is my Customer class (not much relevant, but i'm showing for better explanation of the problem)
class Customer
attr_reader :name, :age, :email, :gender
def initialize(attributes = {})
#name = attributes.values_at(:name)
#age = attributes.values_at(:age)
#email = attributes.values_at(:email)
#gender = attributes.values_at(:gender)
end
end
As you can see, my Invoice class has 3 instance variables and my Address class has 2 instance variables.
So, if i test something like that:
cliente = Customer.new(name: "Lucas", age: 28, email: "abc#gmail.com", gender: "masculino")
endereco = Address.new(zipcode: 41701035)
entrega = Invoice.new(billing_address: endereco, shipping_address: endereco)
p endereco.instance_variables
[:#zipcode, :#full_address]
p entrega.shipping_address.instance_variables
[]
My instance variables can be acessed through the variable "endereco", that is an Address object, but can't be acessed through entrega.shipping_address that is also an Address object.
To be more precise, if a try this:
p entrega.shipping_address
I get this return:
[#<Address:0x00000001323d58 #zipcode=41701035, #full_address={"cep"=>"41701-035", "logradouro"=>"Rua Parati", "complemento"=>"", "bairro"=>"Alphaville I", "localidade"=>"Salvador", "uf"=>"BA", "unidade"=>"", "ibge"=>"2927408", "gia"=>""}>]
My full object are being returned, but i can't access the content of my #full_address instance variable.
If a do this:
p entrega.shipping_address.full_address
I get a NoMethodError:
solucao.rb:8:in `<main>': undefined method `full_address' for #<Array:0x000000012d25e8> (NoMethodError)
I'm trying to understand why i can't access the content inside my object if i have the full object. Maybe i'm trying to access in the wrong way, i don't know.
Can someone help ?
values_at returns an array of values (see https://apidock.com/ruby/Hash/values_at
for explanation)
Change
#shipping_address = attributes.values_at(:shipping_address)
into
#shipping_address = attributes[:shipping_address]
And that way #shipping_address will contain an Address object, not an array that contains an Address object
If you take a look at the error, it says
undefined method `full_address' for #<Array:0x000000012d25e8>
You're trying to call full_address on an array. So this means that entrega.shipping_address returns you an array (which it does, of course, take a closer look at the output).
If I were you, I'd look into how shipping_address is implemented. It's a simple attr_reader, so it's backed by an instance variable. Must be you initialize that instance variable to a wrong value (it gets an array instead of an address). Look closely at that initialization code and try to run it in IRB session. You should see the problem.
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.
Hi i have a question because i usually use multiple classes in Ruby but i dont know if is that correct?, for example the next code:
Class Main.rb
class Main
require_relative 'Archivo.rb'
require_relative 'Diccionario.rb'
require_relative 'Jsn.rb'
hsh = Diccionario.new
jsn = Jsn.new
fl = Archivo.new
puts "Ingresa Nombre"
nombre = gets
puts "Ingresa Correo"
correo = gets
puts "Ingresa password"
password = gets
hsh.usuario(nombre,correo,password)
jsn.convert_json(hsh.get_usuario)
fl.write('usuario.json',jsn.get_json)
fl.read('usuario.json')
puts fl.get_line
end
Class Diccionario.rb in other file
class Diccionario
$usuarios = Hash.new
require 'json'
def usuario(nombre, correo, password)
$usuarios = {nombre: nombre, correo: correo, password: password}
end
def get_usuario
$usuarios
end
end
Class Jsn.rb in other file
class Jsn
require 'json'
$cadena
def convert_json(cadena)
$cadena = cadena.to_json
end
def get_json
$cadena
end
end
Class Archivo.rb in other file
class Archivo
$line
def read(file)
File.open(file,"r") {|archivo|
$line =archivo.gets
}
end
def write(file,cadena)
File.open(file, "w+") do |f|
f.puts cadena
end
end
def get_line()
$line.to_s
end
end
Thanks :D
You should definitely be creating multiple classes! I would try to think in terms of SOLID principles to ascertain where behaviour belongs and which objects/classes have responsibility for implementing them.
There's an excellent book called Practical Object Oriented Design in Ruby by Sandi Metz. It's a great book about how to organise your code into classes. It talks about SRP (Single Responsibility Principle) and how to drill down to the essence of the problem you're trying to solve. If you can't buy the book then there's a talk that covers the main points here:
https://www.youtube.com/watch?v=8bZh5LMaSmE
She actually mentions that it's a common error to "not create enough classes" and that you should feel confident to break the implementation down further to smaller manageable classes.
I am writing a small program for a train system.
I have a passenger, coach, train and station class (and thus, a spec test for each).
My test for my passenger class is as such:
let (:passenger) {Passenger.new}
it "should not be touched in to a station when initialized" do
expect(passenger.touchedin?).to be false
end
it "should be able to enter coach" do
coach = Coach.new
passenger.enter(coach)
expect{coach.to receive(:enter)}
end
it "should be able to alight coach" do
coach = Coach.new
passenger.alight(coach)
expect{coach.to receive(:alight)}
end
it "should be able to touch into station" do
station = Station.new
passenger.touchin(station)
expect{station.to receive(:touchin)}
end
it "should be able to touch out of station" do
station = Station.new
passenger.touchout(station)
expect{station.to receive(:touchout)}
end
end
And my passenger class is like this (at the moment :p):
class Passenger
def initialize
#touchedin = false
end
def enter(coach)
end
def touchedin?
#touchedin
end
def alight(coach)
end
def touchin(station)
end
def touchout(station)
end
end
I am unsure how to satisfy my tests, if my tests are even correct in the first place.
Any help is really appreciated!
You've not really said how you're modeling the relationship between coaches and passengers, but one way I could think of could be as follows. I'm just putting enough for the coach/passenger relationship (so nothing about touching in as this involves the station) - and I'm using minitest syntax, but I think you can get the idea of what's happening.
class Coach
def initialize
#passengers = []
end
...
end
class Passenger
def initialize
#touched_in = false
end
def alight(coach)
coach.passengers << self.uid # or self, if you want the whole passenger object available
end
...
end
coach = Coach.new
assert_empty coach.passengers
joe = Passenger.new
refute_includes coach.passengers, joe.uid # or joe
joe.alight(coach)
assert_includes coach.passengers, joe.uid # or joe
So I copied this code from a youtube video https://www.youtube.com/watch?v=V9OySOWLYIg
He ran it in his video no problem, but when I run it, it gives me an error
C:/rails/11.rb:17:in create': Unknown hero (RuntimeError)
from C:/rails/11.rb:6:inblock in initialize'
from C:/rails/11.rb:6:in times'
from C:/rails/11.rb:6:ininitialize'
from C:/rails/11.rb:22:in new'
from C:/rails/11.rb:22:in'
class Party
attr_reader :members
def initialize(number, occupation)
#members = []
number.times { members << create(occupation)}
end
end
class PartyFactory < Party
def create(occupation)
if occupation == :warrior
Warrior.new
elseif occupation == :mage
Mage.new
else
raise "Unknown hero"
end
end
end
party = PartyFactory.new(2, :mage)
Another question I have is what if intead of Mage.new , I do Mage.new("fred"), to set the name for mage, where does the "fred" part end up?
Sorry, I am very new to Ruby and can not find a working example to understand how to set up factory methods.
Change elseif to elsif (without the second e).
Then make sure to initialize Mage and Warrior classes as you'll get a NameError if you don't.