├── .rspec ├── lib ├── records │ └── items.json ├── classes │ ├── genre.rb │ ├── author.rb │ ├── label.rb │ ├── musicalbum.rb │ └── game.rb ├── models │ └── book.rb ├── item.rb ├── helpers │ └── data_manager.rb └── controllers │ └── menu_controller.rb ├── GemFile ├── main.rb ├── .github └── workflows │ └── linters.yml ├── spec ├── author_spec.rb ├── game_spec.rb ├── genre_spec.rb ├── label_spec.rb ├── book_spec.rb ├── musicalbum_spec.rb └── spec_helper.rb ├── Gemfile.lock ├── LICENSE ├── .rubocop.yml ├── schema.sql ├── app.rb └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /lib/records/items.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /GemFile: -------------------------------------------------------------------------------- 1 | gem 'rubocop', '>= 1.0', '< 2.0' -------------------------------------------------------------------------------- /main.rb: -------------------------------------------------------------------------------- 1 | require_relative 'app' 2 | require './lib/controllers/menu_controller' 3 | 4 | def main 5 | app = App.new 6 | menu = Menu.new(app) 7 | menu.display_menu 8 | end 9 | 10 | main 11 | -------------------------------------------------------------------------------- /lib/classes/genre.rb: -------------------------------------------------------------------------------- 1 | class Genre 2 | attr_accessor :name 3 | attr_reader :id, :items 4 | 5 | def initialize(name) 6 | @id = rand(1..1000) 7 | @name = name 8 | @items = [] 9 | end 10 | 11 | def to_s 12 | "Genre: #{@name}" 13 | end 14 | 15 | def add_item(item) 16 | @items << item 17 | item.genre = self 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/classes/author.rb: -------------------------------------------------------------------------------- 1 | class Author 2 | attr_accessor :first_name, :last_name 3 | attr_reader :id, :items 4 | 5 | def initialize(first_name, last_name) 6 | @id = Random.rand(1...1000) 7 | @first_name = first_name 8 | @last_name = last_name 9 | @items = [] 10 | end 11 | 12 | def add_item(item) 13 | @items.push(item) unless @items.include?(item) 14 | item.author = self 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/models/book.rb: -------------------------------------------------------------------------------- 1 | require_relative '../item' 2 | 3 | class Book < Item 4 | attr_accessor :title, :cover_state 5 | attr_reader :publisher 6 | 7 | def initialize(title, publisher, cover_state, *args) 8 | super(*args) 9 | @title = title 10 | @publisher = publisher 11 | @cover_state = cover_state 12 | end 13 | 14 | def can_be_archived? 15 | super || @cover_state == 'bad' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/classes/label.rb: -------------------------------------------------------------------------------- 1 | require_relative '../item' 2 | 3 | class Label 4 | attr_accessor :title, :color, :items 5 | attr_reader :id 6 | 7 | def initialize(title, color) 8 | @id = Random.rand(1..10_000) 9 | @title = title 10 | @color = color 11 | @items = [] 12 | end 13 | 14 | def add_item(item) 15 | return if @items.include?(item) 16 | 17 | @items << item 18 | item.label = self 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/classes/musicalbum.rb: -------------------------------------------------------------------------------- 1 | require_relative '../item' 2 | class MusicAlbum < Item 3 | attr_accessor :on_spotify 4 | 5 | def initialize(publish_date, on_spotify, archived) 6 | super(publish_date, archived) 7 | @on_spotify = on_spotify 8 | @publish_date = publish_date 9 | end 10 | 11 | def to_s 12 | "Album: #{@id} - #{@genre} - Publish Date: #{@publish_date} - On Spotify: #{@on_spotify}" 13 | end 14 | 15 | def can_be_archived? 16 | super && @on_spotify 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/classes/game.rb: -------------------------------------------------------------------------------- 1 | require_relative '../item' 2 | require 'date' 3 | 4 | class Game < Item 5 | attr_accessor :multiplayer, :last_played_at 6 | 7 | def initialize(multiplayer, last_played_at, publish_date) 8 | super(publish_date, archived) 9 | @multiplayer = multiplayer 10 | @last_played_at = last_played_at 11 | end 12 | 13 | def can_be_archived? 14 | current_year = Date.today.year 15 | last_played_at_year = Date.parse(@last_played_at, '%Y-%m-%d').year 16 | super && current_year - last_played_at_year > 2 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | name: Linters 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | rubocop: 7 | name: Rubocop 8 | runs-on: ubuntu-22.04 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-ruby@v1 13 | with: 14 | ruby-version: ">=3.1.x" 15 | - name: Setup Rubocop 16 | run: | 17 | gem install --no-document rubocop -v '>= 1.0, < 2.0' # https://docs.rubocop.org/en/stable/installation/ 18 | [ -f .rubocop.yml ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/ruby/.rubocop.yml 19 | - name: Rubocop Report 20 | run: rubocop --color 21 | -------------------------------------------------------------------------------- /spec/author_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/classes/author' 2 | require_relative '../lib/item' 3 | 4 | RSpec.describe Author do 5 | describe '#add_item' do 6 | it 'adds the item to the author' do 7 | author = Author.new('Ahmed', 'Eid') 8 | item = Item.new('01/01/2019', false) 9 | 10 | author.add_item(item) 11 | 12 | expect(author.items).to include(item) 13 | expect(item.author).to eq(author) 14 | end 15 | 16 | it 'does not add the same item twice' do 17 | author = Author.new('Ahmed', 'Eid') 18 | item = Item.new('01/01/2019', false) 19 | 20 | author.add_item(item) 21 | author.add_item(item) 22 | 23 | expect(author.items.size).to eq(1) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/item.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | 3 | class Item 4 | attr_accessor :publish_date, :genre, :label, :author 5 | attr_reader :id, :archived 6 | 7 | def initialize(publish_date, archived) 8 | @id = Random.rand(1..10_000) 9 | @publish_date = publish_date 10 | @archived = archived 11 | @author = nil 12 | @genre = nil 13 | @label = nil 14 | end 15 | 16 | def add_author(author) 17 | author.add_item(self) 18 | end 19 | 20 | def add_genre(genre) 21 | genre.add_item(self) 22 | end 23 | 24 | def add_label(label) 25 | label.add_item(self) 26 | end 27 | 28 | def can_be_archived? 29 | Date.today.year - Date.parse(@publish_date).year > 10 30 | end 31 | 32 | def move_to_archive 33 | @archived = can_be_archived? 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/game_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/classes/game' 2 | 3 | RSpec.describe Game do 4 | describe '#can_be_archived?' do 5 | context 'when last played more than 2 years ago' do 6 | it 'returns true' do 7 | current_year = Date.today.year 8 | last_played_at = "#{current_year - 3}-01-01" 9 | game = Game.new(true, last_played_at, '2000-01-01') 10 | 11 | expect(game.can_be_archived?).to eq(true) 12 | end 13 | end 14 | 15 | context 'when last played within 2 years' do 16 | it 'returns false' do 17 | current_year = Date.today.year 18 | last_played_at = "#{current_year - 1}-01-01" 19 | game = Game.new(true, last_played_at, '2000-01-01') 20 | 21 | expect(game.can_be_archived?).to eq(false) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | specs: 3 | ast (2.4.2) 4 | json (2.6.3) 5 | parallel (1.23.0) 6 | parser (3.2.2.1) 7 | ast (~> 2.4.1) 8 | rainbow (3.1.1) 9 | regexp_parser (2.8.0) 10 | rexml (3.2.5) 11 | rubocop (1.50.2) 12 | json (~> 2.3) 13 | parallel (~> 1.10) 14 | parser (>= 3.2.0.0) 15 | rainbow (>= 2.2.2, < 4.0) 16 | regexp_parser (>= 1.8, < 3.0) 17 | rexml (>= 3.2.5, < 4.0) 18 | rubocop-ast (>= 1.28.0, < 2.0) 19 | ruby-progressbar (~> 1.7) 20 | unicode-display_width (>= 2.4.0, < 3.0) 21 | rubocop-ast (1.28.1) 22 | parser (>= 3.2.1.0) 23 | ruby-progressbar (1.13.0) 24 | unicode-display_width (2.4.2) 25 | 26 | PLATFORMS 27 | x86_64-linux 28 | 29 | DEPENDENCIES 30 | rubocop (>= 1.0, < 2.0) 31 | 32 | BUNDLED WITH 33 | 2.4.13 34 | -------------------------------------------------------------------------------- /spec/genre_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/classes/genre' 2 | require_relative '../lib/item' 3 | 4 | describe Genre do 5 | before(:each) do 6 | @genre = Genre.new('Action') 7 | end 8 | 9 | describe '#new' do 10 | it 'Should return a new Genre object' do 11 | expect(@genre).to be_an_instance_of(Genre) 12 | end 13 | 14 | it 'Should have a name' do 15 | expect(@genre.name).to eql('Action') 16 | end 17 | 18 | it 'Should not have a setter for id' do 19 | expect { @genre.id = 1 }.to raise_error(NameError) 20 | end 21 | end 22 | 23 | describe '#add_item' do 24 | it 'Should add an item to the genre' do 25 | item = Item.new('01/01/2019', false) 26 | genre = Genre.new('Action') 27 | genre.add_item(item) 28 | expect(genre.items).to include(item) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/label_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/classes/label' 2 | 3 | describe Label do 4 | before(:each) do 5 | @label = Label.new('Label', 'Red') 6 | @item = Item.new('2010-01-01', true) 7 | end 8 | 9 | describe '#new' do 10 | it 'Should return a new Label object' do 11 | expect(@label).to be_an_instance_of(Label) 12 | end 13 | 14 | it 'should set title to Label' do 15 | expect(@label.title).to eq('Label') 16 | end 17 | 18 | it 'should set color to Red' do 19 | expect(@label.color).to eq('Red') 20 | end 21 | end 22 | 23 | describe '#add_item' do 24 | it 'should add an item to the label' do 25 | @label.add_item(@item) 26 | expect(@label.items[0]).to be(@item) 27 | end 28 | 29 | it 'should add the item only once' do 30 | @label.add_item(@item) 31 | @label.add_item(@item) 32 | expect(@label.items.length).to eq(1) 33 | end 34 | 35 | it 'should add the label to the item' do 36 | @label.add_item(@item) 37 | expect(@item.label).to be(@label) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mathias Wismann, Ahmed Eid, Reza Merzaie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | NewCops: enable 3 | Exclude: 4 | - "Guardfile" 5 | - "Rakefile" 6 | - "node_modules/**/*" 7 | 8 | DisplayCopNames: true 9 | 10 | Layout/LineLength: 11 | Max: 120 12 | Metrics/MethodLength: 13 | Max: 20 14 | Metrics/AbcSize: 15 | Max: 50 16 | Metrics/ClassLength: 17 | Max: 150 18 | Metrics/BlockLength: 19 | IgnoredMethods: ["describe"] 20 | Max: 30 21 | 22 | Style/Documentation: 23 | Enabled: false 24 | Style/ClassAndModuleChildren: 25 | Enabled: false 26 | Style/EachForSimpleLoop: 27 | Enabled: false 28 | Style/AndOr: 29 | Enabled: false 30 | Style/DefWithParentheses: 31 | Enabled: false 32 | Style/FrozenStringLiteralComment: 33 | EnforcedStyle: never 34 | 35 | Layout/HashAlignment: 36 | EnforcedColonStyle: key 37 | Layout/ExtraSpacing: 38 | AllowForAlignment: false 39 | Layout/MultilineMethodCallIndentation: 40 | Enabled: true 41 | EnforcedStyle: indented 42 | Lint/RaiseException: 43 | Enabled: false 44 | Lint/StructNewOverride: 45 | Enabled: false 46 | Style/HashEachMethods: 47 | Enabled: false 48 | Style/HashTransformKeys: 49 | Enabled: false 50 | Style/HashTransformValues: 51 | Enabled: false 52 | -------------------------------------------------------------------------------- /schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE author ( 2 | id INT NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 3 | first_name VARCHAR(20), 4 | last_name VARCHAR(20) 5 | ); 6 | 7 | CREATE TABLE game( 8 | id INT NOT NULL, 9 | multiplayer BOOLEAN, 10 | last_played_at DATE, 11 | FOREIGN KEY (id) REFERENCES item (id) ON DELETE RESTRICT ON UPDATE CASCADE 12 | ); 13 | 14 | CREATE TABLE genre ( 15 | id INT NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 16 | name VARCHAR(30) NOT NULL 17 | ); 18 | 19 | CREATE TABLE music_album ( 20 | id INT PRIMARY KEY REFERENCES item(id), 21 | on_spotify BOOLEAN NOT NULL, 22 | FOREIGN KEY (id) REFERENCES item (id) ON DELETE RESTRICT ON UPDATE CASCADE 23 | ); 24 | 25 | CREATE TABLE books ( 26 | id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 27 | publisher VARCHAR(200) NOT NULL, 28 | cover_state VARCHAR(200) NOT NULL, 29 | publish_date DATE NOT NULL, 30 | archived BOOLEAN NOT NULL DEFAULT false, 31 | genre_id INT REFERENCES genre(id), 32 | author_id INT REFERENCES author(id), 33 | label_id INT REFERENCES label(id) 34 | ); 35 | 36 | CREATE TABLE label ( 37 | id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 38 | title VARCHAR(200) NOT NULL, 39 | color VARCHAR(200) NOT NULL 40 | ); 41 | -------------------------------------------------------------------------------- /spec/book_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/models/book' 2 | 3 | describe Book do 4 | before(:each) do 5 | @book = Book.new('The Pragmatic Programmer', 'Addison-Wesley', 'bad', '1999-10-10', true) 6 | end 7 | 8 | describe '#new' do 9 | it 'Should return a new Book object' do 10 | expect(@book).to be_an_instance_of(Book) 11 | end 12 | 13 | it 'Should be an extended Item object' do 14 | expect(@book).to be_a_kind_of(Item) 15 | end 16 | 17 | it 'Should have a title' do 18 | expect(@book.title).to eql('The Pragmatic Programmer') 19 | end 20 | 21 | it 'Should not have a setter for publisher' do 22 | expect { @book.publisher = 'O\'Reilly' }.to raise_error(NameError) 23 | end 24 | end 25 | 26 | describe '#can_be_archived?' do 27 | it 'Should return true if the publish date is older than 10 years' do 28 | expect(@book.can_be_archived?).to eql(true) 29 | end 30 | 31 | it 'Should return true if cover state is bad' do 32 | expect(@book.can_be_archived?).to eql(true) 33 | end 34 | 35 | it 'Should return false if publish_date < 10 && cover_state != bad' do 36 | book = Book.new('Book', 'Publisher', 'good', '2019-10-10', true) 37 | expect(book.can_be_archived?).to eql(false) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/musicalbum_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/classes/musicalbum' 2 | require_relative '../lib/classes/genre' 3 | 4 | describe MusicAlbum do 5 | before(:each) do 6 | @music_album = MusicAlbum.new('01/01/2019', false, false) 7 | end 8 | 9 | describe '#new' do 10 | it 'Should return a new MusicAlbum object' do 11 | expect(@music_album).to be_an_instance_of(MusicAlbum) 12 | end 13 | 14 | it 'Should have a publish date' do 15 | expect(@music_album.publish_date).to eql('01/01/2019') 16 | end 17 | 18 | it 'Should have a boolean value for on_spotify' do 19 | expect(@music_album.on_spotify).to eql(false) 20 | end 21 | 22 | it 'Should not have a setter for id' do 23 | expect { @music_album.id = 1 }.to raise_error(NameError) 24 | end 25 | end 26 | 27 | describe '#add_genre' do 28 | it 'Should add a genre to the music album' do 29 | genre = Genre.new('Action') 30 | music_album = MusicAlbum.new('01/01/2019', false, false) 31 | music_album.add_genre(genre) 32 | expect(music_album.genre).to eql(genre) 33 | end 34 | end 35 | 36 | describe '#can_be_archived?' do 37 | it 'Should return true if the publish date is older than 10 years' do 38 | music_album = MusicAlbum.new('01/01/2009', true, true) 39 | expect(music_album.can_be_archived?).to eq(true) 40 | end 41 | 42 | it 'Should return true if on_spotify is ture' do 43 | music_album = MusicAlbum.new('01/01/2000', true, false) 44 | expect(music_album.can_be_archived?).to eq(true) 45 | end 46 | 47 | it 'Should return false if publish_date < 10 && on_spotify != false' do 48 | music_album = MusicAlbum.new('01/01/2019', true, false) 49 | expect(music_album.can_be_archived?).to eq(false) 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/helpers/data_manager.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | class DataManager 4 | def initialize(items) 5 | @items = items 6 | end 7 | 8 | def save_data(items) 9 | items_data = serialize_data(items) 10 | File.write('./lib/records/items.json', JSON.generate(items_data)) 11 | end 12 | 13 | def load_data 14 | items_data = File.exist?('./lib/records/items.json') ? JSON.parse(File.read('./lib/records/items.json')) : [] 15 | deserialize_data(items_data) 16 | end 17 | 18 | # rubocop:disable Metrics/MethodLength 19 | # rubocop:disable Metrics/BlockLength 20 | def serialize_data(items) 21 | items.map do |item| 22 | if item.instance_of?(MusicAlbum) 23 | { 24 | type: 'MusicAlbum', 25 | id: item.id, 26 | genre_name: item.genre.name, 27 | publish_date: item.publish_date, 28 | on_spotify: item.on_spotify 29 | } 30 | elsif item.instance_of?(Game) 31 | { 32 | type: 'Game', 33 | id: item.id, 34 | multiplayer: item.multiplayer, 35 | last_played_at: item.last_played_at, 36 | publish_date: item.publish_date, 37 | author: "#{item.author.first_name} #{item.author.last_name}" 38 | } 39 | elsif item.instance_of?(Book) 40 | { 41 | type: 'Book', 42 | id: item.id, 43 | title: item.title, 44 | publisher: item.publisher, 45 | cover_state: item.cover_state, 46 | publish_date: item.publish_date, 47 | label: { 48 | title: item.label.title, 49 | color: item.label.color 50 | } 51 | } 52 | else 53 | next 54 | end 55 | end 56 | end 57 | 58 | def deserialize_data(items_data) 59 | items = [] 60 | items_data.each do |item_data| 61 | if item_data['type'] == 'MusicAlbum' 62 | genre_name = item_data['genre_name'] 63 | publish_date = item_data['publish_date'].to_i 64 | on_spotify = item_data['on_spotify'] 65 | 66 | genre = Genre.new(genre_name) 67 | music_album = MusicAlbum.new(publish_date, on_spotify, false) 68 | music_album.add_genre(genre) 69 | items << music_album 70 | end 71 | 72 | if item_data['type'] == 'Game' 73 | multiplayer = item_data['multiplayer'] 74 | last_played_at = item_data['last_played_at'] 75 | publish_date = item_data['publish_date'] 76 | first_name = item_data['author'].split[0] 77 | last_name = item_data['author'].split[1] 78 | 79 | new_game = Game.new(multiplayer, last_played_at, publish_date) 80 | game_author = Author.new(first_name, last_name) 81 | 82 | new_game.add_author(game_author) 83 | items << new_game 84 | end 85 | 86 | next unless item_data['type'] == 'Book' 87 | 88 | title = item_data['title'] 89 | publisher = item_data['publisher'] 90 | cover_state = item_data['cover_state'] 91 | publish_date = item_data['publish_date'] 92 | archived = item_data['archived'] 93 | label_title = item_data['label']['title'] 94 | label_color = item_data['label']['color'] 95 | 96 | new_book = Book.new(title, publisher, cover_state, publish_date, archived) 97 | book_label = Label.new(label_title, label_color) 98 | 99 | new_book.add_label(book_label) 100 | items << new_book 101 | end 102 | items 103 | end 104 | 105 | # rubocop:enable Metrics/MethodLength 106 | # rubocop:enable Metrics/BlockLength 107 | end 108 | -------------------------------------------------------------------------------- /lib/controllers/menu_controller.rb: -------------------------------------------------------------------------------- 1 | class Menu 2 | MENU_OPTIONS = [ 3 | { number: 1, description: 'List all books', action: :list_all_books }, 4 | { number: 2, description: 'List all music albums', action: :list_all_albums }, 5 | { number: 3, description: 'List of games', action: :list_of_games }, 6 | { number: 4, description: 'List all genres', action: :list_all_genres }, 7 | { number: 5, description: 'List all labels', action: :list_all_labels }, 8 | { number: 6, description: 'List all authors', action: :list_all_authors }, 9 | { number: 7, description: 'Add a book', action: :add_book }, 10 | { number: 8, description: 'Add a music album', action: :add_album }, 11 | { number: 9, description: 'Add a game', action: :add_game }, 12 | { number: 10, description: 'Exit', action: :exit_app } 13 | ].freeze 14 | 15 | def initialize(app) 16 | @app = app 17 | end 18 | 19 | def display_menu 20 | loop do 21 | show_options 22 | 23 | option = gets.chomp.to_i 24 | 25 | menu_option = MENU_OPTIONS.find { |item| item[:number] == option } 26 | if menu_option 27 | send(menu_option[:action]) 28 | break if menu_option[:action] == :exit_app # Break out of the loop when "Exit" option is selected 29 | else 30 | puts 'Please select a valid option' 31 | end 32 | end 33 | end 34 | 35 | private 36 | 37 | def show_options 38 | puts "\nSelect an option by entering a number:" 39 | MENU_OPTIONS.each { |item| puts "#{item[:number]} - #{item[:description]}" } 40 | end 41 | 42 | def list_all_books 43 | @app.list_all_books 44 | end 45 | 46 | def list_all_albums 47 | @app.list_all_albums 48 | end 49 | 50 | def list_of_games 51 | @app.list_of_games 52 | end 53 | 54 | def list_all_genres 55 | @app.list_all_genres 56 | end 57 | 58 | def list_all_labels 59 | @app.list_all_labels 60 | end 61 | 62 | def list_all_authors 63 | @app.list_all_authors 64 | end 65 | 66 | def add_book 67 | print 'Title: ' 68 | title = gets.chomp 69 | print 'Publisher: ' 70 | publisher = gets.chomp 71 | print 'Cover state [Good/Bad]: ' 72 | cover_state = gets.chomp.downcase 73 | print 'Publish date [YYY-MM-DD]: ' 74 | publish_date = gets.chomp 75 | print 'Archived? [Y/N]: ' 76 | archived = gets.chomp.match?(/^[yY]$/) 77 | print 'Enter book label: ' 78 | label_title = gets.chomp 79 | print 'Enter label color: ' 80 | label_color = gets.chomp 81 | @app.add_a_book(title: title, publisher: publisher, 82 | cover_state: cover_state, publish_date: publish_date, archived: archived, 83 | label_title: label_title, label_color: label_color) 84 | end 85 | 86 | def add_album 87 | puts 'Enter the genre name: ' 88 | genre_name = gets.chomp 89 | 90 | puts 'Enter the publish data: ' 91 | publish_date = gets.chomp 92 | 93 | puts 'On Spotify? (true/false)' 94 | on_spotify = gets.chomp.downcase == 'true' 95 | 96 | @app.add_an_album(genre_name, publish_date, on_spotify) 97 | end 98 | 99 | def add_game 100 | puts 'Enter the author first name: ' 101 | author_first_name = gets.chomp 102 | puts 'Enter the author last name: ' 103 | author_last_name = gets.chomp 104 | 105 | print 'Is this game for multiple players? [Y/N]: ' 106 | multiplayer = gets.chomp.downcase 107 | multiplayer = multiplayer == 'y' 108 | 109 | print 'Please enter the date this game was last played in: ' 110 | last_played_at = gets.chomp 111 | 112 | print 'Please enter the date this game was published: ' 113 | publish_date = gets.chomp 114 | 115 | @app.add_a_game(author_first_name, author_last_name, multiplayer, last_played_at, publish_date) 116 | end 117 | 118 | def exit_app 119 | puts 'Exiting the app...' 120 | @app.exit 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /app.rb: -------------------------------------------------------------------------------- 1 | require './lib/classes/musicalbum' 2 | require './lib/classes/genre' 3 | require './lib/helpers/data_manager' 4 | require_relative 'lib/models/book' 5 | require_relative 'lib/classes/label' 6 | require_relative 'lib/classes/author' 7 | require_relative 'lib/classes/game' 8 | require 'set' 9 | 10 | class App 11 | def initialize 12 | @items = [] 13 | @genres = [] 14 | @authors = [] 15 | @labels = [] 16 | @data_manager = DataManager.new(@items) 17 | load_data 18 | end 19 | 20 | def save_data 21 | @data_manager.save_data(@items) 22 | end 23 | 24 | def load_data 25 | @items = @data_manager.load_data 26 | end 27 | 28 | def list_all_books 29 | puts 'List of books:' 30 | @items.each_with_index do |item, index| 31 | if item.instance_of?(Book) 32 | print "#{index + 1}) id: #{item.id} | title: #{item.title} | publisher: #{item.publisher} " 33 | puts "| cover state: #{item.cover_state} | publish date: #{item.publish_date}" 34 | end 35 | end 36 | end 37 | 38 | def list_all_labels 39 | puts 'List of labels:' 40 | @items.each_with_index do |item, index| 41 | puts "#{index + 1}) #{item.label.title}" if item.instance_of?(Book) 42 | end 43 | end 44 | 45 | def add_a_book(options) 46 | title = options[:title] 47 | publisher = options[:publisher] 48 | cover_state = options[:cover_state] 49 | publish_date = options[:publish_date] 50 | archived = options[:archived] 51 | label_title = options[:label_title] 52 | label_color = options[:label_color] 53 | 54 | new_book = Book.new(title, publisher, cover_state, publish_date, archived) 55 | book_label = Label.new(label_title, label_color) 56 | 57 | new_book.add_label(book_label) 58 | @labels << book_label 59 | 60 | @items << new_book 61 | puts 'Book created!' 62 | end 63 | 64 | def list_all_albums 65 | puts 'List of albums:' 66 | @items.each_with_index do |item, index| 67 | puts "#{index + 1}- #{item}" if item.instance_of?(MusicAlbum) 68 | end 69 | end 70 | 71 | def list_all_genres 72 | puts 'List of genres:' 73 | @items.each_with_index do |item, index| 74 | puts "#{index + 1}- #{item.genre.name}" if item.instance_of?(MusicAlbum) 75 | end 76 | end 77 | 78 | def add_an_album(genre_name, publish_date, on_spotify) 79 | genre = Genre.new(genre_name) 80 | music_album = MusicAlbum.new(publish_date, on_spotify, false) 81 | 82 | music_album.add_genre(genre) 83 | @items << music_album 84 | puts 'Album added!' 85 | end 86 | 87 | def add_a_game(author_first_name, author_last_name, multiplayer, last_played_at, publish_date) 88 | new_game = Game.new(multiplayer, last_played_at, publish_date) 89 | game_author = Author.new(author_first_name, author_last_name) 90 | 91 | new_game.add_author(game_author) 92 | @authors << game_author 93 | 94 | @items << new_game 95 | puts 'Game added!' 96 | end 97 | 98 | def list_of_games 99 | puts 'List of games:' 100 | @items.each_with_index do |item, index| 101 | next unless item.instance_of?(Game) 102 | 103 | print "#{index + 1}) id: #{item.id} - author: #{"#{item.author.first_name} #{item.author.first_name}"} " 104 | # rubocop:disable Layout/LineLength 105 | puts "- multiplayer: #{item.multiplayer} - last played at: #{item.last_played_at} - publish date: #{item.publish_date}" 106 | # rubocop:enable Layout/LineLength 107 | end 108 | end 109 | 110 | def list_all_authors 111 | puts 'List of authors:' 112 | authors = Set.new 113 | 114 | @items.each do |item| 115 | authors.add("#{item.author.first_name} #{item.author.last_name}") if item.instance_of?(Game) 116 | end 117 | 118 | authors.each_with_index do |author, index| 119 | puts "#{index + 1}) #{author}" 120 | end 121 | end 122 | 123 | def exit 124 | save_data 125 | puts 'Bye!' 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rspec --init` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 16 | RSpec.configure do |config| 17 | # rspec-expectations config goes here. You can use an alternate 18 | # assertion/expectation library such as wrong or the stdlib/minitest 19 | # assertions if you prefer. 20 | config.expect_with :rspec do |expectations| 21 | # This option will default to `true` in RSpec 4. It makes the `description` 22 | # and `failure_message` of custom matchers include text for helper methods 23 | # defined using `chain`, e.g.: 24 | # be_bigger_than(2).and_smaller_than(4).description 25 | # # => "be bigger than 2 and smaller than 4" 26 | # ...rather than: 27 | # # => "be bigger than 2" 28 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 29 | end 30 | 31 | # rspec-mocks config goes here. You can use an alternate test double 32 | # library (such as bogus or mocha) by changing the `mock_with` option here. 33 | config.mock_with :rspec do |mocks| 34 | # Prevents you from mocking or stubbing a method that does not exist on 35 | # a real object. This is generally recommended, and will default to 36 | # `true` in RSpec 4. 37 | mocks.verify_partial_doubles = true 38 | end 39 | 40 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 41 | # have no way to turn it off -- the option exists only for backwards 42 | # compatibility in RSpec 3). It causes shared context metadata to be 43 | # inherited by the metadata hash of host groups and examples, rather than 44 | # triggering implicit auto-inclusion in groups with matching metadata. 45 | config.shared_context_metadata_behavior = :apply_to_host_groups 46 | 47 | # The settings below are suggested to provide a good initial experience 48 | # with RSpec, but feel free to customize to your heart's content. 49 | # # This allows you to limit a spec run to individual examples or groups 50 | # # you care about by tagging them with `:focus` metadata. When nothing 51 | # # is tagged with `:focus`, all examples get run. RSpec also provides 52 | # # aliases for `it`, `describe`, and `context` that include `:focus` 53 | # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 54 | # config.filter_run_when_matching :focus 55 | # 56 | # # Allows RSpec to persist some state between runs in order to support 57 | # # the `--only-failures` and `--next-failure` CLI options. We recommend 58 | # # you configure your source control system to ignore this file. 59 | # config.example_status_persistence_file_path = "spec/examples.txt" 60 | # 61 | # # Limits the available syntax to the non-monkey patched syntax that is 62 | # # recommended. For more details, see: 63 | # # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ 64 | # config.disable_monkey_patching! 65 | # 66 | # # This setting enables warnings. It's recommended, but in some cases may 67 | # # be too noisy due to issues in dependencies. 68 | # config.warnings = true 69 | # 70 | # # Many RSpec users commonly either run the entire suite or an individual 71 | # # file, and it's useful to allow more verbose output when running an 72 | # # individual spec file. 73 | # if config.files_to_run.one? 74 | # # Use the documentation formatter for detailed output, 75 | # # unless a formatter has already been configured 76 | # # (e.g. via a command-line flag). 77 | # config.default_formatter = "doc" 78 | # end 79 | # 80 | # # Print the 10 slowest examples and example groups at the 81 | # # end of the spec run, to help surface which specs are running 82 | # # particularly slow. 83 | # config.profile_examples = 10 84 | # 85 | # # Run specs in random order to surface order dependencies. If you find an 86 | # # order dependency and want to debug it, you can fix the order by providing 87 | # # the seed, which is printed after each run. 88 | # # --seed 1234 89 | # config.order = :random 90 | # 91 | # # Seed global randomization in this process using the `--seed` CLI option. 92 | # # Setting this allows you to use `--seed` to deterministically reproduce 93 | # # test failures related to randomization by passing the same `--seed` value 94 | # # as the one that triggered the failure. 95 | # Kernel.srand config.seed 96 | end 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |