Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
919217f
Created Room class and test file for it.
npogodina Mar 2, 2020
e84382d
Added ReservationDesk class with #rooms method and its tests.
npogodina Mar 2, 2020
808a723
Added Reservation class and its tests.
npogodina Mar 2, 2020
a19a52c
Added ReservationDesk#new_reservation and add_reservation methods.
npogodina Mar 3, 2020
3daf2a3
Updated arguments to be keyword in Reservation constructor.
npogodina Mar 3, 2020
fbd6f3d
Added ReservationDesk#find_reservations.
npogodina Mar 3, 2020
92fd3e5
Added ReservationDesk#find_room_by_id.
npogodina Mar 3, 2020
d1f4381
Fixed bug when ReservationDesk#rooms was creating a new array each ti…
npogodina Mar 3, 2020
f09559e
Moved ReservationDesk#make_rooms to private.
npogodina Mar 3, 2020
7b6b246
Added ReservationDesk#add_reservation method.
npogodina Mar 3, 2020
9c03227
Added ReservationDesk#find_reservations method.
npogodina Mar 4, 2020
4667767
Added test outlines for ReservationDesk#find_reservations.
npogodina Mar 4, 2020
7a2130b
Changed all ReservationDesk#find_reservations arguments to be require…
npogodina Mar 4, 2020
1627956
Added DateRange class and DateRange#overlap?.
npogodina Mar 4, 2020
4bb0daf
Updated Reservation class and ReservationDesk#find_reservations to us…
npogodina Mar 4, 2020
e4faec3
Updated ReservationDesk#find_reservations: room_id is optional.
npogodina Mar 4, 2020
1054cdc
Added DateRange#nights.
npogodina Mar 5, 2020
517de12
Added Reservation#cost.
npogodina Mar 5, 2020
b8b358c
Added ReservationDesk#find_available_room.
npogodina Mar 5, 2020
296d1e7
Added ReservationDesk#make_reservation.
npogodina Mar 5, 2020
f9687ca
Added SimpleCov.
npogodina Mar 6, 2020
1b758d8
Filtered tests to be excluded out of SimpleCov report.
npogodina Mar 6, 2020
f230fa3
Added ReservationDesk#find_reservations.
npogodina Mar 6, 2020
2691071
Replaced ReservationDesk#find_available_room with check_availability.
npogodina Mar 6, 2020
aa5b820
Added Room#available? and Room#reserve, refactored ReservationDesk.
npogodina Mar 7, 2020
f8e561b
Fixed tests for ReservationDesk#find_reservations.
npogodina Mar 7, 2020
ad95773
Added Room#select_reservations.
npogodina Mar 7, 2020
1bd279c
Added ReservationDesk#make_block and Room#add_block_participation.
npogodina Mar 7, 2020
ca06712
Added functionality to specify rate when making reservation/block.
npogodina Mar 7, 2020
ee4a5ad
Added helper methods Room#reserved? and Room#in_block?.
npogodina Mar 8, 2020
cb44c5f
Added ReservationDesk#reserve_from_block.
npogodina Mar 9, 2020
afdbe86
Added ReservationDesk#check_block_availability.
npogodina Mar 9, 2020
586eef5
Added more tests.
npogodina Mar 9, 2020
47056a5
Cleaned up files, added TODOs.
npogodina Mar 9, 2020
3007f63
Added refactors.txt.
npogodina Mar 9, 2020
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.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/test/tmp/
/test/version_tmp/
/tmp/
/lib/main.rb

# Used by dotenv library to load environment variables.
# .env
Expand Down
25 changes: 25 additions & 0 deletions lib/date_range.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Hotel
class DateRange
attr_reader :start_date, :end_date

def initialize(start_date:, end_date:)
#TODO: validate dates' input
#TODO: add defaults (for example, one year from/before Date.today)
@start_date = Date.parse(start_date)
@end_date = Date.parse(end_date)

raise ArgumentError.new("End date cannot be equal to or come before start date.") if @end_date <= @start_date
end

def overlap?(date_range)
raise ArgumentError.new("Must provide a date range.") unless date_range.is_a? DateRange

#TODO: efficient date checking (first compare years? months?)
start_date < date_range.end_date && end_date > date_range.start_date
end

def nights
(end_date - start_date).to_i
end
end
end
22 changes: 22 additions & 0 deletions lib/reservation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require "date"

module Hotel
class Reservation
attr_reader :date_range, :room_id, :rate

def initialize(room_id: , start_date:, end_date:, rate: :default)
unless (rate == :default || rate.respond_to?(:>) && rate >= 0)
raise ArgumentError.new("Invalid rate.")
end

@date_range = DateRange.new(start_date: start_date, end_date: end_date)
@room_id = room_id
@rate = (rate == :default) ? 200 : rate

Choose a reason for hiding this comment

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

Nice use of a ternary, and an optional keyword argument

end

def cost
date_range.nights * rate
end
end
end

119 changes: 119 additions & 0 deletions lib/reservation_desk.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
module Hotel
class ReservationDesk
# Class responsibility: to organize reservations across hotel
attr_reader :room_num, :rooms, :reservations, :blocks

def initialize(room_num: 20)
@room_num = room_num
@rooms = make_rooms
@blocks = {}
end

def find_room_by_id(id)
rooms.find {|room| room.id == id}
end

def find_reservations(room_id: nil , start_date: , end_date: )
date_range = DateRange.new(start_date: start_date, end_date: end_date)

if room_id
room = rooms.find { |room| room.id == room_id}
return nil if room == nil
reservations = room.select_reservations(date_range)
else
reservations = []
rooms.each do |room|
room_reservations = room.select_reservations(date_range)
reservations += room_reservations
end
end
return reservations
end

def find_available_rooms(start_date:, end_date:)
available_rooms = []
rooms.each do |room|
available_rooms << room if room.available?(start_date: start_date, end_date: end_date)
end
return available_rooms.empty? ? nil : available_rooms
end

def make_reservation(room_id: nil, start_date:, end_date:, rate: :default)
if room_id
room = find_room_by_id(room_id)
raise ArgumentError.new("Invalid room ID.") if room == nil
else
room = find_available_rooms(start_date: start_date, end_date: end_date)[0]
raise StandardError.new("No rooms available for these dates.") if room == nil
end
room.reserve(start_date: start_date, end_date: end_date, rate: rate)
end

def make_block(room_ids:, start_date:, end_date:, rate: :default)
#TODO: add no rooms given scenario
raise ArgumentError.new("Blocks should contain 2-5 rooms.") if (room_ids.length > 5 || room_ids.length < 2)

block_id = create_block(start_date: start_date, end_date: end_date)
room_ids.each do |id|
room = find_room_by_id(id)
raise ArgumentError.new("Invalid room ID.") if room == nil
room.reserve(start_date: start_date, end_date: end_date, block: block_id, rate: rate)
blocks[block_id][:rooms] << room
end
end

def reserve_from_block(room_id: , block_id: )
raise ArgumentError.new("Invalid block ID.") unless blocks[block_id]

blocks[block_id][:rooms].each do |room|
if room.id == room_id
start_date = blocks[block_id][:date_range][0]
end_date = blocks[block_id][:date_range][1]
unless room.reserved?(start_date: start_date, end_date: end_date)
room.block_participation.each do |block_part|
if block_part.date_range.start_date == Date.parse(start_date)
room.reservations << block_part
return true
end
end
end
raise StandardError.new("This room has already been reserved.")
end
end
raise ArgumentError.new("Invalid room ID.")
end

def check_block_availability(block_id)
raise ArgumentError.new("Invalid block ID.") unless blocks[block_id]
available_rooms = []
start_date = blocks[block_id][:date_range][0]
end_date = blocks[block_id][:date_range][1]

blocks[block_id][:rooms].each do |room|
available_rooms << room unless room.reserved?(start_date: start_date, end_date: end_date)
end
return available_rooms
end

private
def make_rooms
rooms = []
room_num.times do |i|
rooms << Room.new(i+1)
end
return rooms
end

def generate_block_id
blocks.length + 1
end

def create_block(start_date:, end_date:)
block_id = generate_block_id
blocks[block_id] = {}
blocks[block_id][:date_range] = [start_date, end_date]
blocks[block_id][:rooms] = []
return block_id
end
end
end
64 changes: 64 additions & 0 deletions lib/room.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
module Hotel

class Room
# Class responsibility: to reserve individual rooms
attr_reader :id, :reservations, :block_participation

def initialize(id)
@id = id
@reservations = []
@block_participation = []
end

# available? helper method / public interface
def reserved?(start_date:, end_date:)
date_range = DateRange.new(start_date: start_date, end_date: end_date)
reservations.each do |reservation|
if reservation.date_range.overlap? (date_range)
return true
end
end
return false
end

# available? helper method / public interface
def in_block?(start_date:, end_date:)
date_range = DateRange.new(start_date: start_date, end_date: end_date)
block_participation.each do |block_reservation|
if block_reservation.date_range.overlap? (date_range)
return true
end
end
return false
end

def available?(start_date:, end_date:)
(reserved?(start_date: start_date, end_date: end_date) || in_block?(start_date: start_date, end_date: end_date)) ? false : true
end

