-
Notifications
You must be signed in to change notification settings - Fork 46
Maddie Shields - Edges - Hotel #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
b54122b
15f4230
a09dc7d
429f097
74bc59f
a441636
000220e
b06bd0e
18898cc
b228e7c
7885738
37f92ca
ad982ae
ed4f567
d0c316b
b22dfee
ff868d2
b4b9624
fb34629
2244510
7cdfe2f
1470dfc
8b489fa
624b788
c750086
bf6ef69
d476813
7f1cef2
332266d
967b5e4
d2bd211
2d227b3
589c382
9fccd84
e0ae3d0
3ca3159
44d734f
7a130e9
39b1dc4
9648ad7
78c6e3f
5c5927c
2d93ea1
a31731d
2368802
f961658
543f239
cb79779
1c35334
315d098
0c5ef02
8561951
ae5bbc4
1b5b5ca
4db99a0
bbdf4fc
a03c2c0
0545a73
67a83a6
1551edf
e16f5a6
d871608
7d47391
ba827a8
e78a463
4adee57
effff4e
cd8755c
aa5f229
19b27cc
12f0707
70994a9
dadd039
872668d
6680842
cd4bf27
41b8f35
ac08c97
38719df
1097230
5a0dc5a
c25ccfe
a26a18a
bb42116
02326cd
c9d819e
757ac95
e41ae6d
9224ebb
3fe9e89
513d04a
7453a40
376df99
6381459
43d3a28
a95ad3e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| module Hotel | ||
| class Block | ||
| attr_reader :id, :party, :date_range | ||
|
|
||
| def initialize(input) | ||
| @id = input[:id] | ||
| @party = input[:party] | ||
| @date_range = input[:date_range] | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| module Hotel | ||
| class DateRange | ||
| attr_reader :start_date, :end_date | ||
|
|
||
| def initialize(start_date, end_date) | ||
| @start_date = start_date | ||
| @end_date = end_date | ||
| end | ||
|
|
||
| def confirm_valid_dates(start_date, end_date) | ||
| check_prior_today(start_date) | ||
| check_dates_validity?(start_date, end_date) | ||
| check_dates_order?(start_date, end_date) | ||
| end | ||
|
|
||
| def overlaps?(other) | ||
| other_range = other.get_range | ||
| my_range = self.get_range | ||
| return !(other_range & my_range).empty? | ||
| end | ||
|
|
||
| def get_range | ||
| return [*@start_date...@end_date] | ||
| end | ||
|
|
||
| def find_num_nights | ||
| return @end_date - @start_date | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def check_prior_today(start_date) | ||
| if start_date < Date.today | ||
| raise OldStartDateError.new("You cannot have a start date prior to Today") | ||
| end | ||
| end | ||
|
|
||
| def check_dates_validity(start_date, end_date) | ||
| unless (start_date.is_a?(Date) && end_date.is_a?(Date)) | ||
| raise InvalidDateError.new("That is not a Date type") | ||
| end | ||
| end | ||
|
|
||
| def check_dates_order(start_date, end_date) | ||
| unless start_date < end_date | ||
| raise DatesOrderError.new("Start date must be before end date") | ||
| end | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| BLOCK_DISCOUNT = 0.80 | ||
|
|
||
| module Hotel | ||
| class Reservation | ||
| attr_reader :id, :block_id, :room, :cost, :date_range | ||
|
|
||
| def initialize(input) | ||
| @id = input[:id] | ||
| @block_id = input[:block_id].nil? ? nil : input[:block_id] | ||
| @room = input[:room] | ||
| @date_range = input[:date_range] | ||
| @cost = calculate_cost | ||
| end | ||
|
|
||
| def calculate_cost | ||
| num_of_nights = @date_range.find_num_nights | ||
| rate = @room.rate | ||
|
|
||
| reg_cost = (rate * num_of_nights).round(2) | ||
| blocked_cost = (reg_cost * BLOCK_DISCOUNT).round(2) | ||
|
|
||
| @block_id.nil? ? reg_cost : blocked_cost | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,242 @@ | ||
| require 'csv' | ||
|
|
||
| require_relative 'date_range' | ||
| require_relative 'reservation' | ||
| require_relative 'room' | ||
| require_relative 'block' | ||
|
|
||
| NUM_OF_ROOMS = 20 | ||
| MAX_BLOCK_NUM = 5 | ||
|
|
||
| module Hotel | ||
| class ReservationTracker | ||
| attr_reader :rooms, :blocks, :reservations | ||
|
|
||
| def initialize(room_file = 'support/rooms.csv', block_file = 'support/blocks.csv', reservation_file = 'support/reservations.csv') | ||
| @rooms = load_rooms(room_file) | ||
| @blocks = load_blocks(block_file) | ||
| @reservations = load_reservations(reservation_file) | ||
| end | ||
|
|
||
| def load_rooms(filename) | ||
| rooms = [] | ||
|
|
||
| CSV.read(filename, headers: true).each do |line| | ||
|
|
||
| input_data = {} | ||
| input_data[:room_num] = line[0].to_i | ||
| input_data[:rate] = line[1].to_i | ||
|
|
||
| room = Room.new(input_data) | ||
| rooms << room | ||
| end | ||
| return rooms | ||
| end | ||
|
|
||
| def load_blocks(filename) | ||
| blocks = [] | ||
| block_data = CSV.open(filename, 'r', headers: true, header_converters: :symbol) | ||
|
|
||
| block_data.each do |raw_block| | ||
| start_date = Date.parse(raw_block[:start_date]) | ||
| end_date = Date.parse(raw_block[:end_date]) | ||
|
|
||
| requested_qty = raw_block[:party].to_i | ||
| block_qty_rooms = @rooms.take(requested_qty) | ||
| requested_dates = DateRange.new(start_date, end_date) | ||
|
|
||
| parsed_block = { | ||
| id: raw_block[:id].to_i, | ||
| party: block_qty_rooms, | ||
| date_range: requested_dates | ||
| } | ||
|
|
||
| block = Block.new(parsed_block) | ||
| blocks << block | ||
| end | ||
| return blocks | ||
| end | ||
|
|
||
| def load_reservations(filename) | ||
| reservations = [] | ||
| reservation_data = CSV.open(filename, 'r', headers: true, header_converters: :symbol) | ||
|
|
||
| reservation_data.each do |raw_reservation| | ||
| block_id = find_block_id(raw_reservation[:block_id].to_i) | ||
| room = find_room(raw_reservation[:room].to_i) | ||
|
|
||
| start_date = Date.parse(raw_reservation[:start_date]) | ||
| end_date = Date.parse(raw_reservation[:end_date]) | ||
|
|
||
| range = DateRange.new(start_date, end_date) | ||
|
|
||
| parsed_reservation = { | ||
| id: raw_reservation[:id].to_i, | ||
| block_id: block_id, | ||
| room: room, | ||
| date_range: range | ||
| } | ||
|
|
||
| reservation = Reservation.new(parsed_reservation) | ||
| reservations << reservation | ||
| end | ||
| return reservations | ||
| end | ||
|
|
||
| def find_block_id(id) | ||
| check_id(id) | ||
| return @blocks.find { |block| block.id == id } | ||
| end | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name of this method is a little misleading: it finds a block by ID, it does not find a block ID. Something like |
||
|
|
||
| def find_room(room_num) | ||
| check_room_num(room_num) | ||
| return @rooms.find { |room| room.room_num == room_num } | ||
| end | ||
|
|
||
| def list_reservations_by_date(specified_date) | ||
| reservations_by_date = @reservations.find_all do |reservation| | ||
| date_range = reservation.date_range.get_range | ||
| date_range.include?(specified_date) | ||
| end | ||
| return reservations_by_date | ||
| end | ||
|
|
||
| def reservations_overlaps?(requested_dates) | ||
| matching_reservations = @reservations.find_all do |reservation| | ||
| date_range = reservation.date_range | ||
| reservation if date_range.overlaps?(requested_dates) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have several things to say here. First, this is a great example of loose coupling. Rather than pulling out the dates and doing a bunch of math yourself, you call a method and let the date range take care of it. Right on! Second, I would like to see an explicit return here (i.e. Third, I think you're somewhat misusing matching_reservations = @reservations.find_all do |reservation|
reservation.date_range.overlaps?(requested_dates)
end |
||
| end | ||
| end | ||
|
|
||
| def find_reserved_rooms(requested_dates) | ||
| matching_reservations = reservations_overlaps?(requested_dates) | ||
|
|
||
| reserved_rooms = matching_reservations.map do |reservation| | ||
| reservation.room | ||
| end | ||
| return reserved_rooms | ||
| end | ||
|
|
||
| def find_blocks(requested_dates) | ||
| blocks_by_date = @blocks.map do |room| | ||
| date_range = room.date_range | ||
| room if date_range.overlaps?(requested_dates) | ||
| end | ||
| return blocks_by_date.compact | ||
| end | ||
|
|
||
| def find_unavailable_rooms(requested_dates) | ||
| reserved_rooms = find_reserved_rooms(requested_dates) | ||
| blocks = find_blocks(requested_dates) | ||
| unavailable_rooms = reserved_rooms + blocks | ||
| return unavailable_rooms | ||
| end | ||
|
|
||
| def find_available_rooms(requested_dates) | ||
| unavailable_rooms = find_unavailable_rooms(requested_dates) | ||
| available_rooms = @rooms.reject { |room| unavailable_rooms.include?(room) } | ||
| check_availablity?(available_rooms) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You've done a great job throughout this file of breaking your code up into multiple small methods, each of which solves a small piece of the puzzle. Good work! |
||
| return available_rooms | ||
| end | ||
|
|
||
| def get_first_available_room(requested_dates) | ||
| all_available_rooms = find_available_rooms(requested_dates) | ||
| first_available_room = all_available_rooms.first | ||
| return first_available_room | ||
| end | ||
|
|
||
| def get_requested_dates(start_date, end_date) | ||
| return DateRange.new(start_date, end_date) | ||
| end | ||
|
|
||
| def reserve_room(input) | ||
| requested_dates = get_requested_dates(input[:start_date], input[:end_date]) | ||
|
|
||
| reservation_id = @reservations.length + 1 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the idea of using hash initialization here. However, modern Ruby style is to use keyword arguments to solve the same problem. See https://robots.thoughtbot.com/ruby-2-keyword-arguments#keyword-arguments-vs-options-hash |
||
| block_id = input[:block_id] | ||
| room = get_first_available_room(requested_dates) | ||
|
|
||
| reservation_data = { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't you need to do something different here if this room is part of a block? For example, making sure that the block has a room, and taking that room from the block? |
||
| id: reservation_id, | ||
| block_id: block_id, | ||
| room: room, | ||
| date_range: requested_dates | ||
| } | ||
|
|
||
| new_reservation = Reservation.new(reservation_data) | ||
| @reservations << new_reservation | ||
| return new_reservation | ||
| end | ||
|
|
||
| def confirm_valid_qty?(requested_qty) | ||
| check_valid_num?(requested_qty) | ||
| check_num_requested?(requested_qty) | ||
| end | ||
|
|
||
| def get_blocks(requested_qty, requested_dates) | ||
| confirm_valid_qty?(requested_qty) | ||
| available_rooms = find_available_rooms(requested_dates) | ||
| check_enough_rooms?(available_rooms, requested_qty) | ||
| block = available_rooms.take(requested_qty) | ||
| return block | ||
| end | ||
|
|
||
| def block_rooms(input) | ||
| requested_dates = get_requested_dates(input[:start_date], input[:end_date]) | ||
| block_id = @blocks.length + 1 | ||
| blocked_qty_rooms = get_blocks(input[:party], requested_dates) | ||
|
|
||
| block_data = { | ||
| id: block_id, | ||
| party: blocked_qty_rooms, | ||
| date_range: requested_dates | ||
| } | ||
|
|
||
| new_block = Block.new(block_data) | ||
| @blocks << new_block | ||
| return new_block | ||
| end | ||
|
|
||
| private | ||
|
|
||
| class OldStartDateError < StandardError; end | ||
| class InvalidDateError < StandardError; end | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like that you made all these custom exceptions. You probably don't want to make them |
||
| class DatesOrderError < StandardError; end | ||
| class NoRoomsError < StandardError; end | ||
| class InvalidAmountRoomsError < StandardError; end | ||
| class TooManyRoomsError < StandardError; end | ||
| class NotEnoughError < StandardError; end | ||
|
|
||
| def check_id(id) | ||
| unless id.nil? || id >= 0 | ||
| raise ArgumentError, "ID cannot be less than zero. (got #{id})" | ||
| end | ||
| end | ||
|
|
||
| def check_room_num(num) | ||
| raise ArgumentError, "Room num cannot be less than 1 or greater than #{NUM_OF_ROOMS} (got #{num})" if num <= 0 || num > NUM_OF_ROOMS | ||
| end | ||
|
|
||
| def check_enough_rooms?(available_rooms, qty) | ||
| if available_rooms.length < qty | ||
| raise NotEnoughError.new("There are not enough rooms to block") | ||
| end | ||
| end | ||
|
|
||
| def check_valid_num?(num) | ||
| if !num.is_a?(Integer) || num <= 0 | ||
| raise InvalidAmountRoomsError.new("That is not a valid amount to request to block") | ||
| end | ||
| end | ||
|
|
||
| def check_num_requested?(num) | ||
| if num > MAX_BLOCK_NUM | ||
| raise TooManyRoomsError.new("Cannot block more than 5 rooms") | ||
| end | ||
| end | ||
|
|
||
| def check_availablity?(available_rooms) | ||
| raise NoRoomsError.new("NO ROOMS AVAILABLE!!!") if available_rooms.empty? | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| require_relative 'reservation' | ||
|
|
||
| RATE = 200 | ||
|
|
||
| module Hotel | ||
| class Room | ||
| attr_reader :room_num, :rate | ||
|
|
||
| def initialize(input) | ||
| check_rate_validity?(input[:rate]) | ||
| @room_num = input[:room_num] | ||
| @rate = input[:rate].nil? ? RATE : input[:rate] | ||
| end | ||
|
|
||
| private | ||
|
|
||
| class InvalidRateError < StandardError; end | ||
|
|
||
| def check_rate_validity?(rate) | ||
| valid_types = /^(10|\d)(\.\d{1,2})?$/ | ||
| if valid_types.match(rate.to_s) && !(rate.is_a?(Float) || rate.is_a?(Integer)) | ||
| raise InvalidRateError.new("That is not a valid Rate amount") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are going a little overboard here on type checking here. Trust whoever gives you this variable to give you a value you can use, and trust Ruby to throw an error if something really bad happens. See Metz ch 5 for more on this. |
||
| end | ||
| end | ||
| end | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ternary on line 9 is redundant. You check if
input[:block_id]isnil, if it is usenilexplicitly otherwise useinput[:block_id]. Why not just say@block_id = input[:block_id]?