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

sammi-jo's HOTBOOK (nodes) #28

Open
wants to merge 59 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
9ae2d21
Added class files, spec files, spec helper, and Guardfile for envi setup
sjlee3157 Sep 4, 2018
c619148
Added reservation.rb and spec
sjlee3157 Sep 5, 2018
b43773d
Updated Wave1 lib file setup
sjlee3157 Sep 6, 2018
a22d58f
Updated Wave1 spec files setup
sjlee3157 Sep 6, 2018
d285e3b
Added pseudocode.md for code design
sjlee3157 Sep 6, 2018
d5a339d
Wave1 hotel_spec tests written
sjlee3157 Sep 6, 2018
57b76b1
Wave1 hotel_spec tests written
sjlee3157 Sep 6, 2018
91403fd
Merge branch 'master' of https://github.com/sjlee3157/hotel
sjlee3157 Sep 6, 2018
e095f19
Hotel class passes specs
sjlee3157 Sep 6, 2018
1fe9023
Hotel class passes specs
sjlee3157 Sep 6, 2018
792761a
Deleted loader class and spec
sjlee3157 Sep 6, 2018
ea85f45
Added DateRange class
sjlee3157 Sep 7, 2018
00e2798
Finished DateRange class & spec for Wave 1, tests pass
sjlee3157 Sep 7, 2018
2043e50
Added to_range method in DateRange, test passes
sjlee3157 Sep 7, 2018
b7ef34b
Good chunk of design refactors for Wave 1
sjlee3157 Sep 8, 2018
fcc08ba
Nvm - don't need daterange.to_range
sjlee3157 Sep 8, 2018
d4da006
Wave 1 Reservation class done, tests passing
sjlee3157 Sep 8, 2018
1a71d74
Updates to DateRange- specs passing, 100% cov
sjlee3157 Sep 8, 2018
336113a
Wave1 Book class complete, tests pass
sjlee3157 Sep 8, 2018
c4ca4d0
Wave1 Hotel class done, tests pass.
sjlee3157 Sep 8, 2018
163211e
Added data/ and room_numbers.csv
sjlee3157 Sep 8, 2018
9c00164
Hotel now loads CSV file of room numbers, tests pass
sjlee3157 Sep 8, 2018
198151f
Added support folder for test data files
sjlee3157 Sep 8, 2018
210d953
Added private save_reservation method to Res class, tests pass
sjlee3157 Sep 8, 2018
659cc3f
Added conflict? method in DateRange, tests pass
sjlee3157 Sep 8, 2018
a271409
Merge branch 'master' of https://github.com/sjlee3157/hotel
sjlee3157 Sep 9, 2018
7106eff
Created list_avail_rooms method in Book, tests pass, support data added
sjlee3157 Sep 9, 2018
4790fbe
Added suggested room and error checking functionality to new reservat…
sjlee3157 Sep 9, 2018
a2b717b
Tests passing for Wave 2 requirements
sjlee3157 Sep 9, 2018
007fc01
Added empty files for new Block class
sjlee3157 Sep 9, 2018
f64f67a
Finished basic methods and initialize for Block, tests pass
sjlee3157 Sep 10, 2018
1063289
Fixed mistake in to_range method--should be exclusive
sjlee3157 Sep 10, 2018
a8e62c0
Hotel.find_rate class method still needs to be tested after CSV load …
sjlee3157 Sep 10, 2018
c74aac3
Ready to work on and test block methods in book.rb
sjlee3157 Sep 10, 2018
dbb6b69
Added code to load reservations from CSV, not yet tested
sjlee3157 Sep 10, 2018
b00e918
Better comments explaining range as overnights
sjlee3157 Sep 10, 2018
25a7649
Added CSV loading method, tests pass
sjlee3157 Sep 10, 2018
2157e3b
Daterange now has conflict? method of its own
sjlee3157 Sep 10, 2018
712d117
Reservation range bug fixed - no longer includes checkout date
sjlee3157 Sep 10, 2018
6ec25ee
private validate method checks all arguments
sjlee3157 Sep 10, 2018
f2868ab
modified test reservation data
sjlee3157 Sep 10, 2018
b5fa688
Added custom errors in errors.rb to HotBook module
sjlee3157 Sep 10, 2018
20e9c2f
Book class can now identify reservation and block conflicts
sjlee3157 Sep 10, 2018
f7efff1
Added hotbook.rb file
sjlee3157 Sep 10, 2018
65c20ba
Book class can make new block and check for conflicts
sjlee3157 Sep 10, 2018
f81e93b
Book class can make new block reservations and check for conflicts
sjlee3157 Sep 10, 2018
fa642ea
all current specs passing and 100% coverage
sjlee3157 Sep 10, 2018
097f6b7
Tested many new edge cases in Book.rb
sjlee3157 Sep 10, 2018
d9b0806
Deleted pseudocode.rb
sjlee3157 Sep 10, 2018
9249d5f
Notes added to refactors.txt
sjlee3157 Sep 10, 2018
6addaa5
Added new reservation nominal case in book sepc
sjlee3157 Sep 10, 2018
cf6fff8
Deleted pseudocode.md
sjlee3157 Sep 10, 2018
2ac71d8
Notes added to refactors.txt
sjlee3157 Sep 10, 2018
2f55f2d
Added new reservation nominal case in book sepc
sjlee3157 Sep 10, 2018
3959abe
Tagged a todo - inconsistency: daterange.conflict?(other) vs. block.c…
sjlee3157 Sep 12, 2018
ed6bead
Renamed Book class to BookingsManager
sjlee3157 Sep 30, 2018
379c1f2
Merge branch 'master' of https://github.com/sjlee3157/hotel
sjlee3157 Sep 30, 2018
64be846
Added new items to refactors list
sjlee3157 Sep 30, 2018
c884592
Added design-activity.md
sjlee3157 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.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ build-iPhoneSimulator/

# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc
sandbox.rb
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
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ Remember that your job is only to build the classes that store information and h
### User Stories

- As an administrator, I can view a list of rooms that are not reserved for a given date range
- reservation class returns reservations for that range
- book class looks at reservations
- As an administrator, I can reserve an available room for a given date range

### Constraints
Expand Down Expand Up @@ -123,7 +125,7 @@ If you are not familiar with what a block of hotel rooms, here is a brief descri

## Before Submissions

Usually by the end of a project, we can look back on what we made with a clearer understanding of what we actually needed. In industry, this is a great time to do a refactor of some sort. For this project however, you're off the hook... for the moment. We will be revisiting our hotels later on on the course, and you may want to make some changes at that point.
Usually by the end of a project, we can look back on what we made with a clearer understanding of what we actually needed. In industry, this is a great time to do a refactor of some sort. For this project however, you're off the hook... for the moment. We will be revisiting our hotels later on on the course, and you may want to make some changes at that point.

- Create a new file in the project called `refactors.txt`
- Make a short list of the changes that you could make, particularly in terms of naming conventions
Expand All @@ -139,4 +141,4 @@ You should not be working on these (or even thinking about them) until you have
- Create a CLI to interact with your hotel system

## What we're looking for
You can find what instructors will be looking for in the [feedback](feedback.md) markdown document.
You can find what instructors will be looking for in the [feedback](feedback.md) markdown document.
1 change: 1 addition & 0 deletions data/room_numbers.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20
59 changes: 59 additions & 0 deletions design-activity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Design Activity
https://github.com/Ada-Developers-Academy/textbook-curriculum/blob/master/02-intermediate-ruby/exercises/hotel-revisited.md

## Prompts

#### What classes does each implementation include? Are the lists the same?
Both implementations have the same three classes: `CartEntry`, `ShoppingCart`, and `Order`.

#### Write down a sentence to describe each class.
| Class | A | B |
| :------------ | :------------- | :------------- |
| CartEntry | **State:** knows its unit price and its quantity. | **State:** knows its unit price and its quantity. <br><br> **Behavior:** can calculate its own price. |
| ShoppingCart | **State:** stores an array of CartEntries. | **State:** stores an array of CartEntries. <br><br> **Behavior:** can ask each CartEntry for its price, and then calculate the subtotal (price of all entries in the array). |
| Order | **State:** knows the SALES TAX constant, stores an instance of the ShoppingCart class. <br><br> **Behavior:** can calculate a CartEntries price, can calculate a ShoppingCart's price, and can assemble the two into a total price. | **State:** knows the SALES TAX constant, stores an instance of the ShoppingCart class. <br><br> **Behavior:** can ask the ShoppingCart for its price (subtotal), and then calculate the total price. |

("stores" == "knows")

#### How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper.

The difference between A and B is that the Order class is concerned with *how* (A) versus *what* (B).

In A, the Order class knows too much outside of its "jurisdiction" -- it knows *how* to calculate CartEntry's price and *how* to calculate ShoppingCart's price. In A, classes are tightly coupled.

In B, the Order class asks CartEntry *what* its price is, asks ShoppingCart *what* its price is, and, from there, knows *how* to calculate a total price. In B, classes are loosely coupled.

#### What **data** does each class store? How (if at all) does this differ between the two implementations?
Classes store the **State** described in the above table.

#### What **methods** does each class have? How (if at all) does this differ between the two implementations?
Classes have methods for the **Behavior** described in the above table.

#### 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`?

A: the latter. B: the former.

- Does `total_price` directly manipulate the instance variables of other classes?

