Skip to content
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

Pipes - Kate Evans-Spitzer - Hotel #48

Open
wants to merge 132 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
132 commits
Select commit Hold shift + click to select a range
bdc1eab
add rakefile
Guribot Sep 5, 2017
0f80e82
create files for classes & specs
Guribot Sep 5, 2017
5b5b85b
comments basic method/variable names
Guribot Sep 5, 2017
936ce00
add files to spec_helper require list
Guribot Sep 5, 2017
ddbe4ed
add initialize method
Guribot Sep 5, 2017
6cdb1ff
initialize
Guribot Sep 5, 2017
387c256
add #all_rooms
Guribot Sep 5, 2017
b150d60
add #initialize
Guribot Sep 5, 2017
6c38487
pseudocode remaining methods
Guribot Sep 6, 2017
43d7f68
add room_num parameter to initialize method
Guribot Sep 6, 2017
0f42fca
pseudocode #includes_dates? method
Guribot Sep 6, 2017
eeae8f8
test for @total_cost
Guribot Sep 6, 2017
69d7740
parses checkin & checkout into dates array
Guribot Sep 6, 2017
96ea3df
fixed broken it block
Guribot Sep 6, 2017
25c2edc
implement ::validate method
Guribot Sep 6, 2017
77c88ac
implement ::range_to method
Guribot Sep 6, 2017
3e6c923
implement ::range_with method
Guribot Sep 6, 2017
8e8afd8
add room_num parameter to initialize
Guribot Sep 6, 2017
e64ba26
implement #initialize and @dates
Guribot Sep 6, 2017
c8d8597
skip future tests
Guribot Sep 6, 2017
2382fa0
implement @id and #create_id
Guribot Sep 6, 2017
ce2065b
add Hotel ROOM_COST constant of 200, pass as parameter to Room.new
Guribot Sep 6, 2017
facc88f
move comments
Guribot Sep 6, 2017
46be315
unskip test
Guribot Sep 6, 2017
41b1d0f
implement #room method
Guribot Sep 6, 2017
5b016f4
implement @total_cost and #get_cost
Guribot Sep 6, 2017
8edfa6c
Hotel module requires classes
Guribot Sep 6, 2017
6bc2383
remove to_do comment
Guribot Sep 6, 2017
1b55142
passes Room into Reservation.initialize instead of room number
Guribot Sep 6, 2017
09e964c
implement #make_reservation
Guribot Sep 6, 2017
e2ff5fd
moved Wave 1 TODO comment from hotel to reservation spec
Guribot Sep 6, 2017
67306ab
implement #find_available_rooms
Guribot Sep 6, 2017
c79af96
require date
Guribot Sep 6, 2017
d37bc74
implement #view_reservations method
Guribot Sep 6, 2017
6a51556
create class AlreadyBookedError
Guribot Sep 6, 2017
48475b0
add Wave 2 comments
Guribot Sep 6, 2017
7805a12
implement #includes_dates? method
Guribot Sep 6, 2017
e0e27b4
#view_avail method checks reservations
Guribot Sep 6, 2017
3058ab5
renamed already_booked_error to no_room_error
Guribot Sep 6, 2017
51d3893
implement NoRoomError, tests for availability
Guribot Sep 6, 2017
db06bfd
#all_rooms returns list of Rooms, not numbers
Guribot Sep 6, 2017
b2d1a36
rubocop style guide tweaks
Guribot Sep 6, 2017
ba9c895
#make_reservation returns reservation, pseudocode for #make_block
Guribot Sep 7, 2017
2864b1b
add #initialize for Block class, implement basic #make_block method
Guribot Sep 7, 2017
e4d8791
Hotel.create_block will not take booked room
Guribot Sep 7, 2017
1de2b4b
dry out #make_reservation tests
Guribot Sep 7, 2017
492cfc0
added 'block = false' argument to #make_reservation, #find_available_…
Guribot Sep 7, 2017
53439e8
add leading B or R to block & reservation IDs
Guribot Sep 7, 2017
67f6684
pseudocode and skeleton tests for adding block functionality to #find…
Guribot Sep 7, 2017
e9b1155
fix date issue in @id and #create_id
Guribot Sep 7, 2017
ed39e6a
moved Reservation.includes_dates? functionality into DateRange.overlap?
Guribot Sep 7, 2017
ca39406
add reader methods for dates
Guribot Sep 7, 2017
33dce75
#find_available_rooms raises RangeError if passed incongruent dates &…
Guribot Sep 7, 2017
c40839c
add block functionality to #make_reservation
Guribot Sep 7, 2017
8eba6cf
add #includes_dates?
Guribot Sep 7, 2017
eac2592
renamed no_room_error to exceptions
Guribot Sep 8, 2017
4951fdd
streamlined #block and #room
Guribot Sep 8, 2017
f04af7e
add TODO
Guribot Sep 8, 2017
0532edf
refactored #range_to and #range_with to be more DRY
Guribot Sep 8, 2017
573dff1
change RangeError to InvalidDatesError
Guribot Sep 8, 2017
e45f56c
rubocop style guide tweaks
Guribot Sep 8, 2017
f70d6e0
add TODO: implement block discount
Guribot Sep 8, 2017
626f97a
rubocop style guide tweaks
Guribot Sep 8, 2017
9ffeef1
added comparability for rooms by room number - not sure if this will …
Guribot Sep 8, 2017
8b134cc
much-needed refactor for #find_available_rooms
Guribot Sep 8, 2017
4c87057
removed unnecessary code (Hotel to Reservation, etc)
Guribot Sep 8, 2017
4622f6f
formatting
Guribot Sep 8, 2017
14fcd5d
fix broken @block assignment
Guribot Sep 8, 2017
f34830b
implement #block_availability? method
Guribot Sep 8, 2017
41bdcc2
removed room comparability, wasnt necessary
Guribot Sep 8, 2017
543d4b2
removed unnecessary private methods
Guribot Sep 8, 2017
328ed62
removed unnecessary hotel parameter
Guribot Sep 8, 2017
d5fdd64
comment format
Guribot Sep 8, 2017
84a61ce
validate input in initialize, remove useless #all_rooms method
Guribot Sep 8, 2017
e70a665
add validate_order method
Guribot Sep 8, 2017
8cc9530
validate input for #find_available_rooms, reorder methods
Guribot Sep 8, 2017
c3ea60f
removed unnecessary Reservation ID
Guribot Sep 8, 2017
c89fc53
add InvalidBlockError to #find_available_rooms
Guribot Sep 8, 2017
20c543e
#validate_order will raise exception if given invalid input
Guribot Sep 8, 2017
0021d77
changed InvalidDatesError to ArgumentError to align with Date class
Guribot Sep 8, 2017
37ce94c
edge testing for #find_available_rooms
Guribot Sep 8, 2017
113069b
rename InvalidBlockError to NoBlockError
Guribot Sep 8, 2017
3876d77
edge tests for #make_reservation'
Guribot Sep 8, 2017
94abb1e
reformat comments / add TODO
Guribot Sep 8, 2017
ba9026a
edge testing for reservations
Guribot Sep 8, 2017
8af2eb4
add InvalidDiscountError
Guribot Sep 8, 2017
2a690db
edge tests for #make_block
Guribot Sep 8, 2017
3ae8819
renamed InvalidDatesError to DatesError and InvalidDiscountError to D…
Guribot Sep 8, 2017
9c43bda
input validation for Block.discount_rate
Guribot Sep 8, 2017
5adc193
add missing end
Guribot Sep 8, 2017
3244b40
clarify error messages
Guribot Sep 8, 2017
9b83883
add descriptions for exceptions defining their use scenarios
Guribot Sep 8, 2017
03f59e9
input validation for #make_block num_rooms
Guribot Sep 8, 2017
6b2cbdf
edge tests for #make_block
Guribot Sep 8, 2017
77fd316
edge tests for #find_rooms_not_in_blocks and #block_availability?
Guribot Sep 10, 2017
795c446
add #include_all? method
Guribot Sep 10, 2017
dce26c5
add #include_all_dates? method
Guribot Sep 10, 2017
586ddbe
add date confirmation for #make_reservation in block
Guribot Sep 10, 2017
9198637
remove TODO
Guribot Sep 10, 2017
dbd91fb
#initialize validates instead of parses date input - can take Date or…
Guribot Sep 10, 2017
d8425e4
--amend
Guribot Sep 10, 2017
20a1113
remembered why @reservation has to take hotel parameter
Guribot Sep 10, 2017
c93eaab
add reader method for @discount_rate and convert output into Float
Guribot Sep 10, 2017
563f649
#make_reservation passes hotel as argument
Guribot Sep 10, 2017
dce178c
implement block discount in #get_total
Guribot Sep 10, 2017
1758ca8
remove unused #create_id method
Guribot Sep 10, 2017
218b77e
moved Reservation date validation from Hotel to Reservation.initialize
Guribot Sep 10, 2017
15a5121
removed comments
Guribot Sep 10, 2017
fe28cff
block discount can be 0% or 100%
Guribot Sep 10, 2017
7279cbd
discount can be float, discounts to 0%
Guribot Sep 10, 2017
399d924
discount defaults to 0
Guribot Sep 10, 2017
4cdd9c7
discount defaults to 0
Guribot Sep 10, 2017
1aed359
edge tests for #get_total
Guribot Sep 10, 2017
b525ab6
renamed Room.cost to Room.rate
Guribot Sep 10, 2017
d6964ce
did not need Block.includes_dates? method - replaced all with Block.i…
Guribot Sep 10, 2017
860bc5e
edge tests for #initialize
Guribot Sep 10, 2017
fda8015
tentatively removing unused #range_with
Guribot Sep 10, 2017
07fb17f
edge tests for #includes_dates?
Guribot Sep 10, 2017
0fc3390
edge tests for #get_discount_rate
Guribot Sep 10, 2017
836a997
removed unnecessary methods to consolidate self.range_to
Guribot Sep 10, 2017
e299acf
edge tests for #range_to, #overlap? and #include_all?
Guribot Sep 10, 2017
be990e7
self.validate_order returns true if input is correct
Guribot Sep 10, 2017
5a91bf4
edge tests for #validate_order
Guribot Sep 10, 2017
00b65d8
edge tests for #validate
Guribot Sep 10, 2017
450c168
add test for block dates logic
Guribot Sep 11, 2017
de2755e
add design.md to .gitignore
Guribot Sep 11, 2017
da4f5b5
ShoppingCart design activity
Guribot Sep 28, 2017
7253647
remove unnecessary input validation in #initialize
Guribot Sep 28, 2017
007a5d1
complete Revisiting Hotel portion
Guribot Sep 28, 2017
a84a19d
REFACTOR: moved DateRange.validate method to Date class
Guribot Oct 2, 2017
e65e5ea
REFACTOR: moved DateRange.validate method to Date class
Guribot Oct 2, 2017
95cb711
revisiting hotel answer
Guribot Oct 2, 2017
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
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/
design.md

