Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
b54122b
Change line per Dan's instructions
madaleines Sep 4, 2018
15f4230
Add simplecov
madaleines Sep 4, 2018
a09dc7d
Create files for necessary classes and specs that correlate"
madaleines Sep 4, 2018
429f097
begin writing attributes and behavior for each class
madaleines Sep 4, 2018
74bc59f
Add duration method
madaleines Sep 4, 2018
a441636
Def list_reservations
madaleines Sep 4, 2018
000220e
Add lines to require relative for lib files
madaleines Sep 4, 2018
b06bd0e
Begin writing tests for room instance
madaleines Sep 4, 2018
18898cc
remove super in input, and modify checking room number range
madaleines Sep 4, 2018
b228e7c
Save minor changes
madaleines Sep 4, 2018
7885738
Add more tests for an instance of Room
madaleines Sep 4, 2018
37f92ca
Write tests to add booked reservations to a room
madaleines Sep 4, 2018
ad982ae
Rename methods and variables in preparation for tests
madaleines Sep 5, 2018
ed4f567
Begin writing tests for creating a Reservation instance
madaleines Sep 5, 2018
d0c316b
Fix var name
madaleines Sep 5, 2018
b22dfee
Write tests for methods for Reservation
madaleines Sep 5, 2018
ff868d2
Remove status attribute
madaleines Sep 5, 2018
b4b9624
Remove tests for status attribute
madaleines Sep 5, 2018
fb34629
Rewrite methods and tests to accurately determine how many nights stayed
madaleines Sep 5, 2018
2244510
Create DateRange class
madaleines Sep 5, 2018
7cdfe2f
Create spec file with tests for DateRange class
madaleines Sep 5, 2018
1470dfc
Modify code to take DateRange
madaleines Sep 5, 2018
8b489fa
Modify tests to take into account DateRange
madaleines Sep 5, 2018
624b788
Begin code to list reservations
madaleines Sep 5, 2018
c750086
Remove classes not planning to use
madaleines Sep 5, 2018
bf6ef69
Begin writing methods to find available rooms to reserve it
madaleines Sep 6, 2018
d476813
add to reserve room method
madaleines Sep 6, 2018
7f1cef2
Begin creating rooms in reservation_tracker and write code to find av…
madaleines Sep 6, 2018
332266d
Add date_range
madaleines Sep 6, 2018
967b5e4
Begin writing tests for reservation_tracker
madaleines Sep 6, 2018
d2bd211
Edit find_unavailable_rooms method
madaleines Sep 6, 2018
2d227b3
write tests and modify code as needed to pass tests
madaleines Sep 6, 2018
589c382
Add date everywhere and modify tests and code to pass tests
madaleines Sep 7, 2018
9fccd84
modify tests and delete room class and tests
madaleines Sep 7, 2018
e0ae3d0
Modify method names, remove commented out and unnecessary code, adjus…
madaleines Sep 7, 2018
3ca3159
Make changes for calling upon DateRange in all classes to loosen depe…
madaleines Sep 7, 2018
44d734f
Remove get_range as needed as it is called in the method overlaps?
madaleines Sep 7, 2018
7a130e9
Write helper method to find overlapping reservations to lead into fin…
madaleines Sep 7, 2018
39b1dc4
Remove binding.pry
madaleines Sep 7, 2018
9648ad7
Write tests and code if there are no rooms available
madaleines Sep 7, 2018
78c6e3f
Change ArgumentError to Standard Error for if end_date is before star…
madaleines Sep 7, 2018
5c5927c
Add standard error and tests if start_date and end_date are not dates
madaleines Sep 7, 2018
2d93ea1
Write further tests if start_date or end_date is a Date and the other…
madaleines Sep 7, 2018
a31731d
Modify raise argument error if no rooms are available
madaleines Sep 7, 2018
2368802
Add passing tests to find_first_available_room method
madaleines Sep 7, 2018
f961658
Write passing tests for reserve_room method
madaleines Sep 7, 2018
543f239
Move argument error to method in private
madaleines Sep 7, 2018
cb79779
remove argument error and tests for checking NUM_OF_ROOMS
madaleines Sep 7, 2018
1c35334
Refactor load_rooms method
madaleines Sep 7, 2018
315d098
remove unnecessary spacing and require's
madaleines Sep 7, 2018
0c5ef02
Begin writing edge cases for overlaps? method
madaleines Sep 7, 2018
8561951
Write passing tests to check if the date range for a reservation conf…
madaleines Sep 7, 2018
ae5bbc4
Clean up slightly
madaleines Sep 7, 2018
1b5b5ca
Begin creating Block class for wave 3
madaleines Sep 7, 2018
4db99a0
Modify Standard Errors and moved to private
madaleines Sep 7, 2018
bbdf4fc
Begin writing code to block_rooms in ReservationTracker and also writ…
madaleines Sep 8, 2018
a03c2c0
Write test in reservation spec to accurately calculate the reservatio…
madaleines Sep 8, 2018
0545a73
Write passing tests that the num requested to block rooms is a valid amt
madaleines Sep 8, 2018
67a83a6
Write passing tests and modify code for get_blocked_rooms method
madaleines Sep 8, 2018
1551edf
Write tests to block_rooms method and reserve rooms with block_id
madaleines Sep 8, 2018
e16f5a6
Change num_rooms to be called 'party'
madaleines Sep 8, 2018
d871608
Begin writing code and break up helper methods to take blocked rooms …
madaleines Sep 8, 2018
7d47391
Move errors for invalid dates to Reservation Tracker away from DateRa…
madaleines Sep 8, 2018
ba827a8
Move before do block
madaleines Sep 8, 2018
e78a463
Write tests to find blocked rooms and rename misc. variables
madaleines Sep 8, 2018
4adee57
remove require pry
madaleines Sep 8, 2018
effff4e
Rename variables
madaleines Sep 8, 2018
cd8755c
Rename variables again
madaleines Sep 8, 2018
aa5f229
Create Room class to take unique rates if desired
madaleines Sep 8, 2018
19b27cc
Change code to take into account new Room instance
madaleines Sep 8, 2018
12f0707
Write tests when calculating cost for unique rates entered as well as…
madaleines Sep 8, 2018
70994a9
Add standard error to room class if rate entered is invalid
madaleines Sep 9, 2018
dadd039
Create support folder with empy csv files
madaleines Sep 9, 2018
872668d
Write some csv files
madaleines Sep 10, 2018
6680842
Add file without block_id
madaleines Sep 10, 2018
cd4bf27
Load CSV info for rooms
madaleines Sep 10, 2018
41b8f35
Change spec to specs
madaleines Sep 10, 2018
ac08c97
Uncomment tests and delete unnecessary commented suff
madaleines Sep 10, 2018
38719df
Remove unnecessary comments, can't figure out csv with date_range
madaleines Sep 10, 2018
1097230
remove pry
madaleines Sep 10, 2018
5a0dc5a
load blocks with csv info
madaleines Sep 10, 2018
c25ccfe
properly load block csv data and modify tests as needed
madaleines Sep 10, 2018
a26a18a
Refactor and fix spacing
madaleines Sep 10, 2018
bb42116
begin modifying main.rb
madaleines Sep 11, 2018
02326cd
Add test that dates cannot be equal
madaleines Sep 11, 2018
c9d819e
Refactor, move thigns around, fix tests
madaleines Sep 11, 2018
757ac95
Modify main more to be more interactive
madaleines Sep 11, 2018
e41ae6d
Fix part where checking if requested dates overlaps with reservation …
madaleines Sep 12, 2018
9224ebb
Finish main.rb
madaleines Sep 12, 2018
3fe9e89
Write private method to check if start_date entered to reserve or blo…
madaleines Sep 12, 2018
513d04a
Change var blocked_rooms to blocks
madaleines Sep 12, 2018
7453a40
Change block_room to block
madaleines Sep 12, 2018
376df99
Refactor
madaleines Sep 12, 2018
6381459
Change variable names
madaleines Sep 12, 2018
43d3a28
Refactor to not say return true or false explicitly
madaleines Sep 14, 2018
a95ad3e
Begin moving methods to check date validity to to DateRange class
madaleines Oct 1, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion Guardfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
guard :minitest, bundler: false, rubygems: false do
guard :minitest, bundler: false, autorun: false, rubygems: false do
# with Minitest::Spec
watch(%r{^spec/(.*)_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ require 'rake/testtask'
Rake::TestTask.new do |t|
t.libs = ["lib"]
t.warning = true
t.test_files = FileList['spec/*_spec.rb']
t.test_files = FileList['specs/*_spec.rb']
end

task default: :test
11 changes: 11 additions & 0 deletions lib/block.rb
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
50 changes: 50 additions & 0 deletions lib/date_range.rb
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
25 changes: 25 additions & 0 deletions lib/reservation.rb
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]

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] is nil, if it is use nil explicitly otherwise use input[:block_id]. Why not just say @block_id = input[:block_id]?

@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
242 changes: 242 additions & 0 deletions lib/reservation_tracker.rb
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

Choose a reason for hiding this comment

The 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 find_block or find_block_by_id might be better.


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)

Choose a reason for hiding this comment

The 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. return instead of matching_reservations =, or end with return matching_reservations).

Third, I think you're somewhat misusing find_all. The block to find_all should produce a true or false value, whereas yours produces either the reservation (if the date range overlaps) or the nil (if it does not). This ends up working, but is not particularly clear. I would rewrite this loop as:

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)

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The 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 = {

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The 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 private though, because then people outside this class won't be able to access it (for example to rescue those specific exceptions).

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
26 changes: 26 additions & 0 deletions lib/room.rb
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")

Choose a reason for hiding this comment

The 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
Loading