Skip to content

Conversation

@julalam
Copy link

@julalam julalam commented Sep 11, 2017

Hotel

Congratulations! You're submitting your assignment!

Comprehension Questions

Question Answer
Describe a design decision you had to make when working on this project. What options were you considering? What helped you make your final decision? I considered to make more classes for this project. For example I was thinking to have separate class for room objects.
Describe a concept that you gained more clarity on as you worked on this assignment. Got more experienced in working with agile methodology, TDD and line coverage. Also got more comfortable with frequently committing atomic changes.
Describe a nominal test that you wrote for this assignment. Checked that new successful reservation was added to all_reservations instance variable.
Describe an edge case test that you wrote for this assignment. Checked that error was raised when unavailable room was requested for reservation.
How do you feel you did in writing pseudocode first, then writing the tests and then the code? I am still practicing the TDD, so far I would say I am working on the code and on the tests at the same time.

@droberts-sea
Copy link

Hotel

What We're Looking For

Feature Feedback
Baseline
Used git regularly yes - good work!
Answer comprehension questions yes
Design
Each class is responsible for a single piece of the program mostly - there are a few places where Hotel takes on responsibilities that could live in other classes
Classes are loosely coupled mostly - see inline comment in make_reservation_from_block
Wave 1
List rooms yes
Reserve a room for a given date range yes
List reservations for a given date yes
Calculate reservation price yes
Invalid date range produces an error yes
Test coverage some - see inline comment
Wave 2
View available rooms for a given date range yes
Reserving a room that is not available produces an error yes
Test coverage some - see inline comment
Wave 3
Create a block of rooms yes
Check if a block has rooms yes
Reserve a room from a block yes
Test coverage yes
Additional Feedback

Good job overall. Your high-level design is deliberate and well-thought-out, though there are still a couple places where the Hotel class takes on responsibilities it probably shouldn't. The actual Ruby code looks quite good, and though there are some places where test coverage could be expanded, this is a good start. Keep up the hard work!


def dates_within_range
dates = (@check_in .. @check_out - 1).map { |date| date }
return dates

Choose a reason for hiding this comment

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

Here you're mapping the list of dates to itself. You could instead say:

return (@check_in..@check_out-1).to_a

def list_of_available_rooms(date_range)
available_rooms = {}
@rooms.each do |room, price|
booked_dates = room_unavailable(room) #array of dates

Choose a reason for hiding this comment

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

This certainly solves the problem, but the logic is pretty complex, especially when you factor in the call to room_unavailable. Your strategy seems to be:

available_rooms starts empty
for each room
  unavailable_dates = []
  for each reservation or block
    if room matches
      add dates to list of unavailable_dates
  if unavailable_dates does not include desired dates
    add room to available_rooms

The general strategy is to start with an empty list and add rooms to it one by one, and the outer loop is through the set of rooms.

An alternative approach that I think produces cleaner code is to start with a list of all the rooms, and have the outer loop go through reservations and blocks. When you find one that matches the desired date, you remove the room(s) from the list. In pseudocode:

available_rooms = list of all rooms
for each reservation or block rb
  if rb.dates overlaps the desired dates
    remove rb.rooms from available_rooms

Note that we're able to remove the inner loop entirely.


def list_of_reservations(date)
list = @all_reservations.map { |reservation| reservation if reservation.date_range.dates_within_range.include?(date) }

Choose a reason for hiding this comment

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

The select enumerable might be a better choice than map here.


if @check_in.class != Date || @check_out.class != Date || @check_in > @check_out || @check_in < Date.today
raise InvalidDate.new("Invalid date or date range")
end

Choose a reason for hiding this comment

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

Good use of a custom exception here

lib/hotel.rb Outdated
date_range = block.date_range
cost = @rooms[room] * (1 - @block_discount / 100.0)
new_reservation_from_block = Reservation.new(date_range, room, cost)
block.rooms.delete(room)

Choose a reason for hiding this comment

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

This is an example of tight coupling between Hotel and Block. You manipulate the block's date_range and list of rooms from the Hotel method. A cleaner approach might be to define a method Block#reserve_room that creates the reservation and removes the room from its internal list. Then if the implementation of Block ever changes, this code will not have to.

date_range.dates_within_range.each do |date|
if !booked_dates.include?(date)
count += 1
end

Choose a reason for hiding this comment

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

Instead of having this loop here, you might write a method DateRange#overlap?(other_date_range). This would both be easier to read and easier to test!

it "Raise an error if check_in or check_out values are not types of Date class" do
proc { BookingSystem::DateRange.new("2017-9-15", "2017-9-17") }.must_raise BookingSystem::InvalidDate
end
it "Raise an error if given check_out date is before check_in date" do

Choose a reason for hiding this comment

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

Good failure cases!

reservation = @two_room_hotel.make_reservation(@date_range, 1)
second_reservation = @two_room_hotel.make_reservation(@date_range, 2)
proc { @two_room_hotel.list_of_available_rooms(@date_range) }.must_raise BookingSystem::NoRoomAvailableError
end

Choose a reason for hiding this comment

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

Good testing for the failure case here. Other cases I'd be interested in:

  • What if there are no reservations? (it should return a list of all the rooms)
  • Do reservations that don't overlap the date range affect the list? (they shouldn't)
    • Check the boundaries of what counts as an overlap. What about a reservation that ends on the checkin date, or starts on the checkout date?
  • What if your date range spans several small reservations? (all of the rooms from those reservations should be omitted from the list)

end
if available_rooms.length == 0
raise NoRoomAvailableError.new("No room is available on given dates")
end

Choose a reason for hiding this comment

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

I'm not sure I agree that an exception is justified here if there are no available rooms. In that case there's a way to tell the caller what happened using the existing channels: return an empty array.

Once the caller has that information, they can raise an error or not as they see fit.

it "Raise an error if asked to reserve a room that is not available" do
reservation = @two_room_hotel.make_reservation(@date_range, 1)
proc { @two_room_hotel.make_reservation(@date_range, 1) }.must_raise BookingSystem::NoRoomAvailableError
end

Choose a reason for hiding this comment

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

This is a good start, but I'd like to see more coverage around what date ranges will conflict with each other. Cases I'd be interested in:

  • Not available:
    • Same dates
    • Overlaps in the front
    • Overlaps in the back
    • Completely contained
    • Completely containing
  • Available:
    • Completely before
    • Completely after
    • Ends on the checkin date
    • Starts on the checkout date

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