I am a newb and struggling with making a test pass. I have 3 classes, artists, songs, and genres. The test I am trying to make pass is below:
test 'A genre has many artists' do
genre = Genre.new.tap{|g| g.name = 'rap'}
[1,2].each do
artist = Artist.new
song = Song.new
song.genre = genre
artist.add_song(song)
end
assert_equal genre.artists.count, 2
end
This is my artist class, the add_song method is the one I need to tweak. When a song is added to an artist I am trying to instantiate a new Genre object and add the artist to that genre as well. Currently not working though, when I call genre.artists it returns an empty array.
class Artist
attr_accessor :name, :songs, :genres, :genre, :artists
##artists = []
def initialize(name = name, genre = genre)
#artists = []
#songs = []
#genre = genre
#genres = []
#name = name
##artists << self
end
def self.all
##artists
end
def self.reset_artists
##artists = []
end
def self.count
self.all.size
end
def songs_count
self.songs.size
end
def count
self.size
end
def add_song(song)
#songs << song
#genres << song.genre
Genre.new(self)
end
end
class Genre
attr_accessor :name, :songs, :artists
##genres = []
def initialize(artists = artists)
#songs = []
#artists = artists
#name = name
##genres << self
end
def count
self.artists.count
end
def self.all
##genres
end
def self.reset_genres
##genre = []
end
end
class Song
attr_accessor :name, :genre, :artist
def initialize(name = name, artist = artist, genre = genre)
#name = name
#artist = artist
#genre = genre
end
end
When you create a new artist, you add it to Artist::artists - a class variable of Artist. The array you test is genre.artists - an object variable of Genre. That's a different variable from Artist::artists, and I don't see you updating genre.artists anywhere in your code - I'm surprised it's even an array, seeing that you don't initialize it to an array...
You are returning a creating a new instance of Genre with the current artist in the add_song method. You could get your test to pass in a couple of ways.
This will add an artist to the genre referenced in the song instance.
def add_song(song)
#songs << song
#genres << song.genre
song.genre.artists << self
end
The second option would be fix your test if you want to return a new Genre instance from your add_song method. This is most likely not what you want, however this would set artist the reference in Genre.
test 'A genre has many artists' do
genre = Genre.new.tap{|g| g.name = 'rap'}
[1,2].each do
art ist = Artist.new
song = Song.new
song.genre = genre
genre = artist.add_song(song)
end
assert_equal genre.artists.count, 2
end
Related
here is my code. I've encountered the bug that said: test.rb:101:in <main>': undefined method location' for nil:NilClass (NoMethodError)
although i've defined the method tracks above. I've tried to debug and got the result that nothing was pushed into the tracks variable. Please help me in this stage.
puts albums[0].tracks[0].location
`
class Album
attr_accessor :author, :name, :year, :genre, :cover, :count, :tracks
def initialize (author, name, year, genre, cover, count, tracks)
#author = author
#name = name
#year = year
#genre = genre
#cover = cover
#count = count
#tracks = tracks
end
end
class Track
attr_accessor :name, :location
def initialize (name, location)
#name = name
#location = location
end
end
def read_track(pineapple_music)
song = pineapple_music.gets
file = pineapple_music.gets
t = Track.new(song, file)
return t
end
def read_tracks(pineapple_music, count)
tracks = Array.new
trackcount = count
while trackcount < count
track = read_track(pineapple_music)
tracks << track
trackcount += 1
end
return tracks
end
def read_album(music_file)
album_author = music_file.gets.chomp
album_name = music_file.gets.chomp
album_year = music_file.gets.chomp.to_i
album_genre = music_file.gets.chomp.to_i
cover = music_file.gets.chomp
count = music_file.gets.chomp.to_i
tracks = read_tracks(music_file, count)
album = Album.new(album_author, album_name, album_year, album_genre, cover, count, tracks)
return album
end
def read_albums(pineapple_music)
count = pineapple_music.gets.to_i
albums = Array.new
albumcount = 0
while albumcount < count
album = read_album(pineapple_music)
albums << album
albumcount += 1
end
return albums
end
def read_file(pineapple_music)
music_file = File.new(pineapple_music, "r")
albums = read_albums(music_file)
music_file.close()
return albums
end
def read_number_albums(pineapple_music)
music_file = File.new(pineapple_music, "r")
count = music_file.gets.to_i
music_file.close()
return count
end
`
I've tried some methods like try to output in every function to see what happened. I'm expecting the file name in the library to be loaded in the program, but nothing was loaded and the output was nil afterall.
So when I run my lab i am getting the following error:
Associations — Song and Artist: Artist #add_song adds the song to the current artist's 'songs' collection
Failure/Error: expect(artist.songs).to include(song)
expected ["In the Aeroplane Over the Sea"] to include #<Song:0x0000000001496e88 #name="In the Aeroplane Over the Sea", #artist=#<Artist:0x0000000001496f50 #name="Neutral Milk Hotel", #songs=["In the Aeroplane Over the Sea"]>>
Diff:
## -1,2 +1,2 ##
-[#<Song:0x0000000001496e88 #name="In the Aeroplane Over the Sea", #artist=#<Artist:0x0000000001496f50 #name="Neutral Milk Hotel", #songs=["In the Aeroplane Over the Sea"]>>]
+["In the Aeroplane Over the Sea"]
But when I run my code through pry, I can see the song added into the array of the instance variable, #songs. I am working with two classes, a song class and artist class.
class Artist
attr_accessor :name
attr_reader :songs
##all = []
def initialize(name)
#name = name
save
#songs = []
end
def self.all
##all
end
def save
##all << self
end
def self.destroy_all
##all.clear
end
def self.create(name)
self.new(name)
end
def add_song(song)
if song.artist == nil
song.artist = self
end
#checks if the same song has already been added, otherwise adds new song
if #songs.include?(song.name) == false
#songs << song.name
end
binding.pry
end
end
class Song
attr_accessor :name, :artist
##all =[]
def initialize(name, artist = nil)
#name = name
#artist = artist
save
end
def self. all
##all
end
def save
##all << self
end
def self.destroy_all
##all.clear
end
def self.create(name)
self.new(name)
end
end
Your tests are expecting an aray of Song instances. But what they find is an array of strings.
To fix it you'd just need to change #songs << song.name to #songs << song.
I am sorry if I just gave you the answer instead of hinting you along to solve it yourself, but this isn't really the right platform for that kind of thing. Every question on StackOverflow can be searched by users and we want to keep things helpful to them by getting straight to the point.
However, I can give some advice. Practice reading errors. Try and understand what the error is telling you.
Item class
class Item
def initialize(options = {})
#name = options[:name]
#code = options[:code]
#category = options[:category]
#size = options[:size]
end
attr_accessor :name, :code, :category, :size
end
Music class
class Music < Item
def initialize(options = {})
super
#singer = options[:singer]
#duration = options[:duration]
end
attr_accessor :singer, :duration
end
Movie class
def initialize(options = {})
super
#director = options[:director]
#main_actor = options[:main_actor]
#main_actress = options[:main_actress]
end
attr_accessor :director, :main_actor, :main_actress
end
class Catalog
attr_reader :items_list
def initialize
#items_list = Array.new
end
def add(item)
#items_list.push item
end
def remove(code)
#items_list.delete_if { |i| i.code == code }
end
def show(code)
# comming soon
end
def list
#items_list.each do |array|
array.each { |key, value| puts "#{key} => #{value}" }
end
end
end
catalog1 = Catalog.new
music1 = Music.new(name: "Venom", code: 1, category: :music, size: 1234, singer: "Some singer", duration: 195)
music2 = Music.new(name: "Champion of Death", code: 2, category: :music, size: 1234, singer: "Some singer", duration: 195)
catalog1.add(music1)
catalog1.add(music2)
ruby version 2.6.0
list method is not working. I got undefined method `each' for <#Music:0x0000562e8ebe9d18>.
How can I list all keys and values in another way? Like:
name - "Venom"
code - 1
category - music.
I was thinking about it, but also I got a Movie class and that method gonna be too long
You push instances of Music into #items_list. That means #items_list.each do not return an array, but instances of Music and that Musik instances do not respond do each nor they have keys and values.
I suggest adding an instance method to your Music class that returns the expected output. For example a to_s method like this:
def to_s
"name \"#{name}\" code - #{code} category - #{category}"
end
and to change the list method in your Catalog to something like this:
def list
#items_list.each do |music|
puts music.to_s
end
end
Or when you want to return the values an array of hashed then add a to_h method to Music like this:
def to_h
{ name: name, code: code, category: category }
end
and call it like this:
def list
#items_list.map do |music|
music.to_h
end
end
I have this classes (just a simplified example, not real ones)
class Shop
attr_accessor :name, :products
def initialize(name, products_names=%w(apple orange apple cucumber fennel))
self.name = name
self.products = products_names.map {|name| Product.new(self, name)}
end
end
class Product
attr_accessor :shop, :name
def initialize(shop, name)
self.shop = shop
self.name = name
end
def existing_products_count # problem_comes_here
shop.products #i need to process all products initialized for current shop
.select{|p| p.name==name}.size
end
def uniq_code
"#{name}_#{existing_products_count+1}"
end
end
And here is two questions:
Is this a good approach to pass self for Product instance initialization
and
How can i solve my case to process all already existing shop products for new product initialization
Thank you
UPDATE
all i invented for now is (at least it works like i need)
class Shop
attr_accessor :name, :products
def initialize(name, products_names=%w(apple orange apple cucumber fennel))
Product.class_variable_set(:##all, []) # << added
self.name = name
self.products = products_names.map {|name| Product.new(self, name)}
end
end
class Product
attr_accessor :shop, :name
def self.all # << added
##all
end
def initialize(shop, name)
self.shop = shop
self.name = name
self.class.all << self # << added
end
def existing_products_count # problem goes away here
self.class.all.products # << changed
.select{|p| p.name==name}.size
end
def uniq_code
"#{name}_#{existing_products_count+1}"
end
end
but i feel bad about this kind of solution (i don't know why) and will be appreciate for better one
I'm not sure if I understand the question, but here is one interpretation. Note how I've changed the method Product#uniq_code.
class Shop
attr_accessor :name, :products
def initialize(name, products_names=%w(apple orange apple cucumber fennel))
self.name = name
self.products = products_names.map {|name| Product.new(self, name)}
end
end
class Product
attr_accessor :shop, :name
def initialize(shop, name)
self.shop = shop
self.name = name
end
def uniq_code # problem_comes_here
puts "buy #{self.name} #{self.shop.name}"
end
end
Now let's have an example:
at_store = Shop.new("in store", ["bulldozer", "Ducati"])
online = Shop.new("online", ["can opener", "Makita router"])
arr = [at_store, online]
We can do this:
arr.flat_map { |s| s.products }.each { |p| p.send(:uniq_code) }
# buy bulldozer in store
# buy Ducati in store
# buy can opener online
# buy Makita router online
Is this roughly what you were looking for?
Edit: to save the product instances for a given shop instance:
product_instances = at_store.products
#=> [#<Product:0x007f93f984f938
# #shop=#<Shop:0x007f93f984f988 #name="in_store",
# #products=[...]>, #name="bulldozer">,
# #<Product:0x007f93f984f910
# #shop=#<Shop:0x007f93f984f988 #name="in_store",
# #products=[...]>, #name="Ducati">]
for use later:
product_instances.each { |p| p.uniq_code }
# buy bulldozer in_store
# buy Ducati in_store
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
#name = name,
#book_id = book_id
end
end
class BookCollection
def intialize
#book_names = []
end
def add_to_books(book_name)
book_name.push(book_names)
end
end
book1 = Books.new("catch22", "12345")
book_collection1 = BookCollection.new
book_collection1.add_to_books(book1.name)
puts book_collection1
end
That is my code and the error I'm getting is "undefined local variable or method `book_names'". I tried adding " attr_accessor :book_names" and when I do that the printed output doesn't make sense.
There are a few mistakes in your code:
line 4 should not end with a comma.
initialize in class BookCollection is misspelled, resulting in #book_names not being initialized. #book_names therefore equals nil when you attempt to add an element to it with push. nil does not have a method push; hence the exception, and the message printed with the exception.
book_name.push(book_names) should be #book_name.push(book_name). (#book_name must be an instance_variable, as opposed to a local variable, to be visible outside a method, within the class definition.
puts book_collection1 prints the class instance; you want to print #book_names.
Here I've fixed your code. I've used << instead of push. Either is OK, but the former seems to be favored my most.
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
puts "name = #{name}, book_id = #{book_id}"
#name = name
#book_id = book_id
end
end
class BookCollection
attr :book_names
def initialize
#book_names = []
end
def add_to_books(book_name)
#book_names << book_name
end
end
book_collection1 = BookCollection.new
book1 = Books.new("Catch22", "12345")
book2 = Books.new("Hawaii", "67890")
book_collection1.add_to_books(book1.name)
book_collection1.add_to_books(book2.name)
book_collection1.book_names # => ["Catch22", "Hawaii"]
Probably just a typo at
book_name.push(book_names)
Should have been
book_names.push(book_name)
With attr_accessor :book_names