We have updated the content of our program. To access the current Software Engineering curriculum visit curriculum.turing.edu.
Market Money Requirements
1. Setup
-
Create a Rails API project called
market_money
(make sure you do not set up a “traditional” Rails project with a frontend, this is an API-only project):rails new market_money -T -d postgresql --api
-
Set Up SimpleCov to track test coverage in your rails-engine API project.
-
Download market_money_development.pgdump and move it into the
/db/
folder in another folder called/data/
, so your project files look like this:
/app
/bin
/config
/db
/data # <-- create this folder
market_money_development.pgdump # <-- put the .pgdump file in the data folder
seeds.rb # <-- seeds.rb is in `/db/` folder, not `/db/data/`
/lib
/log
etc
- Note that this file is in a binary format and your browser may try to automatically download the file instead of viewing it. Also, opening this file in VSCode directly will produce a warning, and a lot of garbled text. It is NOT meant to be human-readable.
- Set up your
db/seeds.rb
file with the following content:cmd = "pg_restore --verbose --clean --no-acl --no-owner -h localhost -U $(whoami) -d market_money_development db/data/market_money_development.pgdump" puts "Loading PostgreSQL Data dump into local database with command:" puts cmd system(cmd)
-
Run
rails db:{drop,create,migrate,seed}
and you may see lots of output including some warnings/errors frompg_restore
that you can ignore. If you’re unsure about the errors you’re seeing, ask an instructor. - Run
rails db:schema:dump
. Then, check to see that yourschema.rb
exists and has the proper tables/attributes that match the data in Postico. You can do the following to check to see if you have set up rails to effectively communicate with the database.- Add a
market.rb
file to your models directory - Create a
Market
class that inherits fromApplicationRecord
- run
rails c
to jump into your rails console. - run
Market.first
to see the object:##<Market id: 322458, name: "14&U Farmers' Market", street: "1400 U Street NW ", city: "Washington", county: "District of Columbia", state: "District of Columbia", zip: "20009", lat: "38.9169984", lon: "-77.0320505">
- run
Market.last
to see the object:#<Market id: 331081, name: "Year-Round Cedar City Farmer's Market ", street: "905 South Main Street at IFA", city: "Cedar City", county: nil, state: "Utah", zip: "84720", lat: "37.6619", lon: "-113.069">
- If this all checks out you should be good to go.
- Add a
- Use a tool like Postico to examine the 3 tables that were created. Pay careful attention to the data types of each field:
- markets
- vendors
- market_vendors
NOTE If your rails new ...
project name from above is NOT exactly called “market_money” you will need to modify the cmd
variable to change the -d
parameter from market_money_development
to <YOUR PROJECT NAME>_development
instead. If you have questions, ask your instructors.
Finally, commit your setup steps and push to a new repo. Share that new repo with your project partner(s). Be sure to add them as a collaborator.
2. General Requirements
Required Endpoints
You will need to expose the data through a multitude of API endpoints. All of your endpoints should follow these technical expectations:
- All endpoints should be fully tested for happy path AND sad path. The Postman tests are not a substitute for writing your own tests.
- All endpoints that return data will expect to return JSON data only
- All endpoints should be exposed under an
api
and version (v0
) namespace (e.g./api/v0/markets
) - API will be compliant to the JSON API spec and match our requirements below precisely
- Controller actions should be limited to only the standard Rails actions and follow good RESTful convention.
- Endpoints such as
GET /api/v1/markets/search?parameters
will NOT follow RESTful convention, and that’s okay. Consider creating an action that appears restful. -
In total, you will create 11 endpoints (9 ReSTful, 2 non-ReSTful)
ReSTful Endpoints:
- Market Endpoints
- get all markets
- get one market
- get all vendors for a market
- Vendor Endpoints
- get one vendor
- create a vendor
- update a vendor
- delete a vendor
- MarketVendor Endpoints
- create a market_vendor
- delete a market_vendor
Non-ReSTful Endpoints:
- AR Endpoint
- get all markets within a city or state that’s name or description match a string fragment
- Consume API Endpoint
- get cash dispensers (ATMs) close to a market location
- Market Endpoints
Error Responses
If the user causes an error for which you are sending a 400-series error code, the JSON body of the response should be an error object that contains a detailed description of the error.
For example,
{
"errors": [
{
"detail": "Couldn't find Market with 'id'=123123123123"
}
]
}
You can customize the value of the detail
element. But, you must always have a top level errors
key that points to a collection.
3. API requests/responses, more details
Below, you will see the 11 required endpoints for this project. Click on each endpoint to get more details on what each endpoint should be doing, returning, and considering. Each endpoint also has some example request/responses to help you understand the requirements of the endpoint. Happy Path examples are denoted with a 😁 . Sad Path examples are denoted with a 😭 .
1. Get All Markets
Details:
- This endpoint should follow the pattern of
GET /api/v0/markets
and should return ALL markets in the database. -
In addition to the market’s main attributes, the market resource should also list an attribute for
vendor_count
, which is the number of vendors that are associated with that market.Example #1 😁
Request:
GET /api/v0/markets Content-Type: application/json Accept: application/json
Response:
status: 200
{ "data": [ { "id": "322458", "type": "market", "attributes": { "name": "14&U Farmers' Market", "street": "1400 U Street NW ", "city": "Washington", "county": "District of Columbia", "state": "District of Columbia", "zip": "20009", "lat": "38.9169984", "lon": "-77.0320505", "vendor_count": 1 } }, { "id": "322474", "type": "market", "attributes": { "name": "2nd Street Farmers' Market", "street": "194 second street", "city": "Amherst", "county": "Amherst", "state": "Virginia", "zip": "24521", "lat": "37.583311", "lon": "-79.048573", "vendor_count": 35 } }, ..., ..., ] }
2. Get One Market
Details:
- This endpoint should follow the pattern of
GET /api/v0/markets/:id
. - If a valid market id is passed in, all market attributes, as well as a
vendor_count
should be returned. -
If an invalid market id is passed in, a 404 status as well as a descriptive error message should be sent back in the response.
Example #1 😁
Request:
GET /api/v0/markets/322458 Content-Type: application/json Accept: application/json
Response:
status: 200
{ "data": { "id": "322458", "type": "market", "attributes": { "name": "14&U Farmers' Market", "street": "1400 U Street NW ", "city": "Washington", "county": "District of Columbia", "state": "District of Columbia", "zip": "20009", "lat": "38.9169984", "lon": "-77.0320505", "vendor_count": 1 } } }
Example #2 😭
Request:
GET /api/v0/markets/123123123123 (where `123123123123` is an invalid Market id) Content-Type: application/json Accept: application/json
Response:
status: 404
{ "errors": [ { "detail": "Couldn't find Market with 'id'=123123123123" } ] }
3. Get All Vendors for a Market
Details
- This endpoint should follow the pattern of
GET /api/v0/markets/:id/vendors
- If a valid market id is passed in, a JSON object is sent back with a top-level
data
key that points to a collection of that market’s vendors. Each vendor contains all of it’s attributes. -
If an invalid market id is passed in, a 404 status as well as a descriptive error message should be sent back in the response.
Example #1 😁
Request:
GET /api/v0/markets/322474/vendors Content-Type: application/json Accept: application/json
Response:
status: 200
{ "data": [ { "id": "55297", "type": "vendor", "attributes": { "name": "Orange County Olive Oil", "description": "Handcrafted olive oil made from locally grown olives", "contact_name": "Syble Hamill", "contact_phone": "1-276-593-3530", "credit_accepted": false } }, { "id": "56227", "type": "vendor", "attributes": { "name": "The Vodka Vault", "description": "Handcrafted vodka with a focus on unique and unusual flavors", "contact_name": "Rueben Parker DVM", "contact_phone": "1-140-885-8633", "credit_accepted": true } }, ..., ..., ] }
Example #2 😭
Request:
GET /api/v0/markets/123123123123/vendors (where `123123123123` is an invalid Market id) Content-Type: application/json Accept: application/json
Response:
status: 404
{ "errors": [ { "detail": "Couldn't find Market with 'id'=123123123123" } ] }
4. Get One Vendor
Details
- This endpoint should follow the pattern of
GET /api/v0/vendors/:id
- If a valid vendor id is passed in, a JSON object is sent back with a top-level
data
key that points to the vendor resource with that id, and all attributes for that vendor. -
If an invalid vendor id is passed in, a 404 status as well as a descriptive error message should be sent back in the response.
Example #1 😁
Request:
GET /api/v0/vendors/55297 Content-Type: application/json Accept: application/json
Response:
status: 200
{ "data": { "id": "55297", "type": "vendor", "attributes": { "name": "Orange County Olive Oil", "description": "Handcrafted olive oil made from locally grown olives", "contact_name": "Syble Hamill", "contact_phone": "1-276-593-3530", "credit_accepted": false } } }
Example #2 😭
Request:
GET /api/v0/vendors/123123123123 (where `123123123123` is an invalid Vendor id) Content-Type: application/json Accept: application/json
Response:
status: 404
{ "errors": [ { "detail": "Couldn't find Vendor with 'id'=123123123123" } ] }
5. Create a Vendor
Details
- This endpoint should follow the pattern of
POST /api/v0/vendors
, and should pass ALL attributes required to create a vendor (name
,description
,contact_name
,contact_phone
, andcredit_accepted
) as JSON in the body of the request. (In postman, navigate toBody
tab, selectraw
and change the format toJSON
instead ofText
) - This endpoint should create a new vendor resource.
- A successful response will return a response with a
201
status code, and return the newly created vendor resource. - If any number of attributes are left out in the body of the request, a status code of
400
, as well as a descriptive error message should be sent back in the response. -
Validating the presence of a boolean value can be tricky since
false
is evaluated asnil
. Validating the presence of a field that could be false will generate some a validation error when we don’t mean it to. We’d suggest creating your own custom validation for validating the presence of a boolean field.Example #1 😁
Request:
POST /api/v0/vendors Content-Type: application/json Accept: application/json
Body:
{ "name": "Buzzy Bees", "description": "local honey and wax products", "contact_name": "Berly Couwer", "contact_phone": "8389928383", "credit_accepted": false }
Response:
status: 201
{ "data": { "id": "56542", "type": "vendor", "attributes": { "name": "Buzzy Bees", "description": "local honey and wax products", "contact_name": "Berly Couwer", "contact_phone": "8389928383", "credit_accepted": false } } }
Example #2 😭
Request:
POST /api/v0/vendors Content-Type: application/json Accept: application/json
Body:
{ "name": "Buzzy Bees", "description": "local honey and wax products", "credit_accepted": false }
Response:
status: 400
{ "errors": [ { "detail": "Validation failed: Contact name can't be blank, Contact phone can't be blank" } ] }
6. Update a Vendor
Details
- This endpoint should follow the pattern of
PATCH /api/v0/vendors/:id
, and can pass any number and combination of attribtues to be updated (name
,description
,contact_name
,contact_phone
, andcredit_accepted
) as JSON in the body of the request. (In postman, navigate toBody
tab, selectraw
and change the format toJSON
instead ofText
) - This endpoint should update an existing vendor with any parameters sent in via the body.
- If someone were to try to update a vendor resource to have a
nil
or empty attribute, a proper 400-level status code as well as a descriptive error message should be sent back in the response. -
A successful response will return the newly updated vendor resource.
Example #1 😁
Request:
PATCH /api/v0/vendors/56542 Content-Type: application/json Accept: application/json
Body:
{ "contact_name": "Kimberly Couwer", "credit_accepted": false }
Response:
status: 200
{ "data": { "id": "56542", "type": "vendor", "attributes": { "name": "Buzzy Bees", "description": "local honey and wax products", "contact_name": "Kimberly Couwer", "contact_phone": "8389928383", "credit_accepted": false } } }
Example #2 😭
Request:
PATCH /api/v0/vendors/123123123123 (where `123123123123` is an invalid Vendor id) Content-Type: application/json Accept: application/json
Body:
{ "contact_name": "Kimberly Couwer", "credit_accepted": false }
Response:
status: 404
{ "errors": [ { "detail": "Couldn't find Vendor with 'id'=123123123123" } ] }
Example #3 😭
Request:
PATCH /api/v0/vendors/56542 (where `56542` is a valid Vendor id) Content-Type: application/json Accept: application/json
Body:
{ "contact_name": "", "credit_accepted": false }
Response:
status: 400
{ "errors": [ { "detail": "Validation failed: Contact name can't be blank" } ] }
7. Delete a Vendor
Details
- This endpoint should follow the pattern of
DELETE /api/v0/vendors/:id
- When a valid id is passed in, that vendor will be destroyed, as well as any associations that vendor had. A status code of
204
should be sent back, without any content in the body. -
If an invalid id is passed in, a 404 status code as well as a descriptive message should be sent back with the response.
Example #1 😁
Request:
DELETE /api/v0/vendors/56542 Content-Type: application/json Accept: application/json
Response:
status: 204
Example #2 😭
Request:
DELETE /api/v0/vendors/123123123123 (where `123123123123` is an invalid Vendor id) Content-Type: application/json Accept: application/json
Response:
status: 404
{ "errors": [ { "detail": "Couldn't find Vendor with 'id'=123123123123" } ] }
8. Create a MarketVendor
Details
- This endpoint should follow the pattern of
POST /api/v0/market_vendors
, and it should create a new association between a market and a vendor (so then, the vendor has a new market that they sell at). - When valid ids for vendor and market are passed in, a MarketVendor will be created, and a response will be sent back with a
201
status, detailing that a Vendor was added to a Market. - After implementing the happy path for this endpoint, run it, and check that when you call
GET /api/v0/markets/:id/vendors
for the market to which you just added a vendor, that you see the newly associated vendor listed. - If an invalid vendor id or and invalid market id is passed in, a
404
status code as well as a descriptive message should be sent back with the response. - If a vendor id and/or a market id are not passed in, a
400
status code as well as a descriptive message should be sent back with the response. -
If there already exists a MarketVendor with that
market_id
and thatvendor_id
, a response with a422
status code and a message informing the client that that association already exists, should be sent back. Looking at custom validation might help to implement a validation for uniqueness of the attributes for this resource.Example #1 😁
Request:
POST /api/v0/market_vendors Content-Type: application/json Accept: application/json
Body:
{ "market_id": 322474, "vendor_id": 54861 } (where 322474 and 54861 are valid market and vendor id's.)
Response:
status: 201
{ "message": "Successfully added vendor to market" }
Example #2 😭
Request:
POST /api/v0/market_vendors Content-Type: application/json Accept: application/json
Body:
{ "market_id": 987654321, "vendor_id": 54861 } (where 987654321 is an invalid market id)
Response:
status: 404
{ "errors": [ { "detail": "Validation failed: Market must exist" } ] }
Example #3 😭
Request:
POST /api/v0/market_vendors Content-Type: application/json Accept: application/json
Body:
{ "market_id": 322474, "vendor_id": 54861 } (where 322474 and 54861 are valid market and vendor id's, but an existing MarketVendor with those values already exists.)
Response:
status: 422
{ "errors": [ { "detail": "Validation failed: Market vendor asociation between market with market_id=70 and vendor_id=1150 already exists" } ] }
9. Delete a MarketVendor
Details
- This endpoint should follow the pattern of
DELETE /api/v0/market_vendors
, and it should destroy an existing association between a market and a vendor (so that a vendor no longer is listed at a certain market). - The
market_id
and thevendor_id
should be passed in via the body. - When a MarketVendor resource can be found with the passed in
vendor_id
andmarket_id
, that resource should be destroyed, and a response will be sent back with a204
status, with nothing returned in the body of the request. - After implementing the happy path for this endpoint, run it, and check that when you call
GET /api/v0/markets/:id/vendors
for the market in which you just deleted an association to a vendor, that you don’t see the recently removed vendor listed. -
If a MarketVendor resource can NOT be found with the passed in
vendor_id
andmarket_id
, a 404 status code as well as a descriptive message should be sent back with the response.Example #1 😁
Request:
DELETE /api/v0/market_vendors Content-Type: application/json Accept: application/json
Body:
{ "market_id": 322474, "vendor_id": 54861 }
Response:
status: 204
Example #2 😭
Request:
DELETE /api/v0/market_vendors Content-Type: application/json Accept: application/json
Body:
{ "market_id": 4233, "vendor_id": 11520 } (where there is no MarketVendor that has a market_id=4233 AND a vendor_id=11520)
Response:
status: 404
{ "errors": [ { "detail": "No MarketVendor with market_id=4233 AND vendor_id=11520 exists" } ] }
10. Search Markets by state, city, and/or name
Details:
- The endpoint should be in the pattern of
GET /api/v0/markets/search
, and can acceptcity
,state
, andname
parameters. - The following combination of parameters can be sent in at any time:
state
state
,city
state
,city
,name
state
,name
name
- The following combination of parameters can NOT be sent in at any time:
city
city
,name
- If an invalid set of parameters are sent in, a proper error message should be sent back, along with a
422
status code. - In the event that valid parameters are sent in, and only one market is returned from the search, the
data
top level key should still point to an array holding that one market resource data. -
Similar to above, in the event that valid parameters are sent in, and NO markets are returned, the
data
top level key should point to an empty array. And a status code of200
should still be returnedExample #1 😁
Request:
GET /api/v0/markets/search?city=albuquerque&state=new Mexico&name=Nob hill Content-Type: application/json Accept: application/json
Response:
status: 200
{ "data": [ { "id": "327794", "type": "market", "attributes": { "name": "Nob Hill Growers' Market", "street": "Lead & Morningside SE", "city": "Albuquerque", "county": "Bernalillo", "state": "New Mexico", "zip": null, "lat": "35.077529", "lon": "-106.600449", "vendor_count": 5 } } ] }
Example #2 😭
Request:
GET /api/v0/markets/search?city=albuquerque Content-Type: application/json Accept: application/json
Response:
status: 422
{ "errors": [ { "detail": "Invalid set of parameters. Please provide a valid set of parameters to perform a search with this endpoint." } ] }
11. Get Cash Dispensers Near a Market
Details:
- The endpoint should be in the pattern of
GET /api/v0/markets/:id/nearest_atms
- You will need to utilize the TomTom API for this. Specifically, the category search endpoint. Find a category that would work for ATM’s, and use the API to find ATM’s near the location of the Farmer’s Market.
- The atms that are returned should be in the order of closest to furthest away.
- If an invalid market id is passed in, a 404 status as well as a descriptive error message should be sent back in the response.
-
The
data
top level key should always point to an array even if one or zero atms were located near the market location.Example #1 😁
Request:
GET /api/v0/markets/327794/nearest_atms Content-Type: application/json Accept: application/json
Response:
status: 200
{ "data": [ { "id": null, "type": "atm", "attributes": { "name": "ATM", "address": "3902 Central Avenue Southeast, Albuquerque, NM 87108", "lat": 35.07904, "lon": -106.60068, "distance": 0.10521432030421865 } }, { "id": null, "type": "atm", "attributes": { "name": "ATM", "address": "4100 Central Avenue Southeast, Albuquerque, NM 87108", "lat": 35.0788, "lon": -106.59842, "distance": 0.14448001321588486 } }, ..., ..., ..., ] }
Example #2 😭
Request:
GET /api/v0/markets/123123123123/nearest_atm (where `123123123123` is an invalid Market id) Content-Type: application/json Accept: application/json
Response:
status: 404
{ "errors": [ { "detail": "Couldn't find Market with 'id'=123123123123" } ] }