Skip to content

Conversation

@npogodina
Copy link

Assignment Submission: Video Store API

Congratulations! You're submitting your assignment. Please reflect on the assignment with these questions.

If you didn't get to the functionality the question is asking about, reply with what you would have done if you had completed it.

Reflection

Prompt Response
Explain how you came up with the initial design of your ERD, based on the seed data and reading through the API endpoints The seeds contained the available information for customers and videos which I turned into two Models (Customer and Video) with corresponding attributes. The API documentation gave insight into the renting process which allowed me to design a Rental model.
What would be the Big-O time complexity of your /customers & /videos endpoints? What does the time complexity depend on? Explain your reasoning. The index route will be O(n) because the size of the displayed data will directly depend on the table size in the database. The show route will be O(1) because it's a look-up with id (primary key). And for the create route, I want to say O(1) because it merely inserts one entry into the table, but it probably makes sense to call it O(n) as well because it somewhat depends on the size of the input (what if the video has a very long overview? :))
What is the Big-O time complexity of the POST /rentals/check-in endpoint? What does the time complexity depend on? Explain your reasoning. I think check-in route is O(n), because it has to find the rental which matches the requirements (provided customer ID and video ID). Since rental ID is not provided, it's not a fast look-up.
Describe a specific set of positive and negative test cases you implemented for a model. For example, I have a validation rule for :total_inventory attribute of Video model - it is required and it should be an integer greater than 0. I have a positive test case for when it is a valid number, and multiple negative tests - when it is not an integer, but a string, when it is a negative number and when it is zero.
Describe a specific set of positive and negative test cases you implemented for a controller. When testing an index route for videos, I expect the response to be 200 success and in JSON format. Specifically, I expect the response to be an array of hashes with information about videos. And I also test for a negative (edge) case when there are no videos in the database.
Broadly, describe how an API should respond when it handles a request with invalid/erroneous parameters. It renders JSON with errors field that displays the error message (what went wrong). It also returns a corresponding response status (not fount or bad request).
Describe one of your custom model methods and why you chose to wrap that functionality into a method. I have a default values method for Rental model which sets the due date to 7 days in advance from today's date. I call this method before saving the entry so that it automatically fills in the due date.

@kaidamasaki
Copy link

kaidamasaki commented Jun 8, 2020

Video Store API

Major Learning Goals/Code Review

Criteria yes/no, and optionally any details/lines of code to reference
Practices git with at least 10 small commits and meaningful commit messages ✔️
Understands the importance of how APIs handle invalid/erroneous data in their reflection questions ✔️
Practices Rails best practices and well-designed separation, and encapsulates business logic around check-out and check-in in models Check-in/out logic in controller.
Uses controller tests to ensure quality code for every route, and checks for response status and content, with positive cases and negative cases ✔️
Uses controller tests to ensure correctness for check out and check in requirement, and that checked out counts and inventories appropriately change ✔️

Functional Requirements

Functional Requirement yes/no
All provided smoke tests for Wave 1 pass ✔️
All provided smoke tests for Wave 2 pass ✔️

Overall Feedback

Overall Feedback Criteria yes/no
Green (Meets/Exceeds Standards) 3+ in Code Review && 2 in Functional Requirements ✔️
Yellow (Approaches Standards) 2+ in Code Review && 1+ in Functional Requirements, or the instructor judges that this project needs special attention
Red (Not at Standard) 0-1 in Code Review or 0 in Functional Reqs, or assignment is breaking/doesn’t run with less than 5 minutes of debugging, or the instructor judges that this project needs special attention

Additional Feedback

Comprehension Questions

I'm just expanding on these because I know that you like detailed explanations.

The show route will be O(1) because it's a look-up with id (primary key).

Looking up on the primary key in a database is generally an O(log(n)) operation since database tables are stored as trees (and are typically organized by primary keys). However, the base of that log tends to be very large because they use a structure called a B-Tree. (Feel free to ask me more about B-Trees if the Wikipedia article is confusing.)

I think check-in route is O(n), because it has to find the rental which matches the requirements (provided customer ID and video ID). Since rental ID is not provided, it's not a fast look-up.

This is actually also a quite fast operation. Because you added indexes to your foreign keys the database actually created one additional B-Tree per reference which means looking up against each of these is a O(log(n)) operation which means that the total complexity of check-in is O(log(n)).

