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

Completed shop manager solo project #162

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

class Application

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

def run

# until @break == true do
@io.puts "Welcome to the shop management program!"
@io.puts "What do you want to do?"
@io.puts " 1 = list all shop items"
@io.puts " 2 = create a new item"
@io.puts " 3 = list all orders"
@io.puts " 4 = create a new order"

choice = @io.gets.chomp

execute(choice)
# end
end

def execute(choice)
if choice == "1"
@item_repository.all.each do |record|
@io.puts "##{record.id} #{record.name} - Unit price: £#{record.unit_price} - Quantity: #{record.quantity}"
end
elsif choice == "2"
new_item = Item.new

@io.puts "Please enter the item's name"
new_item.name = @io.gets.chomp
@io.puts "Please enter the item's unit price"
new_item.unit_price = @io.gets.chomp
@io.puts "Please enter the item's quantity"
new_item.quantity = @io.gets.chomp

@item_repository.create(new_item)

@io.puts "Item #{new_item.name} successfully added to the shop!"
elsif choice == "3"
@order_repository.all.each do |record|
@io.puts "##{record.id} #{record.customer_name} - Date: #{record.date} - Item: #{@item_repository.find(record.item_id).name}"
end
elsif choice == "4"
new_order = Order.new

@io.puts "Please enter the customer's name"
new_order.customer_name = @io.gets.chomp
new_order.date = Date.today
@io.puts "Please enter the item they'd like to order"
item_input = @io.gets.chomp

@item_repository.all.each do |item|
if item.name == item_input
new_order.item_id = item.id
end
end

@order_repository.create(new_order)

@io.puts "Order successfully created for #{new_order.customer_name} today!"
else
# @break = true
end
end

end

# item_repository = ItemRepository.new
# order_repository = OrderRepository.new
# io = Kernel
# app = Application.new('shop_manager', io, item_repository, order_repository)
# app.run

# uncomment all comments to run the Application class with a loop and using shop_manager production database
214 changes: 214 additions & 0 deletions items_table_model_repository_class_recipe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# Items Model and Repository Classes Design Recipe
```
## 2. 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
-- EXAMPLE
-- (file: spec/seeds_items.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 RESTART IDENTITY CASCADE; -- 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, unit_price, quantity) VALUES ('Correction tape', '4.95', '26');
INSERT INTO items (name, unit_price, quantity) VALUES ('Cute eraser', '3.25', '14');
```

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 < seeds_items.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
# EXAMPLE
# Table name: items

# Model class
# (in lib/item.rb)
class Item
end

# Repository class
# (in lib/item_repository.rb)
class ItemRepository
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
# EXAMPLE
# Table name: items

# Model class
# (in lib/item.rb)

class Item

# Replace the attributes by your own columns.
attr_accessor :id, :name, :unit_price, :quantity
end

# The keyword attr_accessor is a special Ruby feature
# which allows us to set and get attributes on an object,
# here's an example:
#
# item = Item.new
# item.name = 'Jo'
# item.name
```

*You may choose to test-drive this class, but unless it contains any more logic than the example above, it is probably not needed.*

## 5. Define the Repository Class interface

Your Repository class will need to implement methods for each "read" or "write" operation you'd like to run against the database.

Using comments, define the method signatures (arguments and return value) and what they do - write up the SQL queries that will be used by each method.

```ruby
# EXAMPLE
# Table name: items

# Repository class
# (in lib/item_repository.rb)

class ItemRepository

# Selecting all records
# No arguments
def all
# Executes the SQL query:
# SELECT * FROM items;

# Returns an array of Item objects.
end

# Gets a single record by its ID
# One argument: the id (number)
def find(id)
# Executes the SQL query:
# SELECT * FROM items WHERE id = $1;

# Returns a single Item object.
end

# Add more methods below for each operation you'd like to implement.

def create(item)
# 'INSERT INTO items (name, unit_price, quantity) VALUES ($1, $2, $3);'
end

# def update(item)
# end

# def delete(item)
# end
end
```

## 6. Write Test Examples

Write Ruby code that defines the expected behaviour of the Repository class, following your design from the table written in step 5.

These examples will later be encoded as RSpec tests.

```ruby
# EXAMPLES

# 1
# Get all items

repo = ItemRepository.new

items = repo.all

items.length # => 2

items[0].id # => '1'
items[0].name # => 'Correction tape'
items[0].unit_price # => '4.95'
items[0].quantity # => '26'

items[1].id # => '2'
items[1].name # => 'Cute eraser'
items[1].unit_price # => '3.25'
items[1].quantity # => '14'

# 2
# Get a single item

repo = ItemRepository.new

item = repo.find(1)

item.id # => '1'
item.name # => 'Correction tape'
item.unit_price # => '4.95'
item.quantity # => '26'

# Creates an item

new_item = Item.new
new_item.name = 'Protractor'
new_item.unit_price = '9.99'
new_item.quantity = '33'
repo = ItemRepository.new
repo.create(new_item)
items = repo.all

items[-1].name # => 'Protractor'
items[-1].unit_price # => '9.99'
items[-1].quantity # => '33'

```

Encode this example as a test.

## 7. 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_items_table
seed_sql = File.read('spec/seeds_items.sql')
connection = PG.connect({ host: '127.0.0.1', dbname: 'items' })
connection.exec(seed_sql)
end

describe ItemRepository do
before(:each) do
reset_items_table
end

# (your tests will go here).
end
```

## 8. Test-drive and implement the Repository class behaviour

_After each test you write, follow the test-driving process of red, green, refactor to implement the behaviour._
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
5 changes: 5 additions & 0 deletions lib/item.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Item

# Replace the attributes by your own columns.
attr_accessor :id, :name, :unit_price, :quantity
end
45 changes: 45 additions & 0 deletions lib/item_repository.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require_relative './item'

class ItemRepository

def all
query = 'SELECT * FROM items;'
result = DatabaseConnection.exec_params(query, [])

items = []

result.each do |record|
items << create_item_object(record)
end

return items
end

def find(id)
query = 'SELECT * FROM items WHERE id = $1;'
param = [id]

result = DatabaseConnection.exec_params(query, param)[0]

return create_item_object(result)
end

def create(item)
query = 'INSERT INTO items (name, unit_price, quantity) VALUES ($1, $2, $3);'
params = [item.name, item.unit_price, item.quantity]

DatabaseConnection.exec_params(query, params)
end

private

def create_item_object(record)
item = Item.new
item.id = record['id']
item.name = record['name']
item.unit_price = record['unit_price']
item.quantity = record['quantity']

return item
end
end
5 changes: 5 additions & 0 deletions lib/order.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Order

# Replace the attributes by your own columns.
attr_accessor :id, :customer_name, :date, :item_id
end
Loading