Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

database solo challenge - week 3 #147

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
153 changes: 153 additions & 0 deletions app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
require_relative 'lib/database_connection'
require_relative 'lib/item_repository.rb'
require_relative 'lib/customer_repository.rb'
require_relative 'lib/order_repository.rb'

class Application

def initialize(database_name, io, customer_repository, item_repository, order_repository)
DatabaseConnection.connect(database_name)
@io = io
@customer_repository = customer_repository
@item_repository = item_repository
@order_repository = order_repository
end

def run
show "Welcome to shop manager"
## Run method is kept as light as possible to be able to test individual
## methods without having to go through terminal to test them
app_running = true
while app_running
user_choice = ask_for_choice
loop do
break if user_input_validated(user_choice)
user_choice = prompt "Invalid input, please try again."
end
app_running = false if apply_selection(user_input_validated(user_choice)) == 'exit'
end
end

def apply_selection(choice)
case choice
when 1
print_neat(@item_repository.price_list)
when 2
print_neat(@item_repository.inventory_stock_list)
when 3
print_neat(@order_repository.order_list)
when 4
customer = verify_customer_exists
customer[:exists] ? print_neat(@customer_repository.list_of_items_ordered_by_customer(customer[:name])) : show("Sorry, no such customer exists.")
when 5
@item_repository.add_item(ask_for_item_parameters_to_insert)
show "Item added successfully, returning to main menu."
when 6
customer = verify_customer_exists
unless customer[:exists]
show "Looks like this customer has not shopped here before. Creating a new customer with the name."
@customer_repository.add_customer(customer[:name])
customer = {id: @customer_repository.retrieve_customer_by_name(customer[:name])}
end
parameters = {item_id: ask_for_item_information, customer_id: customer[:id]}
@order_repository.add_order(parameters)
show "Order added successfully, returning to main menu."
when 9
return 'exit'
end
end

### <--- UI METHODS --- > ###
## These methods handle the terminal UI interface
## They print out messages / handle user inputs
## They are tested separate to the main run method to prevent
## integration tests getting too repetitive with terminal output expectations

def ask_for_choice
show ""
show "What would you like to do? Choose one from below"
show "1 to list all items with their prices."
show "2 to list all items with their stock quantities"
show "3 to list all orders made to this day"
show "4 to list all orders made by a specific customer"
show "5 to create a new item"
show "6 to create a new order"
show "9 to exit program"
return @io.gets.chomp
end

def print_neat(input)
input.each {|row| show(row)}
end

### <--- VALIDATOR METHODS --- > ###
## These methods receive input from users and make sure they are valid to prevent errors when program runs

def verify_customer_exists
## This method asks the database if a certain customer exists.
## It returns their name and ID and the result of the query in a hash
customer_name = prompt "What is the customer name?"
customer_id = @customer_repository.retrieve_customer_by_name(customer_name)
customer_id ? exists = true : exists = false
return {name: customer_name, id: customer_id, exists: exists}
end

def ask_for_item_information
item_name = prompt "What would you like to order?"
item_id = @item_repository.retrieve_item_id_by_name(item_name)
loop do
break if item_id
item_name = prompt "That item has either run out or does not exist. Please try again."
item_id = @item_repository.retrieve_item_id_by_name(item_name)
end
return item_id
end

def ask_for_item_parameters_to_insert
item_name = ""
loop do
item_name = prompt "What is the item name?"
break unless @item_repository.retrieve_item_id_by_name(item_name)
show "Item with the same name already exists!"
end
item_price = integer_validator(prompt("What is the item price?"))
item_quantity = integer_validator(prompt("How many items do you have in stock?"))
return {name: item_name, unit_price: item_price, quantity: item_quantity}
end

def user_input_validated(input)
[1,2,3,4,5,6,9].include?(input.to_i) ? input.to_i : false
end

def integer_validator(input)
loop do
return input.to_i if /^\d+$/.match?(input)
input = prompt("Please enter an integer number:")
end
end

### <--- HELPER METHODS --- > ###
## These methods help prevent test errors that might arise from forgetting the @io before puts in the code
## Lets face it, we've all been there :)

def show(message)
@io.puts(message)
end

def prompt(message)
@io.puts(message)
return @io.gets.chomp
end
end


if __FILE__ == $0
app = Application.new(
'shop_manager',
Kernel,
CustomerRepository.new,
ItemRepository.new,
OrderRepository.new
)
app.run
end
3 changes: 3 additions & 0 deletions lib/customer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Customer
attr_accessor :id, :name
end
44 changes: 44 additions & 0 deletions lib/customer_repository.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require_relative 'customer'

class CustomerRepository

### <--- DB METHODS --- > ###
### This section includes methods which interact directly with database.

def orders_of_customer(customer_name)
sql_statement = "SELECT orders.id, order_time, items.name FROM orders
JOIN items ON items.id = orders.item_id
JOIN customers on customers.id = orders.customer_id
WHERE customers.name = $1
ORDER BY order_time DESC"
results = DatabaseConnection.exec_params(sql_statement, [customer_name])
return results
end

def retrieve_customer_by_name(customer_name)
## This method checks the query result. It returns the id
## if customer records exist, returns false if no records exist with that name
sql_statement = "SELECT * FROM customers WHERE name = $1"
results = DatabaseConnection.exec_params(sql_statement, [customer_name])
results.ntuples.zero? ? false : results[0]['id'].to_i
end

