I want to add elements to an array. Each time, elements are passed as parameters by calling a different method. That is, calling the following:
new_register = CashRegister.new
new_register.add_item("eggs", 1.99)
new_register.add_item("tomato", 1.76, 3)
should return ["eggs", "tomato", "tomato", "tomato"].
Here's my code in its entirety:
class CashRegister
attr_accessor :total, :discount, :title, :price, :array_of_all_items
def initialize(discount = 0)
#total = 0
#discount = discount
end
def add_item (title, price, quantity = 1)
#title = title
self.total = self.total + (price * quantity)
end
def apply_discount
if
discount == 0
"There is no discount to apply."
else
total_discount = self.total * (discount / 100.0)
self.total = self.total - total_discount
"After the discount, the total comes to $#{self.total.round}."
end
end
def items
array_of_all_items << self.title
end
end
Within def items method, I'm stumped on how to add items to the array without overriding the array that was there previously. Also confused about whether I needed to use attr_accessor to set and get the parameters that are passed to def add_item so that I can use them in def items.
If add items is meant to change the state of the cashregister, then it will change(override) the array. To add to an array you can use ruby's push method, or the shovel operator <<.
def add_items thing, price, number = 1
number.times { #log << thing }
or
number.times { #log.push thing }
return #log
end
unless you want the storage array instance variable accessible outside of the class, you do not need attr_*'s.
Related
I have tried at least 5 different ways to do it, currently using the .each method.
I believe the problem lies in the print_songs method at the bottom of the code block.
The error I'm getting:
Artist #print_songs lists all of the artist's songs
Failure/Error: expect{artist.print_songs}.to output("Dirty Diana\nBillie Jean\n").to_stdout
expected block to output "Dirty Diana\nBillie Jean\n" to stdout, but output "#<Song:0x000000019757b0>\n#<Song:0x000000019756e8>\n"
Diff:
## -1,3 +1,3 ##
-Dirty Diana
-Billie Jean
+#<Song:0x000000019757b0>
+#<Song:0x000000019756e8>
The code:
class Artist
attr_accessor :name, :song
##all = []
def initialize(name)
#name = name
#songs = []
end
def add_song(song)
#songs << song
end
def songs
#songs
end
def self.all
##all
end
def save
self.class.all << self
end
def self.create_by_name(name)
artist = Artist.new(name)
end
def self.find_or_create_by_name(name)
artist_name = ##all.detect{|x| x.name == name}
if artist_name == nil
self.create_by_name(name)
else
artist_name
end
end
def print_songs
#songs.each{|song| puts song}
end
end
Song Class:
class Song
attr_accessor :name, :artist
def initialize(name)
#name = name
end
# def artist=(name)
# self.artist = Artist.new(name)
# end
def self.new_by_filename(file_name)
file_name.basename("")
end
end
Answer:
def print_songs
#songs.each{|song| puts song.name}
end
It's apparent that the song is probably just not a string, but an object of some sort. Add a to_s method to whatever object that is and puts should automatically call that, although you could, of course, also call to_s manually.
Here's the quick test I did to confirm this behaviour
irb(main):001:0> class Song
irb(main):002:1> def initialize(title)
irb(main):003:2> #title = title
irb(main):004:2> end
irb(main):005:1> end
=> :initialize
irb(main):006:0> s = Song.new "scarborough fair"
=> #<Song:0x0000000030bb78 #title="scarborough fair">
irb(main):007:0> puts s
#<Song:0x0000000030bb78>
=> nil
irb(main):008:0> class Song
irb(main):009:1> def to_s
irb(main):010:2> return #title
irb(main):011:2> end
irb(main):012:1> end
=> :to_s
irb(main):013:0> puts s
scarborough fair
=> nil
irb(main):014:0>
EDIT:
But why is this?
In short, when you create some custom object class, like Song in my example above, Ruby has no idea how to represent it as a string. The default to_s method simply outputs the class and the object ID, which is OK for debugging, but if there's some obvious way to represent the object as a string, like in the case of Song (one would expect song.to_s to return the song title), one has to override the default to_s method or include / inherit from another class/module that has a more fitting implementation of to_s.
It is working as expected.Lets look at tried.Could you post the test cases you have tried with?
a1 = Artist.create(name: 'Lana del re')
a1.add_song('Young and Beautiful')
a1.add_song('haunted')
a1.print_songs
["Young and Beautiful", "haunted"] #output assuming song as a String object
By default, the call to to_s prints the object's class name and an encoding of the object id like so: "#<Song:0x007f9fd16a0770>". That said, you need to override the to_s method on a Song class:
class Song
def initialize(title)
#title = title
end
def to_s
#title
end
end
Then you would need to modify the print_songs method accordingly on an existing Artist class:
def print_songs
puts #songs.each(&:to_s)
end
The each(&:to_s) bit basically invokes the to_s method on each Song object passed to the block, which is essentially the same as
def print_songs
puts #songs.each { |song| song.to_s }
end
This could've also been rewritten as just
def print_songs
puts #songs
end
and in this case puts would implicitly invoke the to_s method on the elements of #songs.
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
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
I created a class and added objects like so:
class Tops
def initialize(neckline, brand, maincolour, style)
#neckline = neckline
#brand = brand
#maincolour = maincolour
#style = style
end
end
top1 = Tops.new("Sweetheart", "Miss Selfridge", "Blue", "Bardot")
top2 = Tops.new("Scoop", "Target", "Pink", "Vest")
top3 = Tops.new("Boat", "Unknown", "Red", "Tank")
I then used the push method to add some of these objects into an empty array.
currentTopChoices = []
currentTopChoices.push(top1,top3)
I then used some the each method to run a block of code or each object in this array
currentTopChoices.each { |x| puts #neckline }
So my aim was really to get an output of each neckline of the objects in the array but I get an output of:
Boat
Boat
When I really wanted:
Sweetheart
Boat
Which is really the neckline of the last object but printed twice. I get no error messages so what exactly am I missing here?
If you don't mind adding an attr_accessor to Tops (which will provide a Top#neckline accessor method), you can use Array#map or (its alias Array#collect) to build a collection of necklines:
class Tops
attr_accessor :neckline
def initialize(neckline, brand, maincolour, style)
#neckline = neckline
#brand = brand
#maincolour = maincolour
#style = style
end
end
top1 = Tops.new("Sweetheart", "Miss Selfridge", "Blue", "Bardot")
top2 = Tops.new("Scoop", "Target", "Pink", "Vest")
top3 = Tops.new("Boat", "Unknown", "Red", "Tank")
[top1, top3].map(&:neckline) # => ["Sweetheart", "Boat"]
# or, more explicitly
[top1, top3].map { |top| top.neckline } # => ["Sweetheart", "Boat"]
The problem is that somehow you are confusing yourself by either not showing all your code, or maybe running this in irb, where at some point you had a statement like?
#neckline = "Boat"
For example the code you posted, if placed directly in a file_name.rb like so:
class Tops
def initialize(neckline, brand, maincolour, style)
#neckline = neckline
#brand = brand
#maincolour = maincolour
#style = style
end
end
top1 = Tops.new("Sweetheart", "Miss Selfridge", "Blue", "Bardot")
top2 = Tops.new("Scoop", "Target", "Pink", "Vest")
top3 = Tops.new("Boat", "Unknown", "Red", "Tank")
currentTopChoices = []
currentTopChoices.push(top1,top3)
currentTopChoices.each { |x| puts #neckline }
and then run using ruby file_name.rb
would just produce 2 blank lines because #neckline is not define at this scope (toplevel), so it's value is nil
To get this to work the way you intend you can add
attr_reader :neckline
and then refer to x.neckline within the block you pass to each, since x will take one the value of each of the Tops objects in currentTopChoices
note: attr_accessor adds both a setter and a getter for neckline, if you only need read access to that property than that's all you should allow (which is what you get with attr_reader ), if you really need to both get and set the value of neckline on an instance after creating it then att_accessor would make sense.
Here is an updated version of your code that should behave as you are expecting.
class Tops
attr_reader :neckline # added to allow read access to #neckline
def initialize(neckline, brand, maincolour, style)
#neckline = neckline
#brand = brand
#maincolour = maincolour
#style = style
end
end
top1 = Tops.new("Sweetheart", "Miss Selfridge", "Blue", "Bardot")
top2 = Tops.new("Scoop", "Target", "Pink", "Vest")
top3 = Tops.new("Boat", "Unknown", "Red", "Tank")
currentTopChoices = []
currentTopChoices.push(top1,top3)
currentTopChoices.each { |x| puts x.neckline } # access the neckline property for each element
So if a have this code:
class A
def initialize(type)
#type = type
end
end
instance = A.new(2)
another_instance = A.new(1)
array = [instance, another_instance]
is there a way to check if array includes an instance of A where #type is equal to a certain value? say, 2? like the include? method but where instead of checking for an instance of a certain class, it also checks the instance variables of that class?
I would recommend using anattr_reader for this one unless you plan on modifying the type somewhere after (in that case use attr_accessor which is both a writer and reader)
class A
attr_reader :type
def initialize(type)
#type = type
end
end
instance = A.new(2)
another_instance = A.new(1)
array = [instance, another_instance]
array.select do |item|
item.type == 2
end
=>[#<A:0x00000000dc3ea8 #type=2>]
Here I am iterating through an array of instances of A and selecting only the ones that meet the condition item.type == 2
You can just refer to the instance variable.
> array.any? { |item| item.is_a?(A) }
=> true
> array.any? { |item| item.instance_variable_get(:#type) == 1 }
=> true
> array.select { |item| item.instance_variable_get(:#type) == 1 }
=> [#<A:0x007fba7a12c6b8 #type=1>]
Or, use attr_accessor in your class, to make it way easier
class A
attr_accessor :type
def initialize(type)
#type = type
end
end
then you can do something = A.new(5); something.type
I am by no means an expert, and I've run into a little snag when handling objects via a Readline input.
I have a Player class, an Inventory class, and an Item class, and I want the player to be able to type "wear brick" and have it match an Item.name within the array Inventory.contents and add that item to Player.worn. Here's what I have:
class Player
def wear(item)
#worn << item
end
end
class Inventory
attr_accessor :contents
def initialize
#contents = []
#stick = Item.new('a brown stick', 'long and pointy')
#brick = Item.new('a red brick', 'rough and heavy')
#contents << #stick
#contents << #brick
end
def list_contents
#contents.each {|item| puts item.name }
nil
end
end
class Item
attr_accessor :name, :desc
def initialize(name, desc)
#name = name
#desc = desc
end
end
player = Player.new
inv = Inventory.new
I have a Readline prompt to accept the first token of an input string as a command, here 'wear' as a case, but I'm having a hard time grasping how to match the subsequent string within a object inside an array of objects with one word.
inv.contents.inspect:
[#<Item:0x007fcf61935bb0 #name="a brown stick", #desc="long and pointy">, #<Item:0x007fcf61935b38 #name="a red brick", #desc="rough and heavy">]
How could I have "wear brick" search through the Array inv.contents to match #name="a red brick" and select that item to input into Player.wear?
#c650 : thank you. that looks to have solved it!
New Class:
class Player
def initialize
#inventory = Inventory.new
#worn = []
#input = "brick"
end
def wear(keyword)
#inventory.contents.map{|x| #worn << x and #inventory.contents.delete(x) if x.name.include?(keyword) }
end
end