You're going to be implementing a Music Library domain composed of 3 main
models, Song, Artist, and Genre. The models will relate to each other and
collaborate heavily. Additionally, you're going to be extracting some common
functionality out of those models and into a module, Concerns::Findable, which
you'll then mix back into the models. You'll then build a collaborating object,
MusicImporter, that can parse a directory of MP3 files and use the extracted
filenames to create instances of Song, Artist, and Genre objects. Finally,
you'll build a CLI in bin/musiclibrary that is powered by a
MusicLibraryController to provide a simple CLI that lets a user browse the
library of MP3s imported by song, artist, and genre.
This is a complex lab with many parts, so go slowly. Take time to understand
what you're building holistically before starting. Read this entire README
before jumping in. As you go from spec to spec, we recommend doing them in
numbered order.
Instructions
Song, Artist, and Genre basics
The first thing to do is get the basics of the main models working. Each model
has almost the exact same basic requirements, so once you make
001_song_basics_spec.rb pass by building the Song class, the basic Artist
and Genre specs will go quickly.
The requirements for each model are that they can accept a name upon
initialization and set that property correctly. The name property should be
readable and writable by the object.
Song.new("Blank Space").name#=> "Blank Space"`
Additionally, each class should contain a class variable @@all that is set
to an empty array and is prepared to store all saved instances of the class.
This class variable should be accessible via the class method .all.
Song.all#=> []Artist.all#=> []
Instances should respond to a #save method that adds the instance itself into
the appropriate @@all class variable.
Finally, all classes should implement a custom constructor .create that
instantiates an instance using .new but also invokes #save on that instance,
forcing it to persist immediately.
Songs belong to an artist and an artist has many songs. Adding a song to an
artist is done by calling an #add_song method on an instance of the
Artist class.
Songs can be initialized with an optional artist argument.
Songs and Genres
Genres have many songs and are initialized with an empty list of songs.
Songs have one genre.
Songs can be initialized with an optional genre argument.
Artists and Genres
Artists have many genres through their songs. Implement a #genres method
for this association.
Genres have many artists through their songs. Implement an #artists method
for this association.
Note: there are a few tests concerned with switching the
Song#initialize method from setting instance variables for @artist and
@genre to using the custom setter methods that you define (e.g.,
Song#genre=). We want to use the custom setter methods because they keep our
associations in sync. For example, when we call our custom Song#artist=
method, it sets the song's @artist property and adds the song to the
artist's collection of songs. When you reach these tests, make sure those
setter methods are only invoked ifSong#initialize is called with artist
and/or genre arguments. Otherwise, the @artist and/or @genre properties
will be initialized as nil, and you'll have some unexpected consequences in
both your code and the test suite.
If we call Song.new("Song Title", artist_object, genre_object), both
Song#artist= and Song#genre= should be invoked.
If we call Song.new("This Song Has No Artist or Genre"), neither
Song#artist= nor Song#genre= should be invoked.
Finding
Song
First implement the following two methods in your Song class:
Songs should have a find_by_name method.
Songs should have a find_or_create_by_name method.
Concerns::Findable
Now that you've gotten the methods working in Song, let's adapt them for
general reuse by putting them into a module that we can mix into our Artist
and Genre classes. It's Ruby convention to put modules in a concerns/ folder
nested under lib/, and each module should be namespaced like this:
moduleConcerns::ModuleName# Module code hereend
Once the basic module structure is good to go, it's time to code our two class methods again:
Implement a generic #find_by_name method that uses the .all method defined by the class to find an instance of the class by name.
Implement a generic #find_or_create_by_name method that uses the .all method defined by the class to find an instance of the class by name and to create a new instance if a match is not found.
Add this module to your Genre and Artist class.
MusicImporter
Create a MusicImporter class that works with your Song, Genre, and
Artist objects to import a directory of MP3 files. This class will have the
following methods:
#initialize accepts a file path to a directory of MP3 files.
#files returns all of the imported filenames.
.import imports all of the files from the library, instantiating a new
Song object for each file.
In addition, add the following pair of methods to your Song class:
.new_from_filename, which instantiates a new Song object based on a provided filename.
.create_from_filename, which does the same thing as .new_from_filename but also saves the newly-created song to the @@all class variable.
It's CLI time!
Congrats! You've done the heavy lifting. Now let's wrap it all up in a simple
CLI so that users can actually interact with our code. Create a
MusicLibraryController class that:
Upon initialization, accepts an optional path to the library of MP3 files, defaulting to ./db/mp3s. It should then instantiate a MusicImporter object, which it will use to import songs from the specified library.
Has a #call method that starts the CLI and prompts the user for input (try using gets.chomp). Read the tests carefully for specifics.
When you've passed all of the tests, you should be able to type ./bin/musiclibrary into your terminal to play around with your CLI and see how it works.
请发表评论