# 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
99 changes: 99 additions & 0 deletions design-activity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
### Activity
##### What classes does each implementation include? Are the lists the same?

Yes, they are the same. Both implementations have:
* CartEntry
* ShoppingCart
* Order


##### Write down a sentence to describe each class.

Implementation A:
* CartEntry
* Contains basic pricing info entry
* ShoppingCart
* Contains CartEntry objects
* Order
* Contains and totals a ShoppingCart object

Implementation B:
* CartEntry
* Manages all pricing info of a single entry
* ShoppingCart
* Contains and totals CartEntry objects
* Order
* Contains and totals ShoppingCart, accounting for sales tax

##### How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper.
* Order _has a_ ShoppingCart
* ShoppingCart _has many_ CartEntries

##### What data does each class store? How (if at all) does this differ between the two implementations?
Implementation A:
* CartEntry
* Unit price
* Unit quantity
* ShoppingCart
* List of CartEntries
* Order
* ShoppingCart
* Total price of all CartEntries in ShoppingCart

Implementation B:
* CartEntry
* Unit price
* Unit quantity
* Total entry price
* ShoppingCart
* List of CartEntries
* Total of all CartEntry prices
* Order
* ShoppingCart
* Total price of ShoppingCart with tax

##### What methods does each class have? How (if at all) does this differ between the two implementations?
Implementation A:
* CartEntry
* #unit_price
* #quantity
* ShoppingCart
* #entries
* Order
* #cart
* #total_price

