From 57f8e59269f720f684287087b7571b7c3cc4ee31 Mon Sep 17 00:00:00 2001 From: Caroline Evans Date: Fri, 28 Apr 2023 13:24:45 +0100 Subject: [PATCH 1/3] setting up project --- lib/database_connection.rb | 28 +++ lib/item.rb | 3 + lib/item_repository.rb | 0 ...tems_model_and_repository_design_recipe.md | 213 ++++++++++++++++++ shop_manager_tables.sql | 32 +++ ...nager_two_tables_design_recipe_template.md | 140 ++++++++++++ spec/seeds_items.sql | 6 + 7 files changed, 422 insertions(+) create mode 100644 lib/database_connection.rb create mode 100644 lib/item.rb create mode 100644 lib/item_repository.rb create mode 100644 shop_items_model_and_repository_design_recipe.md create mode 100644 shop_manager_tables.sql create mode 100644 shop_manager_two_tables_design_recipe_template.md create mode 100644 spec/seeds_items.sql diff --git a/lib/database_connection.rb b/lib/database_connection.rb new file mode 100644 index 00000000..c8f1ad1d --- /dev/null +++ b/lib/database_connection.rb @@ -0,0 +1,28 @@ +# file: lib/database_connection.rb + +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 \ No newline at end of file diff --git a/lib/item.rb b/lib/item.rb new file mode 100644 index 00000000..be517a36 --- /dev/null +++ b/lib/item.rb @@ -0,0 +1,3 @@ +class Item + attr_accessor :id, :name, :unit_price, :quantity +end \ No newline at end of file diff --git a/lib/item_repository.rb b/lib/item_repository.rb new file mode 100644 index 00000000..e69de29b diff --git a/shop_items_model_and_repository_design_recipe.md b/shop_items_model_and_repository_design_recipe.md new file mode 100644 index 00000000..9c6aeee6 --- /dev/null +++ b/shop_items_model_and_repository_design_recipe.md @@ -0,0 +1,213 @@ +# Shop Items Model and Repository Classes Design Recipe + + +## 1. Design and create the Table + + +``` +Table: items + +Columns: +id | name | unit_price | quantity +``` + +## 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 +-- (file: spec/seeds_items.sql) + +-- 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 RESTART IDENTITY; + +-- 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 ('Cookie Dough', 2.99, 25); +INSERT INTO items (name, unit_price, quantity) VALUES ('Ice Cream', 1.99, 50); + +``` + +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_test < 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 +# 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 + +# 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, + +``` + +*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 +# 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, unit_price, quantity 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 id, name, unit_cost, quantity FROM items WHERE id = $1; + + # Returns a single Item object. + end + + def create(item) + # Executes the SQL query: + # INSERT INTO items (name, unit_cost, quantity) VALUES ($1, $2, $3); + + # returns nil + 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 +# 1 +# Get all items + +repo = ItemRepository.new + +items = repo.all + +items.length # => 2 + +items[0].id # => 1 +items[0].name # => 'Cookie Dough' +items[0].unit_price # => 2.99 +items[0].quantity # => 25 + +items[1].id # => 1 +items[1].name # => 'Ice Cream' +items[1].unit_price # => 1.99 +items[1].quantity # => 50 + +# 2 +# Get a single item + +repo = ItemRepository.new + +item = repo.find(1) + +item.id # => 1 +item.name # => 'Cookie Dough' +item.unit_price # => 2.99 +item.quantity # => 25 + +# 3 +# creates a new item +item = Item.new +item.name # => 'Chocolate Chip Cookie' +item.unit_price # => 2.25 +item.quantity # => 35 + +repo = ItemRepository.new + +repo.create(item) + +last_item = repo.all.last + +last_item.id # => 3 +last_item.name # => 'Chocolate Chip Cookie' +last_item.unit_price # => 2.35 +last_item.quantity # => 35 + + +``` + +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 +# 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: 'shop_manager_test' }) + 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._ \ No newline at end of file diff --git a/shop_manager_tables.sql b/shop_manager_tables.sql new file mode 100644 index 00000000..92f90b82 --- /dev/null +++ b/shop_manager_tables.sql @@ -0,0 +1,32 @@ + +-- Create the table without the foreign key first. +CREATE TABLE items ( + id SERIAL PRIMARY KEY, + name text, + unit_price decimal, + quantity int +); + +-- Then the table with the foreign key. +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + customer_name text, + date date, +-- The foreign key name is always {other_table_singular}_id + item_id int, + constraint fk_item foreign key(item_id) + references items(id) + on delete cascade +); + + +TRUNCATE TABLE items RESTART IDENTITY; + +INSERT INTO items (name, unit_price, quantity) VALUES ('Cookie Dough', 2.99, 25); +INSERT INTO items (name, unit_price, quantity) VALUES ('Ice Cream', 1.99, 50); +INSERT INTO items (name, unit_price, quantity) VALUES ('Oreo Cookie Pie', 3.99, 15); +INSERT INTO items (name, unit_price, quantity) VALUES ('Chocolate Orange Brownie', 3.25, 20); + + + + diff --git a/shop_manager_two_tables_design_recipe_template.md b/shop_manager_two_tables_design_recipe_template.md new file mode 100644 index 00000000..507dcf75 --- /dev/null +++ b/shop_manager_two_tables_design_recipe_template.md @@ -0,0 +1,140 @@ +# Shop Manager - Two Tables Design Recipe Template + +## 1. Extract nouns from the user stories or specification + +``` +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: + +shop items, item name, item unit price, item quantity, orders, customer name, 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 | +| --------------------- | ------------------ | +| item | name, unit_price, quantity +| order | customer_name, date + +1. Name of the first table (always plural): `items` + + Column names: `name`, `unit_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. + +Remember to **always** have the primary key `id` as a first column. Its type will always be `SERIAL`. + +``` +Table: items +id: SERIAL +name: text +unit_price: decimal +quantity: int + +Table: orders +id: SERIAL +customer_name: text +date: date +``` + +## 4. Decide on The Tables Relationship + +Most of the time, you'll be using a **one-to-many** relationship, and will need a **foreign key** on one of the two tables. + +To decide on which one, answer these two questions: + +1. Can one [TABLE ONE] have many [TABLE TWO]? (Yes/No) +2. Can one [TABLE TWO] have many [TABLE ONE]? (Yes/No) + +You'll then be able to say that: + +1. **[A] has many [B]** +2. And on the other side, **[B] belongs to [A]** +3. In that case, the foreign key is in the table [B] + + +``` +1. Can one item have many orders? YES +2. Can one order have many items? NO + +-> Therefore, +-> An item HAS MANY orders +-> An order BELONGS TO an item + +-> Therefore, the foreign key is on the item table. +``` + +*If you can answer YES to the two questions, you'll probably have to implement a Many-to-Many relationship, which is more complex and needs a third table (called a join table).* + +## 4. Write the SQL. + +```sql +-- file: shop_manager_tables.sql + +-- Create the table without the foreign key first. +CREATE TABLE items ( + id SERIAL PRIMARY KEY, + name text, + unit_price decimal, + quantity int +); + +-- Then the table with the foreign key. +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + customer_name text, + date date, +-- The foreign key name is always {other_table_singular}_id + item_id int, + constraint fk_item foreign key(item_id) + references items(id) + on delete cascade +); + +``` + +## 5. Create the tables. + +```bash +psql -h 127.0.0.1 shop_manager < shop_manager_tables.sql +``` + diff --git a/spec/seeds_items.sql b/spec/seeds_items.sql new file mode 100644 index 00000000..97b7cb49 --- /dev/null +++ b/spec/seeds_items.sql @@ -0,0 +1,6 @@ +TRUNCATE TABLE items, orders RESTART IDENTITY; + +INSERT INTO items (name, unit_price, quantity) VALUES ('Cookie Dough', 2.99, 25); +INSERT INTO items (name, unit_price, quantity) VALUES ('Ice Cream', 1.99, 50); + + From 90cf3e8f69635db19e80538fa2bd20c3e8dc29d3 Mon Sep 17 00:00:00 2001 From: Caroline Evans Date: Fri, 28 Apr 2023 15:50:58 +0100 Subject: [PATCH 2/3] finished database layer --- lib/item_repository.rb | 60 +++++ lib/order.rb | 3 + lib/order_repository.rb | 64 ++++++ ...tems_model_and_repository_design_recipe.md | 12 +- shop_manager_tables.sql | 5 +- ...ders_model_and_repository_design_recipe.md | 212 ++++++++++++++++++ spec/item_repository_spec.rb | 64 ++++++ spec/order_repository_spec.rb | 61 +++++ spec/seeds_orders.sql | 4 + spec/spec_helper.rb | 4 + 10 files changed, 482 insertions(+), 7 deletions(-) create mode 100644 lib/order.rb create mode 100644 lib/order_repository.rb create mode 100644 shop_orders_model_and_repository_design_recipe.md create mode 100644 spec/item_repository_spec.rb create mode 100644 spec/order_repository_spec.rb create mode 100644 spec/seeds_orders.sql diff --git a/lib/item_repository.rb b/lib/item_repository.rb index e69de29b..fca8ae84 100644 --- a/lib/item_repository.rb +++ b/lib/item_repository.rb @@ -0,0 +1,60 @@ +require 'database_connection' + +class ItemRepository + + # Selecting all records + # No arguments + def all + # Executes the SQL query: + # SELECT id, name, unit_price, quantity FROM items; + sql = 'SELECT id, name, unit_price, quantity FROM items;' + sql_params = [] + + result_set = DatabaseConnection.exec_params(sql,sql_params) + + items = [] + + result_set.each do |record| + items << get_item(record) + end + + return items + end + + # Gets a single record by its ID + # One argument: the id (number) + def find(id) + # Executes the SQL query: + # SELECT id, name, unit_cost, quantity FROM items WHERE id = $1; + sql = 'SELECT id, name, unit_price, quantity FROM items WHERE id = $1;' + sql_params = [id] + + result = DatabaseConnection.exec_params(sql,sql_params) + + return get_item(result[0]) + end + + def create(item) + # Executes the SQL query: + # INSERT INTO items (name, unit_cost, quantity) VALUES ($1, $2, $3); + sql = 'INSERT INTO items (name, unit_price, quantity) VALUES ($1, $2, $3);' + sql_params = [item.name, item.unit_price, item.quantity] + + result = DatabaseConnection.exec_params(sql,sql_params) + + return nil + end + + private + + def get_item(record) + item = Item.new + item.id = record['id'].to_i + item.name = record['name'] + item.unit_price = record['unit_price'].to_f + item.quantity = record['quantity'].to_i + + return item + end + +end \ No newline at end of file diff --git a/lib/order.rb b/lib/order.rb new file mode 100644 index 00000000..66f57e44 --- /dev/null +++ b/lib/order.rb @@ -0,0 +1,3 @@ +class Order + attr_accessor :id, :customer_name, :date, :item_id +end \ No newline at end of file diff --git a/lib/order_repository.rb b/lib/order_repository.rb new file mode 100644 index 00000000..7f91e154 --- /dev/null +++ b/lib/order_repository.rb @@ -0,0 +1,64 @@ +require 'database_connection' +require 'order' + +class OrderRepository + + # Selecting all records + # No arguments + def all + # Executes the SQL query: + # SELECT id, customer_name, date, item_id FROM orders; + + sql = 'SELECT id, customer_name, date, item_id FROM orders;' + sql_params = [] + + result_set = DatabaseConnection.exec_params(sql,sql_params) + + orders = [] + + result_set.each do |record| + orders << get_order(record) + end + + return orders + + end + + # Gets a single record by its ID + # One argument: the id (number) + def find(id) + # Executes the SQL query: + # SELECT id, customer_name, date, item_id FROM orders WHERE id = $1; + sql = 'SELECT id, customer_name, date, item_id FROM orders WHERE id = $1;' + sql_params = [id] + + result = DatabaseConnection.exec_params(sql,sql_params) + + return get_order(result[0]) + + end + + def create(order) + # Executes the SQL query: + # INSERT INTO orders (customer_name, date, item_id) VALUES ($1, $2, $3); + sql = 'INSERT INTO orders (customer_name, date, item_id) VALUES ($1, $2, $3);' + sql_params = [order.customer_name, order.date, order.item_id] + + result = DatabaseConnection.exec_params(sql,sql_params) + + return nil + + end + + private + + def get_order(record) + order = Order.new + order.id = record['id'].to_i + order.customer_name = record['customer_name'] + order.date = record['date'] + order.item_id = record['item_id'].to_i + + return order + end +end \ No newline at end of file diff --git a/shop_items_model_and_repository_design_recipe.md b/shop_items_model_and_repository_design_recipe.md index 9c6aeee6..c99bd37a 100644 --- a/shop_items_model_and_repository_design_recipe.md +++ b/shop_items_model_and_repository_design_recipe.md @@ -108,14 +108,14 @@ class ItemRepository # One argument: the id (number) def find(id) # Executes the SQL query: - # SELECT id, name, unit_cost, quantity FROM items WHERE id = $1; + # SELECT id, name, unit_price, quantity FROM items WHERE id = $1; # Returns a single Item object. end def create(item) # Executes the SQL query: - # INSERT INTO items (name, unit_cost, quantity) VALUES ($1, $2, $3); + # INSERT INTO items (name, unit_price, quantity) VALUES ($1, $2, $3); # returns nil end @@ -164,9 +164,9 @@ item.quantity # => 25 # 3 # creates a new item item = Item.new -item.name # => 'Chocolate Chip Cookie' -item.unit_price # => 2.25 -item.quantity # => 35 +item.name = 'Chocolate Chip Cookie' +item.unit_price = 2.25 +item.quantity = 35 repo = ItemRepository.new @@ -176,7 +176,7 @@ last_item = repo.all.last last_item.id # => 3 last_item.name # => 'Chocolate Chip Cookie' -last_item.unit_price # => 2.35 +last_item.unit_price # => 2.25 last_item.quantity # => 35 diff --git a/shop_manager_tables.sql b/shop_manager_tables.sql index 92f90b82..8596cb0f 100644 --- a/shop_manager_tables.sql +++ b/shop_manager_tables.sql @@ -28,5 +28,8 @@ INSERT INTO items (name, unit_price, quantity) VALUES ('Oreo Cookie Pie', 3.99, INSERT INTO items (name, unit_price, quantity) VALUES ('Chocolate Orange Brownie', 3.25, 20); +TRUNCATE TABLE orders RESTART IDENTITY; - +INSERT INTO orders (customer_name, date, item_id) VALUES ('Caroline', '27-Apr-2023', 1); +INSERT INTO orders (customer_name, date, item_id) VALUES ('Phil','28-Apr-2023' , 2); +INSERT INTO orders (customer_name, date, item_id) VALUES ('Vass','29-Apr-2023' , 3); diff --git a/shop_orders_model_and_repository_design_recipe.md b/shop_orders_model_and_repository_design_recipe.md new file mode 100644 index 00000000..1b1c5b13 --- /dev/null +++ b/shop_orders_model_and_repository_design_recipe.md @@ -0,0 +1,212 @@ +# Shop Orders Model and Repository Classes Design Recipe + + +## 1. Design and create the Table + + +``` +Table: orders + +Columns: +id | customer_name | date | item_id +``` + +## 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 +-- (file: spec/seeds_orders.sql) + +-- 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 orders RESTART IDENTITY; + +-- Below this line there should only be `INSERT` statements. +-- Replace these statements with your own seed data. + +INSERT INTO orders (customer_name, date, item_id) VALUES ('Caroline', 27-apr-2023, 1); +INSERT INTO orders (customer_name, date, item_id) VALUES ('Phil',28-apr-2023 , 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_test < seeds_orders.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: 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: orders + +# Model class +# (in lib/order.rb) + +class Order + # Replace the attributes by your own columns. + attr_accessor :id, :customer_name, :date, :item_id +end + +# The keyword attr_accessor is a special Ruby feature +# which allows us to set and get attributes on an object, + +``` + +*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 +# 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, item_id FROM orders; + + # Returns an array of Order objects. + end + + # Gets a single record by its ID + # One argument: the id (number) + def find(id) + # Executes the SQL query: + # SELECT id, customer_name, date, item_id FROM orders WHERE id = $1; + + # Returns a single Order object. + end + + def create(order) + # Executes the SQL query: + # INSERT INTO orders (customer_name, date, item_id) VALUES ($1, $2, $3); + + # returns nil + 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 +# 1 +# Get all orders + +repo = OrderRepository.new + +orders = repo.all + +orders.length # => 2 + +orders[0].id # => 1 +orders[0].customer_name # => 'Caroline' +orders[0].date # => '2023-04-27' +orders[0].item_id # => 1 + +orders[1].id # => 2 +orders[1].customer_name # => 'Phil' +orders[1].date # => '2023-04-28' +orders[1].item_id # => 2 + + +# 2 +# Get a single order + +repo = OrderRepository.new + +order = repo.find(1) + +order.id # => 1 +order.customer_name # => 'Caroline' +order.date # => '2023-04-27' +order.item_id # => 1 + +# 3 +# creates a new order +order = Order.new +order.customer_name = 'Pip' +order.date = '28-Apr-2023' +order.item_id = 1 + +repo = OrderRepository.new + +repo.create(order) + +last_order = repo.all.last + +last_order.id # => 3 +last_order.customer_name # => 'Pip' +last_order.date # => '2023-04-28' +last_order.item_id # => 1 + +``` + +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 +# file: spec/order_repository_spec.rb + +def reset_orders_table + seed_sql = File.read('spec/seeds_orders.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'shop_manager_test' }) + connection.exec(seed_sql) +end + +describe OrderRepository do + before(:each) do + reset_orders_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._ \ No newline at end of file diff --git a/spec/item_repository_spec.rb b/spec/item_repository_spec.rb new file mode 100644 index 00000000..52d46454 --- /dev/null +++ b/spec/item_repository_spec.rb @@ -0,0 +1,64 @@ +require 'item_repository' +require 'item' + +def reset_items_table + seed_sql = File.read('spec/seeds_items.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'shop_manager_test' }) + connection.exec(seed_sql) +end + +describe ItemRepository do + before(:each) do + reset_items_table + end + + it 'gets all items' do + repo = ItemRepository.new + + items = repo.all + + expect(items.length).to eq 2 + + expect(items[0].id).to eq 1 + expect(items[0].name).to eq 'Cookie Dough' + expect(items[0].unit_price).to eq 2.99 + expect(items[0].quantity).to eq 25 + + expect(items[1].id).to eq 2 + expect(items[1].name).to eq 'Ice Cream' + expect(items[1].unit_price).to eq 1.99 + expect(items[1].quantity).to eq 50 + + end + + it 'returns one item' do + repo = ItemRepository.new + + item = repo.find(1) + + expect(item.id).to eq 1 + expect(item.name).to eq 'Cookie Dough' + expect(item.unit_price).to eq 2.99 + expect(item.quantity).to eq 25 + + end + + it 'creates new item' do + item = Item.new + item.name = 'Chocolate Chip Cookie' + item.unit_price = 2.25 + item.quantity = 35 + + repo = ItemRepository.new + + repo.create(item) + + last_item = repo.all.last + + expect(last_item.id).to eq 3 + expect(last_item.name).to eq 'Chocolate Chip Cookie' + expect(last_item.unit_price).to eq 2.25 + expect(last_item.quantity).to eq 35 + + end +end \ No newline at end of file diff --git a/spec/order_repository_spec.rb b/spec/order_repository_spec.rb new file mode 100644 index 00000000..5c350229 --- /dev/null +++ b/spec/order_repository_spec.rb @@ -0,0 +1,61 @@ +require 'order_repository' + +def reset_orders_table + seed_sql = File.read('spec/seeds_orders.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'shop_manager_test' }) + connection.exec(seed_sql) +end + +describe OrderRepository do + before(:each) do + reset_orders_table + end + + it 'gets all orders' do + repo = OrderRepository.new + puts "here here" + orders = repo.all + + expect(orders.length).to eq 2 + + expect(orders[0].id).to eq 1 + expect(orders[0].customer_name).to eq 'Caroline' + expect(orders[0].date).to eq '2023-04-27' + expect(orders[0].item_id).to eq 1 + + expect(orders[1].id).to eq 2 + expect(orders[1].customer_name).to eq 'Phil' + expect(orders[1].date).to eq '2023-04-28' + expect(orders[1].item_id).to eq 2 + + end + + it 'returns a single order' do + repo = OrderRepository.new + + order = repo.find(1) + + expect(order.id).to eq 1 + expect(order.customer_name).to eq 'Caroline' + expect(order.date).to eq '2023-04-27' + expect(order.item_id).to eq 1 + end + + it 'creates a new order' do + order = Order.new + order.customer_name = 'Pip' + order.date = '28-Apr-2023' + order.item_id = 1 + + repo = OrderRepository.new + + repo.create(order) + + last_order = repo.all.last + + expect(last_order.id).to eq 3 + expect(last_order.customer_name).to eq 'Pip' + expect(last_order.date).to eq '2023-04-28' + expect(last_order.item_id).to eq 1 + end +end \ No newline at end of file diff --git a/spec/seeds_orders.sql b/spec/seeds_orders.sql new file mode 100644 index 00000000..dcefc240 --- /dev/null +++ b/spec/seeds_orders.sql @@ -0,0 +1,4 @@ +TRUNCATE TABLE orders RESTART IDENTITY; + +INSERT INTO orders (customer_name, date, item_id) VALUES ('Caroline', '27-Apr-2023', 1); +INSERT INTO orders (customer_name, date, item_id) VALUES ('Phil','28-Apr-2023' , 2); \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 252747d8..63baf88c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,9 @@ require 'simplecov' require 'simplecov-console' +require 'database_connection' + + +DatabaseConnection.connect('shop_manager_test') SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ SimpleCov::Formatter::Console, From 1ec042b9084248650f8d55860d426b36f0b2e6c9 Mon Sep 17 00:00:00 2001 From: Caroline Evans Date: Fri, 28 Apr 2023 22:26:40 +0100 Subject: [PATCH 3/3] finished first draft of project --- app.rb | 93 +++++++++++++++++++ lib/item_repository.rb | 3 +- lib/order_repository.rb | 4 +- shop_manager_tables.sql | 2 +- spec/app_spec.rb | 164 ++++++++++++++++++++++++++++++++++ spec/order_repository_spec.rb | 1 - spec/seeds_items.sql | 4 + 7 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 app.rb create mode 100644 spec/app_spec.rb diff --git a/app.rb b/app.rb new file mode 100644 index 00000000..89dd06cc --- /dev/null +++ b/app.rb @@ -0,0 +1,93 @@ +require_relative 'lib/order_repository' +require_relative 'lib/item_repository' +require_relative 'lib/database_connection' + + +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 + end + + def run + print_menu + user_input = @io.gets.chomp.to_i + + case user_input + when 1 + list_all_items + when 2 + create_new_item + when 3 + list_all_orders + when 4 + create_new_order + end + + end + + private + + def print_menu + @io.puts ( + "Welcome to the shop management program! + + What do you want to do? + 1 = list all shop items + 2 = create a new item + 3 = list all orders + 4 = create a new order" + ) + end + + def list_all_items + @item_repository.all.each do |record| + @io.puts("##{record.id} #{record.name} - Unit price: #{record.unit_price} - Quantity: #{record.quantity}") + end + end + + def create_new_item + new_item = Item.new + + @io.puts "Please enter the new item's name:" + new_item.name = @io.gets.chomp + @io.puts "Please enter the new item's unit price:" + new_item.unit_price = @io.gets.chomp.to_f + @io.puts "Please enter the new item's quantity:" + new_item.quantity = @io.gets.chomp.to_i + + @item_repository.create(new_item) + end + + def list_all_orders + @order_repository.all.each do |record| + @io.puts("Date: #{record.date} - Order ID #{record.id} - Customer Name: #{record.customer_name} - item_id: #{record.item_id}") + end + end + + def create_new_order + new_order = Order.new + + @io.puts "Please enter the date (YYYY-MM-DD):" + new_order.date = @io.gets.chomp + @io.puts "Please enter the customer's name:" + new_order.customer_name = @io.gets.chomp + @io.puts "Please enter the item ID:" + new_order.item_id = @io.gets.chomp.to_i + + @order_repository.create(new_order) + end + +end + +if __FILE__ == $0 + app = Application.new( + 'shop_manager', + Kernel, + ItemRepository.new, + OrderRepository.new + ) + app.run +end diff --git a/lib/item_repository.rb b/lib/item_repository.rb index fca8ae84..15bd4622 100644 --- a/lib/item_repository.rb +++ b/lib/item_repository.rb @@ -1,4 +1,5 @@ -require 'database_connection' +require_relative 'database_connection' +require_relative 'item' class ItemRepository diff --git a/lib/order_repository.rb b/lib/order_repository.rb index 7f91e154..2829350f 100644 --- a/lib/order_repository.rb +++ b/lib/order_repository.rb @@ -1,5 +1,5 @@ -require 'database_connection' -require 'order' +require_relative 'database_connection' +require_relative 'order' class OrderRepository diff --git a/shop_manager_tables.sql b/shop_manager_tables.sql index 8596cb0f..e2ef9466 100644 --- a/shop_manager_tables.sql +++ b/shop_manager_tables.sql @@ -20,7 +20,7 @@ CREATE TABLE orders ( ); -TRUNCATE TABLE items RESTART IDENTITY; +TRUNCATE TABLE items, orders RESTART IDENTITY; INSERT INTO items (name, unit_price, quantity) VALUES ('Cookie Dough', 2.99, 25); INSERT INTO items (name, unit_price, quantity) VALUES ('Ice Cream', 1.99, 50); diff --git a/spec/app_spec.rb b/spec/app_spec.rb new file mode 100644 index 00000000..4c8621db --- /dev/null +++ b/spec/app_spec.rb @@ -0,0 +1,164 @@ +require_relative '../app.rb' + +def reset_orders_table + seed_sql = File.read('spec/seeds_orders.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'shop_manager_test' }) + connection.exec(seed_sql) +end + +def reset_items_table + seed_sql = File.read('spec/seeds_items.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'shop_manager_test' }) + connection.exec(seed_sql) +end + + +RSpec.describe Application do + before(:each) do + reset_orders_table + reset_items_table + end + + it 'prints a user menu and prints a list of items' do + + io = double(:io) + + expect(io).to receive(:puts).with( + "Welcome to the shop management program! + + What do you want to do? + 1 = list all shop items + 2 = create a new item + 3 = list all orders + 4 = create a new order" + ).ordered + + expect(io).to receive(:gets).and_return('1').ordered + expect(io).to receive(:puts).with("#1 Cookie Dough - Unit price: 2.99 - Quantity: 25").ordered + expect(io).to receive(:puts).with("#2 Ice Cream - Unit price: 1.99 - Quantity: 50").ordered + + + app = Application.new( + 'shop_manager_test', + io, + ItemRepository.new, + OrderRepository.new + ) + + app.run + + end + + it 'prints a user menu and prints a list of orders' do + + io = double(:io) + + expect(io).to receive(:puts).with( + "Welcome to the shop management program! + + What do you want to do? + 1 = list all shop items + 2 = create a new item + 3 = list all orders + 4 = create a new order" + ).ordered + + expect(io).to receive(:gets).and_return('3').ordered + expect(io).to receive(:puts).with("Date: 2023-04-27 - Order ID 1 - Customer Name: Caroline - item_id: 1").ordered + expect(io).to receive(:puts).with("Date: 2023-04-28 - Order ID 2 - Customer Name: Phil - item_id: 2").ordered + + + app = Application.new( + 'shop_manager_test', + io, + ItemRepository.new, + OrderRepository.new + ) + + app.run + + end + + + it 'prints a user menu and creates a new item' do + + io = double(:io) + + expect(io).to receive(:puts).with( + "Welcome to the shop management program! + + What do you want to do? + 1 = list all shop items + 2 = create a new item + 3 = list all orders + 4 = create a new order" + ).ordered + + expect(io).to receive(:gets).and_return('2').ordered + expect(io).to receive(:puts).with("Please enter the new item's name:").ordered + expect(io).to receive(:gets).and_return('Giant Cookie').ordered + expect(io).to receive(:puts).with("Please enter the new item's unit price:").ordered + expect(io).to receive(:gets).and_return('14.99').ordered + expect(io).to receive(:puts).with("Please enter the new item's quantity:").ordered + expect(io).to receive(:gets).and_return('5').ordered + + app = Application.new( + 'shop_manager_test', + io, + ItemRepository.new, + OrderRepository.new + ) + + app.run + + item_repository = ItemRepository.new + last_item = item_repository.all.last + expect(last_item.id).to eq 3 + expect(last_item.name).to eq 'Giant Cookie' + expect(last_item.unit_price).to eq 14.99 + expect(last_item.quantity).to eq 5 + + end + + it 'prints a user menu and creates a new order' do + + io = double(:io) + + expect(io).to receive(:puts).with( + "Welcome to the shop management program! + + What do you want to do? + 1 = list all shop items + 2 = create a new item + 3 = list all orders + 4 = create a new order" + ).ordered + + expect(io).to receive(:gets).and_return('4').ordered + expect(io).to receive(:puts).with("Please enter the date (YYYY-MM-DD):").ordered + expect(io).to receive(:gets).and_return('2023-04-30').ordered + expect(io).to receive(:puts).with("Please enter the customer's name:").ordered + expect(io).to receive(:gets).and_return('Pipin').ordered + expect(io).to receive(:puts).with("Please enter the item ID:").ordered + expect(io).to receive(:gets).and_return('2').ordered + + app = Application.new( + 'shop_manager_test', + io, + ItemRepository.new, + OrderRepository.new + ) + + app.run + + order_repository = OrderRepository.new + last_order = order_repository.all.last + expect(last_order.id).to eq 3 + expect(last_order.customer_name).to eq 'Pipin' + expect(last_order.date).to eq '2023-04-30' + expect(last_order.item_id).to eq 2 + + end + +end + diff --git a/spec/order_repository_spec.rb b/spec/order_repository_spec.rb index 5c350229..2ddfaf85 100644 --- a/spec/order_repository_spec.rb +++ b/spec/order_repository_spec.rb @@ -13,7 +13,6 @@ def reset_orders_table it 'gets all orders' do repo = OrderRepository.new - puts "here here" orders = repo.all expect(orders.length).to eq 2 diff --git a/spec/seeds_items.sql b/spec/seeds_items.sql index 97b7cb49..5081db5d 100644 --- a/spec/seeds_items.sql +++ b/spec/seeds_items.sql @@ -4,3 +4,7 @@ INSERT INTO items (name, unit_price, quantity) VALUES ('Cookie Dough', 2.99, 25) INSERT INTO items (name, unit_price, quantity) VALUES ('Ice Cream', 1.99, 50); +INSERT INTO orders (customer_name, date, item_id) VALUES ('Caroline', '27-Apr-2023', 1); +INSERT INTO orders (customer_name, date, item_id) VALUES ('Phil','28-Apr-2023' , 2); + +