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

Naheed - Hotel - Edges #43

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 2 deletions Guardfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
guard :minitest, bundler: false, rubygems: false do
# with Minitest::Spec
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" }
watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
Expand Down
25 changes: 25 additions & 0 deletions design-activity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
**What classes does each implementation include? Are the lists the same?**
Both Implementations include the classes CartEntry, ShoppingCart, and Order.
**Write down a sentence to describe each class.**
- In both A and B, CartEntry creates an instance of an item or entry, which has the attributes unit_price and quantity.
- In A, ShoppingCart contains an array of all entries. In B, it contains an array of all entries and calculates the total price of the cart.
- In both, Order calculates the total price of the ShoppingCart including tax.
**How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper.**
- Each class refers to data stored in other classes- total relies on items in shopping cart, tax, etc.
**What data does each class store? How (if at all) does this differ between the two implementations?**
- CartEntry stores unit_price and quantity. ShoppingCart stores entries, and in B, the price of the cart as well. Order stores the cart, the value of the sales tax, and the total price of the order.
**What methods does each class have? How (if at all) does this differ between the two implementations?**
- CartEntry initializes the item in both, but in B it also calculates the price of the item. ShoppingCart initializes the entries array, and in B it additionally calculates the price of the entire cart. Order calculates the total price.
**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?**
- In A, computing the price is the responsibility of Order- although CartEntry stores the price and quantity as well as calculates the price * quantity, order repeats this method. In B, ShoppingCart is responsible for calculating the price of the cart, which makes more sense because it is an attribute of the cart itself, while the order should be responsible for multiplying that price by the SALES_TAX and returning that.
**Does total_price directly manipulate the instance variables of other classes?**
**If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify?**
- A would be easier to modify because the price is only calculated once, in the Order class. We could add code to the effect of "if quantity is larger than x, price is $y".
**Which implementation better adheres to the single responsibility principle?**
- A, because each class serves one purpose, but they still rely on and interact with other classes. Also, there is no repeated code.
**Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled?**

**Refactors**

I had my total cost method in my BookingSystem class, while it should have been in the Reservation class. This can be copied into the Reservation class, but the references to this method will have to be changed to @reservation instead of @system in the tests as well as wherever this is referenced in other methods.
Empty file removed lib/.keep
Empty file.
20 changes: 20 additions & 0 deletions lib/block.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Hotel
class Block
attr_accessor :block_id, :number_of_rooms, :start_date, :end_date, :price_per_night

def initialize(block_id:, number_of_rooms:, start_date:, end_date:, price_per_night:)
@block_id = block_id
@number_of_rooms = number_of_rooms
@start_date = start_date
@end_date = end_date
@price_per_night = price_per_night
end

# def assign_available_room(start_date, end_date)
# if @number_of_rooms > 5
# raise ArgumentError, "Cannot block more than 5 rooms"
# end
# return available_rooms[0..(@number_of_rooms.to_i - 1)]
# end
end
end
14 changes: 14 additions & 0 deletions lib/block_reservation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Hotel
class BlockReservation
attr_accessor :block_id, :reservation_id, :room, :start_date, :end_date, :price_per_night

def initialize(block_id:, reservation_id:, room:, start_date:, end_date:, price_per_night:)

Choose a reason for hiding this comment

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

The only difference between this and a regular reservation is that a block reservation has a block ID. One option that might simplify your design is to consolidate both in the Reservation class, and add in an optional block ID, which would be nil if this reservation isn't part of a block.

@block_id = block_id
@reservation_id = reservation_id
@room = room
@start_date = start_date
@end_date = end_date
@price_per_night = price_per_night
end
end
end
112 changes: 112 additions & 0 deletions lib/booking_system.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
module Hotel
class BookingSystem
attr_accessor :rooms, :reservations, :all_ids, :blocks

def initialize
@rooms = load_rooms
@reservations = []
@all_ids = []
@blocks = []
end

def load_rooms
rooms = []
i = 1

20.times do
rooms << i
i += 1
end
return rooms
end

def make_reservation(start_date, end_date)
reservation = Hotel::Reservation.new(reservation_id: generate_id, room: assign_available_room(start_date, end_date), start_date: start_date, end_date: end_date, price_per_night: 200)
@reservations << reservation

Choose a reason for hiding this comment

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

I love the way you've broken each of these bits of functionality into separate methods! However, it's a little difficult to read all on one line like this. You might split it across multiple lines:

reservation = Hotel::Reservation.new(
  reservation_id: generate_id(),
  room: assign_available_room(start_date, end_date),
  start_date: start_date,
  end_date: end_date,
  price_per_night: 200
)

I also like adding parentheses to the call to generate_id(), even though there are no arguments - this makes it a little clearer that this is a method call.

# binding.pry
end