(A nice thing about databases is that you don't have to tell them to use indexes, they just do automatically when you reference columns.)

Code Style Bonus Awards

Was the code particularly impressive in code style for any of these reasons (or more...?)

Quality Yes?
Perfect Indentation
Elegant/Clever
Descriptive/Readable
Concise
Logical/Organized

Copy link

@kaidamasaki kaidamasaki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job!

Overall your code was quite clean and clear. There were a few little things I'd refresh on for your next project. It's probably worth taking another look at fixtures and model methods.

Your controllers were clearly defined and I appreciated how you split out your logic and especially how thorough your tests were.

Comment on lines +6 to +25
rental = Rental.new(customer_id: @customer.id, video_id: @video.id)

if rental.video.available_inventory == 0
render json: { errors: ["No available copies of the video available"] }, status: :bad_request
return
end

if rental.save
rental.customer.videos_checked_out_count += 1
rental.customer.save
rental.video.available_inventory -= 1
rental.video.save

render json: {
customer_id: rental.customer_id,
video_id: rental.video_id,
due_date: rental.due_date,
videos_checked_out_count: rental.customer.videos_checked_out_count,
available_inventory: rental.video.available_inventory
}, status: :ok

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of this logic should have been encapsulated into a model method to handle checking out.

Suggested change
rental = Rental.new(customer_id: @customer.id, video_id: @video.id)
if rental.video.available_inventory == 0
render json: { errors: ["No available copies of the video available"] }, status: :bad_request
return
end
if rental.save
rental.customer.videos_checked_out_count += 1
rental.customer.save
rental.video.available_inventory -= 1
rental.video.save
render json: {
customer_id: rental.customer_id,
video_id: rental.video_id,
due_date: rental.due_date,
videos_checked_out_count: rental.customer.videos_checked_out_count,
available_inventory: rental.video.available_inventory
}, status: :ok
if video.available_inventory == 0
render json: { errors: ["No available copies of the video available"] }, status: :bad_request
return
end
rental = Rental.new(customer: @customer, video: @video)
if rental.check_out
render json: {
customer_id: rental.customer_id,
video_id: rental.video_id,
due_date: rental.due_date,
videos_checked_out_count: rental.customer.videos_checked_out_count,
available_inventory: rental.video.available_inventory
}, status: :ok
class Rental < ApplicationRecord
  # Relationships, validations, etc...

  def check_out(customer, video)
    if rental.save
      rental.customer.videos_checked_out_count += 1
      rental.customer.save
      rental.video.available_inventory -= 1
      rental.video.save
      
      return rental
    else
      return nil
    end
  end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a production application you would also probably want to use a transaction for safety when updating multiple tables like this, though we haven't taught you those (and don't as part of the curriculum due to a lack of time).

Comment on lines +38 to +42
rental.destroy
rental.customer.videos_checked_out_count -= 1
rental.customer.save
rental.video.available_inventory += 1
rental.video.save

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above this should probably live in a rental.check_in method.

class Rental < ApplicationRecord
  # ...
  def check_in
    customer.videos_checked_out_count -= 1
    customer.save
    video.available_inventory += 1
    video.save

    self.destroy
  end
end

Comment on lines +60 to +72
def require_customer
@customer = Customer.find_by(id: params[:customer_id])
if @customer.nil?
render json: { errors: ["Not Found"] }, status: :not_found
end
end

def require_video
@video = Video.find_by(id: params[:video_id])
if @video.nil?
render json: { errors: ["Not Found"] }, status: :not_found
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent controller filters!

Comment on lines +8 to +12
before_save :default_values
def default_values
self.active = true if self.active.nil?
self.due_date = Date.today + 7.day if self.due_date.nil?
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good use of before_safe! I especially like that your default_values method checks to make sure not to overwrite existing values.

Comment on lines +15 to +33
before do
Customer.create(
name: "Evelynn",
registered_at: "Wed, 29 Apr 2015 14:54:14 UTC +00:00",
address: "13133 111th Dr Se",
city: "Monroe",
state: "WA",
postal_code: "98989",
phone: "425 425 44 44",
videos_checked_out_count: 0
)

Video.create(
title: "Cinderella",
overview: "After her father unexpectedly dies, young Ella (Lily James) finds herself at the mercy of her cruel stepmother (Cate Blanchett) and stepsisters, who reduce her to scullery maid. Despite her circumstances, she refuses to despair.",
release_date: "2015-03-06",
total_inventory: 5,
available_inventory: 5
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably should have been part of your fixtures.

Comment on lines +35 to +36
@customer = Customer.find_by(name: "Evelynn")
@video = Video.find_by(title: "Cinderella")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do additional find_bys for these later, which you don't need to do since they already exist in instance variables.

Comment on lines +67 to +73
it "increase the customer's videos_checked_out_count by one" do
expect(@customer.videos_checked_out_count).must_equal 0

post rentals_path, params: @rental_data
customer = Customer.find_by(name: "Evelynn")
expect(customer.videos_checked_out_count).must_equal 1
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could make this cleaner by using must_differ.

Also, it's a good idea to assert on the response code here as well.

Suggested change
it "increase the customer's videos_checked_out_count by one" do
expect(@customer.videos_checked_out_count).must_equal 0
post rentals_path, params: @rental_data
customer = Customer.find_by(name: "Evelynn")
expect(customer.videos_checked_out_count).must_equal 1
end
it "increase the customer's videos_checked_out_count by one" do
expect do
post rentals_path, params: @rental_data
end.must_differ "@customer.refresh.videos_checked_out_count", 1
must_respond_with :ok
end

# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
resources :videos, only: [:index, :show, :create]
resources :customers, only: [:index]
resources :rentals, only: [:create, :destroy]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You didn't need these routes since you only interact with rentals using the custom routes below.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants