-
Notifications
You must be signed in to change notification settings - Fork 50
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
Leaves - Tiffany #25
base: master
Are you sure you want to change the base?
Leaves - Tiffany #25
Changes from 36 commits
ffd84bd
45c3a5c
b3cf64e
5e39d57
b320079
783424f
092ea03
023e763
00455f0
2f10378
f09ba52
42df5a2
f0c6e82
15f2043
86b7cdb
df40439
4e6f127
fa6ab8b
48918bf
67dbe11
76486fe
bafa5ed
b9b6026
1c841a9
dc88859
746d394
18f55d2
5a4a8e4
551edc3
5e24692
05dae1d
be55658
748f2e0
273e3cf
9e6cbd3
25aa0fa
5f1c7b3
78862b7
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,2 @@ | ||
class FullOccupancyError < StandardError | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
require 'date' | ||
require_relative 'reservation' | ||
|
||
module Hotel | ||
class HotelBlock < Reservation | ||
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. Great use of inheritance. |
||
|
||
attr_reader :status | ||
|
||
# id here is the id of the hotel block | ||
# shared by all rooms within same block | ||
def initialize(id:, room:, start_date:, end_date:, cost:) | ||
super | ||
@status = :AVAILABLE | ||
end | ||
|
||
def change_status | ||
@status = :UNAVAILABLE | ||
end | ||
end | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
require_relative 'room' | ||
require_relative 'reservation' | ||
|
||
module Hotel | ||
class Date | ||
|
||
attr_reader :id, :occupied | ||
|
||
def initialize(id) | ||
# id is a date object | ||
@id = id | ||
# occupied is a hash with key room_id and values of reservation instance | ||
@occupied = {} | ||
end | ||
|
||
# Adds either reservation or hotel blocks to @occupied hash. | ||
# Below the parameter is called "reservation" but | ||
# logic also works for hotel blocks. | ||
def add_occupancy(reservation) | ||
@occupied[reservation.room] = reservation | ||
end | ||
|
||
# Returns only reservations and not hotel blocks as per specs. | ||
def list_reservations | ||
return @occupied.values.select { |value| value.class == Hotel::Reservation } | ||
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. Ruby will be looking for names locally first, prioritizing local names, since they're all in the same module, there's no need to namespace 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. clever use of select! |
||
end | ||
|
||
# Returns all occupied rooms, either reserved or blocked. | ||
def rooms_unavailable | ||
return @occupied.keys | ||
end | ||
|
||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
require 'date' | ||
require_relative 'room' | ||
|
||
module Hotel | ||
class Reservation | ||
|
||
attr_reader :id, :room, :start_date, :end_date, :cost | ||
|
||
def initialize(id:, room:, start_date:, end_date:, cost: room.cost) | ||
if end_date <= start_date | ||
raise ArgumentError, 'End date must be after start date.' | ||
end | ||
|
||
@id = id | ||
@room = room # this is a room object | ||
@start_date = start_date # this is a date instance | ||
@end_date = end_date # this is a date instance | ||
@cost = cost | ||
end | ||
|
||
def find_total_cost | ||
return (@end_date - @start_date) * @cost.to_f | ||
end | ||
|
||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
module Hotel | ||
class Room | ||
|
||
attr_reader :id, :cost | ||
|
||
def initialize(id, cost) | ||
@id = id | ||
@cost = cost | ||
end | ||
|
||
def self.generate_rooms | ||
array_of_room_obj = [] | ||
|
||
(1..20).each do |i| | ||
array_of_room_obj << Hotel::Room.new(i, 200.0) | ||
end | ||
|
||
return array_of_room_obj | ||
end | ||
|
||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
require 'date' | ||
|
||
require_relative 'room' | ||
require_relative 'reservation' | ||
require_relative 'hoteldate' | ||
require_relative 'hotelblock' | ||
require_relative 'custom_errors' | ||
|
||
module Hotel | ||
class System | ||
|
||
attr_reader :rooms, :reservations, :dates, :hotelblocks | ||
|
||
def initialize | ||
@rooms = Hotel::Room.generate_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. Nice use of a class method. |
||
@reservations = [] | ||
@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. A comment to tell us what |
||
|
||
# Keys will be hotel block ids (shared by all rooms within block). | ||
# Values will be arrays of hotel block objects within same block. | ||
# Each array element represents one room. | ||
@hotelblocks = {} | ||
end | ||
|
||
def find_room(room_id) | ||
return @rooms.find { |room| room.id == room_id } | ||
end | ||
|
||
def find_date(date_obj) | ||
return @dates.find { |hotel_date| hotel_date.id == date_obj } | ||
end | ||
|
||
def find_all_available_rooms(start_date, end_date) | ||
current_date = start_date.dup | ||
available_rooms = @rooms.dup | ||
|
||
until current_date == end_date | ||
hotel_date = find_date(current_date) | ||
available_rooms -= hotel_date.rooms_unavailable if hotel_date | ||
current_date += 1 | ||
end | ||
|
||
return available_rooms | ||
end | ||
|
||
def make_reservation(start_date, end_date) | ||
id = @reservations.length + 1 | ||
|
||
# assign the first available room to the reservation | ||
room = find_all_available_rooms(start_date, end_date)[0] | ||
raise FullOccupancyError.new('No rooms available for the date range.') if room == nil | ||
|
||
new_reservation = Hotel::Reservation.new(id: id, room: room, start_date: start_date, end_date: end_date) | ||
|
||
@reservations << new_reservation | ||
add_to_dates(start_date, end_date, new_reservation) | ||
|
||
return new_reservation | ||
end | ||
|
||
# new_occupancy refers to reservation or hotel block to be added to dates | ||
def add_to_dates(start_date, end_date, new_occupancy) | ||
current_date = start_date.dup | ||
|
||
until current_date == end_date | ||
date_obj = find_date(current_date) | ||
|
||
if date_obj | ||
date_obj.add_occupancy(new_occupancy) | ||
else | ||
new_date_obj = Hotel::Date.new(current_date) | ||
@dates << new_date_obj | ||
new_date_obj.add_occupancy(new_occupancy) | ||
end | ||
|
||
current_date += 1 | ||
end | ||
end | ||
|
||
def list_reservations_for(date) | ||
hotel_date = find_date(date) | ||
|
||
if hotel_date | ||
return hotel_date.list_reservations | ||
else | ||
return nil | ||
end | ||
end | ||
|
||
def create_hotelblock(start_date:, end_date:, hb_rooms:, discount_rate:) | ||
# hb_rooms is an array of room_ids | ||
hb_rooms.map! { |room_id| find_room(room_id) } | ||
|
||
if hb_rooms.length > 5 | ||
raise ArgumentError, 'A block can only contain a maximum of 5 rooms.' | ||
end | ||
|
||
available_rooms = find_all_available_rooms(start_date, end_date) | ||
|
||
# Change hb_rooms into a array of boolean values for whether they are available | ||
# Does the array contain false (room is not available)? | ||
# If so raise an error. | ||
if hb_rooms.map{ |room| available_rooms.include? room}.include? false | ||
raise ArgumentError, 'Block contains room that is already booked.' | ||
end | ||
|
||
hb_id = @hotelblocks.length + 1 | ||
@hotelblocks[hb_id] = [] | ||
|
||
hb_rooms.each do |room| | ||
new_hotel_block = Hotel::HotelBlock.new(id: hb_id, room: room, start_date: start_date, end_date: end_date, cost:discount_rate.to_f) | ||
|
||
@hotelblocks[hb_id] << new_hotel_block | ||
|
||
add_to_dates(start_date, end_date, new_hotel_block) | ||
end | ||
end | ||
|
||
def find_open_rooms_from_block(hb_id) | ||
hotel_blocks = @hotelblocks[hb_id] | ||
open_rooms = hotel_blocks.select { |hotel_block| hotel_block.status == :AVAILABLE} | ||
|
||
# Return open rooms as room IDs rather than Room objects for end user readability. | ||
open_rooms.map! { |hotel_block| hotel_block.room.id} | ||
|
||
return open_rooms | ||
end | ||
|
||
def reserve_from_block(hb_id, room_id) | ||
unless find_open_rooms_from_block(hb_id).include? room_id | ||
raise ArgumentError, 'The room you are trying to book is not available.' | ||
end | ||
|
||
# Change room status so room can no longer be booked. | ||
hotel_block = @hotelblocks[hb_id].find {|hb| hb.room.id == room_id} | ||
hotel_block.change_status | ||
|
||
# Reservation is hardcoded to use hotel block dates and cost. | ||
id = @reservations.length + 1 | ||
room = find_room(room_id) | ||
start_date = hotel_block.start_date | ||
end_date = hotel_block.end_date | ||
cost = hotel_block.cost | ||
|
||
new_reservation = Hotel::Reservation.new(id: id, room: room, start_date: start_date, end_date: end_date, cost: cost) | ||
|
||
@reservations << new_reservation | ||
end | ||
|
||
end | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Possible future changes: | ||
- limit reservations to 30 days at a time maximum to discourage users from making long reservations | ||
- include validation for date ranges to throw error if date range is over 30 days | ||
|
||
- change Hotel::HotelBlock to simply Hotel::Block | ||
- cmd + shift + F for HotelBlock and hotel_block across all files | ||
- change delete hotel or hotel_ (case insensitive) | ||
|
||
Possible but improbable future changes: | ||
- remove room and date class | ||
- requires rewriting the entire code | ||
- reduces dependencies | ||
- however, will shift most of the responsibilities to system class | ||
- difficult to flesh out concrete ideas | ||
- room class was created to allow for easier refactoring if room pricing structure changed | ||
- date class allows for more readable code by creating another database that acts as a calendar | ||
- tracking room availability can be done by iterating through all reservations & hotel blocks to look for date matches | ||
- if match, mark the room as unavailable | ||
- or if match, add the reservation to an array to be returned after iteration complete |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
require_relative 'test_helper' | ||
|
||
describe "HotelBlock class" do | ||
describe "HotelBlock instantiation" do | ||
before do | ||
room_15 = Hotel::Room.new(15, 200.00) | ||
start_time = ::Date.parse('2019-09-03') | ||
end_time = ::Date.parse('2019-09-08') | ||
|
||
@hotelblock = Hotel::HotelBlock.new(id: 101, room: room_15, start_date: start_time, end_date: end_time, cost: 100) | ||
end | ||
|
||
it "is an instance of HotelBlock" do | ||
expect(@hotelblock).must_be_kind_of Hotel::HotelBlock | ||
end | ||
|
||
it "is set up for specific attributes and data types" do | ||
[:id, :room, :start_date, :end_date, :cost, :status].each do |prop| | ||
expect(@hotelblock).must_respond_to prop | ||
end | ||
|
||
expect(@hotelblock.id).must_be_instance_of Integer | ||
expect(@hotelblock.room).must_be_instance_of Hotel::Room | ||
expect(@hotelblock.start_date).must_be_instance_of Date | ||
expect(@hotelblock.end_date).must_be_instance_of Date | ||
expect(@hotelblock.status).must_be_instance_of Symbol | ||
end | ||
|
||
it "correctly assigns cost value" do | ||
expect(@hotelblock.cost).must_be_close_to 100.0 | ||
end | ||
|
||
it "correctly calculates total_cost" do | ||
expect(@hotelblock.find_total_cost).must_be_close_to 500.0 | ||
end | ||
end | ||
|
||
describe "the status of hotelblock" do | ||
before do | ||
room_15 = Hotel::Room.new(15, 200.00) | ||
start_time = ::Date.parse('2019-09-03') | ||
end_time = ::Date.parse('2019-09-08') | ||
|
||
@hotelblock = Hotel::HotelBlock.new(id: 101, room: room_15, start_date: start_time, end_date: end_time, cost: 100) | ||
end | ||
|
||
it "initializes hotelblocks with :AVAILABLE" do | ||
expect(@hotelblock.status).must_equal :AVAILABLE | ||
end | ||
|
||
it "can be changed to :UNVAILABLE with the change_status method" do | ||
@hotelblock.change_status | ||
expect(@hotelblock.status).must_equal :UNAVAILABLE | ||
end | ||
end | ||
|
||
describe "raises an exception when an invalid date range is provided" do | ||
before do | ||
@room_15 = Hotel::Room.new(15, 200.00) | ||
@start_time = ::Date.parse('2019-09-03') | ||
end | ||
|
||
it "raises an error if end date is before start date" do | ||
end_time = ::Date.parse('2019-09-01') | ||
|
||
expect{Hotel::HotelBlock.new(id: 101, room: @room_15, start_date: @start_time, end_date: end_time, cost: 100)}.must_raise ArgumentError | ||
end | ||
|
||
it "raises an error if end date is on same day as start date" do | ||
expect{Hotel::HotelBlock.new(id: 101, room: @room_15, start_date: @start_time, end_date: @start_time, cost: 100)}.must_raise ArgumentError | ||
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.
This is a great example of defining your own appropriately named exception type.