A: yes. B: no.

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

In the real world, like on Zazzle.com or CafePress.com, items are likely to each have unique wholesale price brackets based on quantity (though I suppose it could be something like 10% off if you spend $100 or more). Going with the former, we'd do this by adding some data - probably a price_table of quantities and prices, either an array of arrays or an array of hashes.

To modify A, CartEntry's unit_price has to be changed to price_table, and Order's total_price method has to be changed to include an enumerable method to look up a price by a quantity.

To modify B, CartEntry's unit_price also has to be changed to price_table, and CartEntry's price method also has to be changed to look up a price by a quantity.

B is easier to modify because changes in one class do not necessitate changes in another class. The person making the change doesn't have to hunt through the code to figure out effects of the change. The change is more proportional to the cost of change. There's less risk of far-off, hidden, unwanted effects of change.

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

I think B does, but I also think that classes in both A and B are *seemingly* single-responsibility. In both A and B, CartEntry has price and quantity, ShoppingCart has entries, and Order has a cart and a total price.

However, I think B is the better answer because A's CartEntry can and should shift the responsibility of knowing about the instance variables of other classes from itself to the classes in question.

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

B!
24 changes: 24 additions & 0 deletions hotbook.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# gems the project needs

Choose a reason for hiding this comment

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

This file's a neat idea!

require "csv"
require "date"

# Optional - for developer use
require "pry"
require "awesome_print"

# project constants
ROOM_NUMBERS_FILENAME = "data/room_numbers.csv"
TEST_RESERVATION_FILENAME = "support/test_reservation_data.csv"
RESERVATION_DATA_FILENAME = TEST_RESERVATION_FILENAME #"data/reservation_data.csv"

# namespace module
module HotBook;
end

# all of the classes that live in the module
require_relative "lib/block.rb"
require_relative "lib/bookingsmanager.rb"
require_relative "lib/daterange.rb"
require_relative "lib/errors.rb"
require_relative "lib/hotel.rb"
require_relative "lib/reservation.rb"
Empty file removed lib/.keep
Empty file.
25 changes: 25 additions & 0 deletions lib/block.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module HotBook
class Block
attr_reader :available, :rooms, :daterange, :room_rate

def initialize(daterange:, rooms:, room_rate: 185.0)
@daterange = daterange
@rooms = rooms.map! {|room| room.upcase}
@available = rooms.clone
@room_rate = room_rate
end

Choose a reason for hiding this comment

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

So Block has a set of rooms and a way to remove rooms from the list, and tell if the block conflicts, I feel like it should be able to reserve a room in the block as well, maybe then returning a reservation which your booking system could use to update reservations.

# Removes a room from @available once it's reserved
def disable(query)
room = available.find { |room| room == query } # guaranteed to return 1
return available.delete(room) # returns value of what it deleted
end

# Does the block conflict with another block?
# Is this being used??
def conflict?(other)
return daterange.conflict?(other)
end

end
end
170 changes: 170 additions & 0 deletions lib/bookingsmanager.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
module HotBook
# The BookingsManager class is responsible for:
# holding all reservations and blocks,
# searching through them, and
# making new reservations and blocks.
# It includes support methods for determining availability/conflicts.

class BookingsManager
attr_reader :reservations, :hotel, :blocks

def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new)
@hotel = hotel
@reservations = Reservation.from_csv(RESERVATION_DATA_FILENAME)
@blocks = [] # a list of Blocks
end

def new_reservation(daterange, room_number: suggest_room(daterange))
validate(:daterange, daterange)
validate(:room_number, room_number)
room_number = room_number.upcase
if room_taken?(daterange, room_number)
raise RoomIsTakenError, "Room is already reserved some time within "\
"this daterange"
elsif room_blocked?(daterange, room_number)
raise RoomIsBlockedError, "Room is blocked some time within this "\
"daterange"
end

room_rate = hotel.find_rate(room_number)

new_reservation = HotBook::Reservation.new(daterange: daterange,
room_number: room_number,
room_rate: room_rate)
reservations << new_reservation
return new_reservation
end

def new_block(daterange, rooms, discount_rate: 185.0)
validate(:daterange, daterange)
validate(:rooms, rooms)
if rooms.size > 5
raise ArgumentError, "A Block cannot have more than 5 rooms"
end
# Cannot overlap or conflict with existing reservation
rooms.each do |room_number|
if room_taken?(daterange, room_number)
raise RoomIsTakenError, "Room already has reservation during "\
"this daterange"
elsif room_blocked?(daterange, room_number)
raise BlockConflictError, "A block already exists on a room during " \
"this daterange"
end
end
new_block = HotBook::Block.new(daterange: daterange,
rooms: rooms,
room_rate: discount_rate) # Default
blocks << new_block
return new_block
end

