Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
a5fa31f
created a rakefile and spec_helper file
torshimizu Mar 5, 2018
fd5293a
made files for class and tests for room and administrator
torshimizu Mar 5, 2018
09810c1
created production and test files for reservation class
torshimizu Mar 5, 2018
9451431
wrote tests and inital constructor method for Hotel::Room
torshimizu Mar 5, 2018
dfd9f96
wrote tests and production code for Reservation#initialize, updated R…
torshimizu Mar 5, 2018
55a1b80
tests and code for Room#check_availability
torshimizu Mar 5, 2018
0f70e53
updated Room#check_availability to parse dates given
torshimizu Mar 5, 2018
dd12ed5
test and code for Reservation#calculate_cost
torshimizu Mar 5, 2018
5200695
added private Reservation#get_room, called during initalize
torshimizu Mar 5, 2018
3490ad1
removed Reservation#get_room from Reservation#initalize
torshimizu Mar 5, 2018
6c85b76
code for Hotel::Admin#initialize and tests for initialize
torshimizu Mar 5, 2018
5f72484
created a custom exception, tests for new_reservation
torshimizu Mar 6, 2018
82b13d5
functional Admin#new_reservation and tests, custom error for NotAvail…
torshimizu Mar 6, 2018
30afccd
Admin#new_reservation takes a room_id but will create instance of Res…
torshimizu Mar 6, 2018
0a8ab43
test for adding new_reservation to room's list of reservations
torshimizu Mar 6, 2018
16e357d
tests and production code for Admin#list_reservations
torshimizu Mar 6, 2018
e5380d0
refactored Room#check_availability, Admin tests, Reservation#calculat…
torshimizu Mar 6, 2018
48e3609
functional production code for Admin#calculate_reservation_cost
torshimizu Mar 6, 2018
3feab75
refactored Room#initialize and tests
torshimizu Mar 6, 2018
41f6821
refactored to remove unnecessary code
torshimizu Mar 6, 2018
1c6079e
refactored Reservation#initialize to require a guest's last name to b…
torshimizu Mar 6, 2018
a2b5494
wrote tests for Admin#find_available_rooms
torshimizu Mar 6, 2018
3392900
added files for Hotel::Block
torshimizu Mar 7, 2018
62980b9
can create new instance of Hotel::Block
torshimizu Mar 7, 2018
80d6ae1
initial Block constructor method, Admin#reserve_block
torshimizu Mar 7, 2018
44449a9
updated Room to have a list of blocks it is a part of, and tests for it
torshimizu Mar 7, 2018
1fb8f1b
tests for reserving blocks and rooms in blocks, code for finding an a…
torshimizu Mar 7, 2018
b1206f7
updated a block to have a block_last_name
torshimizu Mar 8, 2018
04f4f93
updated Room#check_availability to see if the room was part of a bloc…
torshimizu Mar 8, 2018
6475659
Block now parses dates and now Room#check_availability works for rese…
torshimizu Mar 8, 2018
8c6a3ad
can reserve a room in a block and will raise an error when trying to …
torshimizu Mar 8, 2018
e3f89a5
refactored Room#overlap_date_range, added tests for finding available…
torshimizu Mar 8, 2018
9241237
updated method and tests for Admin#get_available_blockrooms
torshimizu Mar 8, 2018
fa57953
created files and tests for date_helper class, no implementation in o…
torshimizu Mar 8, 2018
ffa90e8
copied overlap_date_range? to DateHelper class
torshimizu Mar 8, 2018
2138682
updated Room#cost
torshimizu Mar 8, 2018
99a310c
deleted overlap_date_range from Room
torshimizu Mar 8, 2018
ca728fd
replaced Date.parse with DateHelper.parse to be able to use strings o…
torshimizu Mar 8, 2018
3ba5008
updated spec files, tests for no input when instantiating
torshimizu Mar 8, 2018
2d56a6b
updated production code and tests for DateHelper.overlap_date_range?
torshimizu Mar 10, 2018
b196b7e
updated tests and code for DateHelper.parse, now 100% coverage
torshimizu Mar 10, 2018
79d5702
updated Block and Reservation initialize to use DateHelper.parse
torshimizu Mar 10, 2018
6647421
updated Room to not track cost
torshimizu Mar 10, 2018
0e12266
updated Admin#new_reservation to use block pricing if associated with…
torshimizu Mar 11, 2018
6fb291e
Reservation once again is responsible for calculating the cost. It wi…
torshimizu Mar 11, 2018
de6fd84
fixed code so that Admin will not block the same rooms if another blo…
torshimizu Mar 11, 2018
676dbff
added a test for a reservation that starts on the same day that anoth…
torshimizu Mar 12, 2018
5f3d231
removed unnecessary requires
torshimizu Mar 12, 2018
86fabbe
added design-activity, inital answering of questions
torshimizu Mar 27, 2018
6faff71
added hotel revisited answers file
torshimizu Apr 1, 2018
acd7a29
starting to change hash input to keyword arguments
torshimizu Apr 2, 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
/test/tmp/
/test/version_tmp/
/tmp/
.gemfile

