diff --git a/Gemfile b/Gemfile index 244c3741..692b9eb7 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,5 @@ source 'https://rubygems.org' -ruby '3.0.2' - group :test do gem 'rspec' gem 'simplecov', require: false @@ -9,7 +7,8 @@ group :test do end group :development, :test do - gem 'rubocop', '1.20' + gem 'rubocop', '1.51' end -gem "pg", "~> 1.3" +gem "pg", "~> 1.5.3" +gem 'colorize' diff --git a/Gemfile.lock b/Gemfile.lock index 76d220da..61c98a27 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,41 +3,44 @@ GEM specs: ansi (1.5.0) ast (2.4.2) - diff-lcs (1.4.4) + colorize (0.8.1) + diff-lcs (1.5.0) docile (1.4.0) - parallel (1.20.1) - parser (3.0.2.0) + json (2.6.3) + parallel (1.23.0) + parser (3.2.2.1) ast (~> 2.4.1) - pg (1.3.5) - rainbow (3.0.0) - regexp_parser (2.1.1) + pg (1.5.3) + rainbow (3.1.1) + regexp_parser (2.8.0) rexml (3.2.5) - rspec (3.10.0) - rspec-core (~> 3.10.0) - rspec-expectations (~> 3.10.0) - rspec-mocks (~> 3.10.0) - rspec-core (3.10.1) - rspec-support (~> 3.10.0) - rspec-expectations (3.10.1) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-mocks (3.10.2) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-support (3.10.2) - rubocop (1.20.0) + rspec-support (~> 3.12.0) + rspec-support (3.12.0) + rubocop (1.51.0) + json (~> 2.3) parallel (~> 1.10) - parser (>= 3.0.0.0) + parser (>= 3.2.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) - rexml - rubocop-ast (>= 1.9.1, < 2.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.28.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.11.0) - parser (>= 3.0.1.1) - ruby-progressbar (1.11.0) - simplecov (0.21.2) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.28.1) + parser (>= 3.2.1.0) + ruby-progressbar (1.13.0) + simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) @@ -46,23 +49,21 @@ GEM simplecov terminal-table simplecov-html (0.12.3) - simplecov_json_formatter (0.1.3) - terminal-table (3.0.1) + simplecov_json_formatter (0.1.4) + terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - unicode-display_width (2.0.0) + unicode-display_width (2.4.2) PLATFORMS - ruby + arm64-darwin-22 DEPENDENCIES - pg (~> 1.3) + colorize + pg (~> 1.5.3) rspec - rubocop (= 1.20) + rubocop (= 1.51) simplecov simplecov-console -RUBY VERSION - ruby 3.0.2p107 - BUNDLED WITH - 2.2.26 + 2.4.13 diff --git a/items_orders.sql b/items_orders.sql new file mode 100644 index 00000000..e4f1f992 --- /dev/null +++ b/items_orders.sql @@ -0,0 +1,23 @@ +-- Create the first table. +CREATE TABLE items ( + id SERIAL PRIMARY KEY, + name text, + price numeric, + quantity int +); + +-- Create the second table. +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + customer_name text, + date date +); + +-- Create the join table. +CREATE TABLE items_orders ( + item_id int, + order_id int, + constraint fk_item foreign key(item_id) references items(id) on delete cascade, + constraint fk_order foreign key(order_id) references orders(id) on delete cascade, + PRIMARY KEY (item_id, order_id) +); diff --git a/lib/app.rb b/lib/app.rb new file mode 100644 index 00000000..a322755f --- /dev/null +++ b/lib/app.rb @@ -0,0 +1,138 @@ +require_relative 'database_connection' +require_relative 'item_repository' +require_relative 'item' +require_relative 'order' +require_relative 'order_repository' +require_relative 'format' + +class Application + def initialize(database_name, io, order_repository, item_repository) + DatabaseConnection.connect(database_name) + @io = io + @order_repository = order_repository + @item_repository = item_repository + @format = Format.new + end + + def show_menu(forever = true) + running = true + + @io.puts @format.header("Welcome to the shop management program!") + while running do + @io.puts @format.string("Choose from one of these options:", :cyan, :before) + @io.puts " 1. List all items" + @io.puts " 2. Create new item" + @io.puts " 3. List all orders" + @io.puts " 4. Create new order" + @io.puts " 5. View all items in an order" + @io.puts " 6. Exit" + input = @io.gets.chomp + process_selection(input) + running = forever + end + end + + def process_selection(input) + case input + when "1" + show_all_items + when "2" + create_new_item + when "3" + show_all_orders + when "4" + create_new_order + when "5" + view_order_items + when "6" + exit # TODO: need to test for exiting + end + end + + def show_all_items(header = true) + @io.puts @format.header("All items") if header == true + items = @item_repository.all + items.each do |item| + @io.puts " #{item.id}: #{item.name} – #{@format.currency(item.price)} (#{item.quantity} in stock)" + end + end + + def create_new_item + @io.puts @format.header("Add a new item") + @io.puts @format.string("Enter the item's details", :cyan) + @io.puts "Item name:" + name = @io.gets.chomp + @io.puts "Item price (e.g. 3.25):" + price = @io.gets.chomp + @io.puts "Quantity in stock:" + quantity = @io.gets.chomp + + item = Item.new + item.name = name + item.price = price + item.quantity = quantity + @item_repository.create(item) + + @io.puts @format.string("#{name} added to the databse", :green, :pad) + end + + def show_all_orders + @io.puts @format.header("All orders") + orders = @order_repository.all + orders.each do |order| + @io.puts " Order ##{order.id}: #{order.date} – #{order.customer_name}" + end + end + + def create_new_order + @io.puts @format.header("New order") + @io.puts @format.string("Enter the order details", :cyan) + @io.puts "Your name:" + name = @io.gets.chomp + @io.puts "Select order items (press enter after each selection, and enter on its own to end):" + order_items = choose_order_items + order = Order.new + order.customer_name = name + order.date = Time.now + order.items = order_items + @order_repository.create(order) + @io.puts @format.string("Order placed", :green, :pad) + end + + def choose_order_items + items = [] + loop do + show_all_items(false) + input = @io.gets.chomp + return items if input == "" + item = @item_repository.find(input.to_i) + if item.quantity.to_i.zero? + @io.puts @format.string("Sorry, #{item.name} is out of stock. Choose something different.", :red, :pad) + next + end + @item_repository.update_quantity(input.to_i) + items << input.to_i + end + end + + def view_order_items + @io.puts @format.string("Which order ID do you want to view items for?", :cyan, :pad) + input = @io.gets.chomp.to_i + items = @order_repository.find_items_by_order(input) + @io.puts @format.header("Items in order ##{input}") + items.each do |item| + @io.puts "#{item.id}: #{item.name} – #{@format.currency(item.price)}" + end + end + +end + +if __FILE__ == $0 + app = Application.new( + 'shop_manager', + Kernel, + OrderRepository.new, + ItemRepository.new + ) + app.show_menu +end diff --git a/lib/database_connection.rb b/lib/database_connection.rb new file mode 100644 index 00000000..470de979 --- /dev/null +++ b/lib/database_connection.rb @@ -0,0 +1,26 @@ +require 'pg' + +# This class is a thin "wrapper" around the +# PG library. We'll use it in our project to interact +# with the database using SQL. + +class DatabaseConnection + # This method connects to PostgreSQL using the + # PG gem. We connect to 127.0.0.1, and select + # the database name given in argument. + def self.connect(database_name) + @connection = PG.connect({ host: '127.0.0.1', dbname: database_name }) + end + + # This method executes an SQL query + # on the database, providing some optional parameters + # (you will learn a bit later about when to provide these parameters). + def self.exec_params(query, params) + if @connection.nil? + raise 'DatabaseConnection.exec_params: Cannot run a SQL query as the connection to'\ + 'the database was never opened. Did you make sure to call first the method '\ + '`DatabaseConnection.connect` in your app.rb file (or in your tests spec_helper.rb)?' + end + @connection.exec_params(query, params) + end +end diff --git a/lib/format.rb b/lib/format.rb new file mode 100644 index 00000000..98863ea5 --- /dev/null +++ b/lib/format.rb @@ -0,0 +1,19 @@ +require 'colorize' + +class Format + def string(text = "", color = :default, spacing = "") + return if text == "" + text = text.colorize(color) + text = "\n#{text}" if spacing == :pad || spacing == :before + text = "#{text}\n" if spacing == :pad || spacing == :after + return text + end + + def currency(price) + return sprintf("£%.2f", price) + end + + def header(text) + return string("====== #{text} ======", :red, :before) + end +end diff --git a/lib/item.rb b/lib/item.rb new file mode 100644 index 00000000..572b9c19 --- /dev/null +++ b/lib/item.rb @@ -0,0 +1,3 @@ +class Item + attr_accessor :id, :name, :price, :quantity +end diff --git a/lib/item_repository.rb b/lib/item_repository.rb new file mode 100644 index 00000000..037474df --- /dev/null +++ b/lib/item_repository.rb @@ -0,0 +1,44 @@ +require_relative 'item' + +class ItemRepository + def all + sql = 'SELECT id, name, price, quantity FROM items ORDER BY id;' + results = DatabaseConnection.exec_params(sql, []) + + items = [] + results.each do |result| + item = Item.new + item.id = result['id'] + item.name = result['name'] + item.price = result['price'] + item.quantity = result['quantity'] + + items << item + end + + return items + end + + def create(item) + sql = 'INSERT INTO items (name, price, quantity) VALUES($1, $2, $3);' + DatabaseConnection.exec_params(sql, [item.name, item.price, item.quantity]) + end + + def find(id) + sql = 'SELECT id, name, price, quantity FROM items WHERE id = $1;' + result = DatabaseConnection.exec_params(sql, [id]) + + item = Item.new + item.id = result.first['id'] + item.name = result.first['name'] + item.price = result.first['price'] + item.quantity = result.first['quantity'] + + return item + end + + def update_quantity(id) + sql = 'UPDATE items SET quantity = quantity - 1 WHERE id = $1;' + result = DatabaseConnection.exec_params(sql, [id]) + end +end diff --git a/lib/order.rb b/lib/order.rb new file mode 100644 index 00000000..295a9e71 --- /dev/null +++ b/lib/order.rb @@ -0,0 +1,3 @@ +class Order + attr_accessor :id, :customer_name, :date, :items +end diff --git a/lib/order_repository.rb b/lib/order_repository.rb new file mode 100644 index 00000000..6089e461 --- /dev/null +++ b/lib/order_repository.rb @@ -0,0 +1,57 @@ +require_relative 'order' +require_relative 'item' + +class OrderRepository + def all + sql = 'SELECT id, customer_name, date FROM orders ORDER BY id;' + results = DatabaseConnection.exec_params(sql, []) + + orders = [] + results.each do |result| + order = Order.new + order.id = result['id'] + order.customer_name = result['customer_name'] + order.date = result['date'] + + orders << order + end + + return orders + end + + def create(order) + sql = 'INSERT INTO orders (customer_name, date) VALUES($1, $2) RETURNING id;' + result = DatabaseConnection.exec_params(sql, [order.customer_name, order.date]) + order_id = result.first['id'] + + order.items.each do |item| + sql = 'INSERT INTO items_orders (item_id, order_id) VALUES($1, $2);' + result = DatabaseConnection.exec_params(sql, [item, order_id]) + end + end + + def find_items_by_order(order_id) + sql = 'SELECT items.id, items.name, items.price, items.quantity + FROM items + JOIN items_orders ON items_orders.item_id = items.id + JOIN orders ON items_orders.order_id = orders.id + WHERE orders.id = $1;' + + params = [order_id] + results = DatabaseConnection.exec_params(sql, params) + items = [] + + results.each do |result| + item = Item.new + + item.id = result['id'] + item.name = result['name'] + item.price = result['price'] + item.quantity = result['quantity'] + + items << item + end + + return items + end +end diff --git a/shop_manager_design.md b/shop_manager_design.md new file mode 100644 index 00000000..a439f411 --- /dev/null +++ b/shop_manager_design.md @@ -0,0 +1,316 @@ +# Shop Manager design recipe + +## 1. Extract nouns from the user stories or specification + +``` +# USER STORIES + +As a shop manager +So I can know which items I have in stock +I want to keep a list of my shop items with their name and unit price. + +As a shop manager +So I can know which items I have in stock +I want to know which quantity (a number) I have for each item. + +As a shop manager +So I can manage items +I want to be able to create a new item. + +As a shop manager +So I can know which orders were made +I want to keep a list of orders with their customer name. + +As a shop manager +So I can know which orders were made +I want to assign each order to their corresponding item. + +As a shop manager +So I can know which orders were made +I want to know on which date an order was placed. + +As a shop manager +So I can manage orders +I want to be able to create a new order. +``` + +``` +Nouns: + +items, name, unit price, stock level, orders, customer name, order items, order date +``` + +## 2. Infer the Table Name and Columns + +Put the different nouns in this table. Replace the example with your own nouns. + +| Record | Properties | +| --------------------- | ------------------ | +| items | name, price, quantity +| orders | customer_name, date + +1. Name of the first table (always plural): `items` + + Column names: `name`, `price`, `quantity` + +2. Name of the second table (always plural): `orders` + + Column names: `customer_name`, `date` + +## 3. Decide the column types. + +[Here's a full documentation of PostgreSQL data types](https://www.postgresql.org/docs/current/datatype.html). + +Most of the time, you'll need either `text`, `int`, `bigint`, `numeric`, or `boolean`. If you're in doubt, do some research or ask your peers. + +``` +Table: items +id: SERIAL +name: text +price: numeric +quantity: int + +Table: orders +id: SERIAL +customer_name: text +date: date +``` + +## 4. Design the Join Table + +``` +Join table for tables: items and orders +Join table name: items_orders +Columns: item_id, order_id +``` + +## 6. Write the SQL. + +```sql +-- file: items_orders.sql + +-- Replace the table name, columm names and types. + +-- Create the first table. +CREATE TABLE items ( + id SERIAL PRIMARY KEY, + name text, + price numeric, + quantity int +); + +-- Create the second table. +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + customer_name text, + date date +); + +-- Create the join table. +CREATE TABLE items_orders ( + item_id int, + order_id int, + constraint fk_item foreign key(item_id) references items(id) on delete cascade, + constraint fk_order foreign key(order_id) references orders(id) on delete cascade, + PRIMARY KEY (item_id, order_id) +); + +``` + +## 7. Create the tables. + +```bash +psql -h 127.0.0.1 shop_manager < items_orders.sql +``` + +---- + +# Shop Manager Model and Repository Classes Design + +## 1. Create Test SQL seeds + +Your tests will depend on data stored in PostgreSQL to run. + +If seed data is provided (or you already created it), you can skip this step. + +```sql +-- (file: spec/seeds.sql) + +-- Write your SQL seed here. + +-- First, you'd need to truncate the table - this is so our table is emptied between each test run, +-- so we can start with a fresh state. +-- (RESTART IDENTITY resets the primary key) + +TRUNCATE TABLE items, orders, items_orders RESTART IDENTITY; -- replace with your own table name. + +-- Below this line there should only be `INSERT` statements. +-- Replace these statements with your own seed data. + +INSERT INTO items (id, name, price, quantity) VALUES (1, 'Bread', 2.50, 10); +INSERT INTO items (id, name, price, quantity) VALUES (2, 'Towels', 9, 5); +INSERT INTO items (id, name, price, quantity) VALUES (3, 'Soap', 3.25, 15); +INSERT INTO items (id, name, price, quantity) VALUES (4, 'Salad', 4.75, 10); +INSERT INTO items (id, name, price, quantity) VALUES (5, 'Pizza', 7.25, 7); +INSERT INTO items (id, name, price, quantity) VALUES (6, 'Gloves', 19.99, 3); +INSERT INTO items (id, name, price, quantity) VALUES (7, 'Sausages', 3.75, 11); +INSERT INTO items (id, name, price, quantity) VALUES (8, 'Cheese', 3.50, 25); +INSERT INTO items (id, name, price, quantity) VALUES (9, 'Chair', 55, 1); +INSERT INTO items (id, name, price, quantity) VALUES (10, 'Orange juice', 1.75, 2); + +INSERT INTO orders (id, customer_name, date) VALUES (1, 'Rodney Howell', '3 May 2023'); +INSERT INTO orders (id, customer_name, date) VALUES (2, 'Lynn Stiedemann', '10 May 2023'); + +INSERT INTO items_orders (item_id, order_id) VALUES (1, 1); +INSERT INTO items_orders (item_id, order_id) VALUES (7, 1); +INSERT INTO items_orders (item_id, order_id) VALUES (10, 1); +INSERT INTO items_orders (item_id, order_id) VALUES (9, 2); +INSERT INTO items_orders (item_id, order_id) VALUES (2, 2); + +``` + +Run this SQL file on the database to truncate (empty) the table, and insert the seed data. Be mindful of the fact any existing records in the table will be deleted. + +```bash +psql -h 127.0.0.1 shop_manager < spec/seeds.sql +``` + +## 3. Define the class names + +Usually, the Model class name will be the capitalised table name (single instead of plural). The same name is then suffixed by `Repository` for the Repository class name. + +```ruby +# Table name: items + +# Model class +# (in lib/item.rb) +class Item +end + +# Repository class +# (in lib/item_repository.rb) +class ItemRepository +end + +# Table name: orders + +# Model class +# (in lib/order.rb) +class Order +end + +# Repository class +# (in lib/order_repository.rb) +class OrderRepository +end + + + +``` + +## 4. Implement the Model class + +Define the attributes of your Model class. You can usually map the table columns to the attributes of the class, including primary and foreign keys. + +```ruby +# Table name: items + +# Model class +# (in lib/item.rb) +class Item + # Replace the attributes by your own columns. + attr_accessor :id, :name, :price, :quantity +end + +# Table name: orders + +# Model class +# (in lib/order.rb) +class Order + # Replace the attributes by your own columns. + attr_accessor :id, :customer_name, :date +end +``` + + +## 5. Define the Repository Class interface + +```ruby +# Table name: items + +# Repository class +# (in lib/item_repository.rb) +class ItemRepository + # Selecting all records + # No arguments + def all + # Executes the SQL query: + # SELECT id, name, price, quantity FROM items; + + # Returns an array of Item objects. + end + # Creates a new record for the Item object passed to it + def create(item) + # Executes the SQL query: + # INSERT INTO items (name, price, quantity) VALUES ($1, $2, $3); + # DatabaseConnection.exec_params(sql, [item.name, item.price, item.quantity]) + + # Returns nothing + end +end + +--- + +# Table name: orders + +# Repository class +# (in lib/order_repository.rb) +class OrderRepository + # Selecting all records + # No arguments + def all + # Executes the SQL query: + # SELECT id, customer_name, date FROM orders; + + # Returns an array of Order objects. + end + # Creates a new record for the Order object passed to it + def create(order) + # Executes the SQL query: + # INSERT INTO orders (customer_name, date) VALUES ($1, $2); + # DatabaseConnection.exec_params(sql, [order.customer_name, order.date]) + # INSERT INTO items_orders (item_id, order_id) VALUES ($1, $2); + # DatabaseConnection.exec_params(sql, [order.customer_name, order.date]) + + # Returns nothing + end +end + +``` + +## 6. Reload the SQL seeds before each test run + +Running the SQL code present in the seed file will empty the table and re-insert the seed data. + +This is so you get a fresh table contents every time you run the test suite. + +```ruby +# EXAMPLE + +# file: spec/item_repository_spec.rb + +def reset_database + seed_sql = File.read('spec/seeds.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'shop_manager' }) + connection.exec(seed_sql) +end + +describe ItemRepository do + before(:each) do + reset_database + end + + # (your tests will go here). +end + +``` diff --git a/spec/app_spec.rb b/spec/app_spec.rb new file mode 100644 index 00000000..c1331282 --- /dev/null +++ b/spec/app_spec.rb @@ -0,0 +1,89 @@ +require 'item_repository' +require 'item' +require 'app' + +def reset_database + seed_sql = File.read('spec/seeds.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'shop_manager' }) + connection.exec(seed_sql) +end + +RSpec.describe Application do + before(:each) do + reset_database + end + + let(:io) { double(:io) } + let(:app) { Application.new( + 'shop_manager', + io, + OrderRepository.new, + ItemRepository.new + ) + } + + describe "#show_menu" do + it "show the user a menu of options" do + expect(io).to receive(:puts).with("\n\e[0;31;49m====== Welcome to the shop management program! ======\e[0m").ordered + expect(io).to receive(:puts).at_least(6).times.ordered + expect(io).to receive(:gets).and_return("1").ordered + app.show_menu(false) + end + end + + describe "#show_all_items" do + it "prints a list of items" do + expect(io).to receive(:puts).with("\n\e[0;31;49m====== All items ======\e[0m").ordered + expect(io).to receive(:puts).at_least(10).times.ordered + app.show_all_items + end + end + + describe "#create_new_item" do + it "gets the item details and passes them to ItemRepository#create" do + expect(io).to receive(:puts).exactly(6).times.ordered + expect(io).to receive(:gets).exactly(3).times.and_return("New test item", "4.50", "3") + app.create_new_item + repo = ItemRepository.new + expect(repo.all.length).to eq 11 + expect(repo.all.last.name).to eq "New test item" + end + end + + describe "#show_all_orders" do + it "lists all the orders" do + expect(io).to receive(:puts).with("\n\e[0;31;49m====== All orders ======\e[0m").ordered + expect(io).to receive(:puts).with(" Order #1: 2023-05-03 – Rodney Howell").ordered + expect(io).to receive(:puts).ordered + app.show_all_orders + end + end + + describe "#create_new_order" do + it "adds a new order to the database" do + expect(io).to receive(:puts).at_least(6).times.ordered + expect(io).to receive(:gets).exactly(5).times.and_return("Sadie Quitzon", "1", "3", "7", "") + app.create_new_order + repo = OrderRepository.new + expect(repo.all.length).to eq 3 + expect(repo.all.last.customer_name).to eq "Sadie Quitzon" + end + end + + describe "#choose_order_items" do + it "shows a menu of items to add to the order" do + allow(io).to receive(:puts) + expect(io).to receive(:gets).exactly(4).times.and_return("1", "3", "7", "") + items = app.choose_order_items + expect(items).to eq([1, 3, 7]) + end + end + + describe "#view_order_items" do + it "shows the items in an order" do + allow(io).to receive(:puts).ordered + allow(io).to receive(:gets).and_return("1").ordered + allow(io).to receive(:puts).at_least(1).time.ordered + end + end +end diff --git a/spec/format_spec.rb b/spec/format_spec.rb new file mode 100644 index 00000000..eb487aae --- /dev/null +++ b/spec/format_spec.rb @@ -0,0 +1,29 @@ +require 'format' + +RSpec.describe Format do + + let(:formatter) { Format.new } + + describe "#currency" do + it "returns a formatted currency string" do + # formatter = Format.new + expect(formatter.currency(4.75)).to eq "£4.75" + expect(formatter.currency(3)).to eq "£3.00" + expect(formatter.currency(25.1)).to eq "£25.10" + end + end + + describe "#header" do + it "returns a header string" do + expect(formatter.header("TEST HEADER")).to eq "\n\e[0;31;49m====== TEST HEADER ======\e[0m" + end + end + + describe "#string" do + it "returns a formatted string" do + expect(formatter.string("Test")).to eq "\e[0;39;49mTest\e[0m" + end + end + + +end diff --git a/spec/item_repository_spec.rb b/spec/item_repository_spec.rb new file mode 100644 index 00000000..5d701762 --- /dev/null +++ b/spec/item_repository_spec.rb @@ -0,0 +1,52 @@ +require 'item_repository' +require 'item' + +def reset_database + seed_sql = File.read('spec/seeds.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'shop_manager' }) + connection.exec(seed_sql) +end + +RSpec.describe ItemRepository do + before(:each) do + reset_database + end + + describe "#all" do + it "returns all items" do + repo = ItemRepository.new + items = repo.all + expect(items.length).to eq 10 + end + end + + describe "#create" do + it "adds a new item to the database" do + repo = ItemRepository.new + item = Item.new + item.name = "Bike" + item.price = 475 + item.quantity = 3 + repo.create(item) + expect(repo.all.length).to eq 11 + expect(repo.all.last.name).to eq "Bike" + end + end + + describe "#find" do + it "returns a single item" do + repo = ItemRepository.new + item = repo.find(1) + expect(item.name).to eq "Bread" + end + end + + describe "#update_quantity" do + it "reduces the stock level by 1 unit" do + repo = ItemRepository.new + repo.update_quantity(1) + item = repo.find(1) + expect(item.quantity).to eq "9" + end + end +end diff --git a/spec/order_repository_spec.rb b/spec/order_repository_spec.rb new file mode 100644 index 00000000..98d470ff --- /dev/null +++ b/spec/order_repository_spec.rb @@ -0,0 +1,56 @@ +require 'order_repository' +require 'order' + +def reset_database + seed_sql = File.read('spec/seeds.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'shop_manager' }) + connection.exec(seed_sql) +end + +RSpec.describe OrderRepository do + before(:each) do + reset_database + end + + describe "#all" do + it "returns all orders" do + repo = OrderRepository.new + orders = repo.all + expect(orders.length).to eq 2 + expect(orders.first.customer_name).to eq 'Rodney Howell' + end + end + + describe "#create" do + it "adds a new order to the `orders` table" do + repo = OrderRepository.new + order = double(Order, customer_name: 'Sheldon Emmerich', date: '27 April 2023', items: [1, 2, 3]) + repo.create(order) + orders = repo.all + expect(orders.length).to eq 3 + expect(orders.last.customer_name).to eq order.customer_name + end + + it "adds a new order to the `orders` table, and updates `items_orders`" do + repo = OrderRepository.new + order = Order.new + + order.customer_name = "Sheldon Emmerich" + order.date = "27 April 2023" + order.items = [1, 2, 3] + repo.create(order) + orders = repo.all + expect(orders.length).to eq 3 + expect(orders.last.customer_name).to eq order.customer_name + end + end + + describe "#find_items_by_order" do + it "returns the order's items" do + repo = OrderRepository.new + items = repo.find_items_by_order(1) + expect(items.length).to eq 3 + expect(items.last.id).to eq '10' + end + end +end diff --git a/spec/seeds.sql b/spec/seeds.sql new file mode 100644 index 00000000..d30d0cf9 --- /dev/null +++ b/spec/seeds.sql @@ -0,0 +1,24 @@ +TRUNCATE TABLE items, orders, items_orders RESTART IDENTITY; -- replace with your own table name. + +-- Below this line there should only be `INSERT` statements. +-- Replace these statements with your own seed data. + +INSERT INTO items (name, price, quantity) VALUES ('Bread', 2.50, 10); +INSERT INTO items (name, price, quantity) VALUES ('Towels', 9, 5); +INSERT INTO items (name, price, quantity) VALUES ('Soap', 3.25, 15); +INSERT INTO items (name, price, quantity) VALUES ('Salad', 4.75, 10); +INSERT INTO items (name, price, quantity) VALUES ('Pizza', 7.25, 7); +INSERT INTO items (name, price, quantity) VALUES ('Gloves', 19.99, 3); +INSERT INTO items (name, price, quantity) VALUES ('Sausages', 3.75, 11); +INSERT INTO items (name, price, quantity) VALUES ('Cheese', 3.50, 25); +INSERT INTO items (name, price, quantity) VALUES ('Chair', 55, 1); +INSERT INTO items (name, price, quantity) VALUES ('Orange juice', 1.75, 2); + +INSERT INTO orders (customer_name, date) VALUES ('Rodney Howell', '3 May 2023'); +INSERT INTO orders (customer_name, date) VALUES ('Lynn Stiedemann', '10 May 2023'); + +INSERT INTO items_orders (item_id, order_id) VALUES (1, 1); +INSERT INTO items_orders (item_id, order_id) VALUES (7, 1); +INSERT INTO items_orders (item_id, order_id) VALUES (10, 1); +INSERT INTO items_orders (item_id, order_id) VALUES (9, 2); +INSERT INTO items_orders (item_id, order_id) VALUES (2, 2); diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 252747d8..cfea319d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,10 +8,13 @@ ]) SimpleCov.start -RSpec.configure do |config| - config.after(:suite) do - puts - puts "\e[33mHave you considered running rubocop? It will help you improve your code!\e[0m" - puts "\e[33mTry it now! Just run: rubocop\e[0m" - end -end +require 'database_connection' +DatabaseConnection.connect('shop_manager') + +# RSpec.configure do |config| +# config.after(:suite) do +# puts +# puts "\e[33mHave you considered running rubocop? It will help you improve your code!\e[0m" +# puts "\e[33mTry it now! Just run: rubocop\e[0m" +# end +# end