diff --git a/.gitignore b/.gitignore index 5e1422c9c..8a3238615 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /test/tmp/ /test/version_tmp/ /tmp/ +.DS_Store # Used by dotenv library to load environment variables. # .env diff --git a/Rakefile b/Rakefile new file mode 100644 index 000000000..deb52f2cd --- /dev/null +++ b/Rakefile @@ -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 diff --git a/design-activity.md b/design-activity.md new file mode 100644 index 000000000..2035f6c3b --- /dev/null +++ b/design-activity.md @@ -0,0 +1,53 @@ + +Both implementations include the following three classes - CartEntry, ShoppingCart, and Order + + +The CartEntry class holds information on price and quantity for a given entry into a shopping cart. +ShoppingCart - is a collection of CartEntry objects. +Order - Responsible for the shopping cart and the total price of the cart. + + + +The order class is the biggest class and draws upon the shopping cart class which in turn is in itself a collection of CartEntries. + + +CartEntry: Stores the unit price and the quantity of each CartEntry object. +ShoppingCart: Stores a collection of CartEntry object in an array. +Order: Stores a ShoppingCart object. + + +CartEntry: + Implementation A - No methods. + Implementation B - A price method to calculate and return the price of the CartEntry. + +ShoppingCart: + Implementation A - No methods. + Implementation B - Has a price method that looks at each entry in the shopping cart, calls the CartEntry price method, and updates the variable sum, then returns the sum variable. + +Order: + Both implementations Order class have a total_price method. They each use the same structure to calculate and add sales tax but they ways they go about calculating the total price of the cart are they are quite different. + + Implementation A - table the total_price method loops through each entry in the cart (CartEntry), calculates the price per entry, updates the sum variable with each entries price. Then the sales tax is calculates and added to the sum and the total_price is returned. + + Implementation B - the Order class total_price method calls the price method built into the ShoppingCart class on the instance of the ShoppingCart object and stores the price into a variable. Then the sales tax is calculates and added to the sum and the total_price is returned. + + Implementation A's total_price method is self contained while the total_price method in implementation B is dependent on the price method in each of the other classes. + + + + In implementation A the total_price method logic is relatively self contained or retained while in Imp B the logic is delegated to both Shopping Cart and CartEntry classes by stringing their price methods together. + + + In implementation A the total_price directly manipulate the instance variables of the CartEntry instances while implementation B just calls the classes method on the instance of the class object. + + +Implementation B would be easier to change because you could add some logic into the CartEntry class so that when a certain quantity is reached you discount the price. In order to do that in Implementation A you would have to add logic to the Orders class which is messy and would be out of place. + + +Implementation B + + + + + +I have sooo much going on in m reservations class. I felt that way when I was making the project and even more so after seeing the solution Dan posted. I started by following a suggestion of Dan's to move some of the responsibility of the check reservations method into the date range class. I honestly think there is more to do for me on this project to make it actually single responsibility but the CS Fun hw took up soo much of my time this past week that this hw didn't get the time and effort it deserved. diff --git a/lib/block.rb b/lib/block.rb new file mode 100644 index 000000000..3666f4302 --- /dev/null +++ b/lib/block.rb @@ -0,0 +1,24 @@ +require 'pry' +require 'date' +require_relative 'booking' + +module Hotel + class Block < Booking + attr_reader :id, :rooms, :date_range, :nights, :total_cost, :block_info, :block_name, :discount, :reserved_rooms + + def initialize(id, rooms, date_range, block_info) + super(id, rooms, date_range) + @block_info = block_info + end + + def rooms_available_block + rooms_available = [] + @rooms.each do |room| + if room.booked == false || room.booked == nil + rooms_available << room + end + end + return rooms_available + end + end +end diff --git a/lib/booking.rb b/lib/booking.rb new file mode 100644 index 000000000..e0fa142d0 --- /dev/null +++ b/lib/booking.rb @@ -0,0 +1,27 @@ +require 'pry' +require 'date' + +module Hotel + class Booking + attr_reader :id, :rooms, :date_range, :nights, :total_cost, :block_info + + def initialize(id, rooms, date_range, block_info: nil) #blockname and price? or whole block? + @id = id + @block_info = block_info + @rooms = rooms + @date_range = date_range + @nights = date_range.nights + @total_cost = (@nights * rooms.inject(0){|sum,room| sum + room.cost}) + end + # ran out of time to update total cost for a block, ideally I would pass through a discount and have that applied to total cost + # to book a roomin a block we would need to have total_cost update with the discounted range_of_dates + # def calc_total_cost + # if @block_info.length > 0 + # total = @rooms.length * @nights * @block_info[1] + # @total_cost = total + # else + # @total_cost = (@nights * rooms.inject(0){|sum,room| sum + room.cost}) + # end + # end + end +end diff --git a/lib/custom_exceptions.rb b/lib/custom_exceptions.rb new file mode 100644 index 000000000..bf581e0d1 --- /dev/null +++ b/lib/custom_exceptions.rb @@ -0,0 +1,5 @@ +class InvalidDateRangeError < StandardError +end + +class InvalidRoomQuantity < StandardError +end diff --git a/lib/date_range.rb b/lib/date_range.rb new file mode 100644 index 000000000..f243f253b --- /dev/null +++ b/lib/date_range.rb @@ -0,0 +1,48 @@ +require 'pry' +require 'date' + +module Hotel + class DateRange + attr_reader :check_in, :check_out, :nights, :nights_arr + + def initialize(check_in, check_out) + @check_in = check_in + @check_out = check_out + @nights = '' + @nights_arr = [] + make_nights_arr + end + + def valid_input? + if @check_in.class != Date || @check_out.class != Date + raise ArgumentError.new("User input Check-in: #{@check_in} and Check-out: #{@check_out} are not valid inputs, must be a Date Object.") + end + end #end valid_input? method + + def valid_date? + valid_input? + if @check_in > @check_out + raise InvalidDateRangeError.new("Invalid Date Range: Check-out #{@check_out} is before Check-in #{@check_in}") + end + end #end valid_date? method + + def make_nights_arr + valid_date? + @nights = (@check_out - @check_in).to_i + counter = 0 + @nights.times do + @nights_arr << (@check_in + counter) + counter += 1 + end + # return @nights_arr + end #end make_nights_arr method + + def overlap?(other) + if other.check_out <= @check_in || other.check_in >= @check_out + return false + else + return true + end + end + end #end DateRange class +end #end Hotel module diff --git a/lib/reservations.rb b/lib/reservations.rb new file mode 100644 index 000000000..a186865f3 --- /dev/null +++ b/lib/reservations.rb @@ -0,0 +1,97 @@ +require 'pry' +require 'date' + +module Hotel + class Reservations + attr_reader :all_rooms, :all_reservations + def initialize + @all_rooms = [] + @all_reservations = [] + 20.times do |i| + i += 1 + @all_rooms << Hotel::Room.new(i) + end + end + + def make_reservation(check_in,check_out,num_rooms, block_name:false) + date_range = DateRange.new(check_in,check_out) + id = (@all_reservations.length + 1) + rooms_available = check_availability(check_in,check_out) + enough_rooms?(rooms_available, num_rooms, block_name) + rooms_to_book = rooms_available.shift(num_rooms) + + if block_name == false + update_room_state(rooms_to_book,num_rooms,true) + booking = Booking.new(id,rooms_to_book,date_range, block_info: false) + else + update_room_state(rooms_to_book,num_rooms,false) + booking = Block.new(id,rooms_to_book,date_range,block_name) + end + @all_reservations << booking + return booking + end + + # booked_rooms = [] + + + def check_reservations(check_in,check_out) + booked_rooms = [] + range = DateRange.new(check_in,check_out) + @all_reservations.each do |booking| + if booking.date_range.overlap?(range) + booked_rooms << booking.rooms + end + end + return booked_rooms + # range = DateRange.new(check_in,check_out).nights_arr + # range.each do |date| + # @all_reservations.each do |booking| + # if booking.date_range.nights_arr.include?(date) + # booking.rooms.each do |room| + # if (!booked_rooms.include?(room)) + # booked_rooms << room + # end + # end + # end + # end + # end + return booked_rooms + end + + def check_availability(check_in,check_out) + booked_rooms = check_reservations(check_in,check_out) + available_rooms = [] + all_rooms.each do |room| + if !(booked_rooms.include?(room)) + available_rooms << room + end + end + return available_rooms + end + + def enough_rooms?(rooms_available, num_rooms, block_name) + if block_name != false && num_rooms > 5 + raise InvalidRoomQuantity.new("You cannot book more than 5 rooms in a block.") + end + if rooms_available.length < num_rooms || num_rooms > 20 + raise InvalidRoomQuantity.new("Unfortunately the hotel does not have enough available rooms to handle your request of #{num_rooms} rooms.") + end + end + + def reserve_from_block(block, num_rooms) + rooms_to_book = block.rooms_available_block + enough_rooms?(rooms_to_book,num_rooms,false) + rooms_to_book = update_room_state(rooms_to_book,num_rooms,false) + id = all_reservations.length + 1 + booking = Booking.new(id,rooms_to_book,block.date_range, block_info:block.block_name) + return booking + end + + def update_room_state(rooms_to_book,num_rooms,state) + num_rooms.times do |i| + rooms_to_book[i].booked = state + end + return rooms_to_book + end + end +end diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..5262e8cef --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,15 @@ +require 'pry' +require 'date' + +module Hotel + class Room + attr_reader :room_number, :cost, :booked + attr_accessor :booked + + def initialize(room_number, booked: nil) + @room_number = room_number + @cost = 200 + @booked = booked + end + end +end diff --git a/specs/block_spec.rb b/specs/block_spec.rb new file mode 100644 index 000000000..582d446f8 --- /dev/null +++ b/specs/block_spec.rb @@ -0,0 +1,31 @@ +require_relative 'spec_helper' + +describe "Block Class" do + before do + id = 1 + rooms = [Hotel::Room.new(3),Hotel::Room.new(2),Hotel::Room.new(1)] + date_range = Hotel::DateRange.new(Date.new(2017,3,9),Date.new(2017,3,13)) + @wedding = Hotel::Block.new(id, rooms, date_range, "Royal Wedding") + end + it "can be instantiated" do + @wedding.must_be_kind_of Hotel::Block + end + it "must respond to all intance variables" do + @wedding.must_respond_to :id + @wedding.must_respond_to :total_cost + @wedding.must_respond_to :rooms + @wedding.must_respond_to :date_range + @wedding.must_respond_to :nights + @wedding.must_respond_to :block_info + @wedding.must_respond_to :reserved_rooms + end + describe "rooms_available_block" do + it "returns an array with the available rooms" do + @wedding.rooms_available_block.length.must_equal 3 + @wedding.rooms_available_block.must_be_kind_of Array + end + it "returns rooms objects" do + @wedding.rooms_available_block[0].must_be_kind_of Hotel::Room + end + end +end diff --git a/specs/booking_spec.rb b/specs/booking_spec.rb new file mode 100644 index 000000000..d79b0f8ff --- /dev/null +++ b/specs/booking_spec.rb @@ -0,0 +1,31 @@ +require_relative 'spec_helper' + +describe "Booking class" do + before do + id = 1 + rooms = [Hotel::Room.new(15),Hotel::Room.new(16)] + date_range = Hotel::DateRange.new(Date.new(2017,9,5),Date.new(2017,9,8)) + block_info = [] + @reservation = Hotel::Booking.new(id, rooms, date_range) + end + describe "Initialize" do + it "can be instantiated" do + @reservation.must_be_kind_of Hotel::Booking + end + it "must respond to all intance variables" do + @reservation.must_respond_to :id + @reservation.must_respond_to :total_cost + @reservation.must_respond_to :rooms + @reservation.must_respond_to :date_range + @reservation.must_respond_to :nights + @reservation.must_respond_to :block_info + end + it "total cost must return the appropriate amount for 2 rooms, and be an Integer" do + # binding.pry + @reservation.total_cost.must_equal 1200 + end + it "total cost must be an integer" do + @reservation.total_cost.must_be_kind_of Integer + end + end +end diff --git a/specs/date_range_spec.rb b/specs/date_range_spec.rb new file mode 100644 index 000000000..a4edd7d65 --- /dev/null +++ b/specs/date_range_spec.rb @@ -0,0 +1,44 @@ +require_relative 'spec_helper' + +describe "DateRange Class" do + before do + @check_in = Date.new(2017,9,5) + @check_out = Date.new(2017,9,8) + @vacation = Hotel::DateRange.new(@check_in,@check_out) + end + describe "initialize" do + it "Instance of DateRange class must respond to instance variables" do + @vacation.must_respond_to :check_in + @vacation.must_respond_to :check_out + @vacation.must_respond_to :nights + @vacation.must_respond_to :nights_arr + end + it "check_in and check_out are of the Date class" do + @vacation.check_in.must_be_kind_of Date + @vacation.check_out.must_be_kind_of Date + end + it "nights is an Integer" do + @vacation.nights.must_be_kind_of Integer + end + it "nights returns the correct number equal to the difference of checkout-checkin" do + @vacation.nights.must_equal 3 + end + it "nights_arr is an array" do + @vacation.nights_arr.must_be_kind_of Array + end + end + describe "valid_date? method" do + it "valid_date? will raise an error if check_out if prior to check_in" do + proc{Hotel::DateRange.new(@check_out,@check_in)}.must_raise InvalidDateRangeError + end + it "valid_date? will raise an error if parameters check_in in check_out are not Date objects" do + proc{Hotel::DateRange.new(@check_in.to_s,@check_out)}.must_raise ArgumentError + proc{Hotel::DateRange.new(@check_out,@check_in.to_s)}.must_raise ArgumentError + end + end + describe "make_nights_arr method" do + it "will populate nights array so that it is equal to the length of nights" do + @vacation.nights_arr.length.must_equal @vacation.nights + end + end +end diff --git a/specs/reservations_spec.rb b/specs/reservations_spec.rb new file mode 100644 index 000000000..9690dafd8 --- /dev/null +++ b/specs/reservations_spec.rb @@ -0,0 +1,130 @@ +require_relative 'spec_helper' +describe "Reservations class" do + before do + @check_in = Date.new(2017,9,5) + @check_out = Date.new(2017,9,6) + @hotel = Hotel::Reservations.new + @hotel.make_reservation(@check_in,@check_out,1) + @hotel.make_reservation(@check_in,@check_out,1) + @block = @hotel.make_reservation(@check_in - 10,@check_out - 10,5, block_name: "party") + end + describe "initializes" do + it "initializes" do + @hotel.must_be_kind_of Hotel::Reservations + end + it "must respond to all_rooms and all_reservations" do + @hotel.must_respond_to :all_rooms && :all_reservations + end + it "all_rooms must give a list of all of the rooms in the hotel" do + @hotel.all_rooms.length.must_equal 20 + end + it "all_reservations must be a kind of array" do + @hotel.all_reservations.must_be_kind_of Array + end + end + describe "check_reservations" do + it "will raise an error if invalid date range" do + proc{@hotel.check_reservations(@check_out,@check_in)}.must_raise InvalidDateRangeError + end + it "must be an array of room objects" do + holder = @hotel.check_reservations(@check_in,@check_out) + holder[0].must_be_kind_of Hotel::Room + holder.must_be_kind_of Array + end + it "return a list of rooms booked for a given date range, rooms should be unique" do + arr_booked = @hotel.check_reservations(@check_in,@check_out) + arr_booked_unique = arr_booked.uniq + arr_booked.length.must_equal 2 + arr_booked.length.must_equal arr_booked_unique.length + end + it "returns an empty array if nothing is booked" do + @hotel.check_reservations(@check_in + 1,@check_out + + 1).must_be_empty + end + end + describe "check_availability" do + it "returns an array of room objects" do + @hotel.check_availability(@check_in,@check_out).must_be_kind_of Array + @hotel.check_availability(@check_in,@check_out)[0].must_be_kind_of Hotel::Room + end + it "returns a list of the correct number of rooms available, rooms should be unique" do + rooms_available = @hotel.check_availability(@check_in,@check_out) + rooms_available.length.must_equal 18 + rooms_available_unique = rooms_available.uniq + rooms_available.length.must_equal rooms_available_unique.length + end + it "returns an empty array if everything is booked" do + @hotel.make_reservation(@check_in,@check_out,18) + @hotel.check_availability(@check_in,@check_out).must_be_empty + end + end + describe "make_reservation" do + it "it will make create object of booking class" do + @hotel.make_reservation(@check_in,@check_out,1).must_be_kind_of Hotel::Booking + end + it "will raise an ArgumentError if no rooms are avabilable" do + proc{@hotel.make_reservation(@check_in,@check_out,19)}.must_raise InvalidRoomQuantity + end + it "it adds a new reservation to all_reservations" do + before = @hotel.all_reservations.length + @hotel.make_reservation(@check_in,@check_out,1) + after = @hotel.all_reservations.length + after.must_equal (before + 1) + end + it "make sure it creates consecutive ID numbers" do + id1 = @hotel.make_reservation(@check_in,@check_out,1).id + id2 = @hotel.make_reservation(@check_in,@check_out,1).id + id2.must_equal (id1 + 1) + end + it "picks the next consecutive room number available" do + room_num1 = @hotel.all_reservations[0].rooms[0].room_number + room_num2 = @hotel.all_reservations[1].rooms[0].room_number + room_num2.must_equal (room_num1 + 1) + end + it "raise ArgumentError if you request more than 20 rooms" do + proc{@hotel.make_reservation(@check_in-3,@check_out-3,21)}.must_raise InvalidRoomQuantity + end + it "A reservation is allowed start on the same day that another reservation for the same room ends" do + # books all room on a given date + @hotel.make_reservation(@check_in + 1, @check_out + 1,20) + # can book rooms on the following day AKA checkout day + @hotel.make_reservation(@check_in + 2, @check_out + 2,20) + end + describe "make a block " do + it "will create a block, " do + @block.must_be_kind_of Hotel::Booking + end + it "will raise an exception if user tried to block off more than 5 rooms." do + proc{@hotel.make_reservation(@check_in - 10,@check_out - 10,7, block_name: "pool party")}.must_raise InvalidRoomQuantity + end + it "block 5 rooms and none are reserved" do + @block.rooms.length.must_equal 5 + @block.rooms.each do |room| + room.booked.must_equal false + end + end + it "return an array of availbe rooms within the block" do + @block.rooms_available_block.must_be_kind_of Array + @block.rooms_available_block.each do |room| + room.booked.must_equal false + end + end + end + end + describe "reserve_from_block" do + it "returns a booking" do + @hotel.reserve_from_block(@block,1).must_be_kind_of Hotel::Booking + end + it "raises an error if user tries to reserve more rooms than are available" do + proc{@hotel.reserve_from_block(@block,7)}.must_raise InvalidRoomQuantity + end + it "changes the status of rooms booked from nil to true" do + @hotel.reserve_from_block(@block,1) + @block.rooms.each do |room| + room.booked.must_equal false + end + end + it "booked room has same date range as block" do + @hotel.reserve_from_block(@block,1).date_range.must_equal @block.date_range + end + end +end diff --git a/specs/room_spec.rb b/specs/room_spec.rb new file mode 100644 index 000000000..8d8210c42 --- /dev/null +++ b/specs/room_spec.rb @@ -0,0 +1,28 @@ +require_relative 'spec_helper' + +describe "Room Class" do + before do + @suite1 = Hotel::Room.new(15) + end + it "can be instantiated" do + room = Hotel::Room.new(15) + room.must_be_kind_of Hotel::Room + end + it "must respond to room_number and cost" do + @suite1.must_respond_to :room_number + @suite1.must_respond_to :cost + @suite1.must_respond_to :booked + end + it "cost must equal 200" do + @suite1.cost.must_equal 200 + end + it "cost must be a kind of Integer" do + @suite1.cost.must_be_kind_of Integer + end + it "room number must equal room number" do + @suite1.room_number.must_equal 15 + end + it "room number must be a kind of Integer" do + @suite1.room_number.must_be_kind_of Integer + end +end diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb new file mode 100644 index 000000000..a9e103dc4 --- /dev/null +++ b/specs/spec_helper.rb @@ -0,0 +1,22 @@ +# specs/spec_helper.rb +require 'simplecov' +SimpleCov.start +require 'minitest' +require 'minitest/autorun' +require 'minitest/reporters' +require 'minitest/pride' +require 'minitest/skip_dsl' +require 'pry' +require 'date' +# require 'pry-nav' + + +# Require any classes +require_relative '../lib/reservations' +require_relative '../lib/booking' +require_relative '../lib/date_range' +require_relative '../lib/room' +require_relative '../lib/custom_exceptions' +require_relative '../lib/block' + +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new