def make_block(start_date, end_date, number_of_rooms)
if number_of_rooms > 5
raise ArgumentError, "Cannot reserve more than 5 rooms"
end

Choose a reason for hiding this comment

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

What if the number of rooms is less than 1?


id = generate_id

block = Hotel::Block.new(block_id: id, number_of_rooms: number_of_rooms, start_date: start_date, end_date: end_date, price_per_night: 150)
@blocks << block

number_of_rooms.times do
block_reservation = Hotel::BlockReservation.new(block_id: id, reservation_id: nil, room: assign_available_room(start_date, end_date), start_date: start_date, end_date: end_date, price_per_night: 150)

Choose a reason for hiding this comment

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

Same comment about line lengths on lines 36 and 40.

@reservations << block_reservation
end
end

def make_block_reservation(block_id)
block_reservation = find_empty_block_reservation(block_id)
block_reservation.reservation_id = generate_id
return block_reservation
end

def assign_available_room(start_date, end_date)
booked_rooms = []
available_rooms = []
@reservations.each do |reservation|
if
reservation.start_date < end_date && start_date < reservation.end_date
booked_rooms << reservation.room

Choose a reason for hiding this comment

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

Here your BookingSystem is taking on some extra responsibility: determining whether a reservation overlaps a set of dates. I would argue that that is really the reservation's responsibility. You could write a method called something like Reservation#overlap?(start_date, end_date) that takes care of this logic.

That simplifies this code, and makes it easier to test the logic in isolation. You also repeat this code on line 72, which means that if the requirements change or you discover a bug, you'll have to change it in two places.

Choose a reason for hiding this comment

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

This also an example of tight coupling, since BookingSystem needs to know about how a Reservation stores its dates.

Choose a reason for hiding this comment

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

Why not call reservations_within_date here to get the list of booked_rooms?

end
end
all_rooms = load_rooms
available_rooms = all_rooms - booked_rooms
if available_rooms[0] == nil
raise ArgumentError, "No rooms available"
end
return available_rooms[0]
end

def search_reservations(start_date, end_date)
reservations_within_date = []
@reservations.each do |reservation|
if
reservation.start_date < end_date && start_date < reservation.end_date

Choose a reason for hiding this comment

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

This and the above method could be streamlined a little using the select enumerable:

return @reservations.select do |reservation|
  reservation.start_date < end_date && start_date < reservation.end_date
end

reservations_within_date << reservation
end
end
# binding.pry
return reservations_within_date
end

def find_reservation(reservation_id)
reservation = @reservations.find { |reservation| reservation.reservation_id == reservation_id }
return reservation
end

def find_empty_block_reservation(block_id)
block_reservation = @reservations.find { |block_reservation| block_reservation.block_id == block_id && block_reservation.reservation_id == nil}
if block_reservation == nil
raise ArgumentError, "all rooms in block have been reserved"
end
return block_reservation
end

# def find_block(block_id)
# block = @reservations.find_all { |block_reservation| block_reservation.block_id == block_id }
# return block
# end

def generate_id
reservation_id = rand(1..100000)
check_id(reservation_id)
@all_ids << reservation_id
return reservation_id
end

def check_id(reservation_id)
if @all_ids.include?(reservation_id)
raise ArgumentError, "reservation_id already exists"
end

Choose a reason for hiding this comment

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

I don't know if raising an argument error is the right choice here, since the user didn't supply this ID, the program generated it. Instead you might regenerate the ID, or assign IDs sequentially so that there's no possibility of an collision (this is what Rails does).

end

end
end
20 changes: 20 additions & 0 deletions lib/reservation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Hotel
class Reservation
attr_accessor :reservation_id, :room, :start_date, :end_date, :price_per_night, :total_cost

def initialize(reservation_id:, room:, start_date:, end_date:, price_per_night:)
@system = Hotel::BookingSystem.new
@reservation_id = reservation_id
@room = room
@start_date = start_date
@end_date = end_date
@price_per_night = price_per_night
end
def total_cost(reservation_id)
reservation = @system.find_reservation(reservation_id)
nights = reservation.end_date - reservation.start_date
total_cost = nights * reservation.price_per_night
return total_cost.to_f.round(2)
end
end
end
4 changes: 4 additions & 0 deletions refactors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Refactors:
- consolidate block and block_reservation classes
- rename block method names to be more descriptive
- add more tests for edge cases
16 changes: 16 additions & 0 deletions spec/block_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require_relative 'spec_helper'
require 'date'
require 'pry'

describe "Block class" do

describe "Block instantiation" do
before do
@block = Hotel::Block.new(block_id: nil, number_of_rooms: nil, start_date: nil, end_date: nil, price_per_night: nil)
end

it "is an instance of block" do
expect(@block).must_be_kind_of Hotel::Block
end
end
end
Loading