def reserve(start_date:, end_date:, block: nil, rate: :default)
unless available?(start_date: start_date, end_date: end_date)
raise StandardError.new("Room #{id} is unavailabe for requested dates.")
end
reservation = new_reservation(start_date: start_date, end_date: end_date, rate: rate)
block ? add_block_participation(reservation) : add_reservation(reservation)
end

def select_reservations(date_range)
reservations.select { |reservation| reservation.date_range.overlap? (date_range)}
end

private
def new_reservation(start_date:, end_date:, rate: :default)
reservation = Reservation.new(room_id: id, start_date: start_date, end_date: end_date, rate: rate)
end

def add_reservation(reservation)
reservations << reservation
end

def add_block_participation(reservation)
block_participation << reservation
end
end
end
17 changes: 17 additions & 0 deletions refactors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
1. Consider adding Block Class instead of hash structure in ReservationDesk
- ReservationDesk will have a blocks array of Block objects.
- ReservationDesk will have find_block_by_id method.
- Block object will have block_id, rooms (array of Room objects), date_range variables.
- Block object will have reserve_from_block and check_room_availability methods which will be called from inside current ReservationDesk methods.

2. Add date input validation.

3. Add defaults for start and end dates.
(gives functionality to select all reservations from/until specific date)

4. Find a more efficient way to compare dates.
(sort and compare years/months first?..)

5. Consider moving away from having start and end_date as arguments towards using date_range.

6. Implement "no rooms given" scenario for make_block.
76 changes: 76 additions & 0 deletions test/date_range_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require_relative "test_helper"

describe "DateRange class" do
describe "DateRange instatiation" do
# before do
# date_range = Hotel::DateRange.new()
# end
it "is an instance of DateRange" do
expect(Hotel::DateRange.new(start_date: "2020-3-1", end_date: "2020-3-10")).must_be_kind_of Hotel::DateRange
end

it "converts its start_date and end_date attributes to Date objects" do
date_range = Hotel::DateRange.new(start_date: "2020-3-1", end_date: "2020-3-10")
expect(date_range.start_date).must_be_kind_of Date
expect(date_range.end_date).must_be_kind_of Date
end

it "raises an ArgumentError if end_date is equal to or comes before start_date" do
expect {Hotel::DateRange.new(start_date: "2020-3-1", end_date: "2020-2-10")}.must_raise ArgumentError
expect {Hotel::DateRange.new(start_date: "2020-3-1", end_date: "2020-3-1")}.must_raise ArgumentError
end

# TODO
# it "can be instantiated with no arguments" do
# expect(Hotel::DateRange.new()).must_be_kind_of Hotel::DateRange
# end
end

describe "overlap?" do
let (:date_range) {
Hotel::DateRange.new(start_date: "2020-3-1", end_date: "2020-3-20")
}

it "expects a DateRange as an argument" do
expect {date_range.overlap?(50)}.must_raise ArgumentError
end

it "returns true if the dates overlap and false if not" do
# TODO: each one should be a new test?

Choose a reason for hiding this comment

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

Since each other this expects are testing a different way that dates could overlap, it would be good to put each of these assertions in their own it block that describes exactly what you are testing.

second_date_range = Hotel::DateRange.new(start_date: "2020-3-2", end_date: "2020-3-10")
expect(date_range.overlap?(second_date_range)).must_equal true

third_date_range = Hotel::DateRange.new(start_date: "2020-3-13", end_date: "2020-3-25")
expect(date_range.overlap?(third_date_range)).must_equal true

forth_date_range = Hotel::DateRange.new(start_date: "2020-2-10", end_date: "2020-2-26")
expect(date_range.overlap?(forth_date_range)).must_equal false

fifth_date_range = Hotel::DateRange.new(start_date: "2020-3-23", end_date: "2020-3-25")
expect(date_range.overlap?(fifth_date_range)).must_equal false

six_date_range = Hotel::DateRange.new(start_date: "2020-3-20", end_date: "2020-3-21")
expect(date_range.overlap?(six_date_range)).must_equal false
end

# TODO:
# it "expects a DateRange provided as an argument to have start_date and end_date rather than nil" do
# other_date_range_1 = Hotel::DateRange.new(end_date: "2020-5-10")
# expect {date_range.overlap? (other_date_range_1)}.must_raise ArgumentError

# other_date_range_2 = Hotel::DateRange.new(start_date: "2020-5-10")
# expect {date_range.overlap? (other_date_range_2)}.must_raise ArgumentError
# end
end

describe "nights" do
it "calculates the number of nights for the date range" do
date_range = Hotel::DateRange.new(start_date: "2020-3-1", end_date: "2020-3-10")
expect(date_range.nights).must_equal 9

date_range = Hotel::DateRange.new(start_date: "2020-3-1", end_date: "2020-3-2")
expect(date_range.nights).must_equal 1
end
end

end
Loading