Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
source 'https://rubygems.org'

ruby '3.0.2'

group :test do
gem 'rspec'
gem 'simplecov', require: false
gem 'simplecov-console', require: false
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'
73 changes: 37 additions & 36 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
23 changes: 23 additions & 0 deletions items_orders.sql
Original file line number Diff line number Diff line change
@@ -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)
);
138 changes: 138 additions & 0 deletions lib/app.rb
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions lib/database_connection.rb
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions lib/format.rb
Original file line number Diff line number Diff line change
@@ -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
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, :price, :quantity
end
44 changes: 44 additions & 0 deletions lib/item_repository.rb
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions lib/order.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Order
attr_accessor :id, :customer_name, :date, :items
end
Loading