Let's simulate a grocery store system! We want to be able to keep track of the orders that folks make.
This project will allow you to explore object-oriented design as well as a few other new topics. This is an individual, stage 1 project.
Due before class, Monday March 4th 2019.
Skills that should be demonstrated through this project:
- Test-Driven-Development
- Using instance variables and methods
- Object composition
- Reading data from a CSV file
- Fork the project master.
- Clone the forked repo:
$ git clone [YOUR FORKED REPO URL] cdinto the dir created$ cd grocery-store- Run
git remote -vto verify the folder you are in corresponds to the fork you have created.
If it is correct it will include your username If it is incorrect it will include "AdaGold" or "Ada-C#" - Run
gem install minitest-skipto install an extra gem for testing (more on what this actually does later).
This is the first project where you'll be writing your own tests. Following the instructions from the TDD lecture, there are three things in our project directory:
Rakefile
lib/
specs/
Each class you write should get its own file, lib/class_name.rb. The specs for that class will be in specs/class_name_spec.rb, and you can run all specs using the rake command from your terminal.
- Create a class
- Write instance methods inside a class to perform actions
- Link two classes using composition
- Use exceptions to handle errors
- Verify code correctness by testing
For Wave 1, all tests have been provided for you. For each piece of functionality that you build, you should run the tests from the command line using the rake command. To focus on only one test at a time, change all it methods to xit except for the one test you'd like to run. All tests provided should be passing at the end of your work on Wave 1.
Create a class called Customer. Each new Customer should include the following attributes:
- ID, a number
- Email address, a string
- Delivery address, a hash
ID should be readable but not writable; the other two attributes can be both read and written.
Create a class called Order. Each new Order should include the following attributes:
- ID, a number (read-only)
- A collection of products and their cost. This data will be given as a hash that looks like this:
{ "banana" => 1.99, "cracker" => 3.00 }
- Zero products is permitted (an empty hash)
- You can assume that there is only one of each product
- An instance of
Customer, the person who placed this order - A
fulfillment_status, a symbol, one of:pending,:paid,:processing,:shipped, or:complete- If no
fulfillment_statusis provided, it will default to:pending - If a status is given that is not one of the above, an
ArgumentErrorshould be raised
- If no
In addition, Order should have:
- A
totalmethod which will calculate the total cost of the order by:- Summing up the products
- Adding a 7.5% tax
- Rounding the result to two decimal places
- An
add_productmethod which will take in two parameters, product name and price, and add the data to the product collection- If a product with the same name has already been added to the order, an
ArgumentErrorshould be raised
- If a product with the same name has already been added to the order, an
Make sure to write tests for any optionals you implement!
- Add a
remove_productmethod to theOrderclass which will take in one parameter, a product name, and remove the product from the collection- If no product with that name was found, an
ArgumentErrorshould be raised
- If no product with that name was found, an
- Create and use class methods
- Use a CSV file for loading data
- Create your own tests to verify method correctness
You enter Wave 2 with all tests from Wave 1 passing. In Wave 2, all the tests for Customer and one of the specs for Order have been provided. The remaining tests are stubbed out in order_spec.rb. Filling in these stubs is part of Wave 2.
When you are done with Wave 2, all your tests from Wave 1 should still pass!
Add the following class methods to the Customer class:
self.all- returns a collection ofCustomerinstances, representing all of the Customer described in the CSV fileself.find(id)- returns an instance ofCustomerwhere the value of the id field in the CSV matches the passed parameter
Customer.find should not parse the CSV file itself. Instead it should invoke Customer.all and search through the results for a customer with a matching ID.
Customer data lives in the file data/customers.csv. The data in this file has the following columns:
| Field | Type | Description |
|---|---|---|
| Customer ID | Integer | A unique identifier corresponding to the Customer |
| String | The customer's e-mail address | |
| Address 1 | String | The customer's street address |
| City | String | The customer's city |
| State | String | The customer's state |
| Zip Code | String | The customer's zip code |
Note: The columns in the CSV file don't quite match the parameters for the constructor. You'll need to do some work
What should your program do if Customer.find is called with an ID that doesn't exist? Hint: what does the find method for a Ruby array do?
Add the following class methods to the Order class:
self.all- returns a collection ofOrderinstances, representing all of the Orders described in the CSV fileself.find(id)- returns an instance ofOrderwhere the value of the id field in the CSV matches the passed parameter
As before, Order.find should call Order.all instead of loading the CSV file itself.
Order data lives in the file data/orders.csv. The data in this file has the following columns:
| Field | Type | Description |
|---|---|---|
| ID | Integer | A unique identifier for that Online Order |
| Products | String | The list of products in the following format: name:price;nextname:nextprice |
| Customer ID | Integer | A unique identifier corresponding to a Customer |
| Status | String | A string representing the order's current status |
The data in this file is very different than what Order.new takes. You will have two big pieces of work here:
- Parse the list of products into a hash
- This would be a great piece of logic to put into a helper method
- You might want to look into Ruby's
splitmethod - We recommend manually copying the first product string from the CSV file and using pry to prototype this logic
- Turn the customer ID into an instance of
Customer- Didn't you just write a method to do this?
Order.find_by_customer(customer_id)- returns a list ofOrderinstances where the value of the customer's ID matches the passed parameter.
Add a new class method to each of Order and Customer called save. The save method should take one parameter, a file name, and save the list of objects to that file in the same format as the original CSV file.
When you're done, you should be able to write code like the following:
$ pry -r ./lib/customer.rb
pry> Customer.save('new_customer_list.csv')
pry> exit
$ cat new_customer_list.csv
1,leonard.rogahn@hagenes.org,71596 Eden Route,Connellymouth,LA,98872-9105
2,ruben_nikolaus@kreiger.com,876 Kemmer Cove,East Luellatown,AL,21362
[ ... ]
We do not require testing for this wave - testing with external resouces like files is tricky to get right. Instead, do some brainstorming: what kind of things would tests need to do, and what error cases would they handle? Why is testing tricky, and what might be done to overcome these problems? How could you learn more about this?
Check out the feedback template to see what instructors will be looking for.