diff --git a/Guardfile b/Guardfile index 6760f9177..471693a4d 100644 --- a/Guardfile +++ b/Guardfile @@ -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' } diff --git a/design-activity.md b/design-activity.md new file mode 100644 index 000000000..32407eaae --- /dev/null +++ b/design-activity.md @@ -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. diff --git a/lib/.keep b/lib/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/lib/block.rb b/lib/block.rb new file mode 100644 index 000000000..1aa689895 --- /dev/null +++ b/lib/block.rb @@ -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 diff --git a/lib/block_reservation.rb b/lib/block_reservation.rb new file mode 100644 index 000000000..49c8eff58 --- /dev/null +++ b/lib/block_reservation.rb @@ -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:) + @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 diff --git a/lib/booking_system.rb b/lib/booking_system.rb new file mode 100644 index 000000000..c6aaf2402 --- /dev/null +++ b/lib/booking_system.rb @@ -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 + # 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 + + 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) + @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 + 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 + 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 + end + + end +end diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..ed41bf72e --- /dev/null +++ b/lib/reservation.rb @@ -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 diff --git a/refactors.txt b/refactors.txt new file mode 100644 index 000000000..e2a9373ec --- /dev/null +++ b/refactors.txt @@ -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 diff --git a/spec/block_spec.rb b/spec/block_spec.rb new file mode 100644 index 000000000..91bb53299 --- /dev/null +++ b/spec/block_spec.rb @@ -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 diff --git a/spec/booking_system_spec.rb b/spec/booking_system_spec.rb new file mode 100644 index 000000000..e5f7f13b3 --- /dev/null +++ b/spec/booking_system_spec.rb @@ -0,0 +1,233 @@ +require_relative 'spec_helper' +require 'date' +require 'pry' + +describe "BookingSystem class" do + + before do + @system = Hotel::BookingSystem.new + @reservation = Hotel::Reservation.new(reservation_id: nil, room: nil, start_date: nil, end_date: nil, price_per_night: 200) + end + + describe "load rooms" do + it "loads rooms in an array" do + @rooms = @system.load_rooms + # binding.pry + expect(@rooms).must_be_kind_of Array + end + end + + describe "make reservation" do + it "makes a reservation" do + @system.make_reservation(Date.new(2018,1,1), Date.new(2018,1,5)) + reservation = Hotel::Reservation.new(reservation_id: 1, room: 1, start_date: Date.new(2018, 1, 1), end_date: Date.new(2018, 1, 5), price_per_night: 200) + expect(@system.reservations.length).must_equal 1 + end + + end + + describe "assign room" do + it "assigns the first available room" do + + # @system.make_reservation(Date.new(2018, 1, 1), Date.new(2018, 1, 5)) + + reservation1 = Hotel::Reservation.new(reservation_id: 1, room: 1, start_date: Date.new(2018, 1, 7), end_date: Date.new(2018, 1, 8), price_per_night: 200) + @system.reservations << reservation1 + + # @system.make_reservation(Date.new(2018, 1, 6), Date.new(2018, 1, 7)) + + reservation2 = Hotel::Reservation.new(reservation_id: 2, room: 2, start_date: Date.new(2018, 1, 1), end_date: Date.new(2018, 1, 5), price_per_night: 200) + @system.reservations << reservation2 + + + reservation3 = Hotel::Reservation.new(reservation_id: 3, room: @system.assign_available_room(Date.new(2018, 1, 6), Date.new(2018, 1, 9)), start_date: Date.new(2018, 1, 6), end_date: Date.new(2018, 1, 9), price_per_night: 200) + @system.reservations << reservation3 + expect(reservation3.room).must_equal 2 + end + + it "raises argument error if no rooms are available" do + + 20.times do + reservation = Hotel::Reservation.new(reservation_id: 1, room: @system.assign_available_room((Date.new(2018, 1, 1)), (Date.new(2018, 1, 8))), start_date: Date.new(2018, 1, 1), end_date: Date.new(2018, 1, 8), price_per_night: 200) + @system.reservations << reservation + + # @booking_system.make_reservation((Date.new(2018, 1, 6)), (Date.new(2018, 1, 6))) + end + # binding.pry + expect {@system.make_reservation((Date.new(2018, 1, 1)), (Date.new(2018, 1, 8)))}.must_raise ArgumentError + end + end + + describe "search reservation dates" do + before do + + reservation1 = Hotel::Reservation.new(reservation_id: 1, room: 1, start_date: Date.new(2018, 1, 1), end_date: Date.new(2018, 1, 8), price_per_night: 200) + @system.reservations << reservation1 + + reservation2 = Hotel::Reservation.new(reservation_id: 2, room: 2, start_date: Date.new(2018, 1, 7), end_date: Date.new(2018, 1, 15), price_per_night: 200) + @system.reservations << reservation2 + + end + it "returns an array of reservations" do + + expect(@system.search_reservations(Date.new(2018, 1, 4), Date.new(2018, 1, 8))).must_be_instance_of Array + end + + it "returns reservations that overlap on the existing reservation start date" do + + expect(@system.search_reservations(Date.new(2017, 12, 30), Date.new(2018, 1, 5))[0].reservation_id).must_equal 1 + + end + + it "returns reservations that overlap on the existing reservation end" do + + expect(@system.search_reservations(Date.new(2018, 1, 10), Date.new(2018, 1, 20))[0].reservation_id).must_equal 2 + + end + + it "returns multiple reservations that are on the same date" do + + expect(@system.search_reservations(Date.new(2018, 1, 7), Date.new(2018, 1, 15)).length).must_equal 2 + + end + + it "does not return reservations that end on the new reservation's start date" do + + expect(@system.search_reservations(Date.new(2018, 1, 15), Date.new(2018, 1, 20)).length).must_equal 0 + + end + + it "does not return reservations that start on the new reservation's end date" do + + expect(@system.search_reservations(Date.new(2017, 12, 20), Date.new(2018, 1, 1)).length).must_equal 0 + + end + + end + + describe "find reservation method with reservation_id" do + it "returns the corresponding reservation given reservation_id" do + reservation = Hotel::Reservation.new(reservation_id: 1, room: 1, start_date: Date.new(2018, 1, 1), end_date: Date.new(2018, 1, 5), price_per_night: 200) + @system.reservations << reservation + + expect(@system.find_reservation(1))[0].reservation_id.must_equal 1 + end + end + + describe "total cost of reservation" do + it "finds the total cost of reservation given reservation_id" do + + @reservation = Hotel::Reservation.new(reservation_id: 1, room: 1, start_date: Date.new(2018, 1, 1), end_date: Date.new(2018, 1, 5), price_per_night: 200) + @system.reservations << @reservation + + expect(@reservation.total_cost(1)).must_equal 800 + end + end + + describe "generate_id" do + it "generates an integer reservation_id" do + expect(@system.generate_id).must_be_kind_of Integer + end + + it "assigns reservation_id to reservation" do + reservation1 = Hotel::Reservation.new(reservation_id: @system.generate_id, room: 1, start_date: Date.new(2018, 1, 1), end_date: Date.new(2018, 1, 5), price_per_night: 200) + @system.reservations << reservation1 + + expect(reservation1.reservation_id).must_be_kind_of Integer + end + + it "raises an Argument Error if reservation_id is not unique" do + # binding.pry + reservation1 = Hotel::Reservation.new(reservation_id: @system.generate_id, room: 1, start_date: Date.new(2018, 1, 1), end_date: Date.new(2018, 1, 5), price_per_night: 200) + @system.reservations << reservation1 + expect { @system.check_id(reservation1.reservation_id) }.must_raise ArgumentError + end + end + + describe "block tests" do + describe "make_block method" do + it "raises ArgumentError if more than 5 rooms booked" do + expect { @system.make_block((Date.new(2018, 1, 1)), (Date.new(2018, 1, 2)), 6) }.must_raise ArgumentError + end + + it "creates a block" do + @system.make_block((Date.new(2018,1,1)), (Date.new(2018,1,5)), 5) + + expect(@system.blocks.length).must_equal 1 + end + + it "creates x number of reservations for x rooms in the block (x = 5)" do + @system.make_block((Date.new(2018,1,1)), (Date.new(2018,1,5)), 5) + + expect(@system.reservations.length).must_equal 5 + end + end + + describe "find_empty_block_reservation" do + it "finds an empty block reservation given the block id" do + block_reservation = Hotel::BlockReservation.new(block_id: 20, reservation_id: nil, room: 2, start_date: Date.new(2018,1,1), end_date: Date.new(2018,1,5), price_per_night: 150) + @system.reservations << block_reservation + + expect(@system.find_empty_block_reservation(20)).must_equal block_reservation + end + + it "will raise argument error if all rooms in block have been reserved" do + block_reservation = Hotel::BlockReservation.new(block_id: 20, reservation_id: 5, room: 2, start_date: Date.new(2018,1,1), end_date: Date.new(2018,1,5), price_per_night: 150) + @system.reservations << block_reservation + + expect { @system.find_empty_block_reservation(20) }.must_raise ArgumentError + end + end + + describe "make_block_reservation" do + + it "changes reservation_id from nil to integer when making a block_reservation" do + block_reservation = Hotel::BlockReservation.new(block_id: 20, reservation_id: nil, room: 2, start_date: Date.new(2018,1,1), end_date: Date.new(2018,1,5), price_per_night: 150) + @system.reservations << block_reservation + + expect((@system.make_block_reservation(20)).reservation_id).must_be_kind_of Integer + end + end + end +end + + +# Hi! In Edges we talked about interesting test cases for date overlaps this afternoon. Here is a full list of all the cases I’ll be looking for when I give feedback: +# +# Two date ranges *do* overlap if range A compared to range B: +# - Same dates +# - Overlaps in the front +# - Overlaps in the back +# - Completely contained +# - Completely containing +# +# Two date ranges are *not* overlapping if range A compared to range B: +# - Completely before +# - Completely after +# - Ends on the checkin date +# - Starts on the checkout date (edited) +# +# +# start1 = 9 +# end1 = 12 +# start2 = 8 +# end2 = 15 +# +# (StartDate1 < EndDate2) and (StartDate2 <= EndDate1) +# +# true true + + +# def search_reservations(start_date_2, end_date_2) +# reservations_within_date = [] +# empty_rooms_within_date = [] +# @reservations.each do |reservation| +# if +# reservation.start_date < end_date_2 && start_date_2 < reservation.end_date +# reservations_within_date << reservation +# else +# empty_rooms_within_date << reservation.room +# end +# # binding.pry +# end +# end diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb new file mode 100644 index 000000000..eadaa66d1 --- /dev/null +++ b/spec/reservation_spec.rb @@ -0,0 +1,30 @@ +require_relative 'spec_helper' +require 'date' +require 'pry' + +describe "Reservation class" do + before do + @reservation = Hotel::Reservation.new(reservation_id: 1, room: nil, start_date: Date.today, end_date: Date.today, price_per_night: 200) + @system = Hotel::BookingSystem.new + end + + describe "Reservation instantiation" do + before do + @reservation = Hotel::Reservation.new(reservation_id: 5, room: 1, start_date: Date.new(2018, 1, 1), end_date: Date.new(2018, 1, 2), price_per_night: 200) + end + + it "is an instance of Reservation" do + expect(@reservation).must_be_kind_of Hotel::Reservation + end + end + describe "total cost of reservation" do + it "accounts for the discounted price when calculating total cost" do + @reservation = Hotel::Reservation.new(reservation_id: 5, room: 1, start_date: Date.new(2018, 1, 1), end_date: Date.new(2018, 1, 2), price_per_night: 200) + + @system.make_block((Date.new(2018,1,1)), (Date.new(2018,1,5)), 1) + id = @system.reservations[0].reservation_id + + expect(@reservation.total_cost(id)).must_equal 600 + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4d1e3fdc8..27045076e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,4 +5,7 @@ Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new -# Require_relative your lib files here! +require_relative '../lib/reservation' +require_relative '../lib/booking_system' +require_relative '../lib/block' +require_relative '../lib/block_reservation'