This project was a solo, week-long project to practice building a RESTful JSON API. Rails Engine includes record endpoints, relationship endpoints, & business intelligence for merchant, customer, invoice, item, invoice item, and transaction data.
This project uses Ruby on Rails framework version 5.1.6, which can be installed here. The gems required for this project were installed using Bundler.
To retrieve a copy of this project, run the following in the CLI:
git clone [email protected]:Autumn-Martin/rails_engine.git
To run this application in the development environment, run the following in the CLI:
bundle
rake db:reset
rake import:all
In order to spin-up the server, run: rails s
Rspec-Rails is used for testing. FactoryBot is used for creating test data, and Shoulda Matchers 3.1 is used for testing validations on the models.
In order to run tests, perform the following:
rake db:test:prepare
rspec
Turings project expectations are located here and below:
Turing School of Software and Design
Rails Engine
In this project, you will use Rails and ActiveRecord to build a JSON API which exposes the SalesEngine data schema.
The project requirements are listed below:
Learn how to to build Single-Responsibility controllers to provide a well-designed and versioned API. Learn how to use controller tests to drive your design. Use Ruby and ActiveRecord to perform more complicated business intelligence.
All endpoints will expect to return JSON data All endpoints should be exposed under an api and version (v1) namespace (e.g. /api/v1/merchants.json) JSON responses should include ids only for associated records unless otherwise indicated (that is, don’t embed the whole associated record, just the id) Prices are in cents, therefore you will need to transform them in dollars. (12345 becomes 123.45) Remember that for a JSON string to be valid, it needs to contain a key and a value. Data Importing You will create an ActiveRecord model for each entity included in the sales engine data. Your application should include a rake task which imports all of the CSV’s and creates the corresponding records.
Index of Record Each data category should include an index action which renders a JSON representation of all the appropriate records:
Request URL
GET /api/v1/merchants.json
JSON Output (The following is an example of a response if only three records were saved in the database)
[
{
"id":1,
"name":"Schroeder-Jerde"
},
{
"id":2,
"name":"Klein, Rempel and Jones"
},
{
"id":3,
"name":"Willms and Sons"
}
]
Show Record Each data category should include a show action which renders a JSON representation of the appropriate record:
Request URL
GET /api/v1/merchants/1.json
JSON Output:
{
"id":1,
"name":"Schroeder-Jerde"
}
Each data category should offer find finders to return a single object representation. The finder should work with any of the attributes defined on the data type and always be case insensitive.
Request URL
GET /api/v1/merchants/find?parameters
Request Parameters
parameter description
id search based on the primary key
name search based on the name attribute
created_at search based on created_at timestamp
updated_at search based on updated_at timestamp
GET /api/v1/merchants/find?name=Schroeder-Jerde
JSON Output:
{
"id":1,
"name":"Schroeder-Jerde"
}
Each category should offer find_all finders which should return all matches for the given query. It should work with any of the attributes defined on the data type and always be case insensitive.
Request URL
GET /api/v1/merchants/find_all?parameters
Request Parameters: parameter description id search based on the primary key name search based on the name attribute created_at search based on created_at timestamp updated_at search based on updated_at timestamp
GET /api/v1/merchants/find_all?name=Cummings-Thiel
JSON Output
[
{
"id":4,
"name":"Cummings-Thiel"
}
]
Note: Although this search returns one record, it comes back in an array.
Request URL Returns a random resource.
api/v1/merchants/random.json
{
"id": 50,
"name": "Nader-Hyatt"
}
In addition to the direct queries against single resources, we would like to also be able to pull relationship data from the API.
We’ll expose these relationships using nested URLs, as outlined in the sections below.
GET /api/v1/merchants/:id/items
returns a collection of items associated with that merchant
GET /api/v1/merchants/:id/invoices
returns a collection of invoices associated with that merchant from their known orders
GET /api/v1/invoices/:id/transactions
returns a collection of associated transactions
GET /api/v1/invoices/:id/invoice_items
returns a collection of associated invoice items
GET /api/v1/invoices/:id/items
returns a collection of associated items
GET /api/v1/invoices/:id/customer
returns the associated customer
GET /api/v1/invoices/:id/merchant
returns the associated merchant
GET /api/v1/invoice_items/:id/invoice
returns the associated invoice
GET /api/v1/invoice_items/:id/item
returns the associated item
GET /api/v1/items/:id/invoice_items
returns a collection of associated invoice items
GET /api/v1/items/:id/merchant
returns the associated merchant
Transactions
GET /api/v1/transactions/:id/invoice
returns the associated invoice
GET /api/v1/customers/:id/invoices
returns a collection of associated invoices
GET /api/v1/customers/:id/transactions
returns a collection of associated transactions
We want to maintain the original Business Intelligence functionality of SalesEngine, but this time expose the data through our API.
Remember that ActiveRecord is your friend. Much of the complicated logic from your original SalesEngine can be expressed quite succinctly using ActiveRecord queries.
GET /api/v1/merchants/most_revenue?quantity=x
returns the top x merchants ranked by total revenue
GET /api/v1/merchants/most_items?quantity=x
returns the top x merchants ranked by total number of items sold
GET /api/v1/merchants/revenue?date=x
returns the total revenue for date x across all merchants
Assume the dates provided match the format of a standard ActiveRecord timestamp.
GET /api/v1/merchants/:id/revenue
returns the total revenue for that merchant across successful transactions
GET /api/v1/merchants/:id/revenue?date=x
returns the total revenue for that merchant for a specific invoice date x
GET /api/v1/merchants/:id/favorite_customer
returns the customer who has conducted the most total number of successful transactions.
BOSS MODE: GET /api/v1/merchants/:id/customers_with_pending_invoices
returns a collection of customers which have pending (unpaid) invoices. A pending invoice has no transactions with a result of success. This means all transactions are failed. Postgres has an EXCEPT operator that might be useful. ActiveRecord also has a find_by_sql that might help.
NOTE: Failed charges should never be counted in revenue totals or statistics.
NOTE: All revenues should be reported as a float with two decimal places.
GET /api/v1/items/most_revenue?quantity=x
returns the top x items ranked by total revenue generated
GET /api/v1/items/most_items?quantity=x
returns the top x item instances ranked by total number sold
GET /api/v1/items/:id/best_day
returns the date with the most sales for the given item using the invoice date. If there are multiple days with equal number of sales, return the most recent day.
Customers
GET /api/v1/customers/:id/favorite_merchant
returns a merchant where the customer has conducted the most successful transactions
Controller Actions It’s expected that you limit your controller actions to only the standard Rails actions. For endpoints such as GET /api/v1/merchants/find?parameters the initial thought might be to do something like this:
module Api
module V1
class MerchantsController
# code omitted
def find
# code omitted
end
end
end
end
This approach can lead to large controllers. For more info on the reasons why, check out this blog post.
Instead try something like this which adheres to the above approach of only using RESTful actions:
module Api
module V1
module Merchants
class SearchController
def show
# code omitted
end
end
end
end
end
Milestones The organization of this project spec is by feature type. However, much of the unfamiliar or more difficult work is in the business intelligence portion of the project. To the degree possible instructors encourage you to use an agile approach to completing this assignment. Furthermore, we encourage you review the work and develop estimated milestones before you begin. Any good set of milestones will allow for some slippage before the project is due.
There is an Advanced ActiveRecord class scheduled for Wednesday. We highly encourage you to have attempted some of the business intelligence queries before that class.
What to expect from instructors There will not be formal check-ins for this project. Instructors will generally be available during scheduled work time to discuss issues absent other commitments. Students should also view this as an opportunity to practice discussing code on GitHub, and instructors will prefer reviewing PRs to discussing code on Slack.
In their reviews, instructors will go over whatever technical, planning or other challenges you’re having. They also may give you feedback, or suggest a different path than the one you’re on.
Feature Delivery Feature completeness will be determined using the spec harness
-
Completion 4: Project completes all base requirements according to the spec harness. 3: Project completes most requirements but fails 4 or fewer spec harness tests. 2: Project completes most requirements but fails 5-8 spec harness tests. 1: Project fails more than 9 spec harness tests. Technical Quality
-
Test-Driven Development 4: Project demonstrates high test coverage (>90%) and tests at the controller and unit levels. 3: Project demonstrates high test coverage (>80%) and tests at the controller and unit levels. 2: Project demonstrates high test coverage (>70%) but does not adequately balance controller and unit tests. 1: Project does not have 70% test coverage.
-
Code Quality 4: Project demonstrates exceptionally well factored code. 3: Project demonstrates solid code quality and MVC principles. 2: Project demonstrates some gaps in code quality and/or application of MVC principles. 1: Project demonstrates poor factoring and/or understanding of MVC.
-
API Design 4: Project exemplifies API design idioms, with consistent and coherent response structures, serializers to format JSON data, and effective request format handling. 3: Project uses strong and consistent data formats throughout, while relying mostly on standard Rails JSON features. 2: Project has inconsistencies or gaps in how its JSON data is organized or formatted. 1: Project’s API is not fully functional or has significant confusion around request formats.
-
Queries: 4: Project makes great use of ActiveRecord relationships and queries, including some advanced query functionality such as joins and select to create virtual attributes. 3: Project makes good use of ActiveRecord, but drops to ruby enumerables for some query methods. 2: Project has some gaps in ActiveRecord usage, including numerous business methods that rely on ruby enumerables to find the appropriate data. 1: Project struggles to establish a coherent ActiveRecords schema, including missing relationships or dysfunctional queries.