def new_block_reservation(block, room_number: block.available.first)
validate(:block, block)
# raise error if the block has no available reservations
if block.available == [] || nil
raise NoRoomsAvailableError, "This block is fully booked"
end
validate(:room_number, room_number)
room_number = room_number.upcase
# make sure the room number is part of the block
unless block.rooms.include?(room_number)
raise ArgumentError, "Room is not part of the given block"
end
# make sure the room is available
unless block.available.include?(room_number)
raise RoomIsTakenError, "Room already reserved during this block"
end
# Remove this room from its memory array of what's still available:
block.disable(room_number)
new_reservation = HotBook::Reservation.new(daterange: block.daterange,
room_number: room_number,
room_rate: block.room_rate)
@reservations << new_reservation
return new_reservation
end

# Returns the first room in the array of available rooms
def suggest_room(daterange)
validate(:daterange, daterange)
available = public_avail_rooms(daterange)
if available == nil || available == []
raise HotBook::NoRoomsAvailableError, "All rooms are booked " \
"during this daterange"
end
return available.first
end

# Returns an array of reservations (EXCLUDING checkout day)
def list_by_nights(date)
validate(:date, date)
return reservations.select {|reservation|
reservation.range.include?(date) }
end

# Returns array of room numbers that are publicly available during a daterange
def public_avail_rooms(daterange)
validate(:daterange, daterange)
a = conflicting_reservations(daterange).map { |reservation|
reservation.room_number }
b = conflicting_blocks(daterange).flat_map { |block| block.rooms }
available_rooms = hotel.room_numbers - a - b
return available_rooms
end

# Searches all reservation dateranges for any conflict with given daterange,
# returns true if room number is already part of a reservation
def room_taken?(daterange, room_number)
validate(:daterange, daterange)
validate(:room_number, room_number)
a = conflicting_reservations(daterange).map { |reservation|
reservation.room_number }
return a.include?(room_number)
end

# Searches all block dateranges for any conflict with given daterange,
# and returns true if room number is already part of a block
def room_blocked?(daterange, room_number)
validate(:daterange, daterange)
validate(:room_number, room_number)
b = conflicting_blocks(daterange).flat_map { |block| block.rooms }
return b.include?(room_number)
end

# Returns an array of reservations with a daterange conflict
def conflicting_reservations(daterange)
validate(:daterange, daterange)
return reservations.select { |reservation|
reservation.daterange.conflict?(daterange) }
end

#TODO: There's an inconsistency here-- daterange.conflict?(other) vs. block.conflict?(daterange)--PICK ONE!
# Returns an array of blocks with a daterange conflict
def conflicting_blocks(daterange)
validate(:daterange, daterange)
return blocks.select { |block| block.conflict?(daterange) }
end

private

def validate(type, var)
case type
when :date
raise ArgumentError, "Invalid date - use Date.parse (expected Date, " \
"not #{var.class})" unless var.is_a?(Date)
when :room_number
raise ArgumentError, "Invalid room number (expected String, " \
"not #{var.class})" unless var.is_a?(String)
when :daterange
raise ArgumentError, "Invalid daterange (expected HotBook::DateRange," \
" not #{var.class})" unless var.is_a?(HotBook::DateRange)
when :rooms
raise ArgumentError, "Invalid rooms (expected Array of " \
"Strings)" unless var.is_a?(Array) && var.first.is_a?(String)
when :block
raise ArgumentError, "Invalid block (expected HotBook::Block, " \
"not #{var.class})" unless var.is_a?(HotBook::Block)
end
end

end
end
34 changes: 34 additions & 0 deletions lib/daterange.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module HotBook
# The DateRange class compares and does calculations on other DateRanges
# Refer to Date gem docu to understand what date format to use (i.e. y-m-d)
class DateRange
attr_reader :start_date, :end_date

def initialize(start_date:, end_date:)
@start_date = Date.parse(start_date.to_s)
@end_date = Date.parse(end_date.to_s)
raise ArgumentError, "Invalid range #{self}: End must be > Start)" unless
@end_date > @start_date
end

def duration
return (end_date - start_date).to_i
end

# Does daterange conflict with another daterange?
def conflict?(other)
if start_date >= other.end_date || end_date <= other.start_date
return false
else
return true
end
end

# Range only includes overnights and EXCLUDES checkout day.
# should change this to "contains(date)"
def to_range
return (@start_date...@end_date)
end
end

end
13 changes: 13 additions & 0 deletions lib/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module HotBook
class RoomIsTakenError < StandardError
end

class RoomIsBlockedError < StandardError
end

class BlockConflictError < StandardError
end

class NoRoomsAvailableError < StandardError
end
end
Loading