Manage your spotify (or compatible) playlists directly from your console.
- About
- Project Requirements
- How do I use this?
- Contributing
- Problem Solving Walkthrough πΆπ»ββοΈ
- Final thoughts π€
This is a solution for this coding challenge powered by Ruby v3.
The input JSON file consists of a set of users
, songs
, and playlists
that are part of an online music service: spotify.json.
- Ingests
spotify.json
- Ingests a
changes file
, which can take whatever form you like (we usechanges.json
in our example, but youβre free to name it whatever, and format it as text, YAML, CSV, or whatever) - Outputs
output.json
in the same structure asspotify.json
, with the changes applied. The types of changes you need to support are enumerated below
- Add an existing song to an existing playlist
- Add a new playlist for an existing user; the playlist should contain at least one existing song
- Remove an existing playlist
- Explains how to use your application and a way to validate its output
- Describes what changes you would need to make in order to scale this application to handle very large input files and/or very large changes files. Just describe these changes β please do not implement a scaled-up version of the application.
- Includes any thoughts on design decisions you made that you think are appropriate.
- Includes how long you spent on the project, and any other thoughts you might have or want to communicate.
- Donβt worry about creating a UI, DB, server, or deployment as a part of the code you're writing.
- Your code should be executable on Mac or Linux.
To run this project you need to have Ruby v3 installed. If you don't have it installed, you can follow this guide.
To use this application, open a terminal in the root of this project, then you need to run it with a command with the following format:
ruby main.rb <input_file>.json <changes_file>.json <output_file>.json
E.g
ruby main.rb ./samples/spotify.json samples/changes.json samples/output.json
The changes.json
file is a JSON file that contains a list of changes that we
want to apply to the spotify.json
file. It's an array of objects with the
following format (all fields are required):
[
{
"action": "add_song_to_playlist",
"data": {
"playlist_id": "1",
"song_id": "5"
}
},
{
"action": "add_playlist_to_user",
"data": {
"user_id": "2",
"playlist": {
"id": "5",
"owner_id": "2",
"song_ids": ["2", "3", "4"]
}
}
},
{
"action": "remove_playlist",
"data": {
"playlist_id": "3"
}
}
]
The changes will be applied in the order they are in the file!
The action
field can have one of the following values:
add_song_to_playlist
add_playlist_to_user
remove_playlist
If you want to contribute to this project, feel free to clone it and submit a pull request. I'll be happy to review it and merge it if it's good to go. Following subsections will help you setup the project and run the tests.
This is a simple Ruby project, so you just need to install the dependencies and you're good to go.
bundle install
If you want to run the tests, you can do so by running rspec
in the root.
bundle exec rspec
The first thing I did was analyzing the spotify.json
file and created
a mermaid diagram to visualize the
relationships between the entities. This project consists of 3 entities: User
,
Song
, and Playlist
. And the relationships between them are as follows:
erDiagram
User ||--o{ Playlist : has
Song ||--o{ Playlist : has
As for their class diagram and attributes, I came up with the following:
classDiagram
Playlist *-- User
Playlist *-- Song
class User {
+String id
+String name
}
class Playlist {
+String id
+String owner_id
+List~String~ song_ids
}
class Song {
+String id
+String artist
+String title
}
With that, I need to keep in mind that we want to achieve the following:
- Add an existing song to an existing playlist
- That is, add
song_id
to aPlaylist.song_ids
list
- That is, add
- Add a new playlist for an existing user
- In other words, create a new
Playlist
with existing user asowner_id
- In other words, create a new
- Remove an existing playlist
- Simply put, remove a
Playlist
from the output file
- Simply put, remove a
Cool! But how will we achieve that with the changes
file? π€
Let's think about that.
The changes
file is a JSON file that contains a list of changes that we want
to apply to the spotify.json
file. It looks like this:
[
{
"action": "add_song",
"playlist_id": "1",
"song_id": "42"
},
{
"action": "add_playlist",
"playlist": {
"id": "4",
"owner_id": "3",
"song_ids": ["6", "8", "11"]
}
},
{
"action": "remove_playlist",
"playlist_id": "2"
}
]
I decided to go with JSON as it's a simple format and it's easy to parse. Also, it's a common format used in the web, so it's a good choice for this project.
The changes will be applied in the order they are in the file, so we need to keep that in mind.
Also we talk about change
, change
, and change
. So sounds like it makes
sense to create a Change
class to represent a change.
The Change
class will be responsible for applying the changes
to the
output file. It will have the following attributes:
classDiagram
Change <|-- AddSongToPlaylistChange
Change <|-- AddPlaylistToUserChange
Change <|-- RemovePlaylistChange
class Change {
+Object data
+apply_to
+create
}
class AddSongToPlaylistChange {
+apply_to
}
class AddPlaylistToUserChange {
+apply_to
}
class RemovePlaylistChange {
+apply_to
}
The Change.create
method will be responsible for creating the appropriate
subclass based on the action
field. And the Change.apply_to
method will be
responsible for applying the change to the output file.
The inspiration for this is the Factory Method design pattern and I believe it suits this project well.
The Playlistify
class will be responsible for ingesting all files,
applying the changes to the output file and writes the output file to disk.
It took me about 4 hours to complete this project. I had a lot of fun doing it and I learned a lot as it's been a while since the last developed a command line application.
I am not used to create applications that handle very large input files, so I might say something silly, but I'll try my best.
Currently it reads the whole input and change files into memory, which is not ideal for very large files as it might run out of memory, or have a very long execution time.
We could use the MapReduce pattern as inspiration to solve this problem. With that, split the input file into chunks and process them in parallel. Then merge the results into a single output file. On the other hand, we need to keep in mind that the changes need to be applied in order, so we need to find a way to do that. Maybe we could split the changes file into chunks per playlist and user.
- Make the script executable and add instructions on how to run it
- Add more validations, e.g, check if files are valid; check if song exist before adding to playlist
- Add more documentation, e.g, add
@param
and@return
to methods - Add more file formats support, e.g, support CSV and YAML files
- Add
--help
flag to show instructions on how to use the script