def add_customer(customer_name)
sql_statement = "INSERT INTO customers(name) VALUES($1)"
results = DatabaseConnection.exec_params(sql_statement, [customer_name])
end

### <--- FORMAT METHODS ---> ###
### These methods rework the information inside model objects into the required format strings.
### They will always return either a single string or an array of strings for the main application to print out.

def list_of_items_ordered_by_customer(customer_name)
orders = orders_of_customer(customer_name)
output = []
orders.each { |order|
formatted_string = "On #{order["order_time"]}, ordered #{order["name"]}"
output << formatted_string
}
return output
end
end
17 changes: 17 additions & 0 deletions lib/database_connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'pg'

class DatabaseConnection

def self.connect(database_name)
@connection = PG.connect({ host: '127.0.0.1', dbname: database_name })
end

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
3 changes: 3 additions & 0 deletions lib/item.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Item
attr_accessor :id, :name, :unit_price, :quantity
end
60 changes: 60 additions & 0 deletions lib/item_repository.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require_relative 'item'

class ItemRepository

### <--- DB METHODS --- > ###
### This section includes methods which interact directly with database.

def all
sql_statement = "SELECT * FROM items"
output = []
results = DatabaseConnection.exec_params(sql_statement, [])
results.each { |raw_data|
item = Item.new
item.id = raw_data['id'].to_i
item.name = raw_data['name']
item.unit_price = raw_data['unit_price'].to_i
item.quantity = raw_data['quantity'].to_i
output << item
}
return output
end

def add_item(input_parameters)
sql_statement = "INSERT INTO items(name, unit_price, quantity) VALUES($1, $2, $3)"
params = [input_parameters[:name], input_parameters[:unit_price], input_parameters[:quantity]]
results = DatabaseConnection.exec_params(sql_statement, params)
end

def retrieve_item_id_by_name(item_name)
## This method checks the query result. It returns the id
## if item exists in store, otherwise returns false
sql_statement = "SELECT * FROM items WHERE name = $1 AND quantity > 0"
results = DatabaseConnection.exec_params(sql_statement, [item_name])
results.ntuples.zero? ? false : results[0]['id'].to_i
end

### <--- FORMAT METHODS ---> ###
### These methods rework the information inside model objects into the required format strings.
### They will always return either a single string or an array of strings for the main application to print out.

def price_list
items = all
output = []
items.each { |item|
formatted_string = "Item: #{item.id}, Name: #{item.name}, Price: #{item.unit_price}"
output << formatted_string
}
return output
end

def inventory_stock_list
items = all
output = []
items.each { |item|
formatted_string = "Item: #{item.id}, Name: #{item.name}, Quantity: #{item.quantity}"
output << formatted_string
}
return output
end
end
4 changes: 4 additions & 0 deletions lib/order.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class Order
## Order class includes more attributes to be able to map foreign table information directly into it
attr_accessor :id, :order_time, :item_id, :customer_id, :customer_name, :item_name
end
58 changes: 58 additions & 0 deletions lib/order_repository.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
require_relative 'order'

class OrderRepository

### <--- DB METHODS --- > ###
### This section includes methods which interact directly with database.

def all_orders
sql_statement = "SELECT orders.id, order_time, items.name AS item_name, customers.name AS customer_name FROM orders
JOIN items ON items.id = orders.item_id
JOIN customers on customers.id = orders.customer_id
ORDER BY order_time DESC"
output = []
results = DatabaseConnection.exec_params(sql_statement, [])
results.each { |raw_data|
order = Order.new
order.id = raw_data['id'].to_i
order.item_name = raw_data['item_name']
order.customer_name = raw_data['customer_name']
order.order_time = raw_data['order_time']
output << order
}
return output
end

def last_order
## This method is only for testing purposes. It checks the last order in database
## to ensure order has been successfully added.
sql_statement = "SELECT * FROM orders ORDER BY id DESC LIMIT 1"
results = DatabaseConnection.exec_params(sql_statement, [])[0]
order = Order.new
order.id = results['id'].to_i
order.item_id = results['item_id'].to_i
order.customer_id = results['customer_id'].to_i
order.order_time = results['order_time']
return order
end

def add_order(input_parameters, date=DateTime.now.strftime("%Y-%m-%d"))
sql_statement = "INSERT INTO orders(order_time, item_id, customer_id) VALUES ($1, $2, $3)"
params = [date, input_parameters[:item_id], input_parameters[:customer_id]]
results = DatabaseConnection.exec_params(sql_statement, params)
end

### <--- FORMAT METHODS ---> ###
### These methods rework the information inside model objects into the required format strings.
### They will always return either a single string or an array of strings for the main application to print out.

def order_list
orders = all_orders
output = []
orders.each { |order|
formatted_string = "Date: #{order.order_time}, Order ID: #{order.id}, Order Item: #{order.item_name}, Customer Name: #{order.customer_name}"
output << formatted_string
}
return output
end
end
20 changes: 20 additions & 0 deletions shop_manager_tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
CREATE TABLE items (
id SERIAL PRIMARY KEY,
name TEXT,
unit_price INT,
quantity INT
);

CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name TEXT
);

CREATE TABLE orders (
id SERIAL PRIMARY KEY,
order_time DATE,
item_id INT,
customer_id INT,
CONSTRAINT fk_item_id FOREIGN KEY(item_id) REFERENCES items(id) ON DELETE CASCADE,
CONSTRAINT fk_customer_id FOREIGN KEY(customer_id) REFERENCES customers(id) ON DELETE CASCADE
);
Loading