In the last lesson, we updated almost all of our Album
methods to use a database. We didn't have to change a single test in the process. Now we'll do the same for the Song
class. In fact, the methods are so similar that we won't walk through each one separately. After walking through the previous lesson with a pair, we recommend trying to write out each method for the Song
class on your own and getting tests passing one at a time. This provides important SQL practice and helps solidify these concepts.
The entire updated class is below:
class Song
attr_reader :id
attr_accessor :name, :album_id
def initialize(attributes)
@name = attributes.fetch(:name)
@album_id = attributes.fetch(:album_id)
@id = attributes.fetch(:id)
end
def ==(song_to_compare)
(self.name() == song_to_compare.name()) && (self.album_id() == song_to_compare.album_id())
end
def self.all
returned_songs = DB.exec("SELECT * FROM songs;")
songs = []
returned_songs.each() do |song|
name = song.fetch("name")
album_id = song.fetch("album_id").to_i
id = song.fetch("id").to_i
songs.push(Song.new({:name => name, :album_id => album_id, :id => id}))
end
songs
end
def save
result = DB.exec("INSERT INTO songs (name, album_id) VALUES ('#{@name}', #{@album_id}) RETURNING id;")
@id = result.first().fetch("id").to_i
end
def self.find(id)
song = DB.exec("SELECT * FROM songs WHERE id = #{id};").first
name = song.fetch("name")
album_id = song.fetch("album_id").to_i
id = song.fetch("id").to_i
Song.new({:name => name, :album_id => album_id, :id => id})
end
def update(name, album_id)
@name = name
@album_id = album_id
DB.exec("UPDATE songs SET name = '#{@name}', album_id = #{@album_id} WHERE id = #{@id};")
end
def delete
DB.exec("DELETE FROM songs WHERE id = #{@id};")
end
def self.clear
DB.exec("DELETE FROM songs *;")
end
def self.find_by_album(alb_id)
songs = []
returned_songs = DB.exec("SELECT * FROM songs WHERE album_id = #{alb_id};")
returned_songs.each() do |song|
name = song.fetch("name")
id = song.fetch("id").to_i
songs.push(Song.new({:name => name, :album_id => alb_id, :id => id}))
end
songs
end
def album
Album.find(@album_id)
end
end
The only key difference worth mentioning here is our new self.find_by_album
method. It looks very similar to our self.all
method. The key difference is that we have a select statement with a WHERE
query included. Specifically, we are looking for songs where the album_id
matches the argument passed into alb_id
. This is a very common query for finding the "many" in a one-to-many relationship. In this case, we're using it to find all the Song
s that belong to an Album
.
At this point, we can also uncomment the remaining test in album_spec.rb as well as any integration specs. All tests should pass and we can navigate through our application just as we did before. This is a great thing about modular, clean code. We were able to update all of our backend logic without touching our tests, app.rb, or our views. Ultimately, our application is doing the exact same thing it was doing before but with one key difference: we now have persistence through a database. From here on out, class variables should be avoided if possible.
Lesson 9 of 29
Last updated August 7, 2022