diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..c75c22637 Binary files /dev/null and b/.DS_Store differ diff --git a/Rakefile b/Rakefile new file mode 100644 index 000000000..5b3221435 --- /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/*_specs.rb'] +end + +task default: :test diff --git a/design-activity.md b/design-activity.md new file mode 100644 index 000000000..a31eb55eb --- /dev/null +++ b/design-activity.md @@ -0,0 +1,89 @@ +What classes does each implementation include? Are the lists the same? +Each implementation have 3 classes: CartEntry, ShoppingCart, and Order. + +Write down a sentence to describe each class. + +Implementation A +Class CartEntry: assign the value of the variables unit_price and quantity and gives access to them from outside the class. +Class ShoppingCart: assign the value of the variable entries and gives access to it from outside. +Class Order: instantiate a new instance of ShoppingCart and calculates the total price of all the entries in the cart. + +Implementation B +Class CartEntry: assign the value of the variables unit_price and quantity and calculates the price of each item according to its quantity. +Class ShoppingCart: assign the value of the variable entries and calculates the price of all the entries in the shopping cart. + +Class Order: instantiate a new instance of ShoppingCart and calculates the total price of all the entries in the cart plus the sales tax using a helper method "price" from the ShoppingCart class. + +How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper. + +Implementation A: +Class CartEntry and class ShoppingCart does not relate to each other but then all 3 classes relate to class Order. +Class Order has-a ShoppingCart instance which has a collection of entries coming from class ShoppingCart. Also, class CartEntry provides to the class Order, the unit_price and quantity of each entry. + +Implementation B: +The class Order has-a ShoppingCart instance that provides a collection of entries each one with its own price that is provided by a helper method price in class CartEntry. + +What data does each class store? How (if at all) does this differ between the two implementations? +Implementation A: +CartEntry: values of unit_price and quantity. +ShoppingCart: collection of entries. +Order: new cart and the total price of all the entries in the cart including the taxes. + +Implementation B: +CartEntry: values of unit_price and quantity +ShoppingCart: collection of entries and the sum of the price of all the entries together. +Order: new cart and the total price of all the entries in the cart including the taxes. Also stores the constant SALES_TAX. + +What methods does each class have? How (if at all) does this differ between the two implementations? + +Implementation A: +Class CartEntry: constructor method +Class ShoppingCart: constructor method +Class Order: constructor method and total_price method. + +Implementation B: +Class CartEntry: constructor method and price method. +Class ShoppingCart: constructor method and price method. +Class Order: constructor method and total_price method. + + +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? + +Implementation A: +The logic for price is retained in Order. + +Implementation B: +The logic for price is delegated to lower level classes like ShoppingCart and CartEntry + +Does total_price directly manipulate the instance variables of other classes? +Implementation A: +Yes, the method total_price in class Order manipulates all the instance variables of the other classes. + +Implementation B: +No, it does not manipulates directly the instance variables but it does indirectly as it manipulates a helper method from class ShoppingCart, and this price method does manipulate instance variables. + + +If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify? + +The price of the items depending on the quantity will change so, this will be easy to change in Implementation B at the helper method price by adding a conditional so that in case of a certain quantity the price will change. +Doing this in Implementation A will be more complicated because it will need to be changed the method total_price in the class Order by adding a conditional there. + +Which implementation better adheres to the single responsibility principle? + +Implementation B. + +Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled? + +Implementation B because class CartEntry does not depend on class ShoppingCart or viceversa, and in implementation A, class Order is very dependent of the other classes. + +------------------------------- + +MY HOTEL PROJECT: +In my Hotel project I have 4 classes each of them with a responsibility: +Booking: to control the reservations of individual and blocked rooms. +Reservation: to calculate the cost of a reservation. +DatesRange: to validate the staying time of a guest. +BlockRooms: to calculate the total cost of a block room when available. + +Overall, I think all of my classes have state and behavior. diff --git a/lib/.DS_Store b/lib/.DS_Store new file mode 100644 index 000000000..5008ddfcf Binary files /dev/null and b/lib/.DS_Store differ diff --git a/lib/blockrooms.rb b/lib/blockrooms.rb new file mode 100644 index 000000000..c62f38e4c --- /dev/null +++ b/lib/blockrooms.rb @@ -0,0 +1,26 @@ +module Hotel + + class BlockRooms + + def initialize(checkin, checkout, blocked_rooms, discounted_rate) + @price_per_night = discounted_rate.to_f + @days_range = Hotel::DatesRange.new(checkin, checkout) + @blocked_rooms = blocked_rooms + end + + def available_room_in_block? + @blocked_rooms.length > 0 ? true : false + end + + def reservation_block_room + raise ArgumentError.new("Unavailable blocked rooms") if !available_room_in_block? + @blocked_rooms.delete_at(0) + return @blocked_rooms + end + + def total_cost_block_room + (@price_per_night * @days_range.amount_days) + end + + end +end diff --git a/lib/booking.rb b/lib/booking.rb new file mode 100644 index 000000000..63ff3fe35 --- /dev/null +++ b/lib/booking.rb @@ -0,0 +1,84 @@ +module Hotel + + AMOUNT_ROOMS_HOTEL = 20 + MAX_BLOCKED_ROOMS = 5 + + class Booking + + attr_reader :rooms_list, :reservations_list + def initialize + @reservations_list = [] + @rooms_list = {} + setup_rooms + + end + + def setup_rooms + AMOUNT_ROOMS_HOTEL.times do |num| + @rooms_list["#{num + 1}"] = [] + end + end + + def is_available?(room_number, dates_to_reserve) + is_available = true + + @rooms_list[room_number].each do |dates| + if dates_to_reserve.overlap?(dates) + is_available = false + end + end + return is_available + end + + def avaliable_rooms_daterange(checkin, checkout) + list_availables = [] + date_range = Hotel::DatesRange.new(checkin,checkout) + @rooms_list.each do |room_number, dates| + if is_available?(room_number, date_range) + list_availables << room_number + end + end + return list_availables + end + + def add_reservation(checkin, checkout) + dates_to_reserve = Hotel::DatesRange.new(checkin, checkout) + @rooms_list.each do |room_number, dates| + if is_available?(room_number, dates_to_reserve) + @rooms_list[room_number] << dates_to_reserve + new_reservation = Hotel::Reservation.new(checkin, checkout, room_number) + @reservations_list << new_reservation + return new_reservation + end + end + raise ArgumentError.new("Sorry!, No rooms available during that date range") + end + + def reservations_per_day(date) + reservations_list_per_day = [] + @reservations_list.each do |reservation| + if reservation.range_of_dates.include?(date) + reservations_list_per_day << reservation + end + end + return reservations_list_per_day + end + + def capacity_for_block_rooms?(checkin, checkout, num_rooms_to_block) + raise ArgumentError.new('The maximum of rooms to block is 5') if num_rooms_to_block > MAX_BLOCKED_ROOMS + return true if avaliable_rooms_daterange(checkin, checkout).length >= num_rooms_to_block + return false + end + + def building_block_rooms(checkin, checkout, num_rooms_to_block, discounted_rate) + array_rooms_blocked = [] + if capacity_for_block_rooms?(checkin, checkout, num_rooms_to_block) + num_rooms_to_block.times do + reservation_block_room = add_reservation(checkin, checkout) + array_rooms_blocked << reservation_block_room.room_number + end + end + return Hotel::BlockRooms.new(checkin, checkout, num_rooms_to_block, discounted_rate) + end + end +end diff --git a/lib/dates.rb b/lib/dates.rb new file mode 100644 index 000000000..56d8e6667 --- /dev/null +++ b/lib/dates.rb @@ -0,0 +1,34 @@ +module Hotel + + class DatesRange + + attr_reader :checkin, :checkout + + def initialize(start_date, end_date) + @checkin = start_date + @checkout = end_date + date_validation + end + + def date_validation + return true if @checkin < @checkout + raise ArgumentError.new("Your date is not a valid input") + end + + def amount_days + (@checkout - @checkin).to_i + end + + def include?(date) + return true if @checkin <= date && @checkout > date + return false + end + + def overlap?(another) + return true if another.checkin <= self.checkin && another.checkout > self.checkin + return true if another.checkin >= self.checkin && another.checkin < self.checkout + return false + end + + end +end diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..df0868f62 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,20 @@ +module Hotel + +PRICE_PER_NIGHT = 200 + + class Reservation + + attr_reader :checkin, :checkout, :range_of_dates, :room_number + + def initialize(checkin, checkout, room_number) + @price_per_night = PRICE_PER_NIGHT + @range_of_dates = Hotel::DatesRange.new(checkin, checkout) + @room_number = room_number + end + + def cost_reservation + (@price_per_night * @range_of_dates.amount_days).to_f + end + + end +end diff --git a/specs/.DS_Store b/specs/.DS_Store new file mode 100644 index 000000000..5008ddfcf Binary files /dev/null and b/specs/.DS_Store differ diff --git a/specs/blockrooms_specs.rb b/specs/blockrooms_specs.rb new file mode 100644 index 000000000..9f9e8fddd --- /dev/null +++ b/specs/blockrooms_specs.rb @@ -0,0 +1,57 @@ +require_relative 'spec_helper' +require 'pry' + +describe 'BlockRooms class' do + before do + @start_date = Date.new(2018, 8, 20) + @end_date = Date.new(2018, 8, 27) + @blocked_rooms = [1,2,3,4] + @discounted_rate = 130.50 + @new_block = Hotel::BlockRooms.new(@start_date, @end_date, @blocked_rooms, @discounted_rate) + end + + describe 'Initialize' do + it "Can be created" do + @new_block.must_be_instance_of Hotel::BlockRooms + end + end + + describe "available_room_in_block? method" do + + it 'returns false if no rooms available in the blocked rooms set' do + 4.times do + @new_block.reservation_block_room + end + @new_block.available_room_in_block?.must_equal false + end + + it 'returns true if they are rooms available in the blocked rooms set' do + @new_block.available_room_in_block?.must_equal true + end + end + + describe "reservation of block rooms method" do + + it "modifies blocked_rooms when a new reservation of a block room is created" do + @new_block.reservation_block_room.must_equal [2, 3, 4] + @new_block.reservation_block_room.must_equal [3,4] + @new_block.reservation_block_room.must_be_kind_of Array + end + + it 'raises an error if no more blocked rooms are available' do + 4.times do + @new_block.reservation_block_room + end + proc{@new_block.reservation_block_room}.must_raise ArgumentError + end + end + + describe "total_cost_block_room method " do + + it 'Returns the total cost for reservation of a blocked room' do + @new_block.total_cost_block_room.must_be_kind_of Float + @new_block.total_cost_block_room.must_equal 913.50 + end + + end +end diff --git a/specs/booking_specs.rb b/specs/booking_specs.rb new file mode 100644 index 000000000..02c8f12b6 --- /dev/null +++ b/specs/booking_specs.rb @@ -0,0 +1,208 @@ +require_relative 'spec_helper' +require 'pry' + +describe 'Booking class' do + before do + @booking = Hotel::Booking.new + end + + describe 'Initialize method' do + it "Can be created" do + @booking.must_be_instance_of Hotel::Booking + end + + it "Creates a list of Reservations" do + @booking.reservations_list.must_equal [] + @booking.must_respond_to :reservations_list + end + + it "has a collection of 20 rooms" do + @booking.rooms_list.length.must_equal 20 + end + + it "Creates a collection of rooms in a hash" do + @booking.rooms_list.must_be_instance_of Hash + @booking.must_respond_to :rooms_list + end + end + + describe 'avaliable_rooms_daterange method' do + before do + @day1 = Date.new(2018,12,13) + @day2 = Date.new(2018,12,19) + @day3 = Date.new(2018,12,07) + @day4 = Date.new(2018,12,12) + @day5 = Date.new(2018,12,24) + @day6 = Date.new(2018,12,14) + @day7 = Date.new(2018,12,16) + @all_rooms = [] + 20.times do |num| + @all_rooms << "#{num+1}" + end + end + + it "returns the rooms avaliables in the date range" do + 5.times do + @booking.add_reservation(@day1, @day2) + end + @booking.add_reservation(@day3,@day4) + @booking.add_reservation(@day4,@day6) + @booking.avaliable_rooms_daterange(@day6,@day7).must_equal @all_rooms[5..19] + end + + it "returns all the rooms if there is 0 reservations for that date range" do + @booking.avaliable_rooms_daterange(@day1, @day2).must_equal @all_rooms + end + + end + + describe "add_reservation method " do + before do + @start_date = Date.new(2018, 8, 20) + @end_date = Date.new(2018, 8, 27) + end + + it "Creates a new reservation" do + day1 = Date.new(2018,11,8) + day2 = Date.new(2018,11,13) + day3 = Date.new(2018,11,25) + day4 = Date.new(2018,11,27) + day5 = Date.new(2018,11,30) + day6 = Date.new(2018,12,4) + day7 = Date.new(2018,12,7) + @booking.add_reservation(day1,day2) + @booking.add_reservation(day3,day4) + @booking.add_reservation(day4,day5) + @booking.add_reservation(day5,day6) + @booking.add_reservation(day6,day7) + @booking.reservations_list.length.must_equal 5 + @booking.reservations_list.must_be_instance_of Array + end + + + it "Creates an instance of reservation for a date range" do + new_reservation = @booking.add_reservation(@start_date,@end_date) + new_reservation.must_be_instance_of Hotel::Reservation + end + + it 'does not create a reservation if no rooms are available' do + day1 = Date.new(2018,11,8) + day2 = Date.new(2018,11,13) + 20.times do + @booking.add_reservation(day1,day2) + end + proc{@booking.add_reservation(day1,day2)}.must_raise ArgumentError + end + + it 'changes the collection of rooms as more reservations are added' do + day1 = Date.new(2018,11,8) + day2 = Date.new(2018,11,13) + day3 = Date.new(2018,11,25) + day4 = Date.new(2018,11,27) + day5 = Date.new(2018,11,30) + day6 = Date.new(2018,12,02) + day7 = Date.new(2018,12,05) + @booking.add_reservation(day1,day2) + @booking.add_reservation(day3,day4) + @booking.add_reservation(day4,day5) + @booking.add_reservation(day5,day6) + @booking.add_reservation(day6,day7) + @booking.rooms_list["1"].length.must_equal 5 + @booking.add_reservation(day4,day5) + @booking.add_reservation(day5,day6) + @booking.add_reservation(day6,day7) + @booking.rooms_list["2"].length.must_equal 3 + @booking.add_reservation(day4,day5) + @booking.add_reservation(day5,day6) + @booking.rooms_list["3"].length.must_equal 2 + @booking.add_reservation(day4,day5) + @booking.rooms_list["4"].length.must_equal 1 + end + + end +end + + +describe "reservations_per_day method" do + before do + @booking = Hotel::Booking.new + @date1 = Date.new(2018,02,20) + @date2 = Date.new(2018,02,24) + @date3 = Date.new(2018,03,31) + @date4 = Date.new(2018,04,04) + @date5 = Date.new(2018,04,29) + @date6 = Date.new(2018,05,02) + @date6 = Date.new(2018,05,10) + @date7 = Date.new(2018,05,13) + @booking.add_reservation(@date1, @date2) + @booking.add_reservation(@date1, @date3) + @booking.add_reservation(@date2, @date3) + @booking.add_reservation(@date4, @date5) + @booking.add_reservation(@date5, @date6) + end + + it 'Returns an array of reservations instances' do + @booking.reservations_per_day(@date1)[0].must_be_instance_of Hotel::Reservation + end + + it 'Returns an array of reservations of the specific date ' do + @booking.reservations_per_day(@date1).must_be_instance_of Array + end + + it 'Returns a empty array if that day there is no reservations' do + @booking.reservations_per_day(@date7).must_equal [] + end + + it 'Returns all the reservations per day, when is not empty' do + @booking.reservations_per_day(@date1).length.must_equal 2 + end + + + describe 'capacity_for_block_rooms? method' do + + before do + @checkin = Date.new(2018,12,7) + @checkout = Date.new(2018,12,13) + end + + it 'returns false if it is not possible to create block' do + 16.times do + @booking.add_reservation(@checkin, @checkout) + end + @booking.capacity_for_block_rooms?(@checkin, @checkout,5).must_equal false + end + + it 'raises an error if the number of rooms to block is greater than 5' do + proc{@booking.capacity_for_block_rooms?(@checkin, @checkout, 8)}.must_raise ArgumentError + end + + it 'returns true if it is possible to create the block' do + 14.times do + @booking.add_reservation(@checkin, @checkout) + end + @booking.capacity_for_block_rooms?(@checkin, @checkout,5).must_equal true + end + + end + + describe 'building_block_rooms method' do + before do + @checkin = Date.new(2018,12,7) + @checkout = Date.new(2018,12,13) + @num_rooms_to_block = 5 + @discounted_rate = 120 + + end + + it 'returns an array of blocked rooms if there is enough rooms available' do + @booking.building_block_rooms(@checkin, @checkout, @num_rooms_to_block,@discounted_rate).must_be_instance_of Hotel::BlockRooms + end + + it 'reserves a room from within a block of rooms' do + @booking.reservations_list.length.must_equal 5 + @booking.building_block_rooms(@checkin, @checkout, @num_rooms_to_block,@discounted_rate) + #list of reservations was updated with the reservations of block rooms + @booking.reservations_list.length.must_equal 10 + end + end +end diff --git a/specs/dates_specs.rb b/specs/dates_specs.rb new file mode 100644 index 000000000..11744ef42 --- /dev/null +++ b/specs/dates_specs.rb @@ -0,0 +1,129 @@ +require_relative 'spec_helper' + +describe 'DatesRange class' do + + describe 'initialize method' do + before do + @checkin = Date.new(2018,6,4) + @checkout = Date.new(2018,6,7) + @date_new = Hotel::DatesRange.new(@checkin ,@checkout) + end + + it "responds to checkin" do + @date_new.checkin.must_equal @checkin + end + + it "responds to checkout" do + @date_new.checkout.must_equal @checkout + end + + it "can be initialize if the dates are valid" do + Hotel::DatesRange.new(@checkin, @checkout).must_be_instance_of Hotel::DatesRange + end + + it "raises an error if checkin and checkout are the same day" do + # checkin overlap with checkout when both are the same day. + proc{Hotel::DatesRange.new(@checkin, @checkin)}.must_raise ArgumentError + end + + it "raises error if checkin is after checkout" do + proc {Hotel::DatesRange.new(@checkout, @checkin)}.must_raise ArgumentError + end + + it "raises error if checkout is before checkin" do + proc {Hotel::DatesRange.new(@checkout, @checkin)}.must_raise StandardError + end + + end + + + describe "amount_days method" do + before do + @checkin = Date.new(2018,2,5) + @checkout = Date.new(2018,2,7) + @date_new = Hotel::DatesRange.new(@checkin ,@checkout) + end + + it "returns the correct amount of days" do + @date_new.amount_days.must_be_kind_of Integer + @date_new.amount_days.must_equal 2 + end + + end + + describe 'include? method' do + before do + @checkin = Date.new(2018,2,5) + @checkout = Date.new(2018,2,8) + @date_new = Hotel::DatesRange.new(@checkin, @checkout) + end + + it 'returns true if the date is included' do + date = Date.new(2018,2,6) + @date_new.include?(date).must_equal true + end + + it 'returns false for a date that is out of the range' do + date = Date.new(2018,2,9) + @date_new.include?(date).must_equal false + end + + it 'returns false if the date is the checkout date' do + @date_new.include?(@checkout).must_equal false + end + + it 'returns true if the date is the checkin date' do + @date_new.include?(@checkin).must_equal true + end + + end + + describe 'overlap?' do + before do + @day1 = Date.new(2018,10,14) + @day2 = Date.new(2018,10,20) + @day3 = Date.new(2018,10,8) + @day4 = Date.new(2018,10,13) + @day5 = Date.new(2018,10,25) + @day6 = Date.new(2018,10,15) + @day7 = Date.new(2018,10,17) + @daterange1 = Hotel::DatesRange.new(@day1,@day2) + @daterange2 = Hotel::DatesRange.new(@day3,@day4) + @daterange3 = Hotel::DatesRange.new(@day2,@day5) + @daterange4 = Hotel::DatesRange.new(@day6,@day7) + @daterange5 = Hotel::DatesRange.new(@day6,@day5) + @daterange6 = Hotel::DatesRange.new(@day3,@day5) + @daterange7 = Hotel::DatesRange.new(@day3,@day6) + @daterange8 = Hotel::DatesRange.new(@day3,@day2) + @daterange9 = Hotel::DatesRange.new(@day1,@day7) + end + + it "returns false if the date range don't overlap" do + # daterange1 completely after daterange2 + @daterange1.overlap?(@daterange2).must_equal false + + # daterange1 completely before daterange3 + # daterange1 ends on the checkin date of daterange3 + @daterange1.overlap?(@daterange3).must_equal false + + end + + it "returns true if the dates ranges overlap" do + # daterange1 is completely containing daterange4 + @daterange1.overlap?(@daterange4).must_equal true + + # daterange8 is completely containing daterange9 + @daterange8.overlap?(@daterange9).must_equal true + + # daterange1 completely contained in daterange6 + @daterange1.overlap?(@daterange6).must_equal true + + # daterange5 overlaps in the front with daterange1 + @daterange5.overlap?(@daterange1).must_equal true + + # daterange7 overlaps in the back with daterange1 + @daterange7.overlap?(@daterange1).must_equal true + end + end + +end diff --git a/specs/reservation_specs.rb b/specs/reservation_specs.rb new file mode 100644 index 000000000..ef3f2a8e8 --- /dev/null +++ b/specs/reservation_specs.rb @@ -0,0 +1,33 @@ +require_relative 'spec_helper' +require 'pry' + +describe 'Reservation class' do + before do + @start_date = Date.new(2018, 8, 20) + @end_date = Date.new(2018, 8, 27) + @room_number = 5 + @reservation_new = Hotel::Reservation.new(@start_date, @end_date, @room_number) + end + + describe 'Initialize method' do + it "can be created" do + @reservation_new.must_be_instance_of Hotel::Reservation + end + + it "does not initialize incorrect dates like using the checkin date for checkout also" do + proc{Hotel::Reservation.new(@start_date, @start_date, @room_number)}.must_raise ArgumentError + end + + it "does not initialize incorrect dates like using the checkout date for checkin also" do + proc{Hotel::Reservation.new(@end_date, @end_date, @room_number)}.must_raise ArgumentError + end + end + + describe "cost_reservation method " do + + it "returns the cost of a reservation" do + @reservation_new.cost_reservation.must_equal 1400 + @reservation_new.cost_reservation.must_be_kind_of Float + end + end +end diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb new file mode 100644 index 000000000..6dd38cbf3 --- /dev/null +++ b/specs/spec_helper.rb @@ -0,0 +1,14 @@ +require 'simplecov' +SimpleCov.start + +require 'time' +require 'minitest' +require 'minitest/autorun' +require 'minitest/reporters' + +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new +# Require_relative your lib files here! +require_relative '../lib/booking' +require_relative '../lib/dates' +require_relative '../lib/reservation' +require_relative '../lib/blockrooms'