From 3b4cec91469bb2e6becba552e4d7ffbe5e562382 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Thu, 7 Sep 2017 17:39:15 -0700 Subject: [PATCH 1/9] Restructure classes. All Wave 1 tests passing. --- Rakefile | 9 +++++++ lib/hotel.rb | 41 +++++++++++++++++++++++++++++ lib/reservation.rb | 31 ++++++++++++++++++++++ lib/room.rb | 10 +++++++ specs/hotel_spec.rb | 55 +++++++++++++++++++++++++++++++++++++++ specs/reservation_spec.rb | 36 +++++++++++++++++++++++++ specs/room_spec.rb | 18 +++++++++++++ specs/spec_helper.rb | 19 ++++++++++++++ 8 files changed, 219 insertions(+) create mode 100644 Rakefile create mode 100644 lib/hotel.rb create mode 100644 lib/reservation.rb create mode 100644 lib/room.rb create mode 100644 specs/hotel_spec.rb create mode 100644 specs/reservation_spec.rb create mode 100644 specs/room_spec.rb create mode 100644 specs/spec_helper.rb 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/lib/hotel.rb b/lib/hotel.rb new file mode 100644 index 000000000..cf7cc8ca3 --- /dev/null +++ b/lib/hotel.rb @@ -0,0 +1,41 @@ +require 'date' + +module Hotel + STANDARD_RATE = 200 + NUMBER_OF_ROOMS = 20 + + class Hotel + + attr_reader :rooms, :reservations + + # get a list of all rooms + def initialize + @rooms = new_rooms + @reservations = [] + end + + def new_reservation(checkin_date, checkout_date, room_number) + reservation = Reservation.new(checkin_date, checkout_date, room_number) + + reservations << reservation + end + # get list of all reservations on a given date + def reservations_on(date) + check_date = Date.parse(date) + + reservations.select { |reserv| check_date >= reserv.checkin && check_date < reserv.checkout } + end + + private + def new_rooms + rooms = [] + i = 0 + NUMBER_OF_ROOMS.times do + rooms << Room.new(i + 1) + i += 1 + end + + rooms + end + end +end diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..7e6c9eb31 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,31 @@ +require 'date' + +module Hotel + class Reservation + attr_reader :checkin, :checkout, :room + + def initialize(checkin_date, checkout_date, room_number) + if valid_dates?(Date.parse(checkin_date), Date.parse(checkout_date)) + @checkin = Date.parse(checkin_date) + @checkout = Date.parse(checkout_date) + @room = room_number + end + end + + def valid_dates?(checkin, checkout) + if checkin > checkout + raise ArgumentError.new("Check-in date must be before check-out date") + elsif !(Date.valid_date?(checkin.year, checkin.month, checkin.day)) || !(Date.valid_date?(checkout.year, checkout.month, checkout.day)) + raise ArgumentError.new("Your check-in and/or check-out date(s) are not valid") + elsif checkin < Date.today + raise ArgumentError.new("Check-in date must not be in the past.") + else + return true + end + end + + def cost + (checkout - checkin) * STANDARD_RATE + end + end +end diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..ffd20b562 --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,10 @@ +module Hotel + class Room + attr_reader :number, :status + + def initialize(number, status= :available) + @number = number + @status = status + end + end +end diff --git a/specs/hotel_spec.rb b/specs/hotel_spec.rb new file mode 100644 index 000000000..7b30eab9b --- /dev/null +++ b/specs/hotel_spec.rb @@ -0,0 +1,55 @@ +require_relative 'spec_helper' + +require 'date' + +describe "Hotel" do + let(:hotel) { Hotel::Hotel.new } + let(:smith) { hotel.new_reservation("2017-10-01", "2017-10-04", 2) } + let(:garcia) { hotel.new_reservation("2017-10-02", "2017-10-06", 4) } + let(:jones) { hotel.new_reservation("2017-10-02", "2017-10-04", 5) } + + describe "initialize" do + it "creates a Hotel instance" do + hotel.must_respond_to :rooms + end + + it "provides a list of all reservations" do + hotel.reservations.must_be_instance_of Array + hotel.reservations.length.must_equal 0 + end + + it "provides a list of all rooms" do + hotel.rooms.must_be_instance_of Array + hotel.rooms.length.must_equal 20 + hotel.rooms[18].number.must_equal 19 + end + end + + +describe "new_reservation" do + before do + hotel + smith + garcia + jones + end + + it "returns an Array of Reservation objects" do + hotel.reservations.each { |reserv| reserv.must_be_instance_of Hotel::Reservation } + hotel.reservations[0].checkin.must_equal Date.new(2017, 10, 1) + end +end + +describe "reservations_on" do + it "provides a list of all reservations on a given date" do + smith + garcia + jones + + hotel.reservations_on("2017-10-02").must_be_instance_of Array + hotel.reservations_on("2017-10-02").length.must_equal 3 + + hotel.reservations_on("2017-10-05").length.must_equal 1 + end +end +end diff --git a/specs/reservation_spec.rb b/specs/reservation_spec.rb new file mode 100644 index 000000000..2a6f6e525 --- /dev/null +++ b/specs/reservation_spec.rb @@ -0,0 +1,36 @@ +require_relative 'spec_helper' + +require 'date' + +describe "Reservation" do +let(:smith) { Hotel::Reservation.new("2017-10-01", "2017-10-04", 2) } + + describe "initialize" do + it "has a date range" do + smith.must_respond_to :checkin + smith.must_respond_to :checkout + smith.checkin.must_be_instance_of Date + smith.checkin.must_equal Date.new(2017, 10, 1) + smith.checkout.must_equal Date.new(2017, 10, 4) + end + + it "has a room" do + smith.room.must_equal 2 + end + + it "has a cost" do + smith.must_respond_to :cost + smith.cost.must_equal 600 + end + + it "raises an error when given an incorrect date range" do + proc { Hotel::Reservation.new("2017-10-02", "2017-09-30", 3) }.must_raise ArgumentError + + proc { Hotel::Reservation.new("2017-08-24", "2017-08-31", 3) }.must_raise ArgumentError + + proc { Hotel::Reservation.new("2017-13-30", "2017-03-03", 3) }.must_raise ArgumentError + + proc { Hotel::Resernation.new("2018-02-25", "2018-02-29")} + end + end +end diff --git a/specs/room_spec.rb b/specs/room_spec.rb new file mode 100644 index 000000000..a99e5607d --- /dev/null +++ b/specs/room_spec.rb @@ -0,0 +1,18 @@ +require_relative 'spec_helper' + +describe "Room" do + let(:room1) { Hotel::Room.new(1) } + let(:room2) { Hotel::Room.new(20) } + let(:room3) { Hotel::Room.new(2) } + + describe "initialize" do + it "each room has a number that can be read" do + room1.number.must_equal 1 + room2.number.must_equal 20 + end + + it "new rooms have a default status :available" do + room3.status.must_equal :available + end + end +end diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb new file mode 100644 index 000000000..d44acd5ea --- /dev/null +++ b/specs/spec_helper.rb @@ -0,0 +1,19 @@ +require 'simplecov' +SimpleCov.start + +# specs/spec_helper.rb +require 'minitest' +require 'minitest/autorun' +require 'minitest/reporters' +require 'minitest/pride' +require 'minitest/skip_dsl' +require 'pry' + +# Require any classes +# ex require_relative 'lib/foo.rb' +require_relative '../lib/hotel.rb' +require_relative '../lib/room.rb' +require_relative '../lib/reservation.rb' + + +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new From dea079bdb74e74c7865bf2c2de9d5760a9fcb787 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Thu, 7 Sep 2017 22:43:57 -0700 Subject: [PATCH 2/9] Test and implement rooms_available method. Adjust date string inputs with strptime. 98.86% test coverage, all pass. --- lib/hotel.rb | 15 +++++++++- lib/reservation.rb | 6 ++-- specs/hotel_spec.rb | 58 ++++++++++++++++++++++++--------------- specs/reservation_spec.rb | 10 +++---- 4 files changed, 58 insertions(+), 31 deletions(-) diff --git a/lib/hotel.rb b/lib/hotel.rb index cf7cc8ca3..ef1770f30 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -21,11 +21,24 @@ def new_reservation(checkin_date, checkout_date, room_number) end # get list of all reservations on a given date def reservations_on(date) - check_date = Date.parse(date) + check_date = Date.strptime(date, '%m-%d-%Y') reservations.select { |reserv| check_date >= reserv.checkin && check_date < reserv.checkout } end + def rooms_available(checkin_date, checkout_date) + checkin = Date.strptime(checkin_date, '%m-%d-%Y') + checkout = Date.strptime(checkout_date, '%m-%d-%Y') + booked_rooms = [] + checkin.upto(checkout) do |date| + booked_rooms << reservations_on(date.strftime('%m-%d-%Y')).collect { |reserv| reserv.room } + end + + booked_rooms.flatten!.uniq! + + rooms.reject { |room| booked_rooms.include?(room.number) } + end + private def new_rooms rooms = [] diff --git a/lib/reservation.rb b/lib/reservation.rb index 7e6c9eb31..35687beb1 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -5,9 +5,9 @@ class Reservation attr_reader :checkin, :checkout, :room def initialize(checkin_date, checkout_date, room_number) - if valid_dates?(Date.parse(checkin_date), Date.parse(checkout_date)) - @checkin = Date.parse(checkin_date) - @checkout = Date.parse(checkout_date) + if valid_dates?(Date.strptime(checkin_date, '%m-%d-%Y'), Date.strptime(checkout_date, '%m-%d-%Y')) + @checkin = Date.strptime(checkin_date, '%m-%d-%Y') + @checkout = Date.strptime(checkout_date, '%m-%d-%Y') @room = room_number end end diff --git a/specs/hotel_spec.rb b/specs/hotel_spec.rb index 7b30eab9b..71317b56e 100644 --- a/specs/hotel_spec.rb +++ b/specs/hotel_spec.rb @@ -4,9 +4,9 @@ describe "Hotel" do let(:hotel) { Hotel::Hotel.new } - let(:smith) { hotel.new_reservation("2017-10-01", "2017-10-04", 2) } - let(:garcia) { hotel.new_reservation("2017-10-02", "2017-10-06", 4) } - let(:jones) { hotel.new_reservation("2017-10-02", "2017-10-04", 5) } + let(:smith) { hotel.new_reservation("10-01-2017", "10-04-2017", 2) } + let(:garcia) { hotel.new_reservation("10-02-2017", "10-06-2017", 4) } + let(:jones) { hotel.new_reservation("10-02-2017", "10-04-2017", 5) } describe "initialize" do it "creates a Hotel instance" do @@ -26,30 +26,44 @@ end -describe "new_reservation" do - before do - hotel - smith - garcia - jones + describe "new_reservation" do + before do + smith + garcia + jones + end + + it "returns an Array of Reservation objects" do + hotel.reservations.each { |reserv| reserv.must_be_instance_of Hotel::Reservation } + hotel.reservations[0].checkin.must_equal Date.new(2017, 10, 1) + end end - it "returns an Array of Reservation objects" do - hotel.reservations.each { |reserv| reserv.must_be_instance_of Hotel::Reservation } - hotel.reservations[0].checkin.must_equal Date.new(2017, 10, 1) + describe "reservations_on" do + it "provides a list of all reservations on a given date" do + smith + garcia + jones + + hotel.reservations_on("10-02-2017").must_be_instance_of Array + hotel.reservations_on("10-02-2017").length.must_equal 3 + + hotel.reservations_on("10-05-2017").length.must_equal 1 + end end -end -describe "reservations_on" do - it "provides a list of all reservations on a given date" do - smith - garcia - jones + describe "rooms_available" do + + + it "provides a list of available rooms for a date range" do + smith + garcia + jones - hotel.reservations_on("2017-10-02").must_be_instance_of Array - hotel.reservations_on("2017-10-02").length.must_equal 3 + open_rooms = hotel.rooms_available("10-01-2017", "10-05-2017") - hotel.reservations_on("2017-10-05").length.must_equal 1 + open_rooms.must_be_instance_of Array + open_rooms.length.must_equal 17 + end end end -end diff --git a/specs/reservation_spec.rb b/specs/reservation_spec.rb index 2a6f6e525..ab7a6830c 100644 --- a/specs/reservation_spec.rb +++ b/specs/reservation_spec.rb @@ -3,7 +3,7 @@ require 'date' describe "Reservation" do -let(:smith) { Hotel::Reservation.new("2017-10-01", "2017-10-04", 2) } +let(:smith) { Hotel::Reservation.new("10-01-2017", "10-04-2017", 2) } describe "initialize" do it "has a date range" do @@ -24,13 +24,13 @@ end it "raises an error when given an incorrect date range" do - proc { Hotel::Reservation.new("2017-10-02", "2017-09-30", 3) }.must_raise ArgumentError + proc { Hotel::Reservation.new("10-02-2017", "09-30-2017", 3) }.must_raise ArgumentError - proc { Hotel::Reservation.new("2017-08-24", "2017-08-31", 3) }.must_raise ArgumentError + proc { Hotel::Reservation.new("08-24-2017", "08-31-2017", 3) }.must_raise ArgumentError - proc { Hotel::Reservation.new("2017-13-30", "2017-03-03", 3) }.must_raise ArgumentError + proc { Hotel::Reservation.new("13-30-2017", "03-03-2018", 3) }.must_raise ArgumentError - proc { Hotel::Resernation.new("2018-02-25", "2018-02-29")} + proc { Hotel::Reservation.new("02-25-2018", "02-29-2018", 3) }.must_raise ArgumentError end end end From 7120ddb31a2ff8beb0dd90a52adbc320a2dc9d5b Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Thu, 7 Sep 2017 23:04:05 -0700 Subject: [PATCH 3/9] Test and implement reservation date error. 98.89% coverage, all tests pass. --- lib/hotel.rb | 8 ++++++-- specs/hotel_spec.rb | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/hotel.rb b/lib/hotel.rb index ef1770f30..ec6122437 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -15,9 +15,13 @@ def initialize end def new_reservation(checkin_date, checkout_date, room_number) - reservation = Reservation.new(checkin_date, checkout_date, room_number) + if rooms_available(checkin_date, checkout_date).any? { |room| room.number == room_number } + reservation = Reservation.new(checkin_date, checkout_date, room_number) - reservations << reservation + reservations << reservation + else + raise ArgumentError.new("Room is not available for those dates.") + end end # get list of all reservations on a given date def reservations_on(date) diff --git a/specs/hotel_spec.rb b/specs/hotel_spec.rb index 71317b56e..1b08b2e7e 100644 --- a/specs/hotel_spec.rb +++ b/specs/hotel_spec.rb @@ -37,6 +37,17 @@ hotel.reservations.each { |reserv| reserv.must_be_instance_of Hotel::Reservation } hotel.reservations[0].checkin.must_equal Date.new(2017, 10, 1) end + + it "raises an ArgumentError when a room is not available for a given range" do + proc { hotel.new_reservation("10-02-2017", "10-04-2017", 5) }.must_raise ArgumentError + end + + it "allows reservation starting on checkout date" do + before = hotel.reservations.length + hotel.new_reservation("10-04-2017", "10-07-2017", 5) + + hotel.reservations.length.must_equal (before + 1) + end end describe "reservations_on" do From ecb87e136081ebe9d577fe165e1247b3fd91d6cf Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Fri, 8 Sep 2017 13:48:37 -0700 Subject: [PATCH 4/9] Add Block class and tests. Define initialize in Block and new_block in Hotel --- lib/block.rb | 11 +++++++++++ lib/hotel.rb | 33 ++++++++++++++++++++++++++------- specs/block_spec.rb | 20 ++++++++++++++++++++ specs/hotel_spec.rb | 35 +++++++++++++++++++++++++++++++++-- specs/spec_helper.rb | 1 + 5 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 lib/block.rb create mode 100644 specs/block_spec.rb diff --git a/lib/block.rb b/lib/block.rb new file mode 100644 index 000000000..9ef423a18 --- /dev/null +++ b/lib/block.rb @@ -0,0 +1,11 @@ +module Hotel + class Block < Reservation + + attr_reader :checkin, :checkout, :rooms, :discount + + def initialize(checkin_date, checkout_date, rooms, discount) + super(checkin_date, checkout_date, rooms) + @discount = discount.to_f / 100 + end + end +end diff --git a/lib/hotel.rb b/lib/hotel.rb index ec6122437..7b3d98c1b 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -6,12 +6,13 @@ module Hotel class Hotel - attr_reader :rooms, :reservations + attr_reader :rooms, :reservations, :blocks # get a list of all rooms def initialize - @rooms = new_rooms + @rooms = make_rooms @reservations = [] + @blocks = [] end def new_reservation(checkin_date, checkout_date, room_number) @@ -23,7 +24,22 @@ def new_reservation(checkin_date, checkout_date, room_number) raise ArgumentError.new("Room is not available for those dates.") end end - # get list of all reservations on a given date + + def new_block(checkin_date, checkout_date, num_of_rooms, discount) + case + when num_of_rooms > 5 + raise ArgumentError.new("Maximum number of rooms for a block is 5") + when num_of_rooms > rooms_available(checkin_date, checkout_date).length + raise ArgumentError.new("There are not enough rooms available for these dates") + else + block_rooms = rooms_available(checkin_date, checkout_date).first(num_of_rooms) + + block = Block.new(checkin_date, checkout_date, block_rooms, discount) + + blocks << block + end + end + def reservations_on(date) check_date = Date.strptime(date, '%m-%d-%Y') @@ -31,20 +47,23 @@ def reservations_on(date) end def rooms_available(checkin_date, checkout_date) + rooms.reject { |room| booked_rooms(checkin_date, checkout_date).include?(room.number) } + end + + def booked_rooms(checkin_date, checkout_date) checkin = Date.strptime(checkin_date, '%m-%d-%Y') checkout = Date.strptime(checkout_date, '%m-%d-%Y') booked_rooms = [] + checkin.upto(checkout) do |date| booked_rooms << reservations_on(date.strftime('%m-%d-%Y')).collect { |reserv| reserv.room } end - booked_rooms.flatten!.uniq! - - rooms.reject { |room| booked_rooms.include?(room.number) } + booked_rooms.flatten.uniq end private - def new_rooms + def make_rooms rooms = [] i = 0 NUMBER_OF_ROOMS.times do diff --git a/specs/block_spec.rb b/specs/block_spec.rb new file mode 100644 index 000000000..c809745d2 --- /dev/null +++ b/specs/block_spec.rb @@ -0,0 +1,20 @@ +require_relative 'spec_helper' + +require 'date' + +describe "Block" do + let(:block) { Hotel::Block.new("10-01-2017", "10-04-2017", [1, 4, 7, 8], "10") } + + it "is-a type of Reservation" do + block.must_be_kind_of Hotel::Reservation + block.must_be_instance_of Hotel::Block + end + + it "can read its attributes" do + block.checkin.must_equal Date.new(2017, 10, 1) + block.must_respond_to :rooms + block.room.must_be_instance_of Array + block.must_respond_to :checkout + block.checkout.must_equal Date.new(2017, 10, 4) + end +end diff --git a/specs/hotel_spec.rb b/specs/hotel_spec.rb index 1b08b2e7e..377a5f9c3 100644 --- a/specs/hotel_spec.rb +++ b/specs/hotel_spec.rb @@ -64,8 +64,6 @@ end describe "rooms_available" do - - it "provides a list of available rooms for a date range" do smith garcia @@ -77,4 +75,37 @@ open_rooms.length.must_equal 17 end end + + describe "new_block" do + it "can create a block of rooms, with date range, discount rate, and room numbers" do + block = hotel.new_block("10-01-2017", "10-04-2017", 4, "10") + hotel.blocks.length.must_equal 1 + # block.must_respond_to :checkin + # block.must_respond_to :checkout + # block.must_respond_to :discount + # block.must_respond_to :rooms + end + + xit "only accepts rooms that are available" do + + end + + xit "prevents its rooms from showing as available" do + + end + + xit "contains a maximum of 5 rooms" do + + end + end + + xdescribe "reserve_from_block" do + it "reserves a room from a block" do + + end + + it "will have the same reservation dates as the block" do + + end + end end diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb index d44acd5ea..8d37fbb9e 100644 --- a/specs/spec_helper.rb +++ b/specs/spec_helper.rb @@ -14,6 +14,7 @@ require_relative '../lib/hotel.rb' require_relative '../lib/room.rb' require_relative '../lib/reservation.rb' +require_relative '../lib/block.rb' Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new From 4c8f66f900f4a7fb0613d31089ae038048085f60 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Fri, 8 Sep 2017 14:40:15 -0700 Subject: [PATCH 5/9] Add block logic to rooms_available method. All tests pass, 98.88% coverage --- lib/hotel.rb | 7 +++++++ specs/hotel_spec.rb | 26 ++++++++++++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/hotel.rb b/lib/hotel.rb index 7b3d98c1b..29d4b9bc7 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -46,6 +46,12 @@ def reservations_on(date) reservations.select { |reserv| check_date >= reserv.checkin && check_date < reserv.checkout } end + def blocks_on(date) + check_date = Date.strptime(date, '%m-%d-%Y') + + blocks.select { |block| check_date >= block.checkin && check_date < block.checkout } + end + def rooms_available(checkin_date, checkout_date) rooms.reject { |room| booked_rooms(checkin_date, checkout_date).include?(room.number) } end @@ -57,6 +63,7 @@ def booked_rooms(checkin_date, checkout_date) checkin.upto(checkout) do |date| booked_rooms << reservations_on(date.strftime('%m-%d-%Y')).collect { |reserv| reserv.room } + booked_rooms << blocks_on(date.strftime('%m-%d-%Y')).collect { |block| block.room.collect { |room| room.number } } end booked_rooms.flatten.uniq diff --git a/specs/hotel_spec.rb b/specs/hotel_spec.rb index 377a5f9c3..bbbc98178 100644 --- a/specs/hotel_spec.rb +++ b/specs/hotel_spec.rb @@ -80,22 +80,32 @@ it "can create a block of rooms, with date range, discount rate, and room numbers" do block = hotel.new_block("10-01-2017", "10-04-2017", 4, "10") hotel.blocks.length.must_equal 1 - # block.must_respond_to :checkin - # block.must_respond_to :checkout - # block.must_respond_to :discount - # block.must_respond_to :rooms + hotel.blocks[0].room.must_be_instance_of Array + hotel.blocks[0].must_respond_to :discount end - xit "only accepts rooms that are available" do + it "only accepts rooms that are available" do + smith + garcia + jones + + block = hotel.new_block("10-01-2017", "10-04-2017", 4, "10") + hotel.blocks[0].room.wont_include 2 + hotel.blocks[0].room.wont_include 4 + hotel.blocks[0].room.wont_include 5 end - xit "prevents its rooms from showing as available" do + it "prevents its rooms from showing as available" do + block = hotel.new_block("10-01-2017", "10-04-2017", 4, "10") - end + open_rooms = hotel.rooms_available("10-01-2017", "10-05-2017") - xit "contains a maximum of 5 rooms" do + open_rooms.length.must_equal 16 + end + it "contains a maximum of 5 rooms" do + proc { hotel.new_block("10-01-2017", "10-04-2017", 10, "10") }.must_raise ArgumentError end end From 6fd7bf71decef246ae2fbfce566a6bc240e51abc Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Sun, 10 Sep 2017 00:18:00 -0700 Subject: [PATCH 6/9] Convert reservation initialize arugments into hash structure. 98.98% coverage, all tests passing. --- lib/block.rb | 12 ++++++---- lib/hotel.rb | 22 +++++++++++++++---- lib/reservation.rb | 13 ++++++----- lib/room.rb | 3 +-- specs/block_spec.rb | 8 +++++-- specs/hotel_spec.rb | 46 ++++++++++++++++++++++++--------------- specs/reservation_spec.rb | 10 ++++----- specs/room_spec.rb | 4 ---- 8 files changed, 73 insertions(+), 45 deletions(-) diff --git a/lib/block.rb b/lib/block.rb index 9ef423a18..34b485b49 100644 --- a/lib/block.rb +++ b/lib/block.rb @@ -1,11 +1,15 @@ module Hotel class Block < Reservation - attr_reader :checkin, :checkout, :rooms, :discount + attr_reader :name, :checkin, :checkout, :room, :discount - def initialize(checkin_date, checkout_date, rooms, discount) - super(checkin_date, checkout_date, rooms) - @discount = discount.to_f / 100 + def initialize(args) + super(args) + @discount = args[:discount].to_f / 100 + end + + def cost + super * (1 - discount) end end end diff --git a/lib/hotel.rb b/lib/hotel.rb index 29d4b9bc7..341a60c56 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -15,9 +15,9 @@ def initialize @blocks = [] end - def new_reservation(checkin_date, checkout_date, room_number) + def new_reservation(name, checkin_date, checkout_date, room_number) if rooms_available(checkin_date, checkout_date).any? { |room| room.number == room_number } - reservation = Reservation.new(checkin_date, checkout_date, room_number) + reservation = Reservation.new({ name: name, checkin: checkin_date, checkout: checkout_date, room: room_number }) reservations << reservation else @@ -25,7 +25,7 @@ def new_reservation(checkin_date, checkout_date, room_number) end end - def new_block(checkin_date, checkout_date, num_of_rooms, discount) + def new_block(name, checkin_date, checkout_date, num_of_rooms, discount) case when num_of_rooms > 5 raise ArgumentError.new("Maximum number of rooms for a block is 5") @@ -34,12 +34,22 @@ def new_block(checkin_date, checkout_date, num_of_rooms, discount) else block_rooms = rooms_available(checkin_date, checkout_date).first(num_of_rooms) - block = Block.new(checkin_date, checkout_date, block_rooms, discount) + block = Block.new({ name: name, checkin: checkin_date, checkout: checkout_date, room: block_rooms, discount: discount }) blocks << block end end + def reserve_from_block(block_name, room_number, name) + block_index = blocks.find_index { |block| block.name == block_name.downcase } + block = blocks[block_index] + + reserv = Block.new({ name: name, checkin: block.checkin.strftime('%m-%d-%Y'), checkout: block.checkout.strftime('%m-%d-%Y'), rooms: room_number, discount: block.discount }) + + reservations << reserv + block.room.delete_if { |room| room.number == room_number } + end + def reservations_on(date) check_date = Date.strptime(date, '%m-%d-%Y') @@ -52,6 +62,10 @@ def blocks_on(date) blocks.select { |block| check_date >= block.checkin && check_date < block.checkout } end + def block_rooms_avail(block_name) + + end + def rooms_available(checkin_date, checkout_date) rooms.reject { |room| booked_rooms(checkin_date, checkout_date).include?(room.number) } end diff --git a/lib/reservation.rb b/lib/reservation.rb index 35687beb1..cbd89ce7e 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -2,13 +2,14 @@ module Hotel class Reservation - attr_reader :checkin, :checkout, :room + attr_reader :name, :checkin, :checkout, :room - def initialize(checkin_date, checkout_date, room_number) - if valid_dates?(Date.strptime(checkin_date, '%m-%d-%Y'), Date.strptime(checkout_date, '%m-%d-%Y')) - @checkin = Date.strptime(checkin_date, '%m-%d-%Y') - @checkout = Date.strptime(checkout_date, '%m-%d-%Y') - @room = room_number + def initialize(args) + if valid_dates?(Date.strptime(args[:checkin], '%m-%d-%Y'), Date.strptime(args[:checkout], '%m-%d-%Y')) + @name = args[:name] + @checkin = Date.strptime(args[:checkin], '%m-%d-%Y') + @checkout = Date.strptime(args[:checkout], '%m-%d-%Y') + @room = args[:room] end end diff --git a/lib/room.rb b/lib/room.rb index ffd20b562..3b1a2be35 100644 --- a/lib/room.rb +++ b/lib/room.rb @@ -2,9 +2,8 @@ module Hotel class Room attr_reader :number, :status - def initialize(number, status= :available) + def initialize(number) @number = number - @status = status end end end diff --git a/specs/block_spec.rb b/specs/block_spec.rb index c809745d2..72fc81f94 100644 --- a/specs/block_spec.rb +++ b/specs/block_spec.rb @@ -3,7 +3,7 @@ require 'date' describe "Block" do - let(:block) { Hotel::Block.new("10-01-2017", "10-04-2017", [1, 4, 7, 8], "10") } + let(:block) { Hotel::Block.new({ name: "blanco", checkin: "10-01-2017", checkout: "10-04-2017", room: [1, 4, 7, 8], discount: "10" }) } it "is-a type of Reservation" do block.must_be_kind_of Hotel::Reservation @@ -12,9 +12,13 @@ it "can read its attributes" do block.checkin.must_equal Date.new(2017, 10, 1) - block.must_respond_to :rooms + block.must_respond_to :room block.room.must_be_instance_of Array block.must_respond_to :checkout block.checkout.must_equal Date.new(2017, 10, 4) end + + it "uses discount to calculate cost" do + block.cost.must_equal 540 + end end diff --git a/specs/hotel_spec.rb b/specs/hotel_spec.rb index bbbc98178..97f6a2d6e 100644 --- a/specs/hotel_spec.rb +++ b/specs/hotel_spec.rb @@ -4,9 +4,9 @@ describe "Hotel" do let(:hotel) { Hotel::Hotel.new } - let(:smith) { hotel.new_reservation("10-01-2017", "10-04-2017", 2) } - let(:garcia) { hotel.new_reservation("10-02-2017", "10-06-2017", 4) } - let(:jones) { hotel.new_reservation("10-02-2017", "10-04-2017", 5) } + let(:smith) { hotel.new_reservation("smith", "10-01-2017", "10-04-2017", 2) } + let(:garcia) { hotel.new_reservation("garcia", "10-02-2017", "10-06-2017", 4) } + let(:jones) { hotel.new_reservation("jones", "10-02-2017", "10-04-2017", 5) } describe "initialize" do it "creates a Hotel instance" do @@ -39,12 +39,12 @@ end it "raises an ArgumentError when a room is not available for a given range" do - proc { hotel.new_reservation("10-02-2017", "10-04-2017", 5) }.must_raise ArgumentError + proc { hotel.new_reservation("blanco", "10-02-2017", "10-04-2017", 5) }.must_raise ArgumentError end it "allows reservation starting on checkout date" do before = hotel.reservations.length - hotel.new_reservation("10-04-2017", "10-07-2017", 5) + hotel.new_reservation("blanco", "10-04-2017", "10-07-2017", 5) hotel.reservations.length.must_equal (before + 1) end @@ -77,19 +77,21 @@ end describe "new_block" do - it "can create a block of rooms, with date range, discount rate, and room numbers" do - block = hotel.new_block("10-01-2017", "10-04-2017", 4, "10") + let(:block) { hotel.new_block("hernandez", "10-01-2017", "10-04-2017", 4, "10") } + + it "can create a block of rooms, with date range, discount rate, and number of rooms" do + block + hotel.blocks.length.must_equal 1 hotel.blocks[0].room.must_be_instance_of Array hotel.blocks[0].must_respond_to :discount end - it "only accepts rooms that are available" do + it "only blocks rooms that are available" do smith garcia jones - - block = hotel.new_block("10-01-2017", "10-04-2017", 4, "10") + block hotel.blocks[0].room.wont_include 2 hotel.blocks[0].room.wont_include 4 @@ -97,25 +99,33 @@ end it "prevents its rooms from showing as available" do - block = hotel.new_block("10-01-2017", "10-04-2017", 4, "10") - + block open_rooms = hotel.rooms_available("10-01-2017", "10-05-2017") - open_rooms.length.must_equal 16 + open_rooms.length.must_equal 16 end it "contains a maximum of 5 rooms" do - proc { hotel.new_block("10-01-2017", "10-04-2017", 10, "10") }.must_raise ArgumentError + proc { hotel.new_block("lopez", "10-01-2017", "10-04-2017", 10, "10") }.must_raise ArgumentError end end - xdescribe "reserve_from_block" do - it "reserves a room from a block" do + describe "reserve_from_block" do + let(:hernandez) { hotel.new_block("hernandez", "10-01-2017", "10-04-2017", 4, "10") } - end + it "removes a room from a block" do + hernandez + hotel.blocks[0].room.length.must_equal 4 - it "will have the same reservation dates as the block" do + hotel.reserve_from_block("hernandez", 3, "fish") + hotel.blocks[0].room.length.must_equal 3 + end + it "adds a blocked room to reservations" do + hernandez + hotel.reservations.length.must_equal 0 + hotel.reserve_from_block("hernandez", 3, "puente") + hotel.reservations.length.must_equal 1 end end end diff --git a/specs/reservation_spec.rb b/specs/reservation_spec.rb index ab7a6830c..396915d86 100644 --- a/specs/reservation_spec.rb +++ b/specs/reservation_spec.rb @@ -3,7 +3,7 @@ require 'date' describe "Reservation" do -let(:smith) { Hotel::Reservation.new("10-01-2017", "10-04-2017", 2) } +let(:smith) { Hotel::Reservation.new({ name: "smith", checkin: "10-01-2017", checkout: "10-04-2017", room: 2 }) } describe "initialize" do it "has a date range" do @@ -24,13 +24,13 @@ end it "raises an error when given an incorrect date range" do - proc { Hotel::Reservation.new("10-02-2017", "09-30-2017", 3) }.must_raise ArgumentError + proc { Hotel::Reservation.new({ name: "steiner", checkin: "10-02-2017", checkout: "09-30-2017", room: 3 }) }.must_raise ArgumentError - proc { Hotel::Reservation.new("08-24-2017", "08-31-2017", 3) }.must_raise ArgumentError + proc { Hotel::Reservation.new({ name: "steiner", checkin: "08-24-2017", checkout: "08-31-2017", room: 3 }) }.must_raise ArgumentError - proc { Hotel::Reservation.new("13-30-2017", "03-03-2018", 3) }.must_raise ArgumentError + proc { Hotel::Reservation.new({ name: "steiner", checkin: "13-30-2017", checkout: "03-03-2018", room: 3 }) }.must_raise ArgumentError - proc { Hotel::Reservation.new("02-25-2018", "02-29-2018", 3) }.must_raise ArgumentError + proc { Hotel::Reservation.new({ name: "steiner", checkin: "02-25-2018", checkout: "02-29-2018", room: 3 }) }.must_raise ArgumentError end end end diff --git a/specs/room_spec.rb b/specs/room_spec.rb index a99e5607d..b73e93091 100644 --- a/specs/room_spec.rb +++ b/specs/room_spec.rb @@ -10,9 +10,5 @@ room1.number.must_equal 1 room2.number.must_equal 20 end - - it "new rooms have a default status :available" do - room3.status.must_equal :available - end end end From ece8bf23643289a95e0b66b11e11dc6182c998f6 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Sun, 10 Sep 2017 00:31:41 -0700 Subject: [PATCH 7/9] Add block_rooms_available method and private block_index_by_name helper method and write tests. 99.03% coverage, all tests pass. --- lib/hotel.rb | 9 ++++++--- specs/hotel_spec.rb | 13 +++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/hotel.rb b/lib/hotel.rb index 341a60c56..5f53a93c7 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -41,8 +41,7 @@ def new_block(name, checkin_date, checkout_date, num_of_rooms, discount) end def reserve_from_block(block_name, room_number, name) - block_index = blocks.find_index { |block| block.name == block_name.downcase } - block = blocks[block_index] + block = blocks[block_index_by_name(block_name)] reserv = Block.new({ name: name, checkin: block.checkin.strftime('%m-%d-%Y'), checkout: block.checkout.strftime('%m-%d-%Y'), rooms: room_number, discount: block.discount }) @@ -63,7 +62,7 @@ def blocks_on(date) end def block_rooms_avail(block_name) - + blocks[block_index_by_name(block_name)].room.collect { |room| room.number } end def rooms_available(checkin_date, checkout_date) @@ -94,5 +93,9 @@ def make_rooms rooms end + + def block_index_by_name(block_name) + blocks.find_index { |block| block.name == block_name.downcase } + end end end diff --git a/specs/hotel_spec.rb b/specs/hotel_spec.rb index 97f6a2d6e..79a7560ae 100644 --- a/specs/hotel_spec.rb +++ b/specs/hotel_spec.rb @@ -10,6 +10,7 @@ describe "initialize" do it "creates a Hotel instance" do + hotel.must_be_instance_of Hotel::Hotel hotel.must_respond_to :rooms end @@ -128,4 +129,16 @@ hotel.reservations.length.must_equal 1 end end + + describe "block_rooms_avail" do + let(:hernandez) { hotel.new_block("hernandez", "10-01-2017", "10-04-2017", 4, "10") } + + it "finds a block given the name it was reserved under" do + hernandez + hotel.block_rooms_avail("hernandez").must_equal [1, 2, 3, 4] + + hotel.reserve_from_block("hernandez", 3, "puente") + hotel.block_rooms_avail("hernandez").must_equal [1, 2, 4] + end + end end From 1ad6891c2e06ff65ee7c3a7a357eafa87457cc29 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Sun, 10 Sep 2017 23:09:00 -0700 Subject: [PATCH 8/9] Turn room data into CSV object. All tests passing, 99.05% coverage. --- csv/.DS_Store | Bin 0 -> 6148 bytes csv/rooms.csv | 21 +++++++++++++++++++++ lib/block.rb | 2 ++ lib/hotel.rb | 20 ++++++++++++-------- lib/reservation.rb | 1 + specs/hotel_spec.rb | 2 +- 6 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 csv/.DS_Store create mode 100644 csv/rooms.csv diff --git a/csv/.DS_Store b/csv/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Tue, 3 Oct 2017 18:29:31 -0700 Subject: [PATCH 9/9] Add design activity file to hotel project. --- designactivity.md | 139 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 designactivity.md diff --git a/designactivity.md b/designactivity.md new file mode 100644 index 000000000..7dcdbd985 --- /dev/null +++ b/designactivity.md @@ -0,0 +1,139 @@ + + 1. What classes does each implementation include? Are the lists the same? + + The classes are the same for both implementations: CartEntry, ShoppingCart, and Order. + + + 2. Write down a sentence to describe each class. + + Implementation A: + * The CartEntry class tracks the quantity and price of an item in a shopping cart. + * The ShoppingCart stores objects of the CartEntry class. + * The Order class initializes with a ShoppingCart instance and calculates the total price with sales tax for the cart. + + Implementation B: + * The CartEntry class calculates the price of an entry (unit price * quantity). + * The ShoppingCart class calculates the price of all entries together. + * The Order class calculates the total price (cart price + sales tax). + + 3. How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper. + + In Implementation A, the CartEntry and ShoppingCart classes store data needed to calculate the order, while the Order class uses the data to calculate the order total. + + Implementation B, the CartEntry and ShoppingCart classes calculate the price of their respective components, and the Order class uses the cart price method to calculate the order total. + + 4. What data does each class store? How (if at all) does this differ between the two implementations? + + In both implementations, the CartEntry class stores its item's price and the quantity ordered, the ShoppingCart class stores an array of cart entry objects, and the Order class stores the sales tax rate (as a constant) and an instance of a ShoppingCart. + + 5. What methods does each class have? How (if at all) does this differ between the two implementations? + In implementation A, the CartEntry class has attribute accessor methods for the instance variables unit_price and quantity. The ShoppingCart class has an attribute accessor method for the instance variable entries. The Order class has a method for total_price. + + In implementation B, the CartyEntry class and the ShoppingCart class both have a price method, and the Order class has a total_price method. + + 6. 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? + + * Does total_price directly manipulate the instance variables of other classes? + + In implementation A, all of the logic for calculating the total price is contained in the total_price method, which directly manipulates the ShoppingCart instance variable, and reads the instance variables of each CartEntry object. + + In implementation B, CartEntry objects each calculate their own price, and the ShoppingCart object can calculate the sum of all its entries. The Order total_price method calls the price method on a ShoppingCart object but does not directly access or manipulate any instance variables from the other classes. + + 7. If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify? + + In implementation A, the instance variable unit_price is read by the Order total_price method, so to avoid changing that call, you could create a unit_price method in the CartEntry class. The unit_price method could use a case when to return a bulk unit price based on the quantity. Then you would change the initialize method to accept base_price as a parameter, change the instance variable to "@base_price", remove :unit_price from the accessor list. This way a base price would be set when the item was created, but need not be directly accessed by the Order class. + + In implementation B, you could similarly define a unit_price method for CartEntry that uses a case statement and the quantity to return a discounted unit price based on each bulk discount level. Then in the price method you call the unit_price method instead of the unit_price instance variable. Because the price method is the only public method of CartEntry that is currently used in our program, no further changes are needed in other classes. + + Implementation A is somewhat harder to change, even though the added feature looks fairly similar. The extra effort is spent abstracting the unit price from the Order class. In System A, the Order class knows that an entry has a unit_price and a quantity, so if the unit price can change the Order class will need to know how to accurately calculate the cart subtotal. To avoid having the Order class directly check and possibly change the unit price each time total_price is called, the CartEntry class instance variable is changed and access to it is removed so that the Order class can call the method unit_price on an entry, and receive the correctly discounted unit price, without having to know any business logic surrounding unit price. + + + 8. Which implementation better adheres to the single responsibility principle? + + Implementation B: The CartEntry class is responsible for knowing the price of any number of a single item. The ShoppingCart class is responsible for knowing the combined price of all its CartEntry objects. The Order class is responsible for knowing the total price of the ShoppingCart object, which in this case includes applying a sales tax (but could also include other costs like shipping). + + By contrast, Implementation A uses two of its classes solely for data storage (unit price and quantity, and a collection of cart entries) without any behavior. Instead the Order class must calculate the total price of each cart entry, add those prices together, then calculate and return the total order price. + + 9. Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled? + + Implementation B is more loosely coupled: because each class protects its instance variables and only knows the minimum of what is needed to fulfill its responsibility. Both ShoppingCart and CartEntry provide the calculated price of its contents through a public method, without allowing direct access to their instance variables. A ShoppingCart instance only has to know that an entry has a price, and an Order instance only has to know that a cart has a price. An Order only knows that calling .price on a ShoppingCart instance will return a number to which sales tax can be applied. You could pass Order any object that responds to a method call for .price and be able to provide a total. + + +For each class in your program, ask yourself the following questions: + +Hotel + What is this class's responsibility? You should be able to describe it in a single sentence. + + This class makes reservations. + + Is this class responsible for exactly one thing? + + Yes, but in order to accomplish that one thing it has many methods for checking room availability + + Does this class take on any responsibility that should be delegated to "lower level" classes? + + Passing around a bunch of date objects (checkin and checkout) is kind of clunky. It might be better if the Room class could determine whether it is booked or not for a given date or date range. + + Is there code in other classes that directly manipulates this class's instance variables? + + No + +Reservation + What is this class's responsibility? You should be able to describe it in a single sentence. + + This class validates check-in and checkout-dates, and knows the cost of stay. + + Is this class responsible for exactly one thing? + + Technically no, it includes a validation method that is called during initialize to verify the date inputs, and it also knows how much the stay will cost. + + Does this class take on any responsibility that should be delegated to "lower level" classes? + + It makes sense to me to validate the input here--the way it is set up you can only create a new reservation through the Hotel class, so you would want to return an error here, rather than give the responsibility to the Hotel class. + + If instead I had implemented a DateRange class, it would probably make sense to delegate validation to that class. + + Is there code in other classes that directly manipulates this class's instance variables? + + Yes, in the Hotel class there is a method new_reservation which creates a new instance of Reservation, passing its arguments as the instance variables. In this way, the Hotel class is acting as a controller, and the Reservation class is a model. + +Block + What is this class's responsibility? You should be able to describe it in a single sentence. + + As a subclass of Reservation, this class has the same responsibilities, except that it also takes a discount argument and calculates its cost with the given discount. + + Is this class responsible for exactly one thing? + + Same for the parent class, this class has two responsibilities: to validate date inputs and know the cost of the room for the length of the stay. + + Does this class take on any responsibility that should be delegated to "lower level" classes? + + Same as above, if there were a DateRange class the validation would be delegated there. + + Is there code in other classes that directly manipulates this class's instance variables? + + Yes, the Hotel class has a new_block method and a reserve_from_block method that both create a new instance of a Block object, passing its arguments for the instance variables. + +Room + What is this class's responsibility? You should be able to describe it in a single sentence. + + This class has no responsibility--currently the Room objects exist to provide a room number to the other classes. + + Is this class responsible for exactly one thing? + + It is currently responsible for nothing except knowing its own existence. I would have removed it as a class entirely but it seemed more likely than not you would eventually want to track more information for the rooms, and setting it up as a class didn't seem any more costly than simply keeping a list of rooms. + + Does this class take on any responsibility that should be delegated to "lower level" classes? + + No, if anything it should take on some responsibility. + + Is there code in other classes that directly manipulates this class's instance variables? + + Yes, when a Hotel object is initialized, it creates the specified number of Room objects, with their room number. + +Activity + +Based on the answers to the above questions, identify one place in your Hotel project where a class takes on multiple roles, or directly modifies the attributes of another class. Describe in design-activity.md what changes you would need to make to improve this design, and how why the resulting design would be an improvement. + +When I re-opened my Hotel project many of my tests were failing. I was not able to take the time to figure out why and make any changes to my code. As it is, I'm not sure how I would have been able to change the code. Adding a DateRange class didn't really seem to take any responsibility away from the Hotel class, or at least, not enough that it was worth making the changes.