Implementation B:
* CartEntry
* #price
* ShoppingCart
* #price
* Order
* #total_price

Implementation A uses attr_accessor methods to make all of the data contained within each class publicly accessible. Implementation B keeps this data private and uses a #price method on CartEntry and ShoppingCart classes to return only the necessary information (in this case, price).

##### Consider the Order#totalprice 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?

Implementation A requires the Order#total_price method to dig through the ShoppingCart class and into the CartEntry class to directly access its variables, while Implementation B takes the returned value of ShoppingCart#price and performs its logic around that.

##### Does totalprice directly manipulate the instance variables of other classes?

Implementation A directly reads the instance variables of the ShoppingCart and CartEntry classes, though it does not modify them. Implementation B does not directly read or modify any other class' instance variables.

##### If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify?

Implementation B would be easier to modify, as this change would only affect the CartEntry class, which could be updated to modify price based on quantity. In Implementation A, this would require the Order#total_price method to perform this additional logic within the same method where it is already calculating the total _and_ applying sales tax.

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

Implementation B better adheres to this principle, as each class manages and encapsulates its relevant information.

##### Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled?

### Revisiting Hotel
Overall, I feel that I did a pretty good job of adhering to the single responsibility principle. The only thing that bothers me about my design is that the price of each room comes from a constant variable defined within the Hotel class, but since the current spec of the project is for all of the hotel's rooms to have a price of 200, I still think it makes the most sense for the time being. How I would refactor the code to account for varying room types would vary depending on how the rooms' prices were being determined, so there's nothing to change at the moment.