.DS_Store

# Used by dotenv library to load environment variables.
# .env
Expand Down
9 changes: 9 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require 'rake/testtask'

Rake::TestTask.new do |t|
t.libs = ["lib"]
t.warning = true
t.test_files = FileList['specs/*_spec.rb']
end

task default: :test
38 changes: 38 additions & 0 deletions design-activity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#### What classes does each implementation include? Are the lists the same?
Both implementations have the same list of classes: CartEntry, ShoppingCart, and Order

#### Write down a sentence to describe each class.
CartEntry: A product with its quantity
ShoppingCart: A list of products
Order: Manages the checkout of the ShoppingCart

#### How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper.
CartEntry < ShoppingCart < Order
An order has a shopping cart and a shopping cart has many cart entries

#### What data does each class store? How (if at all) does this differ between the two implementations?
In Implementation A, both the CartEntry and the ShoppingCart contain the data associated with each instance. The Order class calculates the cost of all the CartEntries in the ShoppingCart.

In Implementation B, the CartEntry is responsible for determining its gross price. The ShoppingCart is also responsible for determining the subtotal of the cart (sum of CartEntries' prices). The Order takes the cart subtotal and calculates the total by adding sales tax.

#### What methods does each class have? How (if at all) does this differ between the two implementations?
Both implementations have a total_price method in the Order class, but in Implementation A, the Order class is the only class to calculate costs/prices.

In Implementation B, each class is responsible for calculating its cost.

#### Consider the Order#total_price method. In each implementation:
- Is logic to compute the price delegated to "lower level" classes like ShoppingCart and CartEntry, or is it retained in Order? <br>
The price computation is retained in Order in Implementation A, in Implementation B, it is delegated to each lower level class.

- Does total_price directly manipulate the instance variables of other classes? <br>
In A, the data is accessed by the accessor method, but Order#total_price has to know about the accessor method (and thusly, the instance variable) for the CartEntry. <br>
In B, each price method of the higher level classes calls the price method of the class directly below it.

#### If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify?
If bulk pricing is desired, each item would need a minimum quantity to activate bulk price and would need the unit pricing for the item when bulk. This would change the calculation of price for each CartEntry. Implementation B would be easier to modify because the bulk pricing could be set or saved in CartEntry and none of the higher level classes would need to change their price method.

#### Which implementation better adheres to the single responsibility principle?
Implementation B

#### Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled?
Implementation B
37 changes: 37 additions & 0 deletions hotel-revisited.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## What is the responsibility of each class?

- **Room**: check if the room is available

- **Reservation**: calculates the cost of the reservation

- **Block**: validate the number of rooms that are to be reserved for the block

- **Admin**: manage rooms

Reference implementation methods:

- reserve
- list reservations
- available rooms (includes block)
- build block
- reserve from block

My implementation

- new reservation
- list reservations
- calculate reservation cost
- find reservation
- find available rooms
- reserve block
- get available block rooms
- helpers:
- get rooms (initialize with a certain number of rooms)
- specified room check
- check for reservation
- find block


## Where does a class directly modify the attributes of another class?

- find_block finds based on attributes (does not modify)
138 changes: 138 additions & 0 deletions lib/admin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
module Hotel
class Admin
attr_reader :rooms, :reservations, :blocks

def initialize(num_of_rooms=20) # so that a new Admin can be instantiated for a different hotel with a diff number of rooms
@rooms = get_rooms(num_of_rooms)
@reservations = []
@blocks = []
end

def new_reservation(start_date:, end_date:, guest_last_name:, block_last_name: nil, cost: STANDARD_RATE, room_id: nil)
start_date = DateHelper.parse(:start_date)
end_date = DateHelper.parse(:end_date)
block_last_name = :block_last_name


available_room = find_available_rooms(start_date, end_date, block_last_name: block_last_name).first

if block_last_name
input[:cost] = find_block(start_date, block_last_name).cost
end

specified_room_check(start_date, room_id)

new_details = {room_id: available_room.room_id, room: available_room}.merge(input) # this should let you specify a room and room_id
new_reservation = Reservation.new(new_details)

@reservations << new_reservation
available_room.add_reservation(new_reservation)

return new_reservation
end

def list_reservations(start_date)
start_date = DateHelper.parse(start_date)

date_reservations = @reservations.select do |reservation|
(reservation.start_date..reservation.end_date).include?(start_date)
end
return date_reservations.empty? ? nil : date_reservations
end

def calculate_reservation_cost(start_date:, room_id:)
reservation = check_for_reservation(find_reservation(start_date: start_date, room_id: room_id))
return reservation.calculate_cost
end

def find_reservation(start_date:, room_id:)
start_date = DateHelper.parse(start_date)
found_reservation = @reservations.find(nil) do |reservation|
reservation.start_date == start_date && reservation.room_id == room_id
end
return found_reservation
end

def find_available_rooms(start_date, end_date, block_last_name: nil)
available_rooms = @rooms.select { |room| room.check_availability(start_date, end_date, block_last_name: block_last_name) == :AVAILABLE }

if available_rooms.empty?
raise NoAvailableRoom.new("No rooms are available for those dates")
end

return available_rooms
end

def reserve_block(input)
start_date = input[:start_date]
end_date = input[:end_date]
room_count = input[:room_count]

available_rooms = find_available_rooms(start_date, end_date)

if available_rooms.length < room_count
raise NoAvailableRoom.new("Not enough rooms for this block, only #{available_rooms.length} rooms available.")
end

rooms_to_block = available_rooms.first(room_count)
block_details = input.merge( { block_rooms: rooms_to_block })
new_block = Block.new(block_details)
@blocks << new_block
new_block.block_rooms.each do |room|
room.add_block(new_block)
end

return new_block
end

def get_available_blockrooms(input)
start_date = DateHelper.parse(input[:start_date])
end_date = DateHelper.parse(input[:end_date])
block_last_name = input[:block_last_name]

sought_block = find_block(start_date, block_last_name)

if sought_block.nil?
raise NoReservation.new("This is not a reserved block")
end

available_rooms = sought_block.block_rooms.select do |room|
room.check_availability(start_date, end_date, block_last_name: block_last_name) == :AVAILABLE
end
return available_rooms.empty? ? nil : available_rooms
end

private

def get_rooms(num_of_rooms) # factory method
rooms = []
num_of_rooms.times do |i|
input = {id: (i + 1)}
rooms << Room.new(input)
end
return rooms
end

def specified_room_check(start_date, room_id)
if room_id
checking_reservation = find_reservation(start_date: start_date, room_id: room_id)
raise NoAvailableRoom.new("This room is already reserved.") unless checking_reservation.nil?
end
end

def check_for_reservation(reservation)
if reservation.nil?
raise NoReservation.new("There is no reservation for that date")
end
return reservation
end

def find_block(start_date, block_last_name)
found_block = @blocks.find do |block|
block.start_date == start_date && block.block_last_name == block_last_name
end
return found_block
end

end
end
25 changes: 25 additions & 0 deletions lib/block.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Hotel
class Block
attr_reader :start_date, :end_date, :block_rooms, :cost, :block_last_name

def initialize(input)
@cost = input[:cost].nil? ? STANDARD_RATE : input[:cost]
@start_date = DateHelper.parse(input[:start_date])
@end_date = DateHelper.parse(input[:end_date])
@block_rooms = check_room_count(input[:block_rooms]) # wouldn't I want this to take room_id's not rooms? or will the rooms be found in admin
@block_last_name = input[:block_last_name]

if @block_last_name.nil?
raise ArgumentError.new("Must enter a last name")
end
end

def check_room_count(block_rooms)
if block_rooms.length > 5
raise StandardError.new("Invalid number of rooms: #{block_rooms.length}. A block can only have up to 5 rooms.")
end
return block_rooms
end

end
end
26 changes: 26 additions & 0 deletions lib/date_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Hotel
class DateHelper
def self.parse(date)
case
when date.instance_of?(Date)
return date
when date.match(/^\d{2,4}-\d{1,2}-\d{1,2}$/)
Date.parse(date)
when date.match(/^\d{1,2}-\d{1,2}-\d{4}$/)
Date.strptime(date, '%m-%d-%Y')
when date.match(/^\d{1,2}\/\d{1,2}\/\d{4}$/)
Date.strptime(date, '%m/%d/%Y')
when date.match(/^\d{6,8}$/)
Date.parse(date)
end
end


def self.overlap_date_range?(start_date, end_date, block_res)
reservation_range = (block_res.start_date...block_res.end_date).to_a
check_range = (parse(start_date)...parse(end_date)).to_a
overlap = reservation_range & check_range
return !overlap.empty?
end
end # class
end # module
5 changes: 5 additions & 0 deletions lib/notavailable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class NoAvailableRoom < StandardError
end

class NoReservation < StandardError
end
33 changes: 33 additions & 0 deletions lib/reservation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Hotel
class Reservation
attr_reader :room, :start_date, :end_date, :guest_last_name, :room_id, :cost

def initialize(input)
@start_date = DateHelper.parse(input[:start_date])
@end_date = DateHelper.parse(input[:end_date])
@room = input[:room].nil? ? nil : input[:room]
@room_id = input[:room_id]
@guest_last_name = input[:guest_last_name]
@guest_first_name = input[:guest_first_name].nil? ? nil : input[:guest_first_name]
@cost = input[:cost] || STANDARD_RATE

if (@start_date == nil || @end_date == nil) || @start_date > @end_date
raise StandardError.new("Invalid dates")
end

if @guest_last_name.nil?
raise StandardError.new("Must enter a last name")
end

if @room_id.nil?
raise StandardError.new("Must enter a room number")
end
end

def calculate_cost
duration = (@end_date - @start_date).to_i
return (duration * @cost).to_f.round(2)
end

end # Reservation
end # Hotel
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
STANDARD_RATE = 200

class Room
attr_reader :room_id, :reservations, :blocks

def initialize(input) # taking a hash so that an array of reservations can be loaded if that's what user wants to do
@room_id = input[:id]
@reservations = input[:reservations] == nil ? [] : input[:reservations] # setting the default to an empty array if no reservations for that room
@blocks = []
end

def check_availability(start_date, end_date, block_last_name: nil) # should this be a date or string instance?
start_date = DateHelper.parse(start_date)
end_date = DateHelper.parse(end_date)

# if there already is a reservation, then this room is not available
@reservations.each do |reservation|
if DateHelper.overlap_date_range?(start_date, end_date, reservation)
return :UNAVAILABLE
end
end

check_for_block(start_date, end_date, block_last_name: block_last_name)
end

def add_reservation(reservation)
@reservations << reservation
end

def add_block(block)
@blocks << block
end

private

def check_for_block(start_date, end_date, block_last_name: nil)

if block_last_name.nil?
overlapping_blocks = @blocks.select do |block|
DateHelper.overlap_date_range?(start_date, end_date, block)
end

if overlapping_blocks.empty?
return :AVAILABLE
else
return :UNAVAILABLE
# raise NoAvailableRoom.new("This room is not available for reserving")
end

else
selected_block = @blocks.find {|block| block.start_date == start_date && block.block_last_name == block_last_name}

if selected_block.nil?
return :UNAVAILABLE
else
return :AVAILABLE
end

end
end

end # class
end # module
Loading