The change I _did_ make was to remove the #validate method from the DateRange class. My thought process behind the change was that the method (which converts valid input into a Date object if it isn't already one) only interacts with one date, and therefore should be part of the Date class, not DateRange.
28 changes: 28 additions & 0 deletions lib/block.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Hotel
class Block
attr_reader :rooms, :id, :start_date, :end_date, :dates, :discount_rate

def initialize(start_date, end_date, rooms, discount_rate = 0)
@start_date = Date.parse(start_date)
@end_date = Date.parse(end_date)
@dates = DateRange.range_to(@start_date, @end_date)
@rooms = rooms
@discount_rate = get_discount_rate(discount_rate)
@id = create_id
end

def create_id
format('B%.2d%.2d%.4d', @start_date.month, @start_date.day, rand(9999))
end

def includes_dates?(checkfirst, checklast)
DateRange.include_all?(checkfirst, checklast, @start_date, @end_date)
end

def get_discount_rate(input)
raise(ArgumentError, "Discount must be number: was #{input.class}") unless (input.class == Integer) || (input.class == Float)
raise(DiscountError, "Discount must be between 0-100%: was #{input}") unless 0 <= input && input <= 100
return (100.0 - input) / 100
end
end
end
68 changes: 68 additions & 0 deletions lib/date_range.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
module DateRange
require 'date'

def self.range_to(start_date, end_date)
start_date = Date.validate(start_date)
end_date = Date.validate(end_date)
validate_order(start_date, end_date)
dates = []
while start_date < end_date
dates << start_date
start_date += 1
end
dates
end

def self.overlap?(first_start, first_end, second_start, second_end)
first_start = Date.validate(first_start)
first_end = Date.validate(first_end)
second_start = Date.validate(second_start)
second_end = Date.validate(second_end)

first_dates = DateRange.range_to(first_start, first_end)
second_dates = DateRange.range_to(second_start, second_end)

first_dates.each do |first_date|
second_dates.each do |second_date|
return true if first_date.strftime == second_date.strftime
end
end
false
end

def self.include_all?(search_start, search_end, contain_start, contain_end)
search_start = Date.validate(search_start)
search_end = Date.validate(search_end)
contain_start = Date.validate(contain_start)
contain_end = Date.validate(contain_end)

search_dates = DateRange.range_to(search_start, search_end)
contain_dates = DateRange.range_to(contain_start, contain_end)

search_dates.each do |search_date|
return false unless contain_dates.include? search_date
end
true
end

def self.validate_order(first, second)
first = Date.validate(first)
second = Date.validate(second)
unless first < second
raise(DatesError, 'Start date must be at least 1 day before end date')
end
true
end
end

class Date
def self.validate(input)
if input.class == Date
return input
elsif input.class == String
return Date.parse(input)
else
raise(ArgumentError, "Input #{input.class} cannot be converted into Date")
end
end
end
16 changes: 16 additions & 0 deletions lib/exceptions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class NoRoomError < StandardError
# Raised when there are not enough available rooms for request
end

class DatesError < StandardError
# Raised when dates are in wrong order, out of range, etc
# NOT raised for invalid date input (use ArgumentError)
end

class NoBlockError < StandardError
# Raised when provided block does not exist/cannot be found
end

class DiscountError < StandardError
# Raised when discount is not number from 0-100
end
104 changes: 104 additions & 0 deletions lib/hotel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
require 'pry'

module Hotel
require_relative 'room'
require_relative 'reservation'
require_relative 'room'
require_relative 'block'
require_relative 'exceptions'

class Hotel
attr_reader :rooms, :reservations, :blocks
ROOM_COST = 200

def initialize(num_rooms)
@rooms = []

num_rooms.times do |i|
@rooms << Room.new(i + 1, ROOM_COST)
end
@reservations = []
@blocks = []
end

def make_reservation(checkin, checkout, block = false)
room_num = find_available_rooms(checkin, checkout, block).first
if room_num.nil?
raise(NoRoomError, "No available rooms for dates #{checkin} - #{checkout}")
end
reservation = Reservation.new(room_num, checkin, checkout, self, block)
@reservations << reservation
reservation
end

def make_block(start_date, end_date, num_rooms, discount = 0)
raise(ArgumentError,"Number of rooms must be Integer: is #{num_rooms.class}") unless num_rooms.class == Integer

rooms = find_available_rooms(start_date, end_date)[0...num_rooms]

raise(NoRoomError, "Not enough available rooms for amount #{num_rooms}") if rooms.length < num_rooms

block = Block.new(start_date, end_date, rooms, discount)
@blocks << block
block
end

def view_reservations(date)
date = Date.validate(date)
reservations = []
@reservations.each do |reservation|
reservations << reservation if reservation.dates.include?(date)
end
reservations
end

def find_available_rooms(checkin, checkout, block_id = false)
DateRange.validate_order(checkin, checkout)
if block_id
unless block_exists?(block_id)
raise(NoBlockError, "No such block: #{block_id}")
end
unless block(block_id).includes_dates?(checkin, checkout)
raise(DatesError, "Dates (#{checkin}, #{checkout}) do not fall within provided block #{block(block_id).id}")
end
rooms = block(block_id).rooms
else
rooms = find_rooms_not_in_blocks(checkin, checkout)
end

@reservations.each do |reservation|
rooms.delete(reservation.room) if (rooms.include? reservation.room) && reservation.includes_dates?(checkin, checkout)
end

Choose a reason for hiding this comment

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

I really like this method - it is well-implemented and very easy to read.

Minor note: you don't need the (rooms.include? reservation.room). Attempting to delete an element from a list in which it doesn't appear will have no effect (not an error).

rooms
end

def block_availability?(checkin, checkout, block_id)
find_available_rooms(checkin, checkout, block_id) != []
end

def room(num)
@rooms.each { |room| return room if room.number == num }
nil
end

def block(id)
@blocks.each { |block| return block if block.id == id }
nil
end

# private
def find_rooms_not_in_blocks(start_date, end_date)
rooms = @rooms.dup
@blocks.each do |block|
if block.includes_dates?(start_date, end_date)
rooms.delete_if { |room| block.rooms.include? room }

Choose a reason for hiding this comment

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

Good use of an instance method (Block#includes_dates?) to keep things loosely coupled.

end
end
rooms
end

def block_exists?(block_id)
block(block_id) != nil
end
end
end
32 changes: 32 additions & 0 deletions lib/reservation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'pry'

module Hotel
class Reservation
require_relative 'date_range'
attr_reader :total_cost, :dates, :checkin, :checkout, :id, :hotel, :room, :block

def initialize(room, checkin, checkout, hotel, block = false)
@room = room
@checkin = Date.validate(checkin)
@checkout = Date.validate(checkout)
@dates = DateRange.range_to(@checkin, @checkout)
if block
@block = hotel.block(block)
raise(DatesError) unless @block.includes_dates?(checkin, checkout)
else
@block = false
end
get_total
end

def get_total
num_nights = @dates.length
@total_cost = @room.rate * num_nights
@total_cost *= @block.discount_rate if @block
end

def includes_dates?(checkfirst, checklast)
DateRange.overlap?(checkfirst, checklast, @checkin, @checkout)
end
end
end
10 changes: 10 additions & 0 deletions lib/room.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Hotel
class Room
attr_reader :number, :rate

def initialize(number, rate)
@number = number
@rate = rate
end
end
end
Loading