From 9ae2d21895e4bda7a8219ab8b9198b1c8b86aa38 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Tue, 4 Sep 2018 12:28:41 -0700 Subject: [PATCH 01/55] Added class files, spec files, spec helper, and Guardfile for envi setup --- .gitignore | 1 + Guardfile | 2 +- lib/.keep | 0 lib/hotel.rb | 10 ++++++++++ lib/room.rb | 10 ++++++++++ refactors.txt | 2 ++ spec/hotel_spec.rb | 2 ++ spec/room_spec.rb | 2 ++ spec/spec_helper.rb | 14 ++++++++++---- 9 files changed, 38 insertions(+), 5 deletions(-) delete mode 100644 lib/.keep create mode 100644 lib/hotel.rb create mode 100644 lib/room.rb create mode 100644 refactors.txt create mode 100644 spec/hotel_spec.rb create mode 100644 spec/room_spec.rb diff --git a/.gitignore b/.gitignore index 5e1422c9c..c0ac3dc53 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ build-iPhoneSimulator/ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc +coverage diff --git a/Guardfile b/Guardfile index 6760f9177..fa59fc3ef 100644 --- a/Guardfile +++ b/Guardfile @@ -1,4 +1,4 @@ -guard :minitest, bundler: false, rubygems: false do +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" } diff --git a/lib/.keep b/lib/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/lib/hotel.rb b/lib/hotel.rb new file mode 100644 index 000000000..7fba53ede --- /dev/null +++ b/lib/hotel.rb @@ -0,0 +1,10 @@ +# hotel/lib/hotel.rb + +# Nessary files - do not remove +require "time" +require "date" # not necessary in my local envi +require_relative "room" + +# Optional files - for developer use +require "pry" +require "awesome_print" diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..52b050aba --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,10 @@ +# room/lib/room.rb + +# Nessary files - do not remove +require "time" +require "date" # not necessary in my local envi +require_relative "hotel" + +# Optional files - for developer use +require "pry" +require "awesome_print" diff --git a/refactors.txt b/refactors.txt new file mode 100644 index 000000000..fdd6c2720 --- /dev/null +++ b/refactors.txt @@ -0,0 +1,2 @@ +# hotel/refactors.txt +Todo: diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb new file mode 100644 index 000000000..c2fa27af5 --- /dev/null +++ b/spec/hotel_spec.rb @@ -0,0 +1,2 @@ +# hotel/specs/hotel_spec.rb +require_relative "spec_helper" diff --git a/spec/room_spec.rb b/spec/room_spec.rb new file mode 100644 index 000000000..6ce3ddcb8 --- /dev/null +++ b/spec/room_spec.rb @@ -0,0 +1,2 @@ +# hotel/specs/room_spec.rb +require_relative "spec_helper" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4d1e3fdc8..3877a0f52 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,14 @@ -require 'minitest' +# hotel/specs/spec_helper.rb +require 'simplecov' +SimpleCov.start + require 'minitest/autorun' require 'minitest/reporters' -# Add simplecov +require 'minitest/skip_dsl' +require "minitest/pride" +require "pry" +require "awesome_print" -Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new +# Add lib files here (require_relative "../") -# Require_relative your lib files here! +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new From c61914854e61face9cb21c7beca385684c8f6429 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Tue, 4 Sep 2018 18:16:50 -0700 Subject: [PATCH 02/55] Added reservation.rb and spec --- lib/hotel.rb | 15 +++++++++++++++ lib/reservation.rb | 11 +++++++++++ lib/room.rb | 1 + spec/reservation_spec.rb | 2 ++ 4 files changed, 29 insertions(+) create mode 100644 lib/reservation.rb create mode 100644 spec/reservation_spec.rb diff --git a/lib/hotel.rb b/lib/hotel.rb index 7fba53ede..a7d17fd1e 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -4,7 +4,22 @@ require "time" require "date" # not necessary in my local envi require_relative "room" +require_relative "reservation" # Optional files - for developer use require "pry" require "awesome_print" + + +## TODO: +# - As an administrator, I can access the list of all of the rooms in the hotel +# - As an administrator, I can reserve a room for a given date range +# - As an administrator, I can access the list of reservations for a specific date +# - As an administrator, I can get the total cost for a given reservation +# - Your code should raise an error ([StandardError](https://ruby-doc.org/core-2.5.0/StandardError.html)) when an invalid date range is provided + +### Constraints +# - The hotel has 20 rooms, and they are numbered 1 through 20 +# - Every room is identical, and a room always costs $200/night +# - The last day of a reservation is the checkout day, so the guest should not be charged for that night +# - For this wave, any room can be reserved at any time, and you don't need to check whether reservations conflict with each other (this will come in wave 2!) diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..c70e82ce6 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,11 @@ +# room/lib/reservation.rb + +# Nessary files - do not remove +require "time" +require "date" # not necessary in my local envi +require_relative "hotel" +require_relative "room" + +# Optional files - for developer use +require "pry" +require "awesome_print" diff --git a/lib/room.rb b/lib/room.rb index 52b050aba..004b89641 100644 --- a/lib/room.rb +++ b/lib/room.rb @@ -4,6 +4,7 @@ require "time" require "date" # not necessary in my local envi require_relative "hotel" +require_relative "reservation" # Optional files - for developer use require "pry" diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb new file mode 100644 index 000000000..b3ccba1df --- /dev/null +++ b/spec/reservation_spec.rb @@ -0,0 +1,2 @@ +# hotel/specs/reservation_spec.rb +require_relative "spec_helper" From b43773d77fd60caffedc30de055eba03e67c2845 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Wed, 5 Sep 2018 22:31:30 -0700 Subject: [PATCH 03/55] Updated Wave1 lib file setup --- lib/book.rb | 14 ++++++++++++++ lib/cost.rb | 14 ++++++++++++++ lib/hotel.rb | 27 ++++++++------------------- lib/loader.rb | 14 ++++++++++++++ lib/reservation.rb | 19 +++++++++++-------- lib/room.rb | 11 ----------- 6 files changed, 61 insertions(+), 38 deletions(-) create mode 100644 lib/book.rb create mode 100644 lib/cost.rb create mode 100644 lib/loader.rb delete mode 100644 lib/room.rb diff --git a/lib/book.rb b/lib/book.rb new file mode 100644 index 000000000..544a04b26 --- /dev/null +++ b/lib/book.rb @@ -0,0 +1,14 @@ +# Nessary - do not remove +# gems +# libfiles + +# Optional - for developer use +require "pry" +require "awesome_print" + +module HotBook + + class Book + end + +end diff --git a/lib/cost.rb b/lib/cost.rb new file mode 100644 index 000000000..39c3016e7 --- /dev/null +++ b/lib/cost.rb @@ -0,0 +1,14 @@ +# Nessary - do not remove +# gems +# libfiles + +# Optional - for developer use +require "pry" +require "awesome_print" + +module HotBook + + class Reservation + end + +end diff --git a/lib/hotel.rb b/lib/hotel.rb index a7d17fd1e..b4abd5a12 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -1,25 +1,14 @@ -# hotel/lib/hotel.rb +# Nessary - do not remove +# gems +# libfiles -# Nessary files - do not remove -require "time" -require "date" # not necessary in my local envi -require_relative "room" -require_relative "reservation" - -# Optional files - for developer use +# Optional - for developer use require "pry" require "awesome_print" +module HotBook -## TODO: -# - As an administrator, I can access the list of all of the rooms in the hotel -# - As an administrator, I can reserve a room for a given date range -# - As an administrator, I can access the list of reservations for a specific date -# - As an administrator, I can get the total cost for a given reservation -# - Your code should raise an error ([StandardError](https://ruby-doc.org/core-2.5.0/StandardError.html)) when an invalid date range is provided + class Hotel + end -### Constraints -# - The hotel has 20 rooms, and they are numbered 1 through 20 -# - Every room is identical, and a room always costs $200/night -# - The last day of a reservation is the checkout day, so the guest should not be charged for that night -# - For this wave, any room can be reserved at any time, and you don't need to check whether reservations conflict with each other (this will come in wave 2!) +end diff --git a/lib/loader.rb b/lib/loader.rb new file mode 100644 index 000000000..bc693717b --- /dev/null +++ b/lib/loader.rb @@ -0,0 +1,14 @@ +# Nessary - do not remove +# gems +# libfiles + +# Optional - for developer use +require "pry" +require "awesome_print" + +module HotBook + + class Loader + end + +end diff --git a/lib/reservation.rb b/lib/reservation.rb index c70e82ce6..39c3016e7 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -1,11 +1,14 @@ -# room/lib/reservation.rb +# Nessary - do not remove +# gems +# libfiles -# Nessary files - do not remove -require "time" -require "date" # not necessary in my local envi -require_relative "hotel" -require_relative "room" - -# Optional files - for developer use +# Optional - for developer use require "pry" require "awesome_print" + +module HotBook + + class Reservation + end + +end diff --git a/lib/room.rb b/lib/room.rb deleted file mode 100644 index 004b89641..000000000 --- a/lib/room.rb +++ /dev/null @@ -1,11 +0,0 @@ -# room/lib/room.rb - -# Nessary files - do not remove -require "time" -require "date" # not necessary in my local envi -require_relative "hotel" -require_relative "reservation" - -# Optional files - for developer use -require "pry" -require "awesome_print" From a22d58f7ee9b46289f4b091325fbda1aefc2473e Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Wed, 5 Sep 2018 22:31:52 -0700 Subject: [PATCH 04/55] Updated Wave1 spec files setup --- spec/{room_spec.rb => book_spec.rb} | 1 - spec/cost_spec.rb | 1 + spec/hotel_spec.rb | 1 - spec/loader_spec.rb | 1 + spec/reservation_spec.rb | 1 - spec/spec_helper.rb | 7 ++++++- 6 files changed, 8 insertions(+), 4 deletions(-) rename spec/{room_spec.rb => book_spec.rb} (53%) create mode 100644 spec/cost_spec.rb create mode 100644 spec/loader_spec.rb diff --git a/spec/room_spec.rb b/spec/book_spec.rb similarity index 53% rename from spec/room_spec.rb rename to spec/book_spec.rb index 6ce3ddcb8..50e909cc2 100644 --- a/spec/room_spec.rb +++ b/spec/book_spec.rb @@ -1,2 +1 @@ -# hotel/specs/room_spec.rb require_relative "spec_helper" diff --git a/spec/cost_spec.rb b/spec/cost_spec.rb new file mode 100644 index 000000000..50e909cc2 --- /dev/null +++ b/spec/cost_spec.rb @@ -0,0 +1 @@ +require_relative "spec_helper" diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index c2fa27af5..50e909cc2 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -1,2 +1 @@ -# hotel/specs/hotel_spec.rb require_relative "spec_helper" diff --git a/spec/loader_spec.rb b/spec/loader_spec.rb new file mode 100644 index 000000000..50e909cc2 --- /dev/null +++ b/spec/loader_spec.rb @@ -0,0 +1 @@ +require_relative "spec_helper" diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index b3ccba1df..50e909cc2 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -1,2 +1 @@ -# hotel/specs/reservation_spec.rb require_relative "spec_helper" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3877a0f52..c11a7c4b9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,3 @@ -# hotel/specs/spec_helper.rb require 'simplecov' SimpleCov.start @@ -10,5 +9,11 @@ require "awesome_print" # Add lib files here (require_relative "../") +require_relative "../book.rb" +require_relative "../cost.rb" +require_relative "../hotel.rb" +require_relative "../loader.rb" +require_relative "../reservation.rb" + Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new From d285e3b87b0ab6c6f48bd510b9f7a3254f1d635e Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Wed, 5 Sep 2018 22:33:07 -0700 Subject: [PATCH 05/55] Added pseudocode.md for code design --- .gitignore | 1 - pseudocode.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 pseudocode.md diff --git a/.gitignore b/.gitignore index c0ac3dc53..5e1422c9c 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,3 @@ build-iPhoneSimulator/ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc -coverage diff --git a/pseudocode.md b/pseudocode.md new file mode 100644 index 000000000..5493fee8e --- /dev/null +++ b/pseudocode.md @@ -0,0 +1,54 @@ +# User Stories + +## Requirements + +- As an administrator, I can access the list of all of the rooms in the hotel +- As an administrator, I can reserve a room for a given date range +- As an administrator, I can access the list of reservations for a specific date +- As an administrator, I can get the total cost for a given reservation +- Your code should raise an error ([StandardError](https://ruby-doc.org/core-2.5.0/StandardError.html)) when an invalid date range is provided + +- The hotel has 20 rooms, and they are numbered 1 through 20 +- Every room is identical, and a room always costs $200/night +- The last day of a reservation is the checkout day, so the guest should not be charged for that night +- For this wave, any room can be reserved at any time, and you don't need to check whether reservations conflict with each other (this will come in wave 2!) + +## Design + + +- ruby returns array of all rooms + - rooms are a hash + - key1 = :room_number (1-20) make them symbols! (don't make anything dependent on this being an integer!) + - key2 = :room_rate (float = 200.0) + - is it easy for 200 to become a default later? + - the block cost can go in the args hash! + - is it easy for this hash to become a class later? +- provide :start_date and :end_date + - later, reservation class comes up with a room number + - ruby makes a reservation object + - :start_date + - :end_date + - :room_number (default to default, define default as room 1) +- provide :date + - book class contains reservations [] + - loader class loads book class with reservations + - loader class loads hotel class with rooms (argument is number of rooms) ##### really? + - hotel class can return a list of all rooms + - book class searches reservations by date + - book class returns array of reservations for that date +- provide reservation + - ruby returns number of nights to charge + - reservation class calculates duration + - WHICH CLASS KNOWS TO CHARGE DURATION-1 NIGHTS? ###### + - cost class multiplies nights by room cost + - reservation shouldn't know anything about cost -- cost class + - ruby returns float of total cost +- provide date range + - reservation class determines if it's valid (start before end) or not + - if invalid, ruby raises StandardError + + + +## Thoughts + +can I do this without a room class? what would a room class do? From d5a339d2650eb8ae65a0815c9ff9ec180b061df8 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Wed, 5 Sep 2018 23:19:35 -0700 Subject: [PATCH 06/55] Wave1 hotel_spec tests written --- spec/hotel_spec.rb | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index 50e909cc2..470145e4a 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -1 +1,42 @@ require_relative "spec_helper" + +describe "HotBook::Hotel class" do + let(:room_count) {20} + let (:hotel) {HotBook::Hotel.new(:room_count)} + + describe "#initialize" do + before {all_rooms = hotel.rooms} + + it "Hotel has reader method for rooms" do + expect(hotel).must_respond_to rooms + end + + it "rooms returns an array of hashes with the correct number of rooms" do + expect(all_rooms).must_be_instance_of Array + all_rooms.each {|room| expect(room).must_be_instance_of Hash} + expect(all_rooms.size).must_equal room_count + end + + it "room numbers are all valid key/value pairs" do + valid_room_numbers = %i(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) + all_room_numbers = rooms.flat_map{ |room| room.values_at(:room_number) } + expec(all_room_numbers).must_equal valid_room_numbers + end + + it "room rates are all valid key/value pairs" do + valid_room_rates = Array.new(size= room_count, default = 200.0)) + all_room_rates = rooms.flat_map{ |room| room.values_at(:room_rate) } + expect(all_room_rates).must_equal valid_room_rates + end + + end + +end + +# - hotel class can return a list of all rooms +# - loader class loads hotel class with rooms (argument is number of rooms) ##### really? +# rooms are a hash +# hash has 20 elements +# expect values at :room_number to be 1-20 +# hash keys are :room_number and :room_rate (float) +# expect :room_rate for all to be 200 From 57b76b107c969922e1f34de6959a8e74153d63cc Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Wed, 5 Sep 2018 23:19:35 -0700 Subject: [PATCH 07/55] Wave1 hotel_spec tests written --- spec/hotel_spec.rb | 41 +++++++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 10 +++++----- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index 50e909cc2..0ff622973 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -1 +1,42 @@ require_relative "spec_helper" + +describe "HotBook::Hotel class" do + let(:room_count) {20} + let (:hotel) {HotBook::Hotel.new(:room_count)} + + describe "#initialize" do + before {all_rooms = hotel.rooms} + + it "Hotel has reader method for rooms" do + expect(hotel).must_respond_to rooms + end + + it "rooms returns an array of hashes with the correct number of rooms" do + expect(all_rooms).must_be_instance_of Array + all_rooms.each {|room| expect(room).must_be_instance_of Hash} + expect(all_rooms.size).must_equal room_count + end + + it "room numbers are all valid key/value pairs" do + valid_room_numbers = %i(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) + all_room_numbers = rooms.flat_map{ |room| room.values_at(:room_number) } + expec(all_room_numbers).must_equal valid_room_numbers + end + + it "room rates are all valid key/value pairs" do + valid_room_rates = Array.new(size= room_count, default = 200.0) + all_room_rates = rooms.flat_map{ |room| room.values_at(:room_rate) } + expect(all_room_rates).must_equal valid_room_rates + end + + end + +end + +# - hotel class can return a list of all rooms +# - loader class loads hotel class with rooms (argument is number of rooms) ##### really? +# rooms are a hash +# hash has 20 elements +# expect values at :room_number to be 1-20 +# hash keys are :room_number and :room_rate (float) +# expect :room_rate for all to be 200 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c11a7c4b9..c1ac46636 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,11 +9,11 @@ require "awesome_print" # Add lib files here (require_relative "../") -require_relative "../book.rb" -require_relative "../cost.rb" -require_relative "../hotel.rb" -require_relative "../loader.rb" -require_relative "../reservation.rb" +require_relative "../lib/book.rb" +require_relative "../lib/cost.rb" +require_relative "../lib/hotel.rb" +require_relative "../lib/loader.rb" +require_relative "../lib/reservation.rb" Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new From e095f195119afccb25c06e19ec940d64cd8dcbcd Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Thu, 6 Sep 2018 01:12:07 -0700 Subject: [PATCH 08/55] Hotel class passes specs --- .DS_Store | Bin 0 -> 6148 bytes lib/hotel.rb | 25 ++++++++++++++++++++++++ spec/hotel_spec.rb | 46 ++++++++++++++++++++++++++++++--------------- 3 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..669c12ca0d4cbd06ffefd4a5278a13b83f67f314 GIT binary patch literal 6148 zcmeHK%}(1u5S|SI>{_afR3VT!Sn8n{4xyKDv{L8^34{?GXq(!2MJ!ofDsu^ka8_(a`pRHXl0Eo^YT?6m|z(FN!xH$YnC{8*h z4eKEkxA;jj0XZwsw#nqtSiOa6P5@o4O;_@I%tKOb0Hgh?Y#df(BSF${$}PV+_1}F`MHOd-EOkHAAIMDAS#hZjAXwzAO zFt8X}tPNrWMVL}VQ!4BcLzr^(2R1LXSQ|9uAoR@G$IdM52}S7H(I4n^5TQXHnE__t zAp+72AF~WiUHBwh&DR-NOo_XdN|r^1Jv)RB$QVhye~mRZ^f8P eTk#UA7W4;XAchuegXlrw9|27R56r-`GH?gJ_F}yN literal 0 HcmV?d00001 diff --git a/lib/hotel.rb b/lib/hotel.rb index b4abd5a12..10a632183 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -9,6 +9,31 @@ module HotBook class Hotel + attr_reader :rooms, :room_count, :room_rate, :room_info + + def initialize(input) #{room_count: 20, room_rate: 200.0} + @room_count = input[:room_count] + @room_rate = input[:room_rate] + @rooms = load_rooms # [{room_number: :1, room_rate: 200.0}] + end + + def load_rooms + new_rooms = [] + room_count.times do |i| + new_room = Hash.new + new_room[:room_number] = room_numbers[i] + new_room[:room_rate] = @room_rate + new_rooms << new_room + end + return new_rooms + end + + def room_numbers + room_numbers = %i(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) + raise ArgumentError, "Wrong number of room numbers" if room_numbers.size != room_count + return room_numbers + end + end end diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index 0ff622973..434768485 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -1,34 +1,50 @@ require_relative "spec_helper" describe "HotBook::Hotel class" do - let(:room_count) {20} - let (:hotel) {HotBook::Hotel.new(:room_count)} - - describe "#initialize" do - before {all_rooms = hotel.rooms} + let(:input) {{room_count: 20, room_rate: 200.0}} + let(:hotel) {HotBook::Hotel.new(input)} + let(:room_count) {hotel.room_count} + let(:room_rate) {hotel.room_rate} + let(:all_rooms) {hotel.rooms} + let(:expected_room_rate) {200.0} + let(:expected_room_count) {20} + + describe "initialize arguments" do + it "loads room count correctly" do + expect(room_count).must_be_instance_of Integer + expect(room_count).must_equal expected_room_count + end - it "Hotel has reader method for rooms" do - expect(hotel).must_respond_to rooms + it "loads room rate correctly" do + expect(room_rate).must_be_instance_of Float + expect(room_rate).must_equal expected_room_rate end + end - it "rooms returns an array of hashes with the correct number of rooms" do + describe "loading and returning list of all rooms" do + it "rooms is initialized as array of hashes with correct number of rooms" do expect(all_rooms).must_be_instance_of Array - all_rooms.each {|room| expect(room).must_be_instance_of Hash} + all_rooms.each do |room| + expect(room).must_be_instance_of Hash + end expect(all_rooms.size).must_equal room_count end it "room numbers are all valid key/value pairs" do valid_room_numbers = %i(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) - all_room_numbers = rooms.flat_map{ |room| room.values_at(:room_number) } - expec(all_room_numbers).must_equal valid_room_numbers + all_rooms.each_with_index do |room, index| + expect(room[:room_number]).must_be_instance_of Symbol + expect(room[:room_number]).must_equal valid_room_numbers[index] + end end it "room rates are all valid key/value pairs" do - valid_room_rates = Array.new(size= room_count, default = 200.0) - all_room_rates = rooms.flat_map{ |room| room.values_at(:room_rate) } - expect(all_room_rates).must_equal valid_room_rates + valid_room_rates = Array.new(size= expected_room_count, default = expected_room_rate) + all_rooms.each_with_index do |room, index| + expect(room[:room_rate]).must_be_instance_of Float + expect(room[:room_rate]).must_equal valid_room_rates[index] + end end - end end From 1fe9023b62a11f2c802bb243b182a534aa5b0fe7 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Thu, 6 Sep 2018 01:12:07 -0700 Subject: [PATCH 09/55] Hotel class passes specs --- .DS_Store | Bin 0 -> 6148 bytes lib/hotel.rb | 24 +++++++++++++++++++++++ spec/hotel_spec.rb | 46 ++++++++++++++++++++++++++++++--------------- 3 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..669c12ca0d4cbd06ffefd4a5278a13b83f67f314 GIT binary patch literal 6148 zcmeHK%}(1u5S|SI>{_afR3VT!Sn8n{4xyKDv{L8^34{?GXq(!2MJ!ofDsu^ka8_(a`pRHXl0Eo^YT?6m|z(FN!xH$YnC{8*h z4eKEkxA;jj0XZwsw#nqtSiOa6P5@o4O;_@I%tKOb0Hgh?Y#df(BSF${$}PV+_1}F`MHOd-EOkHAAIMDAS#hZjAXwzAO zFt8X}tPNrWMVL}VQ!4BcLzr^(2R1LXSQ|9uAoR@G$IdM52}S7H(I4n^5TQXHnE__t zAp+72AF~WiUHBwh&DR-NOo_XdN|r^1Jv)RB$QVhye~mRZ^f8P eTk#UA7W4;XAchuegXlrw9|27R56r-`GH?gJ_F}yN literal 0 HcmV?d00001 diff --git a/lib/hotel.rb b/lib/hotel.rb index b4abd5a12..a2420769d 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -9,6 +9,30 @@ module HotBook class Hotel + attr_reader :room_count, :room_rate, :rooms + + def initialize(input) #{room_count: 20, room_rate: 200.0} + @room_count = input[:room_count] + @room_rate = input[:room_rate] + @rooms = load_rooms # [{room_number: :1, room_rate: 200.0}] + end + + def load_rooms + new_rooms = [] + room_count.times do |i| + new_room = Hash.new + new_room[:room_number] = room_numbers[i] + new_room[:room_rate] = room_rate + new_rooms << new_room + end + return new_rooms + end + + def room_numbers + room_numbers = %i(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) + raise ArgumentError, "Wrong number of room numbers" if room_numbers.size != room_count + return room_numbers + end end end diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index 0ff622973..434768485 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -1,34 +1,50 @@ require_relative "spec_helper" describe "HotBook::Hotel class" do - let(:room_count) {20} - let (:hotel) {HotBook::Hotel.new(:room_count)} - - describe "#initialize" do - before {all_rooms = hotel.rooms} + let(:input) {{room_count: 20, room_rate: 200.0}} + let(:hotel) {HotBook::Hotel.new(input)} + let(:room_count) {hotel.room_count} + let(:room_rate) {hotel.room_rate} + let(:all_rooms) {hotel.rooms} + let(:expected_room_rate) {200.0} + let(:expected_room_count) {20} + + describe "initialize arguments" do + it "loads room count correctly" do + expect(room_count).must_be_instance_of Integer + expect(room_count).must_equal expected_room_count + end - it "Hotel has reader method for rooms" do - expect(hotel).must_respond_to rooms + it "loads room rate correctly" do + expect(room_rate).must_be_instance_of Float + expect(room_rate).must_equal expected_room_rate end + end - it "rooms returns an array of hashes with the correct number of rooms" do + describe "loading and returning list of all rooms" do + it "rooms is initialized as array of hashes with correct number of rooms" do expect(all_rooms).must_be_instance_of Array - all_rooms.each {|room| expect(room).must_be_instance_of Hash} + all_rooms.each do |room| + expect(room).must_be_instance_of Hash + end expect(all_rooms.size).must_equal room_count end it "room numbers are all valid key/value pairs" do valid_room_numbers = %i(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) - all_room_numbers = rooms.flat_map{ |room| room.values_at(:room_number) } - expec(all_room_numbers).must_equal valid_room_numbers + all_rooms.each_with_index do |room, index| + expect(room[:room_number]).must_be_instance_of Symbol + expect(room[:room_number]).must_equal valid_room_numbers[index] + end end it "room rates are all valid key/value pairs" do - valid_room_rates = Array.new(size= room_count, default = 200.0) - all_room_rates = rooms.flat_map{ |room| room.values_at(:room_rate) } - expect(all_room_rates).must_equal valid_room_rates + valid_room_rates = Array.new(size= expected_room_count, default = expected_room_rate) + all_rooms.each_with_index do |room, index| + expect(room[:room_rate]).must_be_instance_of Float + expect(room[:room_rate]).must_equal valid_room_rates[index] + end end - end end From ea85f455b3ca12119c05a2ecb0330266c4517520 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Fri, 7 Sep 2018 12:51:10 -0700 Subject: [PATCH 10/55] Added DateRange class --- .gitignore | 1 + README.md | 7 ++- lib/book.rb | 65 ++++++++++++++++++++++++ lib/cost.rb | 14 ----- lib/daterange.rb | 34 +++++++++++++ lib/hotel.rb | 8 +-- lib/reservation.rb | 49 +++++++++++++++++- pseudocode.md | 4 +- spec/{cost_spec.rb => daterange_spec.rb} | 0 spec/hotel_spec.rb | 4 +- spec/reservation_spec.rb | 47 +++++++++++++++++ spec/spec_helper.rb | 6 +-- 12 files changed, 211 insertions(+), 28 deletions(-) delete mode 100644 lib/cost.rb create mode 100644 lib/daterange.rb rename spec/{cost_spec.rb => daterange_spec.rb} (100%) diff --git a/.gitignore b/.gitignore index 5e1422c9c..a4d4ff8dd 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ build-iPhoneSimulator/ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc +sandbox.rb diff --git a/README.md b/README.md index 07dc42bcf..e8cc263ed 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,9 @@ Remember that your job is only to build the classes that store information and h ### User Stories - As an administrator, I can view a list of rooms that are not reserved for a given date range + + - reservation class returns reservations for that range + - book class looks at reservations - As an administrator, I can reserve an available room for a given date range ### Constraints @@ -123,7 +126,7 @@ If you are not familiar with what a block of hotel rooms, here is a brief descri ## Before Submissions -Usually by the end of a project, we can look back on what we made with a clearer understanding of what we actually needed. In industry, this is a great time to do a refactor of some sort. For this project however, you're off the hook... for the moment. We will be revisiting our hotels later on on the course, and you may want to make some changes at that point. +Usually by the end of a project, we can look back on what we made with a clearer understanding of what we actually needed. In industry, this is a great time to do a refactor of some sort. For this project however, you're off the hook... for the moment. We will be revisiting our hotels later on on the course, and you may want to make some changes at that point. - Create a new file in the project called `refactors.txt` - Make a short list of the changes that you could make, particularly in terms of naming conventions @@ -139,4 +142,4 @@ You should not be working on these (or even thinking about them) until you have - Create a CLI to interact with your hotel system ## What we're looking for -You can find what instructors will be looking for in the [feedback](feedback.md) markdown document. +You can find what instructors will be looking for in the [feedback](feedback.md) markdown document. diff --git a/lib/book.rb b/lib/book.rb index 544a04b26..a86fc916c 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -1,5 +1,9 @@ # Nessary - do not remove # gems +require "date" +require_relative "daterange" +require_relative "hotel" +require_relative "reservation" # libfiles # Optional - for developer use @@ -7,8 +11,69 @@ require "awesome_print" module HotBook +# Book class holds all reservations and searches through them +# Reservation.new(start_date:, end_date:) #optional: room_number: :1, room_rate: 200.0 class Book + attr_reader :reservations, :hotel + + def initialize + @reservations = [] + @hotel = HotBook::Hotel.new #(room_count: 20, room_rate: 200.0) + end + + def new_reservation(daterange) #DateRange class + new_reservation = HotBook::Reservation.new(daterange) + # Reservation.new(start_date:, end_date:) #optional: room_number: :1, room_rate: 200.0 + @reservations << new_reservation + return new_reservation + # + # #suggested_room = suggest_room(daterange) + # args = { room_number: default_room_number, # refactor in Wave 2 to suggest_room(daterange) + # room_rate: find_room_by_number(default_room_number)[:room_rate] + # } + # # raise ArgumentError if room_number is not available during daterange + # args = args.merge(daterange) + + end + + def find_room_by_number(query) + raise ArgumentError, "Room number must be Symbol" unless query.is_a? Symbol + raise StandardError, "No room by that number" unless all_room_numbers.include?(query) + return room = all_rooms.find { |room| room[:room_number] == query} + end + + def all_rooms + return hotel.rooms + end + + def all_room_numbers + return all_rooms.values_at(:room_number) + end + + def default_room_number + return :1 + end + + def list_reservations_by_date(date) # change to DateRange (start = end) + raise ArgumentError, "Invalid date (use Date.parse)" unless date.is_a? Date + return list = reservations.select {|reservation| reservation.range.include? date} + end + + # def available_rooms(daterange) + # validate(daterange) + # + # + # return list +# end + + # def suggest_room(daterange) + # validate(daterange) + # return suggestion = available_rooms(daterange).first + # end + end end + +#Should DateRange be a class of its own? (I think so!) diff --git a/lib/cost.rb b/lib/cost.rb deleted file mode 100644 index 39c3016e7..000000000 --- a/lib/cost.rb +++ /dev/null @@ -1,14 +0,0 @@ -# Nessary - do not remove -# gems -# libfiles - -# Optional - for developer use -require "pry" -require "awesome_print" - -module HotBook - - class Reservation - end - -end diff --git a/lib/daterange.rb b/lib/daterange.rb new file mode 100644 index 000000000..69c0fe51d --- /dev/null +++ b/lib/daterange.rb @@ -0,0 +1,34 @@ +# Nessary - do not remove +# gems +require "date" +# libfiles + +# Optional - for developer use +require "pry" +require "awesome_print" + +module HotBook + class DateRange + attr_reader :start_date, :end_date + + def initialize(start_date:, end_date:) + @start_date = Date.parse(start_date.to_s) + @end_date = Date.parse(end_date.to_s) + end + + def is_valid? + raise StandardError, "Invalid DateRange (end must be > start)" unless end_date > start_date + end + + def duration + return (end_date - start_date).to_i + end + + def follows?(oldrange) # caution: easy to switch... + if self.is_valid? + return oldrange.end_date <= start_date + end + end + + end +end diff --git a/lib/hotel.rb b/lib/hotel.rb index a2420769d..09dd8a687 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -7,13 +7,13 @@ require "awesome_print" module HotBook - +# HOTEL CLASS KNOWS ABOUT ROOMS class Hotel attr_reader :room_count, :room_rate, :rooms - def initialize(input) #{room_count: 20, room_rate: 200.0} - @room_count = input[:room_count] - @room_rate = input[:room_rate] + def initialize(room_count: 20, room_rate: 200.0) #{room_count: 20, room_rate: 200.0} + @room_count = room_count + @room_rate = room_rate @rooms = load_rooms # [{room_number: :1, room_rate: 200.0}] end diff --git a/lib/reservation.rb b/lib/reservation.rb index 39c3016e7..1cca60584 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -1,14 +1,61 @@ # Nessary - do not remove # gems +require "date" # libfiles # Optional - for developer use require "pry" require "awesome_print" -module HotBook +# .conflict? +module HotBook +# Reservation.new(daterange:) #optional: room_number: :1, room_rate: 200.0 class Reservation + attr_reader :daterange, :room_number, :room_rate, + + def initialize(daterange:, room_number: :1, room_rate: 200.0) + @daterange = daterange + @room_number = room_number # what about room.number + @room_rate = room_rate # and room.rate + # check_errors + end + + # def check_errors + # raise ArgumentError, "something's missing in your args hash" if + # start_date == nil || end_date == nil + # raise ArgumentError, "couldn't parse a date to date object" unless + # start_date.class == Date || end_date.class == Date + # raise StandardError, "end date must be later than start date" unless + # duration > 0 + # end + + def cost + return duration * room_rate + end + end end + +# TODO: +# Custom error message! +# valid as a class method? +# +# - provide :start_date and :end_date +# - later, reservation class comes up with a room number +# - ruby makes a reservation object +# - :start_date +# - :end_date +# - :room_number (default to default, define default as room 1) +# provide reservation +# - reservation returns number of nights total +# - RoomCharge class knows it's duration - 1, and multiplies nights by room cost + # - reservation shouldn't know anything about cost +# later, reservation could ask hotel for room number +# to assign to a new reservation + + +# ## WAVE2 +# - provide: start_date and :end_date +# - Reservation class can return diff --git a/pseudocode.md b/pseudocode.md index 7a312339d..bdd3b208c 100644 --- a/pseudocode.md +++ b/pseudocode.md @@ -24,7 +24,7 @@ - the block cost can go in the args hash! - is it easy for this hash to become a class later? --> - provide :start_date and :end_date - - later, reservation class comes up with a room number + - later, hotel class comes up with a room number - ruby makes a reservation object - :start_date - :end_date @@ -38,7 +38,7 @@ - provide reservation - ruby returns number of nights to charge - reservation class calculates duration - - WHICH CLASS KNOWS TO CHARGE DURATION-1 NIGHTS? ###### + - WHICH CLASS KNOWS TO CHARGE DURATION-1 NIGHTS? cost class? ###### - cost class multiplies nights by room cost - reservation shouldn't know anything about cost -- cost class - ruby returns float of total cost diff --git a/spec/cost_spec.rb b/spec/daterange_spec.rb similarity index 100% rename from spec/cost_spec.rb rename to spec/daterange_spec.rb diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index 434768485..ecac4587a 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -1,8 +1,8 @@ require_relative "spec_helper" describe "HotBook::Hotel class" do - let(:input) {{room_count: 20, room_rate: 200.0}} - let(:hotel) {HotBook::Hotel.new(input)} + let(:args) {{room_count: 20, room_rate: 200.0}} + let(:hotel) {HotBook::Hotel.new(args)} let(:room_count) {hotel.room_count} let(:room_rate) {hotel.room_rate} let(:all_rooms) {hotel.rooms} diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index 50e909cc2..f994846ac 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -1 +1,48 @@ require_relative "spec_helper" + +# the Reservation class is all about dates. +# specifically, the start and end date passed in to it + +describe "HotBook::Reservation class" do + let(:args) {{start_date: "18/12/1", end_date: "18/12/3"}} + let(:reservation) {HotBook::Reservation.new(args)} + invalid_args = {start_date: "18/12/3", end_date: "18/12/1"} + let(:bad_reservation) {HotBook::Reservation.new(invalid_args)} + another_invalid_args = {start_date: "18/12/1", end_date: "18/12/1"} + let(:another_bad_reservation) {HotBook::Reservation.new(another_invalid_args)} + + describe "initialize arguments" do + it "will parse any date you throw at it" do + end + + it "raises StandardError for end date == or < start date" do + expect{bad_reservation}.must_raise StandardError + expect{another_bad_reservation}.must_raise StandardError + end + + it "raises ArgumentError for nil or empty or non-Date class" do + tests = [] + tests << missing_args = {start_date: "", end_date: "18/12/3"} + tests << more_missing_args = {end_date: "18/12/3"} + tests << bad_args = {start_date: :rumplestiltskin, end_date: "18/12/3"} + tests << more_bad_args = {start_date: [[3]], end_date: "18/12/3"} + tests.each do |args| + expect{HotBook::Reservation.new(args)}.must_raise ArgumentError + end + end + end + + describe "duration" do + it "calculates duration correctly" do + expect(reservation.duration).must_equal 2 + end + end + + +end + +# @start_date +# @end_date +# defer how the reservations and rooms are linked! +# calculate duration +# standard error if date range is invalid diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c1ac46636..634f2c6ea 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,13 +7,13 @@ require "minitest/pride" require "pry" require "awesome_print" +require "date" # Add lib files here (require_relative "../") require_relative "../lib/book.rb" -require_relative "../lib/cost.rb" require_relative "../lib/hotel.rb" -require_relative "../lib/loader.rb" require_relative "../lib/reservation.rb" - +require_relative "../lib/roomcharge.rb" +require_relative "../lib/daterange.rb" Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new From 00e2798adf3796925f3d59658006be5667b74752 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Fri, 7 Sep 2018 15:06:48 -0700 Subject: [PATCH 11/55] Finished DateRange class & spec for Wave 1, tests pass --- lib/daterange.rb | 14 ++++----- spec/daterange_spec.rb | 70 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/lib/daterange.rb b/lib/daterange.rb index 69c0fe51d..dfddb7edb 100644 --- a/lib/daterange.rb +++ b/lib/daterange.rb @@ -1,30 +1,30 @@ -# Nessary - do not remove -# gems -require "date" -# libfiles - # Optional - for developer use require "pry" require "awesome_print" module HotBook +# The DateRange class compares and does calculations on other DateRanges +# Refer to Date gem documentation to understand what date format to use (i.e. y-m-d) class DateRange + require "date" attr_reader :start_date, :end_date def initialize(start_date:, end_date:) @start_date = Date.parse(start_date.to_s) @end_date = Date.parse(end_date.to_s) + is_valid? end def is_valid? - raise StandardError, "Invalid DateRange (end must be > start)" unless end_date > start_date + raise ArgumentError, "Invalid DateRange #{self}: End must be > Start)" unless end_date > start_date + return true end def duration return (end_date - start_date).to_i end - def follows?(oldrange) # caution: easy to switch... + def is_after?(oldrange) # caution: easy to switch... if self.is_valid? return oldrange.end_date <= start_date end diff --git a/spec/daterange_spec.rb b/spec/daterange_spec.rb index 50e909cc2..6dc764df3 100644 --- a/spec/daterange_spec.rb +++ b/spec/daterange_spec.rb @@ -1 +1,71 @@ require_relative "spec_helper" + +describe "HotBook::DateRange class" do + describe "initialize method" do + it "accepts Strings as args" do + daterange = HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_2") + expect(daterange.start_date).must_equal Date.parse("apr_1") + end + + it "accepts Date objects as args" do + date1 = Date.parse("apr_1_00") + date2 = Date.parse("apr_6_00") + daterange = HotBook::DateRange.new(start_date: date1, end_date: date2) + end + + it "correctly raises a StandardError if end !>= start" do + expect{HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_6")}.must_raise StandardError + expect{HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_1")}.must_raise StandardError + end + + it "works with Time.to_date, including case T2>T1 but same date" do + time1 = Time.now # today + time2 = time1 + 60 # (arbitrary) 1 minute later today + date1 = time1.to_date + date2 = time2.to_date + expect{HotBook::DateRange.new(start_date: date1, end_date: date2)}.must_raise StandardError + end + + it "handles edge cases for both 1 nil arg AND 2 nil args" do # might break if I raise errors elsewhere + expect{HotBook::DateRange.new(start_date: nil, end_date: nil)}.must_raise StandardError + expect{HotBook::DateRange.new(start_date: nil, end_date: "apr_1")}.must_raise StandardError + expect{HotBook::DateRange.new(start_date: "apr_1", end_date: nil)}.must_raise StandardError + end + end + + describe "duration method" do + it "correctly sums total NIGHTS of the stay, even for a one night stay" do + daterange = HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_2") + expect(daterange.duration).must_equal 1 + end + end + + describe "is_after? method" do + # existing range: + oldrange = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_10") + # check these new ranges against existing range: + ok1 = HotBook::DateRange.new(start_date: "apr_29", end_date: "apr_30") + ok2 = HotBook::DateRange.new(start_date: "apr_10", end_date: "apr_30") + notok1= HotBook::DateRange.new(start_date: "apr_9", end_date: "apr_30") + notok2 = HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_6") + notok3 = HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_5") + + it "will return true if ranges don't conflict" do + expect(ok1.is_after?(oldrange)).must_equal true + end + + it "allows a new range (reservation) to start on the same day that another one ends" do + expect(ok2.is_after?(oldrange)).must_equal true + end + + it "will return false if ranges do conflict" do + expect(notok1.is_after?(oldrange)).must_equal false + end + + it "will return false if new range is before oldrange" do + expect(notok2.is_after?(oldrange)).must_equal false + expect(notok3.is_after?(oldrange)).must_equal false + end + end + +end From 2043e50baa9391e5679d4ba03e9d4bf73a07c61b Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Fri, 7 Sep 2018 16:57:28 -0700 Subject: [PATCH 12/55] Added to_range method in DateRange, test passes --- lib/daterange.rb | 6 ++++++ spec/daterange_spec.rb | 40 ++++++++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/lib/daterange.rb b/lib/daterange.rb index dfddb7edb..3991ca917 100644 --- a/lib/daterange.rb +++ b/lib/daterange.rb @@ -2,6 +2,8 @@ require "pry" require "awesome_print" +#TODO: .parse method! + module HotBook # The DateRange class compares and does calculations on other DateRanges # Refer to Date gem documentation to understand what date format to use (i.e. y-m-d) @@ -30,5 +32,9 @@ def is_after?(oldrange) # caution: easy to switch... end end + def to_range + return (@start_date..@end_date) + end + end end diff --git a/spec/daterange_spec.rb b/spec/daterange_spec.rb index 6dc764df3..d4a3caf7f 100644 --- a/spec/daterange_spec.rb +++ b/spec/daterange_spec.rb @@ -1,9 +1,11 @@ require_relative "spec_helper" describe "HotBook::DateRange class" do + let(:daterange) {HotBook::DateRange.new(start_date: "apr_1", + end_date: "apr_2")} + describe "initialize method" do it "accepts Strings as args" do - daterange = HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_2") expect(daterange.start_date).must_equal Date.parse("apr_1") end @@ -13,9 +15,17 @@ daterange = HotBook::DateRange.new(start_date: date1, end_date: date2) end + it "start_date and end_date instance vars are Date class objects" do + expect(daterange.start_date).must_be_instance_of Date + end + it "correctly raises a StandardError if end !>= start" do - expect{HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_6")}.must_raise StandardError - expect{HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_1")}.must_raise StandardError + end_dates = ["apr_6", "apr_1"] + end_dates.each do |end_date| + expect{HotBook::DateRange.new(start_date: "apr_6", + end_date: end_date) + }.must_raise StandardError + end end it "works with Time.to_date, including case T2>T1 but same date" do @@ -23,13 +33,17 @@ time2 = time1 + 60 # (arbitrary) 1 minute later today date1 = time1.to_date date2 = time2.to_date - expect{HotBook::DateRange.new(start_date: date1, end_date: date2)}.must_raise StandardError + expect{HotBook::DateRange.new(start_date: date1, end_date: date2) + }.must_raise StandardError end it "handles edge cases for both 1 nil arg AND 2 nil args" do # might break if I raise errors elsewhere - expect{HotBook::DateRange.new(start_date: nil, end_date: nil)}.must_raise StandardError - expect{HotBook::DateRange.new(start_date: nil, end_date: "apr_1")}.must_raise StandardError - expect{HotBook::DateRange.new(start_date: "apr_1", end_date: nil)}.must_raise StandardError + expect{HotBook::DateRange.new(start_date: nil, end_date: nil) + }.must_raise StandardError + expect{HotBook::DateRange.new(start_date: nil, end_date: "apr_1") + }.must_raise StandardError + expect{HotBook::DateRange.new(start_date: "apr_1", end_date: nil) + }.must_raise StandardError end end @@ -54,7 +68,8 @@ expect(ok1.is_after?(oldrange)).must_equal true end - it "allows a new range (reservation) to start on the same day that another one ends" do + it "allows a new range (reservation) to start on the same day" \ + "that another one ends" do expect(ok2.is_after?(oldrange)).must_equal true end @@ -66,6 +81,15 @@ expect(notok2.is_after?(oldrange)).must_equal false expect(notok3.is_after?(oldrange)).must_equal false end + + describe "to_range method" do + it "will return a Range class object" do + daterange = HotBook::DateRange.new(start_date: "apr_1", + end_date: "apr_2") + range = daterange.to_range + expect(range).must_be_instance_of Range + end + end end end From b7ef34b3e6e641888fccbbcf16556c4905ff5149 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Fri, 7 Sep 2018 17:00:22 -0700 Subject: [PATCH 13/55] Good chunk of design refactors for Wave 1 --- lib/book.rb | 79 ++++++++++++---------------------------- lib/hotel.rb | 46 ++++++++++++++--------- lib/reservation.rb | 27 +++----------- spec/hotel_spec.rb | 4 +- spec/reservation_spec.rb | 42 ++------------------- spec/spec_helper.rb | 3 +- 6 files changed, 65 insertions(+), 136 deletions(-) diff --git a/lib/book.rb b/lib/book.rb index a86fc916c..0603c9276 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -1,79 +1,48 @@ -# Nessary - do not remove -# gems -require "date" -require_relative "daterange" -require_relative "hotel" -require_relative "reservation" -# libfiles - # Optional - for developer use require "pry" require "awesome_print" module HotBook -# Book class holds all reservations and searches through them -# Reservation.new(start_date:, end_date:) #optional: room_number: :1, room_rate: 200.0 +# The Book class is responsible for: +# holding all reservations, +# searching through them, and +# making new reservations. class Book + require "date" + require_relative "daterange" + require_relative "reservation" + attr_reader :reservations, :hotel - def initialize + def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new) + @hotel = hotel @reservations = [] - @hotel = HotBook::Hotel.new #(room_count: 20, room_rate: 200.0) end - def new_reservation(daterange) #DateRange class - new_reservation = HotBook::Reservation.new(daterange) - # Reservation.new(start_date:, end_date:) #optional: room_number: :1, room_rate: 200.0 + def new_reservation(daterange) # expects that arg is a HotBook::DateRange + raise ArgumentError, "Invalid daterange (expected HotBook::DateRange, " \ + "not #{daterange.class})" unless daterange.is_a? Date + new_reservation = HotBook::Reservation.new(daterange: daterange, + room_number: default_room, + room_rate: default_rate) @reservations << new_reservation return new_reservation - # - # #suggested_room = suggest_room(daterange) - # args = { room_number: default_room_number, # refactor in Wave 2 to suggest_room(daterange) - # room_rate: find_room_by_number(default_room_number)[:room_rate] - # } - # # raise ArgumentError if room_number is not available during daterange - # args = args.merge(daterange) - end - def find_room_by_number(query) - raise ArgumentError, "Room number must be Symbol" unless query.is_a? Symbol - raise StandardError, "No room by that number" unless all_room_numbers.include?(query) - return room = all_rooms.find { |room| room[:room_number] == query} + def default_room + return hotel.rooms.first[:room_number] end - def all_rooms - return hotel.rooms + def default_rate + return hotel.rooms.first[:room_rate] end - def all_room_numbers - return all_rooms.values_at(:room_number) + def list_reservations_by_date(date) # expects that arg is a Class::Date + raise ArgumentError, "Invalid date - use Date.parse (expected Date, " \ + "not #{date.class})" unless date.is_a? Date + return reservations.select {|reservation| reservation.range.include? date} end - - def default_room_number - return :1 - end - - def list_reservations_by_date(date) # change to DateRange (start = end) - raise ArgumentError, "Invalid date (use Date.parse)" unless date.is_a? Date - return list = reservations.select {|reservation| reservation.range.include? date} - end - - # def available_rooms(daterange) - # validate(daterange) - # - # - # return list -# end - - # def suggest_room(daterange) - # validate(daterange) - # return suggestion = available_rooms(daterange).first - # end - end end - -#Should DateRange be a class of its own? (I think so!) diff --git a/lib/hotel.rb b/lib/hotel.rb index 09dd8a687..68ffaf11a 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -1,37 +1,47 @@ -# Nessary - do not remove -# gems -# libfiles - # Optional - for developer use require "pry" require "awesome_print" +# TODO: +# replace constant with CSV +# @room_numbers = load_room_numbers (in initialize) +ROOM_NUMBERS = %W(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) + module HotBook -# HOTEL CLASS KNOWS ABOUT ROOMS +# The Hotel class is responsible for knowing about rooms and all rooms + class Hotel - attr_reader :room_count, :room_rate, :rooms + attr_reader :room_rate, :room_numbers, :rooms - def initialize(room_count: 20, room_rate: 200.0) #{room_count: 20, room_rate: 200.0} - @room_count = room_count + def initialize(room_rate: 200.0, room_numbers: ROOM_NUMBERS) @room_rate = room_rate - @rooms = load_rooms # [{room_number: :1, room_rate: 200.0}] + @room_numbers = upcase_room_numbers(room_numbers) + @rooms = load_rooms # array of hashes + # a "room" Hash ==== {room_number: "String", room_rate: 200.0} + end + + def upcase_room_numbers(room_numbers) + return room_numbers.map! { |room_number| room_number.upcase } end def load_rooms new_rooms = [] - room_count.times do |i| - new_room = Hash.new - new_room[:room_number] = room_numbers[i] - new_room[:room_rate] = room_rate - new_rooms << new_room + room_numbers.each do |room_number| + a_room = Hash.new + a_room[:room_number] = room_number + a_room[:room_rate] = room_rate + new_rooms << a_room end return new_rooms end - def room_numbers - room_numbers = %i(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) - raise ArgumentError, "Wrong number of room numbers" if room_numbers.size != room_count - return room_numbers + def find_room_by_number(query) + query = query.upcase + raise ArgumentError, "Your query (room number) must be a String, not " \ + "a #{query.class}" unless query.is_a? String + raise StandardError, "No room by that number" unless + room_numbers.include?(query) # customize this error + return rooms.find { |room| room[:room_number] == query} end end diff --git a/lib/reservation.rb b/lib/reservation.rb index 1cca60584..4db1096df 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -1,35 +1,20 @@ -# Nessary - do not remove -# gems -require "date" -# libfiles - # Optional - for developer use require "pry" require "awesome_print" -# .conflict? - module HotBook -# Reservation.new(daterange:) #optional: room_number: :1, room_rate: 200.0 +# The Reservation class is responsible for calculating reservation cost class Reservation - attr_reader :daterange, :room_number, :room_rate, + require "date" - def initialize(daterange:, room_number: :1, room_rate: 200.0) - @daterange = daterange + attr_reader :range, :room_number, :room_rate + + def initialize(daterange:, room_number:, room_rate:) + @range = daterange.to_range @room_number = room_number # what about room.number @room_rate = room_rate # and room.rate - # check_errors end - # def check_errors - # raise ArgumentError, "something's missing in your args hash" if - # start_date == nil || end_date == nil - # raise ArgumentError, "couldn't parse a date to date object" unless - # start_date.class == Date || end_date.class == Date - # raise StandardError, "end date must be later than start date" unless - # duration > 0 - # end - def cost return duration * room_rate end diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index ecac4587a..cba247c8c 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -31,9 +31,9 @@ end it "room numbers are all valid key/value pairs" do - valid_room_numbers = %i(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) + valid_room_numbers = %W(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) all_rooms.each_with_index do |room, index| - expect(room[:room_number]).must_be_instance_of Symbol + expect(room[:room_number]).must_be_instance_of String expect(room[:room_number]).must_equal valid_room_numbers[index] end end diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index f994846ac..07aa2644b 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -1,48 +1,14 @@ require_relative "spec_helper" -# the Reservation class is all about dates. -# specifically, the start and end date passed in to it - describe "HotBook::Reservation class" do - let(:args) {{start_date: "18/12/1", end_date: "18/12/3"}} + let(:daterange) {HotBook::DateRange.new("apr_6", "apr_8")} + let(:args) {daterange: daterange} let(:reservation) {HotBook::Reservation.new(args)} - invalid_args = {start_date: "18/12/3", end_date: "18/12/1"} - let(:bad_reservation) {HotBook::Reservation.new(invalid_args)} - another_invalid_args = {start_date: "18/12/1", end_date: "18/12/1"} - let(:another_bad_reservation) {HotBook::Reservation.new(another_invalid_args)} - - describe "initialize arguments" do - it "will parse any date you throw at it" do - end - - it "raises StandardError for end date == or < start date" do - expect{bad_reservation}.must_raise StandardError - expect{another_bad_reservation}.must_raise StandardError - end - it "raises ArgumentError for nil or empty or non-Date class" do - tests = [] - tests << missing_args = {start_date: "", end_date: "18/12/3"} - tests << more_missing_args = {end_date: "18/12/3"} - tests << bad_args = {start_date: :rumplestiltskin, end_date: "18/12/3"} - tests << more_bad_args = {start_date: [[3]], end_date: "18/12/3"} - tests.each do |args| - expect{HotBook::Reservation.new(args)}.must_raise ArgumentError - end - end + describe "initialize method" do end - describe "duration" do - it "calculates duration correctly" do - expect(reservation.duration).must_equal 2 - end + describe "cost method" do end - end - -# @start_date -# @end_date -# defer how the reservations and rooms are linked! -# calculate duration -# standard error if date range is invalid diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 634f2c6ea..f156ad244 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,9 +11,8 @@ # Add lib files here (require_relative "../") require_relative "../lib/book.rb" +require_relative "../lib/daterange.rb" require_relative "../lib/hotel.rb" require_relative "../lib/reservation.rb" -require_relative "../lib/roomcharge.rb" -require_relative "../lib/daterange.rb" Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new From fcc08baf7930fb1a9bd796271c4a59ec6135f2d2 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Fri, 7 Sep 2018 17:51:33 -0700 Subject: [PATCH 14/55] Nvm - don't need daterange.to_range --- lib/daterange.rb | 6 +++--- spec/daterange_spec.rb | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/daterange.rb b/lib/daterange.rb index 3991ca917..1ff6ebae4 100644 --- a/lib/daterange.rb +++ b/lib/daterange.rb @@ -32,9 +32,9 @@ def is_after?(oldrange) # caution: easy to switch... end end - def to_range - return (@start_date..@end_date) - end + # def to_range + # return (@start_date..@end_date) + # end end end diff --git a/spec/daterange_spec.rb b/spec/daterange_spec.rb index d4a3caf7f..bb983605e 100644 --- a/spec/daterange_spec.rb +++ b/spec/daterange_spec.rb @@ -82,14 +82,14 @@ expect(notok3.is_after?(oldrange)).must_equal false end - describe "to_range method" do - it "will return a Range class object" do - daterange = HotBook::DateRange.new(start_date: "apr_1", - end_date: "apr_2") - range = daterange.to_range - expect(range).must_be_instance_of Range - end - end + # describe "to_range method" do + # it "will return a Range class object" do + # daterange = HotBook::DateRange.new(start_date: "apr_1", + # end_date: "apr_2") + # range = daterange.to_range + # expect(range).must_be_instance_of Range + # end + # end end end From d4da0060018431195dd94ee9a32fef064365ef7c Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Fri, 7 Sep 2018 21:07:52 -0700 Subject: [PATCH 15/55] Wave 1 Reservation class done, tests passing --- lib/reservation.rb | 30 ++++-------------------------- spec/reservation_spec.rb | 30 +++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/lib/reservation.rb b/lib/reservation.rb index 4db1096df..d10adfea8 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -5,14 +5,14 @@ module HotBook # The Reservation class is responsible for calculating reservation cost class Reservation - require "date" - - attr_reader :range, :room_number, :room_rate + require_relative "daterange" + attr_reader :daterange, :duration, :room_number, :room_rate def initialize(daterange:, room_number:, room_rate:) - @range = daterange.to_range + @daterange = daterange @room_number = room_number # what about room.number @room_rate = room_rate # and room.rate + @duration = daterange.duration end def cost @@ -22,25 +22,3 @@ def cost end end - -# TODO: -# Custom error message! -# valid as a class method? -# -# - provide :start_date and :end_date -# - later, reservation class comes up with a room number -# - ruby makes a reservation object -# - :start_date -# - :end_date -# - :room_number (default to default, define default as room 1) -# provide reservation -# - reservation returns number of nights total -# - RoomCharge class knows it's duration - 1, and multiplies nights by room cost - # - reservation shouldn't know anything about cost -# later, reservation could ask hotel for room number -# to assign to a new reservation - - -# ## WAVE2 -# - provide: start_date and :end_date -# - Reservation class can return diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index 07aa2644b..030629855 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -1,14 +1,38 @@ require_relative "spec_helper" describe "HotBook::Reservation class" do - let(:daterange) {HotBook::DateRange.new("apr_6", "apr_8")} - let(:args) {daterange: daterange} - let(:reservation) {HotBook::Reservation.new(args)} + let(:daterange) { HotBook::DateRange.new(start_date: "apr_6", + end_date: "apr_8") } + let(:reservation) { HotBook::Reservation.new(daterange: daterange, + room_number: "one", + room_rate: 1.0) } describe "initialize method" do + it "will correctly calculate duration" do + expect(reservation.duration).must_equal 2 + end + + it "will correctly accept room_number" do + expect(reservation.room_number).must_equal "one" + end + + it "will correctly accept room_rate" do + expect(reservation.room_rate).must_equal 1.0 + end + + it "will raise NoMethodError if daterange doesn't respond to duration" do + badrange = "some_string" + expect{ + HotBook::Reservation.new(daterange: badrange, + room_number: "one", + room_rate: 0.0) }.must_raise NoMethodError + end end describe "cost method" do + it "will return correct cost" do + expect(reservation.cost).must_equal 2.0 + end end end From 1a71d74c248d7a482da9e8d6e30515bab95dcb1d Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Fri, 7 Sep 2018 21:50:20 -0700 Subject: [PATCH 16/55] Updates to DateRange- specs passing, 100% cov --- lib/daterange.rb | 17 +++++++++-------- spec/daterange_spec.rb | 12 ++++++++++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/daterange.rb b/lib/daterange.rb index 1ff6ebae4..a9f0b6d74 100644 --- a/lib/daterange.rb +++ b/lib/daterange.rb @@ -14,22 +14,23 @@ class DateRange def initialize(start_date:, end_date:) @start_date = Date.parse(start_date.to_s) @end_date = Date.parse(end_date.to_s) - is_valid? + raise ArgumentError, "Invalid range #{self}: End must be > Start)" unless @end_date > @start_date end - def is_valid? - raise ArgumentError, "Invalid DateRange #{self}: End must be > Start)" unless end_date > start_date - return true - end + # def is_valid? + # if end_date > start_date + # return true + # else + # raise ArgumentError, "Invalid range #{self}: End must be > Start)" + # end + # end def duration return (end_date - start_date).to_i end def is_after?(oldrange) # caution: easy to switch... - if self.is_valid? - return oldrange.end_date <= start_date - end + return oldrange.end_date <= start_date end # def to_range diff --git a/spec/daterange_spec.rb b/spec/daterange_spec.rb index bb983605e..5821475ba 100644 --- a/spec/daterange_spec.rb +++ b/spec/daterange_spec.rb @@ -1,8 +1,8 @@ require_relative "spec_helper" describe "HotBook::DateRange class" do - let(:daterange) {HotBook::DateRange.new(start_date: "apr_1", - end_date: "apr_2")} + let(:daterange) { + HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_2") } describe "initialize method" do it "accepts Strings as args" do @@ -53,6 +53,14 @@ expect(daterange.duration).must_equal 1 end end + # + # describe "is_valid? method" do + # it "returns true for valid range" do + # expect(daterange.is_valid?).must_equal true + # end + # it "raises ArgError for invalid range" do + # end + # end describe "is_after? method" do # existing range: From 336113aa57a12dd3cd5e5199bbd1d77be716c0db Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Fri, 7 Sep 2018 22:56:00 -0700 Subject: [PATCH 17/55] Wave1 Book class complete, tests pass --- lib/book.rb | 7 +++--- spec/book_spec.rb | 58 ++++++++++++++++++++++++++++++++++++++++++++++ spec/hotel_spec.rb | 14 +++++------ 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/lib/book.rb b/lib/book.rb index 0603c9276..ed706d03b 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -9,8 +9,6 @@ module HotBook # making new reservations. class Book - require "date" - require_relative "daterange" require_relative "reservation" attr_reader :reservations, :hotel @@ -22,7 +20,8 @@ def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new) def new_reservation(daterange) # expects that arg is a HotBook::DateRange raise ArgumentError, "Invalid daterange (expected HotBook::DateRange, " \ - "not #{daterange.class})" unless daterange.is_a? Date + "not #{daterange.class})" unless + daterange.is_a? HotBook::DateRange new_reservation = HotBook::Reservation.new(daterange: daterange, room_number: default_room, room_rate: default_rate) @@ -38,7 +37,7 @@ def default_rate return hotel.rooms.first[:room_rate] end - def list_reservations_by_date(date) # expects that arg is a Class::Date + def list_by_date(date) # expects that arg is a Class::Date raise ArgumentError, "Invalid date - use Date.parse (expected Date, " \ "not #{date.class})" unless date.is_a? Date return reservations.select {|reservation| reservation.range.include? date} diff --git a/spec/book_spec.rb b/spec/book_spec.rb index 50e909cc2..4715074ca 100644 --- a/spec/book_spec.rb +++ b/spec/book_spec.rb @@ -1 +1,59 @@ require_relative "spec_helper" + +describe "Hotel::Book class" do + let(:hotel) { HotBook::Hotel.new } + let(:book) { HotBook::Book.new(hotel) } + let(:daterange) { HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_6") } + let(:new_reservation) { book.new_reservation(daterange) } + + describe "initialize method" do + it "initializes an empty array for reservations" do + expect(book.reservations).must_equal [] + end + end + + describe "new_reservation method" do + it "raises ArgError if the argument isn't a DateRange" do + badrange = "some_string" + expect{book.new_reservation(badrange)}.must_raise ArgumentError + end + + it "creates a Reservation object and stores in @reservations" do + expect(book.reservations).must_equal [] + expect(new_reservation).must_be_instance_of HotBook::Reservation + expect((book.reservations).size).must_equal 1 + 4.times do + new_reservation = book.new_reservation(daterange) + end + expect((book.reservations).size).must_equal 5 + end + end + + describe "default_room method" do + it "returns what you friggin want" do + expect(book.default_room).must_equal "1" + end + end + + describe "default_rate method" do + it "does the right thing" do + expect(book.default_rate).must_equal 200.0 + end + end + + describe "list_by_date" do + it "raises ArgError if the arg isn't a Date" do + baddate = "some_string" + expect{book.list_by_date(baddate)}.must_raise ArgumentError + end + + it "returns an array of reservations " do + new_reservation = book.new_reservation(daterange) + date = Date.parse("apr_6") + list = book.list_by_date(date) + expect(list).must_be_instance_of Array + expect(list.size).must_equal 1 + end + end + +end diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index cba247c8c..617597763 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -1,13 +1,13 @@ require_relative "spec_helper" describe "HotBook::Hotel class" do - let(:args) {{room_count: 20, room_rate: 200.0}} - let(:hotel) {HotBook::Hotel.new(args)} - let(:room_count) {hotel.room_count} - let(:room_rate) {hotel.room_rate} - let(:all_rooms) {hotel.rooms} - let(:expected_room_rate) {200.0} - let(:expected_room_count) {20} + let(:args) { {room_count: 20, room_rate: 200.0} } + let(:hotel) { HotBook::Hotel.new(args) } + let(:room_count) { hotel.room_count } + let(:room_rate) { hotel.room_rate } + let(:all_rooms) { hotel.rooms } + let(:expected_room_rate) { 200.0 } + let(:expected_room_count) { 20 } describe "initialize arguments" do it "loads room count correctly" do From c4ca4d036f2702052c3e8e5e660744b593d2b083 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Fri, 7 Sep 2018 23:21:12 -0700 Subject: [PATCH 18/55] Wave1 Hotel class done, tests pass. --- lib/daterange.rb | 6 ++-- lib/hotel.rb | 6 ++-- lib/reservation.rb | 4 +++ spec/hotel_spec.rb | 73 ++++++++++++++++++++++++---------------------- 4 files changed, 48 insertions(+), 41 deletions(-) diff --git a/lib/daterange.rb b/lib/daterange.rb index a9f0b6d74..8f59db170 100644 --- a/lib/daterange.rb +++ b/lib/daterange.rb @@ -33,9 +33,9 @@ def is_after?(oldrange) # caution: easy to switch... return oldrange.end_date <= start_date end - # def to_range - # return (@start_date..@end_date) - # end + def to_range + return (@start_date..@end_date) + end end end diff --git a/lib/hotel.rb b/lib/hotel.rb index 68ffaf11a..9a0c3e7b1 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -15,12 +15,12 @@ class Hotel def initialize(room_rate: 200.0, room_numbers: ROOM_NUMBERS) @room_rate = room_rate - @room_numbers = upcase_room_numbers(room_numbers) + @room_numbers = upcase(room_numbers) @rooms = load_rooms # array of hashes # a "room" Hash ==== {room_number: "String", room_rate: 200.0} end - def upcase_room_numbers(room_numbers) + def upcase(room_numbers) return room_numbers.map! { |room_number| room_number.upcase } end @@ -36,9 +36,9 @@ def load_rooms end def find_room_by_number(query) - query = query.upcase raise ArgumentError, "Your query (room number) must be a String, not " \ "a #{query.class}" unless query.is_a? String + query = query.upcase raise StandardError, "No room by that number" unless room_numbers.include?(query) # customize this error return rooms.find { |room| room[:room_number] == query} diff --git a/lib/reservation.rb b/lib/reservation.rb index d10adfea8..afe7d1943 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -19,6 +19,10 @@ def cost return duration * room_rate end + def range + return daterange.to_range + end + end end diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index 617597763..224f49a09 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -1,58 +1,61 @@ require_relative "spec_helper" describe "HotBook::Hotel class" do - let(:args) { {room_count: 20, room_rate: 200.0} } - let(:hotel) { HotBook::Hotel.new(args) } - let(:room_count) { hotel.room_count } + let(:hotel) { HotBook::Hotel.new } let(:room_rate) { hotel.room_rate } - let(:all_rooms) { hotel.rooms } - let(:expected_room_rate) { 200.0 } - let(:expected_room_count) { 20 } - - describe "initialize arguments" do - it "loads room count correctly" do - expect(room_count).must_be_instance_of Integer - expect(room_count).must_equal expected_room_count - end + let(:room_numbers) { hotel.room_numbers } + let(:rooms) { hotel.rooms } + let(:valid_room_numbers) { ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", + "12", "13", "14", "15", "16", "17", "18", "19", "20"] } + describe "initialize method" do it "loads room rate correctly" do expect(room_rate).must_be_instance_of Float - expect(room_rate).must_equal expected_room_rate + expect(room_rate).must_equal 200.0 end - end - describe "loading and returning list of all rooms" do - it "rooms is initialized as array of hashes with correct number of rooms" do - expect(all_rooms).must_be_instance_of Array - all_rooms.each do |room| - expect(room).must_be_instance_of Hash - end - expect(all_rooms.size).must_equal room_count + it "loads room numbers correctly" do + expect(room_numbers).must_equal valid_room_numbers end + it "upcases room numbers before storing them" do + arg = ["room thirteen"] + hotel = HotBook::Hotel.new(room_numbers: arg) + expect(hotel.room_numbers).must_equal ["ROOM THIRTEEN"] + end + end + + describe "load_rooms method" do it "room numbers are all valid key/value pairs" do - valid_room_numbers = %W(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) - all_rooms.each_with_index do |room, index| + rooms.each_with_index do |room, index| expect(room[:room_number]).must_be_instance_of String expect(room[:room_number]).must_equal valid_room_numbers[index] end end it "room rates are all valid key/value pairs" do - valid_room_rates = Array.new(size= expected_room_count, default = expected_room_rate) - all_rooms.each_with_index do |room, index| - expect(room[:room_rate]).must_be_instance_of Float - expect(room[:room_rate]).must_equal valid_room_rates[index] + rooms.each do |room| + expect(room[:room_rate]).must_equal 200.0 end end end -end + describe "find_room_by_number method" do + it "raises StandardError if room not found" do + query = "21" + expect{hotel.find_room_by_number(query)}.must_raise StandardError + end + + it "raises ArgumentError if query != String" do + query = 21 + expect{hotel.find_room_by_number(query)}.must_raise ArgumentError + end -# - hotel class can return a list of all rooms -# - loader class loads hotel class with rooms (argument is number of rooms) ##### really? -# rooms are a hash -# hash has 20 elements -# expect values at :room_number to be 1-20 -# hash keys are :room_number and :room_rate (float) -# expect :room_rate for all to be 200 + it "correctly finds the room you searched for" do + found_room = hotel.find_room_by_number("13") + expect(found_room).must_be_instance_of Hash + expect(found_room[:room_number]).must_equal "13" + end + end + +end From 163211ea5b1460283097a6942a3602cfdc9fa1b5 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sat, 8 Sep 2018 09:16:13 -0700 Subject: [PATCH 19/55] Added data/ and room_numbers.csv --- data/room_numbers.csv | 1 + 1 file changed, 1 insertion(+) create mode 100644 data/room_numbers.csv diff --git a/data/room_numbers.csv b/data/room_numbers.csv new file mode 100644 index 000000000..079740ecf --- /dev/null +++ b/data/room_numbers.csv @@ -0,0 +1 @@ +1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 From 9c00164b64f7232899beb46a04adf5fbc8e45a0a Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sat, 8 Sep 2018 10:51:08 -0700 Subject: [PATCH 20/55] Hotel now loads CSV file of room numbers, tests pass --- lib/hotel.rb | 12 +++++++----- spec/hotel_spec.rb | 9 +++++---- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/hotel.rb b/lib/hotel.rb index 9a0c3e7b1..8e5d96d0a 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -5,23 +5,25 @@ # TODO: # replace constant with CSV # @room_numbers = load_room_numbers (in initialize) -ROOM_NUMBERS = %W(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) +# ROOM_NUMBERS = %W(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) +ROOM_NUMBERS_FILENAME = "data/room_numbers.csv" module HotBook # The Hotel class is responsible for knowing about rooms and all rooms class Hotel + require "csv" attr_reader :room_rate, :room_numbers, :rooms - def initialize(room_rate: 200.0, room_numbers: ROOM_NUMBERS) + def initialize(room_rate: 200.0, room_numbers: ROOM_NUMBERS_FILENAME) @room_rate = room_rate - @room_numbers = upcase(room_numbers) + @room_numbers = load_room_numbers(room_numbers) @rooms = load_rooms # array of hashes # a "room" Hash ==== {room_number: "String", room_rate: 200.0} end - def upcase(room_numbers) - return room_numbers.map! { |room_number| room_number.upcase } + def load_room_numbers(filename) + CSV.open(filename).flat_map{ |line| line.map { |row| row.upcase }} end def load_rooms diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index 224f49a09..a0f661ba5 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -5,8 +5,9 @@ let(:room_rate) { hotel.room_rate } let(:room_numbers) { hotel.room_numbers } let(:rooms) { hotel.rooms } - let(:valid_room_numbers) { ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", - "12", "13", "14", "15", "16", "17", "18", "19", "20"] } + let(:valid_room_numbers) { ["1", "2", "3", "4", "5", "6", "7", "8", "9", + "10", "11", "12", "13", "14", "15", "16", "17", + "18", "19", "20"] } describe "initialize method" do it "loads room rate correctly" do @@ -19,8 +20,8 @@ end it "upcases room numbers before storing them" do - arg = ["room thirteen"] - hotel = HotBook::Hotel.new(room_numbers: arg) + test_room_numbers_filename = "support/test_room_numbers.csv" + hotel = HotBook::Hotel.new(room_numbers: test_room_numbers_filename) expect(hotel.room_numbers).must_equal ["ROOM THIRTEEN"] end end From 198151fc826f044be3e18389183acf70cb609810 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sat, 8 Sep 2018 11:00:19 -0700 Subject: [PATCH 21/55] Added support folder for test data files --- support/test_room_numbers.csv | 1 + 1 file changed, 1 insertion(+) create mode 100644 support/test_room_numbers.csv diff --git a/support/test_room_numbers.csv b/support/test_room_numbers.csv new file mode 100644 index 000000000..a8b97d995 --- /dev/null +++ b/support/test_room_numbers.csv @@ -0,0 +1 @@ +room thirteen From 210d9538a5b93fa7fd4fe14c6cb61145054bc56d Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sat, 8 Sep 2018 13:45:32 -0700 Subject: [PATCH 22/55] Added private save_reservation method to Res class, tests pass --- lib/reservation.rb | 20 +++++++++++++++-- spec/reservation_spec.rb | 42 ++++++++++++++++++++++++++++-------- support/test_reservation.csv | 7 ++++++ 3 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 support/test_reservation.csv diff --git a/lib/reservation.rb b/lib/reservation.rb index afe7d1943..156436751 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -5,14 +5,18 @@ module HotBook # The Reservation class is responsible for calculating reservation cost class Reservation + require "csv" require_relative "daterange" + RESERVATION_DATA_FILENAME = "data/reservation_data.csv" attr_reader :daterange, :duration, :room_number, :room_rate - def initialize(daterange:, room_number:, room_rate:) + def initialize(daterange:, room_number:, room_rate:, + filename: RESERVATION_DATA_FILENAME) @daterange = daterange @room_number = room_number # what about room.number @room_rate = room_rate # and room.rate @duration = daterange.duration + save_reservation(filename) end def cost @@ -23,6 +27,18 @@ def range return daterange.to_range end - end + private +# when you initialize a reservation, it writes information about itself into the next line in the CSV +# later, add a reservation ID + def save_reservation(filename) + headers = ["start date", "end date", "room number", "room rate"] + CSV.open(filename, "a+") { |file| + if file.first == nil + file << headers + end + file << [self.daterange.start_date, self.daterange.end_date, + self.room_number, self.room_rate] } + end + end end diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index 030629855..b8ac4b6fe 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -1,11 +1,23 @@ require_relative "spec_helper" +require "csv" +TEST_FILENAME = "support/test_reservation.csv" +# First, overwrite the test CSV +CSV.open(TEST_FILENAME, "w") #["start date", "end date", "room number", "room rate"] } +# Next, initialize a new Reservation, which stores itself in the CSV along with +# a header row +daterange = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_8") +HotBook::Reservation.new(daterange: daterange, room_number: "before tests", + room_rate: 1.0, filename: TEST_FILENAME) + +## Project Requirement: User can reserve a room for a given date range describe "HotBook::Reservation class" do let(:daterange) { HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_8") } let(:reservation) { HotBook::Reservation.new(daterange: daterange, room_number: "one", - room_rate: 1.0) } + room_rate: 1.0, + filename: TEST_FILENAME) } describe "initialize method" do it "will correctly calculate duration" do @@ -19,20 +31,32 @@ it "will correctly accept room_rate" do expect(reservation.room_rate).must_equal 1.0 end - - it "will raise NoMethodError if daterange doesn't respond to duration" do - badrange = "some_string" - expect{ - HotBook::Reservation.new(daterange: badrange, - room_number: "one", - room_rate: 0.0) }.must_raise NoMethodError - end end +### Project Requirement: Code should raise StandardError when an invalid +### DateRange is provided + +##### Project Requirement: User can get the total cost for a given reservation +#### The last day of a reservation is the checkout day, so the guest should not +#### be charged for that night describe "cost method" do it "will return correct cost" do expect(reservation.cost).must_equal 2.0 end end + describe "save_reservation method" do + it "will add itself to CSV upon initialization" do + before_data = CSV.open(TEST_FILENAME).map { |line| line } + HotBook::Reservation.new(daterange: daterange, + room_number: "thisisanewroom", + room_rate: 99.99, + filename: TEST_FILENAME) + after_data = CSV.open(TEST_FILENAME).map { |line| line } + expect(after_data.size).must_equal (before_data.size + 1) + expect(after_data.last).must_equal ["2018-04-06", "2018-04-08", + "thisisanewroom","99.99"] + end + end + end diff --git a/support/test_reservation.csv b/support/test_reservation.csv new file mode 100644 index 000000000..c0f4a9565 --- /dev/null +++ b/support/test_reservation.csv @@ -0,0 +1,7 @@ +start date,end date,room number,room rate +2018-04-06,2018-04-08,before tests,1.0 +2018-04-06,2018-04-08,one,1.0 +2018-04-06,2018-04-08,thisisanewroom,99.99 +2018-04-06,2018-04-08,one,1.0 +2018-04-06,2018-04-08,one,1.0 +2018-04-06,2018-04-08,one,1.0 From 659cc3f24c6bc2596d572917541bd76c3bc0d4c8 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sat, 8 Sep 2018 13:45:32 -0700 Subject: [PATCH 23/55] Added conflict? method in DateRange, tests pass --- lib/reservation.rb | 20 +++++++++++++++-- spec/reservation_spec.rb | 42 ++++++++++++++++++++++++++++-------- support/test_reservation.csv | 7 ++++++ 3 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 support/test_reservation.csv diff --git a/lib/reservation.rb b/lib/reservation.rb index afe7d1943..156436751 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -5,14 +5,18 @@ module HotBook # The Reservation class is responsible for calculating reservation cost class Reservation + require "csv" require_relative "daterange" + RESERVATION_DATA_FILENAME = "data/reservation_data.csv" attr_reader :daterange, :duration, :room_number, :room_rate - def initialize(daterange:, room_number:, room_rate:) + def initialize(daterange:, room_number:, room_rate:, + filename: RESERVATION_DATA_FILENAME) @daterange = daterange @room_number = room_number # what about room.number @room_rate = room_rate # and room.rate @duration = daterange.duration + save_reservation(filename) end def cost @@ -23,6 +27,18 @@ def range return daterange.to_range end - end + private +# when you initialize a reservation, it writes information about itself into the next line in the CSV +# later, add a reservation ID + def save_reservation(filename) + headers = ["start date", "end date", "room number", "room rate"] + CSV.open(filename, "a+") { |file| + if file.first == nil + file << headers + end + file << [self.daterange.start_date, self.daterange.end_date, + self.room_number, self.room_rate] } + end + end end diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index 030629855..b8ac4b6fe 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -1,11 +1,23 @@ require_relative "spec_helper" +require "csv" +TEST_FILENAME = "support/test_reservation.csv" +# First, overwrite the test CSV +CSV.open(TEST_FILENAME, "w") #["start date", "end date", "room number", "room rate"] } +# Next, initialize a new Reservation, which stores itself in the CSV along with +# a header row +daterange = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_8") +HotBook::Reservation.new(daterange: daterange, room_number: "before tests", + room_rate: 1.0, filename: TEST_FILENAME) + +## Project Requirement: User can reserve a room for a given date range describe "HotBook::Reservation class" do let(:daterange) { HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_8") } let(:reservation) { HotBook::Reservation.new(daterange: daterange, room_number: "one", - room_rate: 1.0) } + room_rate: 1.0, + filename: TEST_FILENAME) } describe "initialize method" do it "will correctly calculate duration" do @@ -19,20 +31,32 @@ it "will correctly accept room_rate" do expect(reservation.room_rate).must_equal 1.0 end - - it "will raise NoMethodError if daterange doesn't respond to duration" do - badrange = "some_string" - expect{ - HotBook::Reservation.new(daterange: badrange, - room_number: "one", - room_rate: 0.0) }.must_raise NoMethodError - end end +### Project Requirement: Code should raise StandardError when an invalid +### DateRange is provided + +##### Project Requirement: User can get the total cost for a given reservation +#### The last day of a reservation is the checkout day, so the guest should not +#### be charged for that night describe "cost method" do it "will return correct cost" do expect(reservation.cost).must_equal 2.0 end end + describe "save_reservation method" do + it "will add itself to CSV upon initialization" do + before_data = CSV.open(TEST_FILENAME).map { |line| line } + HotBook::Reservation.new(daterange: daterange, + room_number: "thisisanewroom", + room_rate: 99.99, + filename: TEST_FILENAME) + after_data = CSV.open(TEST_FILENAME).map { |line| line } + expect(after_data.size).must_equal (before_data.size + 1) + expect(after_data.last).must_equal ["2018-04-06", "2018-04-08", + "thisisanewroom","99.99"] + end + end + end diff --git a/support/test_reservation.csv b/support/test_reservation.csv new file mode 100644 index 000000000..c0f4a9565 --- /dev/null +++ b/support/test_reservation.csv @@ -0,0 +1,7 @@ +start date,end date,room number,room rate +2018-04-06,2018-04-08,before tests,1.0 +2018-04-06,2018-04-08,one,1.0 +2018-04-06,2018-04-08,thisisanewroom,99.99 +2018-04-06,2018-04-08,one,1.0 +2018-04-06,2018-04-08,one,1.0 +2018-04-06,2018-04-08,one,1.0 From 7106eff0448286b625aaece2de02e9dfb77fb215 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sat, 8 Sep 2018 19:01:49 -0700 Subject: [PATCH 24/55] Created list_avail_rooms method in Book, tests pass, support data added --- lib/book.rb | 32 ++++++++++++++++++++++-- spec/book_spec.rb | 41 +++++++++++++++++-------------- support/test_reservation_data.csv | 7 ++++++ 3 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 support/test_reservation_data.csv diff --git a/lib/book.rb b/lib/book.rb index ed706d03b..8c27cfd02 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -8,6 +8,8 @@ module HotBook # searching through them, and # making new reservations. +TEST_FILENAME = "support/test_reservation_data.csv" + class Book require_relative "reservation" @@ -15,7 +17,7 @@ class Book def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new) @hotel = hotel - @reservations = [] + @reservations = load_reservations(TEST_FILENAME) end def new_reservation(daterange) # expects that arg is a HotBook::DateRange @@ -29,6 +31,24 @@ def new_reservation(daterange) # expects that arg is a HotBook::DateRange return new_reservation end + def load_reservations(filename) + reservations = CSV.open(filename, headers: true).map { |row| + start_date = row["start date"] + end_date = row["end date"] + room_number = row["room number"] + room_rate = row["room rate"].to_f + notes = row["notes"] + HotBook::Reservation.new( + daterange: HotBook::DateRange.new(start_date: start_date, + end_date: end_date), + room_number: room_number, room_rate: room_rate, notes: notes) + } + return reservations + end + + def save_reservations(filename) + end + def default_room return hotel.rooms.first[:room_number] end @@ -37,11 +57,19 @@ def default_rate return hotel.rooms.first[:room_rate] end - def list_by_date(date) # expects that arg is a Class::Date + def list_reservations(date) # expects that arg is a Class::Date raise ArgumentError, "Invalid date - use Date.parse (expected Date, " \ "not #{date.class})" unless date.is_a? Date return reservations.select {|reservation| reservation.range.include? date} end + + def list_available_rooms(daterange) # expects that arg is a HotBook::DateRange + conflicting_reservations = reservations.select { |reservation| + reservation.daterange.conflict?(daterange) } + unavailable_room_numbers = conflicting_reservations.map { |reservation| + reservation.room_number } + return hotel.room_numbers - unavailable_room_numbers + end end end diff --git a/spec/book_spec.rb b/spec/book_spec.rb index 4715074ca..3edbf9849 100644 --- a/spec/book_spec.rb +++ b/spec/book_spec.rb @@ -1,17 +1,12 @@ require_relative "spec_helper" -describe "Hotel::Book class" do +describe "HotBook::Book class" do let(:hotel) { HotBook::Hotel.new } let(:book) { HotBook::Book.new(hotel) } - let(:daterange) { HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_6") } + let(:daterange) { HotBook::DateRange.new( + start_date: "apr_1", end_date: "apr_2") } let(:new_reservation) { book.new_reservation(daterange) } - describe "initialize method" do - it "initializes an empty array for reservations" do - expect(book.reservations).must_equal [] - end - end - describe "new_reservation method" do it "raises ArgError if the argument isn't a DateRange" do badrange = "some_string" @@ -19,13 +14,10 @@ end it "creates a Reservation object and stores in @reservations" do - expect(book.reservations).must_equal [] expect(new_reservation).must_be_instance_of HotBook::Reservation - expect((book.reservations).size).must_equal 1 - 4.times do - new_reservation = book.new_reservation(daterange) - end - expect((book.reservations).size).must_equal 5 + previous_size = book.reservations.size + 3.times { book.new_reservation(daterange) } + expect((book.reservations).size).must_equal previous_size + 3 end end @@ -41,19 +33,30 @@ end end - describe "list_by_date" do +### PROJECT REQ: User can access the list of reservations for a specific date + describe "list_reservations" do it "raises ArgError if the arg isn't a Date" do baddate = "some_string" - expect{book.list_by_date(baddate)}.must_raise ArgumentError + expect{book.list_reservations(baddate)}.must_raise ArgumentError end it "returns an array of reservations " do - new_reservation = book.new_reservation(daterange) + note = "This is the first reservation" date = Date.parse("apr_6") - list = book.list_by_date(date) + list = book.list_reservations(date) expect(list).must_be_instance_of Array - expect(list.size).must_equal 1 + expect(list.first.notes).must_equal note end end + describe "list_available_rooms method" do + it "returns an array that's a subset (or equal to) all room numbers" do + all = hotel.room_numbers + list = book.list_available_rooms(daterange) + expect(book.reservations.size).must_equal 6 + expect((all - list).size).must_equal (all.size - list.size) + expect(list.first).must_equal "4" + expect(list.last).must_equal "20" + end + end end diff --git a/support/test_reservation_data.csv b/support/test_reservation_data.csv new file mode 100644 index 000000000..5089d76cb --- /dev/null +++ b/support/test_reservation_data.csv @@ -0,0 +1,7 @@ +start date,end date,room number,room rate,notes +2018-04-01,2018-04-06,1,200.0,"This is the first reservation" +2018-04-01,2018-04-06,2,200.0, +2018-04-01,2018-04-06,2,200.0, +2018-04-01,2018-04-06,3,200.0, +2018-04-01,2018-04-06,3,200.0, +2018-04-01,2018-04-06,3,200.0, From 4790fbe3ec812a7ffdb4ed92f979395b987425ea Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sat, 8 Sep 2018 20:39:01 -0700 Subject: [PATCH 25/55] Added suggested room and error checking functionality to new reservation method, tests pass --- lib/book.rb | 60 +++++++++++++++++++++++++++++++++++------------ spec/book_spec.rb | 31 +++++++++++++++++++----- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/lib/book.rb b/lib/book.rb index 8c27cfd02..cffad2b83 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -12,7 +12,7 @@ module HotBook class Book require_relative "reservation" - + require "date" attr_reader :reservations, :hotel def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new) @@ -20,12 +20,12 @@ def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new) @reservations = load_reservations(TEST_FILENAME) end - def new_reservation(daterange) # expects that arg is a HotBook::DateRange - raise ArgumentError, "Invalid daterange (expected HotBook::DateRange, " \ - "not #{daterange.class})" unless - daterange.is_a? HotBook::DateRange + def new_reservation(daterange, room_number: suggested_room(daterange)) # expects that arg is a HotBook::DateRange + raise_data_type_error(:daterange, daterange) + raise_data_type_error(:room_number, room_number) + room_is_bookable?(room_number, daterange) # raises error if not new_reservation = HotBook::Reservation.new(daterange: daterange, - room_number: default_room, + room_number: room_number, room_rate: default_rate) @reservations << new_reservation return new_reservation @@ -38,6 +38,7 @@ def load_reservations(filename) room_number = row["room number"] room_rate = row["room rate"].to_f notes = row["notes"] + HotBook::Reservation.new( daterange: HotBook::DateRange.new(start_date: start_date, end_date: end_date), @@ -49,8 +50,9 @@ def load_reservations(filename) def save_reservations(filename) end - def default_room - return hotel.rooms.first[:room_number] + def suggested_room(daterange) + available_room_numbers = list_available_room_numbers(daterange) + return available_room_numbers.first end def default_rate @@ -58,18 +60,46 @@ def default_rate end def list_reservations(date) # expects that arg is a Class::Date - raise ArgumentError, "Invalid date - use Date.parse (expected Date, " \ - "not #{date.class})" unless date.is_a? Date + raise_data_type_error(:date, date) return reservations.select {|reservation| reservation.range.include? date} end - def list_available_rooms(daterange) # expects that arg is a HotBook::DateRange + def room_is_bookable?(room_number, daterange) + raise_data_type_error(:room_number, room_number) + raise_data_type_error(:daterange, daterange) + query = room_number.upcase + available_room_numbers = list_available_room_numbers(daterange) + raise StandardError, "No room by that number" unless + available_room_numbers.include?(query) + return true + end + + def list_available_room_numbers(daterange) # expects that arg is a HotBook::DateRange + raise_data_type_error(:daterange, daterange) conflicting_reservations = reservations.select { |reservation| - reservation.daterange.conflict?(daterange) } + reservation.daterange.conflict?(daterange) } unavailable_room_numbers = conflicting_reservations.map { |reservation| - reservation.room_number } - return hotel.room_numbers - unavailable_room_numbers + reservation.room_number } + available_room_numbers = hotel.room_numbers - unavailable_room_numbers + return available_room_numbers end - end + private + + def raise_data_type_error(type, var) + case type + when :date + raise ArgumentError, "Invalid date - use Date.parse (expected Date, " \ + "not #{var.class})" unless var.is_a? Date + when :room_number + raise ArgumentError, "Invalid room number (expected String, " \ + "not #{var.class})" unless var.is_a? String + when :daterange + raise ArgumentError, "Invalid daterange (expected HotBook::DateRange, " \ + "not #{var.class})" unless + var.is_a? HotBook::DateRange + end + end + + end end diff --git a/spec/book_spec.rb b/spec/book_spec.rb index 3edbf9849..d72630589 100644 --- a/spec/book_spec.rb +++ b/spec/book_spec.rb @@ -8,11 +8,21 @@ let(:new_reservation) { book.new_reservation(daterange) } describe "new_reservation method" do - it "raises ArgError if the argument isn't a DateRange" do + it "raises ArgError if the daterange isn't a DateRange" do badrange = "some_string" expect{book.new_reservation(badrange)}.must_raise ArgumentError end + it "raises ArgError if the room number isn't a String" do + bad_room_number = 99 + expect{book.new_reservation(daterange, room_number: bad_room_number)}.must_raise ArgumentError + end + + it "raises StandardError if there's no room by that number" do + bad_room_number = "not a room number" + expect{book.new_reservation(daterange, room_number: bad_room_number)}.must_raise StandardError + end + it "creates a Reservation object and stores in @reservations" do expect(new_reservation).must_be_instance_of HotBook::Reservation previous_size = book.reservations.size @@ -21,9 +31,9 @@ end end - describe "default_room method" do - it "returns what you friggin want" do - expect(book.default_room).must_equal "1" + describe "suggested_room method" do + it "returns the correct room number" do + expect(book.suggested_room(daterange)).must_equal "4" end end @@ -49,10 +59,19 @@ end end - describe "list_available_rooms method" do + describe "room_is_bookable? method" do + it "correctly checks if a room has a reservation on that date" do + room_number = "10" + expect(book.room_is_bookable?(room_number, daterange)).must_equal true + room_number = "2" + expect{book.room_is_bookable?(room_number, daterange)}.must_raise StandardError + end + end + + describe "list_available_room_numbers method" do it "returns an array that's a subset (or equal to) all room numbers" do all = hotel.room_numbers - list = book.list_available_rooms(daterange) + list = book.list_available_room_numbers(daterange) expect(book.reservations.size).must_equal 6 expect((all - list).size).must_equal (all.size - list.size) expect(list.first).must_equal "4" From a2b717b067855fe0fcfbb711c389742d51845c6e Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sat, 8 Sep 2018 20:45:03 -0700 Subject: [PATCH 26/55] Tests passing for Wave 2 requirements --- README.md | 1 - lib/daterange.rb | 23 +++++------ lib/hotel.rb | 28 ++++++------- lib/reservation.rb | 63 ++++++++++++++++++---------- pseudocode.md | 53 ------------------------ refactors.txt | 5 ++- spec/daterange_spec.rb | 79 +++++++++++++++++++++--------------- spec/hotel_spec.rb | 24 ++++------- spec/reservation_spec.rb | 52 +++++++++++++----------- spec/spec_helper.rb | 1 + support/test_reservation.csv | 7 ---- 11 files changed, 153 insertions(+), 183 deletions(-) delete mode 100644 support/test_reservation.csv diff --git a/README.md b/README.md index e8cc263ed..735f9ab65 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,6 @@ Remember that your job is only to build the classes that store information and h ### User Stories - As an administrator, I can view a list of rooms that are not reserved for a given date range - - reservation class returns reservations for that range - book class looks at reservations - As an administrator, I can reserve an available room for a given date range diff --git a/lib/daterange.rb b/lib/daterange.rb index 8f59db170..17e5c762b 100644 --- a/lib/daterange.rb +++ b/lib/daterange.rb @@ -6,7 +6,7 @@ module HotBook # The DateRange class compares and does calculations on other DateRanges -# Refer to Date gem documentation to understand what date format to use (i.e. y-m-d) +# Refer to Date gem docu to understand what date format to use (i.e. y-m-d) class DateRange require "date" attr_reader :start_date, :end_date @@ -14,28 +14,25 @@ class DateRange def initialize(start_date:, end_date:) @start_date = Date.parse(start_date.to_s) @end_date = Date.parse(end_date.to_s) - raise ArgumentError, "Invalid range #{self}: End must be > Start)" unless @end_date > @start_date + raise ArgumentError, "Invalid range #{self}: End must be > Start)" unless + @end_date > @start_date end - # def is_valid? - # if end_date > start_date - # return true - # else - # raise ArgumentError, "Invalid range #{self}: End must be > Start)" - # end - # end - def duration return (end_date - start_date).to_i end - def is_after?(oldrange) # caution: easy to switch... - return oldrange.end_date <= start_date + def conflict?(other) + if self.start_date >= other.end_date || self.end_date <= other.start_date + return false + else + return true + end end def to_range return (@start_date..@end_date) end - end + end diff --git a/lib/hotel.rb b/lib/hotel.rb index 8e5d96d0a..04a07cf21 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -2,22 +2,18 @@ require "pry" require "awesome_print" -# TODO: -# replace constant with CSV -# @room_numbers = load_room_numbers (in initialize) -# ROOM_NUMBERS = %W(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) -ROOM_NUMBERS_FILENAME = "data/room_numbers.csv" - module HotBook # The Hotel class is responsible for knowing about rooms and all rooms class Hotel require "csv" + ROOM_NUMBERS_FILENAME = "data/room_numbers.csv" attr_reader :room_rate, :room_numbers, :rooms def initialize(room_rate: 200.0, room_numbers: ROOM_NUMBERS_FILENAME) @room_rate = room_rate - @room_numbers = load_room_numbers(room_numbers) + @room_numbers = load_room_numbers(room_numbers) # Array of Strings + # do i even need room hash? or just room numbers? @rooms = load_rooms # array of hashes # a "room" Hash ==== {room_number: "String", room_rate: 200.0} end @@ -37,14 +33,16 @@ def load_rooms return new_rooms end - def find_room_by_number(query) - raise ArgumentError, "Your query (room number) must be a String, not " \ - "a #{query.class}" unless query.is_a? String - query = query.upcase - raise StandardError, "No room by that number" unless - room_numbers.include?(query) # customize this error - return rooms.find { |room| room[:room_number] == query} - end + # def has_this_room?(query) + # raise ArgumentError, "Your query (room number) must be a String, not " \ + # "a #{query.class}" unless query.is_a? String + # query = query.upcase + # if room_numbers.include?(query) + # return true + # else + # raise StandardError, "No room by that number" + # end + # end end end diff --git a/lib/reservation.rb b/lib/reservation.rb index 156436751..ffb1a35f5 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -7,16 +7,20 @@ module HotBook class Reservation require "csv" require_relative "daterange" - RESERVATION_DATA_FILENAME = "data/reservation_data.csv" - attr_reader :daterange, :duration, :room_number, :room_rate + attr_reader :daterange, :room_number, :room_rate, :notes - def initialize(daterange:, room_number:, room_rate:, - filename: RESERVATION_DATA_FILENAME) + # RESERVATION_DATA_FILENAME = "data/reservation_data.csv" + + def initialize(daterange:, room_number:, room_rate:, notes: nil) @daterange = daterange - @room_number = room_number # what about room.number - @room_rate = room_rate # and room.rate - @duration = daterange.duration - save_reservation(filename) + @room_number = room_number + @room_rate = room_rate + @notes = notes + # @csv_headers = ["start date", "end date", "room number", "room rate", "notes"] + end + + def duration + return daterange.duration end def cost @@ -27,18 +31,35 @@ def range return daterange.to_range end - private - -# when you initialize a reservation, it writes information about itself into the next line in the CSV -# later, add a reservation ID - def save_reservation(filename) - headers = ["start date", "end date", "room number", "room rate"] - CSV.open(filename, "a+") { |file| - if file.first == nil - file << headers - end - file << [self.daterange.start_date, self.daterange.end_date, - self.room_number, self.room_rate] } - end +# Returns an array of hashes of all reservations + # def self.all(filename) + # return data = CSV.open(filename, headers:true).map { |reservation| + # p reservation + # self.new(daterange: HotBook::DateRange.new(start_date: reservation["start date"], + # end_date: reservation["end date"]), + # room_number: reservation["room number"], + # room_rate: reservation["room rate"], + # notes: reservation["notes"] ) + # } + # end + + # def self.list_by_date(date) + # raise ArgumentError, "Invalid date - use Date.parse (expected Date, " \ + # "not #{date.class})" unless date.is_a? Date + # return self.all.select {|reservation| reservation.range.include? date} + # end + + # def append(filename) + # CSV.open(filename, "a+") { |file| + # if file.first == nil + # file << @csv_headers + # end + # file << [self.daterange.start_date, self.daterange.end_date, + # self.room_number, self.room_rate, self.notes] } + # end end end +# +# daterange = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_8") +# HotBook::Reservation.new(daterange: daterange, room_number: "one", +# room_rate: 1.0, filename: RESERVATION_DATA_FILENAME) diff --git a/pseudocode.md b/pseudocode.md index bdd3b208c..e69de29bb 100644 --- a/pseudocode.md +++ b/pseudocode.md @@ -1,53 +0,0 @@ -# User Stories - -## Requirements - -- As an administrator, I can access the list of all of the rooms in the hotel -- As an administrator, I can reserve a room for a given date range -- As an administrator, I can access the list of reservations for a specific date -- As an administrator, I can get the total cost for a given reservation -- Your code should raise an error ([StandardError](https://ruby-doc.org/core-2.5.0/StandardError.html)) when an invalid date range is provided - -- The hotel has 20 rooms, and they are numbered 1 through 20 -- Every room is identical, and a room always costs $200/night -- The last day of a reservation is the checkout day, so the guest should not be charged for that night -- For this wave, any room can be reserved at any time, and you don't need to check whether reservations conflict with each other (this will come in wave 2!) - -## Design - - - -- provide :start_date and :end_date - - later, hotel class comes up with a room number - - ruby makes a reservation object - - :start_date - - :end_date - - :room_number (default to default, define default as room 1) -- provide :date - - book class contains reservations [] - - (argument is number of rooms) ##### really? - - - book class searches reservations by date - - book class returns array of reservations for that date -- provide reservation - - ruby returns number of nights to charge - - reservation class calculates duration - - WHICH CLASS KNOWS TO CHARGE DURATION-1 NIGHTS? cost class? ###### - - cost class multiplies nights by room cost - - reservation shouldn't know anything about cost -- cost class - - ruby returns float of total cost -- provide date range - - reservation class determines if it's valid (start before end) or not - - if invalid, ruby raises StandardError - - - -## Thoughts - -can I do this without a room class? what would a room class do? diff --git a/refactors.txt b/refactors.txt index fdd6c2720..98451d453 100644 --- a/refactors.txt +++ b/refactors.txt @@ -1,2 +1,5 @@ # hotel/refactors.txt -Todo: +Notes: + +does hotel really need to have room numbers and rates? +yes, because reservation doesn't need to know that, really diff --git a/spec/daterange_spec.rb b/spec/daterange_spec.rb index 5821475ba..01c98dd3b 100644 --- a/spec/daterange_spec.rb +++ b/spec/daterange_spec.rb @@ -62,42 +62,57 @@ # end # end - describe "is_after? method" do - # existing range: - oldrange = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_10") - # check these new ranges against existing range: - ok1 = HotBook::DateRange.new(start_date: "apr_29", end_date: "apr_30") - ok2 = HotBook::DateRange.new(start_date: "apr_10", end_date: "apr_30") - notok1= HotBook::DateRange.new(start_date: "apr_9", end_date: "apr_30") - notok2 = HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_6") - notok3 = HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_5") + describe "conflict? method" do + range1 = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_10") + # check these new ranges against range1: + noconflict1 = HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_6") + noconflict2 = HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_3") + noconflict3 = HotBook::DateRange.new(start_date: "apr_10", end_date: "apr_30") + noconflict4 = HotBook::DateRange.new(start_date: "apr_11", end_date: "apr_30") + conflict1= HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_7") + conflict2 = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_7") + conflict3= HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_10") + conflict4 = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_12") + conflict5 = HotBook::DateRange.new(start_date: "apr_7", end_date: "apr_10") + conflict6 = HotBook::DateRange.new(start_date: "apr_7", end_date: "apr_30") - it "will return true if ranges don't conflict" do - expect(ok1.is_after?(oldrange)).must_equal true - end - - it "allows a new range (reservation) to start on the same day" \ - "that another one ends" do - expect(ok2.is_after?(oldrange)).must_equal true - end + it "will return false if ranges don't conflict" do + expect(range1.conflict?(noconflict1)).must_equal false + expect(range1.conflict?(noconflict2)).must_equal false + expect(range1.conflict?(noconflict3)).must_equal false + expect(range1.conflict?(noconflict4)).must_equal false + end - it "will return false if ranges do conflict" do - expect(notok1.is_after?(oldrange)).must_equal false - end + it "will return true if ranges do conflict" do + expect(range1.conflict?(conflict1)).must_equal true + expect(range1.conflict?(conflict2)).must_equal true + expect(range1.conflict?(conflict3)).must_equal true + expect(range1.conflict?(conflict4)).must_equal true + expect(range1.conflict?(conflict5)).must_equal true + expect(range1.conflict?(conflict6)).must_equal true + end - it "will return false if new range is before oldrange" do - expect(notok2.is_after?(oldrange)).must_equal false - expect(notok3.is_after?(oldrange)).must_equal false - end + it "will work in reverse" do + expect(noconflict1.conflict?(range1)).must_equal false + expect(noconflict2.conflict?(range1)).must_equal false + expect(noconflict3.conflict?(range1)).must_equal false + expect(noconflict4.conflict?(range1)).must_equal false + expect(conflict1.conflict?(range1)).must_equal true + expect(conflict2.conflict?(range1)).must_equal true + expect(conflict3.conflict?(range1)).must_equal true + expect(conflict4.conflict?(range1)).must_equal true + expect(conflict5.conflict?(range1)).must_equal true + expect(conflict6.conflict?(range1)).must_equal true + end + end - # describe "to_range method" do - # it "will return a Range class object" do - # daterange = HotBook::DateRange.new(start_date: "apr_1", - # end_date: "apr_2") - # range = daterange.to_range - # expect(range).must_be_instance_of Range - # end - # end + describe "to_range method" do + it "will return a Range class object" do + daterange = HotBook::DateRange.new(start_date: "apr_1", + end_date: "apr_2") + range = daterange.to_range + expect(range).must_be_instance_of Range + end end end diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index a0f661ba5..8eb60b420 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -26,36 +26,26 @@ end end +## Project Requirement: User can view a list of rooms describe "load_rooms method" do - it "room numbers are all valid key/value pairs" do + it "room numbers are all valid-type key/value pairs" do rooms.each_with_index do |room, index| expect(room[:room_number]).must_be_instance_of String expect(room[:room_number]).must_equal valid_room_numbers[index] end end - it "room rates are all valid key/value pairs" do + it "room rates are all valid-type key/value pairs" do rooms.each do |room| expect(room[:room_rate]).must_equal 200.0 end end end - describe "find_room_by_number method" do - it "raises StandardError if room not found" do - query = "21" - expect{hotel.find_room_by_number(query)}.must_raise StandardError - end - - it "raises ArgumentError if query != String" do - query = 21 - expect{hotel.find_room_by_number(query)}.must_raise ArgumentError - end - - it "correctly finds the room you searched for" do - found_room = hotel.find_room_by_number("13") - expect(found_room).must_be_instance_of Hash - expect(found_room[:room_number]).must_equal "13" + describe "project requirement: user can view a list of rooms" do + # This requires that test(s) MUST pass for "load rooms method" + it "can return a list of rooms" do + expect(hotel.rooms).must_be_instance_of Array end end diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index b8ac4b6fe..fb7b66b60 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -1,14 +1,15 @@ require_relative "spec_helper" require "csv" -TEST_FILENAME = "support/test_reservation.csv" +# TEST_FILENAME = "support/test_reservation.csv" -# First, overwrite the test CSV -CSV.open(TEST_FILENAME, "w") #["start date", "end date", "room number", "room rate"] } -# Next, initialize a new Reservation, which stores itself in the CSV along with -# a header row -daterange = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_8") -HotBook::Reservation.new(daterange: daterange, room_number: "before tests", - room_rate: 1.0, filename: TEST_FILENAME) +# # First, overwrite the test CSV +# CSV.open(TEST_FILENAME, "w") #["start date", "end date", "room number", "room rate"] } +# # Next, initialize a new Reservation, which stores itself in the CSV along with +# # a header row +# daterange = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_8") +# note_string = "This Reservation is instantiated above the spec tests" +# HotBook::Reservation.new(daterange: daterange, room_number: "one", +# room_rate: 1.0, notes: note_string) ## Project Requirement: User can reserve a room for a given date range describe "HotBook::Reservation class" do @@ -16,8 +17,7 @@ end_date: "apr_8") } let(:reservation) { HotBook::Reservation.new(daterange: daterange, room_number: "one", - room_rate: 1.0, - filename: TEST_FILENAME) } + room_rate: 1.0) } describe "initialize method" do it "will correctly calculate duration" do @@ -45,18 +45,24 @@ end end - describe "save_reservation method" do - it "will add itself to CSV upon initialization" do - before_data = CSV.open(TEST_FILENAME).map { |line| line } - HotBook::Reservation.new(daterange: daterange, - room_number: "thisisanewroom", - room_rate: 99.99, - filename: TEST_FILENAME) - after_data = CSV.open(TEST_FILENAME).map { |line| line } - expect(after_data.size).must_equal (before_data.size + 1) - expect(after_data.last).must_equal ["2018-04-06", "2018-04-08", - "thisisanewroom","99.99"] - end - end + # describe "save_reservation method" do + # it "will add itself to CSV upon initialization" do + # before_data = CSV.open(TEST_FILENAME).map { |line| line } + # note_string = "This is the test Reservation" + # HotBook::Reservation.new(daterange: daterange, + # room_number: "one", + # room_rate: 1.0, + # notes: note_string) + # after_data = CSV.open(TEST_FILENAME).map { |line| line } + # expect(after_data.size).must_equal (before_data.size + 1) + # expect(after_data.last[4]).must_equal note_string + # end + # end + + # describe "self.all" do + # it "will return an array of hashes" do + # p HotBook::Reservation.all(TEST_FILENAME) + # end + # end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f156ad244..afaa1d6e4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,6 +8,7 @@ require "pry" require "awesome_print" require "date" +require "csv" # Add lib files here (require_relative "../") require_relative "../lib/book.rb" diff --git a/support/test_reservation.csv b/support/test_reservation.csv deleted file mode 100644 index c0f4a9565..000000000 --- a/support/test_reservation.csv +++ /dev/null @@ -1,7 +0,0 @@ -start date,end date,room number,room rate -2018-04-06,2018-04-08,before tests,1.0 -2018-04-06,2018-04-08,one,1.0 -2018-04-06,2018-04-08,thisisanewroom,99.99 -2018-04-06,2018-04-08,one,1.0 -2018-04-06,2018-04-08,one,1.0 -2018-04-06,2018-04-08,one,1.0 From 007fc019e17ff8a2b92ee670b15bae1634b60b71 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sun, 9 Sep 2018 11:26:19 -0700 Subject: [PATCH 27/55] Added empty files for new Block class --- lib/block.rb | 10 ++++++++++ spec/block_spec.rb | 5 +++++ 2 files changed, 15 insertions(+) create mode 100644 lib/block.rb create mode 100644 spec/block_spec.rb diff --git a/lib/block.rb b/lib/block.rb new file mode 100644 index 000000000..e335f4c93 --- /dev/null +++ b/lib/block.rb @@ -0,0 +1,10 @@ +# Optional - for developer use +require "pry" +require "awesome_print" + +module HotBook +# The Reservation class is responsible for calculating reservation cost + class Block + require_relative "daterange" + end +end diff --git a/spec/block_spec.rb b/spec/block_spec.rb new file mode 100644 index 000000000..5955e82ee --- /dev/null +++ b/spec/block_spec.rb @@ -0,0 +1,5 @@ +require_relative "spec_helper" + +describe "HotBook::Block class" do + +end From f64f67a87b7b452744c01fd4a978f9055149e470 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sun, 9 Sep 2018 17:45:42 -0700 Subject: [PATCH 28/55] Finished basic methods and initialize for Block, tests pass --- lib/block.rb | 38 +++++++++++++++++++++++++++++++++++++- spec/block_spec.rb | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/lib/block.rb b/lib/block.rb index e335f4c93..7b515190b 100644 --- a/lib/block.rb +++ b/lib/block.rb @@ -3,8 +3,44 @@ require "awesome_print" module HotBook -# The Reservation class is responsible for calculating reservation cost + +# a block holds: + # a list of blocked room numbers (need to be able to add a room to a block later!) + # available room numbers array (is updated by book.rb code) + # daterange + # room rate + class Block require_relative "daterange" + attr_reader :available, :rooms, :daterange, :room_rate + + def initialize(daterange:, rooms:, room_rate: 185.0) + @daterange = daterange + @rooms = rooms.map! {|room| room.upcase} + @available = rooms.clone + @room_rate = room_rate + end + +# remove a room from @available once it's reserved + def disable(query) + room = available.find { |room| room == query } # guaranteed to return 1 + return available.delete(room) # returns value of what it deleted + end + + def conflict?(other) + return daterange.conflict?(other) + end + end end + +# def self.from_csv(filename) +# # a block is a group of 1-5 reservations with the same daterange and unique +# # room numbers. (eventually, this could scale to >5 unique room numbers.) +# # parse the list into blocks +# blocked_reservations = Reservation.from_csv(filename) # returns array of Reservations +# # parse the +# # create new Block from groups of reservations +# # return an array of Blocks +# Block.new() +# end diff --git a/spec/block_spec.rb b/spec/block_spec.rb index 5955e82ee..4cd725a7f 100644 --- a/spec/block_spec.rb +++ b/spec/block_spec.rb @@ -1,5 +1,44 @@ require_relative "spec_helper" describe "HotBook::Block class" do + let(:rooms) { ["1","2","3"] } + let(:daterange) { + HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_7") } + let(:block) { + HotBook::Block.new(daterange: daterange, + rooms: rooms) } + + describe "initialize method" do + it "will store an array of rooms" do + expect(block.rooms).must_equal rooms + expect(block.available).must_equal rooms + end + it "will store upcase room numbers" do + rooms = ["one", "two"] + block = HotBook::Block.new(daterange: daterange, + rooms: rooms) + expect(block.available).must_equal ["ONE", "TWO"] + end + end + + describe "disable method" do + it "won't alter the rooms array when it executes" do + block.disable("1") + expect(block.available).must_equal ["2", "3"] + expect(block.rooms).must_equal ["1", "2", "3"] + end + end + + describe "conflict? method" do + it "will correctly return a daterange conflict" do + conflict = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_7") + anotherconflict = HotBook::DateRange.new(start_date: "apr_5", end_date: "apr_8") + noconflict = HotBook::DateRange.new(start_date: "apr_1", end_date: "apr_6") + + expect(block.conflict?(conflict)).must_equal true + expect(block.conflict?(anotherconflict)).must_equal true + expect(block.conflict?(noconflict)).must_equal false + end + end end From 1063289200e8a41257b2fa0245726d187f40b68c Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sun, 9 Sep 2018 17:51:55 -0700 Subject: [PATCH 29/55] Fixed mistake in to_range method--should be exclusive --- lib/daterange.rb | 15 +++++++++++---- spec/daterange_spec.rb | 8 ++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/daterange.rb b/lib/daterange.rb index 17e5c762b..a5d4f33fb 100644 --- a/lib/daterange.rb +++ b/lib/daterange.rb @@ -2,8 +2,6 @@ require "pry" require "awesome_print" -#TODO: .parse method! - module HotBook # The DateRange class compares and does calculations on other DateRanges # Refer to Date gem docu to understand what date format to use (i.e. y-m-d) @@ -23,7 +21,7 @@ def duration end def conflict?(other) - if self.start_date >= other.end_date || self.end_date <= other.start_date + if start_date >= other.end_date || end_date <= other.start_date return false else return true @@ -31,8 +29,17 @@ def conflict?(other) end def to_range - return (@start_date..@end_date) + return (@start_date...@end_date) end + + # def match?(other) + # if start_date == other.start_date && end_date == other.end_date + # return true + # else + # return false + # end + # end + end end diff --git a/spec/daterange_spec.rb b/spec/daterange_spec.rb index 01c98dd3b..61adce117 100644 --- a/spec/daterange_spec.rb +++ b/spec/daterange_spec.rb @@ -108,11 +108,15 @@ describe "to_range method" do it "will return a Range class object" do - daterange = HotBook::DateRange.new(start_date: "apr_1", - end_date: "apr_2") range = daterange.to_range expect(range).must_be_instance_of Range end + + it "correctly allows a res end and another res start on the same day" do + date = Date.parse("apr_2") + range = daterange.to_range + expect(range.include? date).must_equal false + end end end From a8e62c0e590668f0fd4356367da0ecc94d5fa92b Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sun, 9 Sep 2018 17:59:27 -0700 Subject: [PATCH 30/55] Hotel.find_rate class method still needs to be tested after CSV load method is written --- lib/hotel.rb | 18 +++++++----------- spec/hotel_spec.rb | 6 ++++++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/hotel.rb b/lib/hotel.rb index 04a07cf21..4a8b6ca0b 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -22,6 +22,13 @@ def load_room_numbers(filename) CSV.open(filename).flat_map{ |line| line.map { |row| row.upcase }} end +# refactor with load CSV of rooms in order to test + def self.find_rate(room_number) + room_number = room_number.upcase + room = rooms.select{|room| room[:room_number] == room_number} + return room[:room_rate] + end + def load_rooms new_rooms = [] room_numbers.each do |room_number| @@ -32,17 +39,6 @@ def load_rooms end return new_rooms end - - # def has_this_room?(query) - # raise ArgumentError, "Your query (room number) must be a String, not " \ - # "a #{query.class}" unless query.is_a? String - # query = query.upcase - # if room_numbers.include?(query) - # return true - # else - # raise StandardError, "No room by that number" - # end - # end end end diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index 8eb60b420..7d3e6cf1f 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -49,4 +49,10 @@ end end +# TODO: AFter the CSV load function is added + describe "self.find_rate method" do + it "Hotel class method can look up the correct room" do + end + end + end From c74aac3d3bea1a569c48eac4a4ad1f3a239c4c31 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sun, 9 Sep 2018 18:04:53 -0700 Subject: [PATCH 31/55] Ready to work on and test block methods in book.rb --- lib/block.rb | 7 -- lib/book.rb | 189 +++++++++++++++++++++--------- lib/reservation.rb | 20 ++++ pseudocode.md | 32 +++++ refactors.txt | 25 +++- spec/spec_helper.rb | 1 + support/test_reservation_data.csv | 1 - 7 files changed, 208 insertions(+), 67 deletions(-) diff --git a/lib/block.rb b/lib/block.rb index 7b515190b..2d6c26445 100644 --- a/lib/block.rb +++ b/lib/block.rb @@ -3,13 +3,6 @@ require "awesome_print" module HotBook - -# a block holds: - # a list of blocked room numbers (need to be able to add a room to a block later!) - # available room numbers array (is updated by book.rb code) - # daterange - # room rate - class Block require_relative "daterange" attr_reader :available, :rooms, :daterange, :room_rate diff --git a/lib/book.rb b/lib/book.rb index cffad2b83..d8f1273e6 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -4,102 +4,177 @@ module HotBook # The Book class is responsible for: -# holding all reservations, +# holding all reservations and blocks, # searching through them, and -# making new reservations. +# making new reservations and blocks. +# It includes support methods for determining availability. -TEST_FILENAME = "support/test_reservation_data.csv" +TEST_RESERVATION_FILENAME = "support/test_reservation_data.csv" +# TEST_BLOCK_FILENAME = "support/test_block_data.csv" class Book - require_relative "reservation" + require_relative "hotel.rb" + require_relative "reservation.rb" + require_relative "block.rb" require "date" - attr_reader :reservations, :hotel + attr_reader :reservations, :hotel, :blocks def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new) + # but what if hotel changes? it only updates @ initialize time... + # then it needs to change to be treated like Reservation class + # luckily, it's already in a class of its own, so it's ready + # to do that (rather than hotel having methods for loading etc.) @hotel = hotel - @reservations = load_reservations(TEST_FILENAME) + @reservations = Reservation.from_csv(TEST_RESERVATION_FILENAME) + @blocks = [] # a list of Blocks end - def new_reservation(daterange, room_number: suggested_room(daterange)) # expects that arg is a HotBook::DateRange - raise_data_type_error(:daterange, daterange) - raise_data_type_error(:room_number, room_number) - room_is_bookable?(room_number, daterange) # raises error if not + def new_reservation(daterange, room_number: suggest_room(daterange)) + room_number = room_number.upcase + validate(:daterange, daterange) + validate(:room_number, room_number) + if room_taken?(daterange, room_number) + raise StandardError, "Room already has reservation on this daterange" + elsif room_blocked?(daterange, room_number) + raise StandardError, "This room has a bock conflict during this daterange" + end + + room_rate = HotBook::Hotel.find_rate(room_number) + new_reservation = HotBook::Reservation.new(daterange: daterange, room_number: room_number, - room_rate: default_rate) + room_rate: room_rate) + reservations << new_reservation + return new_reservation + end + + def new_block(daterange, rooms) + validate(:daterange, daterange) + validate(:rooms, rooms) + if rooms.size > 5 + raise ArgumentError, "A Block cannot have more than 5 rooms" + elsif room_taken?(daterange, room_number) + raise StandardError, "Room already has reservation on this daterange" + end + new_block = HotBook::Block.new(daterange: daterange, + rooms: rooms, + room_rate: room_rate) + blocks << new_block + return new_block + end + + def new_block_reservation(block, room_number: block.available.first) + room_number = room_number.upcase + validate(:room_number, room_number) + validate(:block, block) + # make sure the room number is part of the block + unless block.rooms.include? room_number + raise StandardError, "Room is not part of the given block" + end + unless block.available.include? room_number + raise StandardError, "Room already resurved during this block" + end + # Remove this room from its memory array of what's still available: + block.disable(room_number) + new_reservation = HotBook::Reservation.new(daterange: block.daterange, + room_number: room_number, + room_rate: block.room_rate) @reservations << new_reservation return new_reservation end - def load_reservations(filename) - reservations = CSV.open(filename, headers: true).map { |row| - start_date = row["start date"] - end_date = row["end date"] - room_number = row["room number"] - room_rate = row["room rate"].to_f - notes = row["notes"] - - HotBook::Reservation.new( - daterange: HotBook::DateRange.new(start_date: start_date, - end_date: end_date), - room_number: room_number, room_rate: room_rate, notes: notes) - } - return reservations + def suggest_room(daterange) + validate(:daterange, daterange) + return public_avail_rooms.first end - def save_reservations(filename) +# Returns an array of reservations + def list_reservations(date) + validate(:date, date) + return reservations.select {|reservation| reservation.range.include? date} end - def suggested_room(daterange) - available_room_numbers = list_available_room_numbers(daterange) - return available_room_numbers.first +# Returns an array of room numbers that are publicly available during a daterange + def public_avail_rooms(daterange) + validate(:daterange, daterange) + a = conflicting_reservations(daterange).map { |reservation| reservation.room_number } + b = conflicting_blocks(daterange).flat_map { |block| block.rooms } + available_rooms = hotel.room_numbers - a - b + return available_rooms end - def default_rate - return hotel.rooms.first[:room_rate] +# Searches all reservation dateranges for any conflict with given daterange, +# returns true if room number is already part of a reservation + def room_taken?(daterange, room_number) + validate(:daterange, daterange) + validate(:room_number, room_number) + a = conflicting_reservations(daterange).map { |reservation| reservation.room_number } + return a.include? room_number end - def list_reservations(date) # expects that arg is a Class::Date - raise_data_type_error(:date, date) - return reservations.select {|reservation| reservation.range.include? date} +# Searches all block dateranges for any conflict with given daterange, +# and returns true if room number is already part of a block + def room_blocked?(daterange, room_number) + validate(:daterange, daterange) + validate(:room_number, room_number) + b = conflicting_blocks(daterange).flat_map { |block| block.rooms } + return b.include? room_number end - def room_is_bookable?(room_number, daterange) - raise_data_type_error(:room_number, room_number) - raise_data_type_error(:daterange, daterange) - query = room_number.upcase - available_room_numbers = list_available_room_numbers(daterange) - raise StandardError, "No room by that number" unless - available_room_numbers.include?(query) - return true +# Returns an array of reservations with a daterange conflict + def conflicting_reservations(daterange) + validate(:daterange, daterange) + return reservations.select { |reservation| reservation.conflict?(daterange) } end - def list_available_room_numbers(daterange) # expects that arg is a HotBook::DateRange - raise_data_type_error(:daterange, daterange) - conflicting_reservations = reservations.select { |reservation| - reservation.daterange.conflict?(daterange) } - unavailable_room_numbers = conflicting_reservations.map { |reservation| - reservation.room_number } - available_room_numbers = hotel.room_numbers - unavailable_room_numbers - return available_room_numbers +# Returns an array of blocks with a daterange conflict + def conflicting_blocks(daterange) + validate(:daterange, daterange) + return blocks.select { |block| block.conflict?(daterange) } end private - def raise_data_type_error(type, var) + def validate(type, var) case type when :date raise ArgumentError, "Invalid date - use Date.parse (expected Date, " \ - "not #{var.class})" unless var.is_a? Date + "not #{var.class})" unless var.is_a? Date when :room_number raise ArgumentError, "Invalid room number (expected String, " \ - "not #{var.class})" unless var.is_a? String + "not #{var.class})" unless var.is_a? String when :daterange raise ArgumentError, "Invalid daterange (expected HotBook::DateRange, " \ - "not #{var.class})" unless - var.is_a? HotBook::DateRange - end - end + "not #{var.class})" unless var.is_a? HotBook::DateRange + when :rooms + raise ArgumentError, "Invalid rooms (expected Array, " \ + "not #{var.class})" unless var.is_a? Array + when :block + raise ArgumentError, "Invalid block (expected HotBook::Block, " \ + "not #{var.class})" unless var.is_a? HotBook::Block + end + end end end + + + + # def load_reservations(filename) + # reservations = CSV.open(filename).map { |row| + # start_date = row[0] + # end_date = row[1] + # room_number = row[2] + # room_rate = row[3].to_f + # notes = row[4] + # + # HotBook::Reservation.new( + # daterange: HotBook::DateRange.new(start_date: start_date, + # end_date: end_date), + # room_number: room_number, room_rate: room_rate, notes: notes) + # } + # return reservations + # end + + # def save_reservations(filename) + # end diff --git a/lib/reservation.rb b/lib/reservation.rb index ffb1a35f5..e83d9e0fd 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -19,6 +19,10 @@ def initialize(daterange:, room_number:, room_rate:, notes: nil) # @csv_headers = ["start date", "end date", "room number", "room rate", "notes"] end + def conflict?(daterange) + return (self.daterange).conflict? daterange + end + def duration return daterange.duration end @@ -31,6 +35,22 @@ def range return daterange.to_range end + def self.from_csv(filename) + reservations = CSV.open(filename).map { |row| + start_date = row[0] + end_date = row[1] + room_number = row[2] + room_rate = row[3].to_f + notes = row[4] + + HotBook::Reservation.new( + daterange: HotBook::DateRange.new(start_date: start_date, + end_date: end_date), + room_number: room_number, room_rate: room_rate, notes: notes) + } + return reservations + end + # Returns an array of hashes of all reservations # def self.all(filename) # return data = CSV.open(filename, headers:true).map { |reservation| diff --git a/pseudocode.md b/pseudocode.md index e69de29bb..bdbffb6a1 100644 --- a/pseudocode.md +++ b/pseudocode.md @@ -0,0 +1,32 @@ +## Wave Three: Blocks of Rooms + +If you are not familiar with what a block of hotel rooms, here is a brief description: + +> A Block Booking refers to a group of rooms set aside for a specific group of customers for a set period of time. +> +> Room blocks are commonly created for large events like weddings or conventions. A number of rooms are set aside, and are made available for reservation by certain customers at a discounted rate. These rooms are not available to be reserved by the general public. + +### User Stories + +- As an administrator, I can create a block of rooms + - To create a block you need a date range, collection of rooms and a discounted room rate + - The collection of rooms should only include rooms that are available for the given date range + - If a room is set aside in a block, it is not available for reservation by the general public, nor can it be included in another block +- As an administrator, I can check whether a given block has any rooms available +- As an administrator, I can reserve a room from within a block of rooms + +is a reservation a specialized block? + +a block takes: daterange, room numbers + (potentially hashes) +a reservation has status: public, block, +a book has a new method for making a block reservation + +the new_reservation method has to check + + +### Constraints + +- A block can contain a maximum of 5 rooms +- When a room is reserved from a block of rooms, the reservation dates will always match the date range of the block +- All of the availability checking logic from Wave 2 should now respect room blocks as well as individual reservations diff --git a/refactors.txt b/refactors.txt index 98451d453..3b9bd1e1e 100644 --- a/refactors.txt +++ b/refactors.txt @@ -1,5 +1,26 @@ # hotel/refactors.txt Notes: -does hotel really need to have room numbers and rates? -yes, because reservation doesn't need to know that, really +data file: +Room hash +Reservation hash + + +Other classes +DateRange (has behavior and attributes) + +it was really easy to move my .from_csv loader from Book to Reservation, make +its a self method, and tests still passed. (which is suspicious, but hey.) + +why "hotel" stores "rooms": +hotel should be renamed rooms. +what if the room information changes? what if we add a room? + +inheritance seems complicated. +i wanted to make choices within what I know how to do. +otherwise, it's a can of worms. + + +### TODO: Make sure you can't book the same reservation twice + +# assumption: that there won't be that many blocks diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index afaa1d6e4..86b62842e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,6 +11,7 @@ require "csv" # Add lib files here (require_relative "../") +require_relative "../lib/block.rb" require_relative "../lib/book.rb" require_relative "../lib/daterange.rb" require_relative "../lib/hotel.rb" diff --git a/support/test_reservation_data.csv b/support/test_reservation_data.csv index 5089d76cb..32329f468 100644 --- a/support/test_reservation_data.csv +++ b/support/test_reservation_data.csv @@ -1,4 +1,3 @@ -start date,end date,room number,room rate,notes 2018-04-01,2018-04-06,1,200.0,"This is the first reservation" 2018-04-01,2018-04-06,2,200.0, 2018-04-01,2018-04-06,2,200.0, From dbb6b699b06cc0f7072f2d4de3a932276d0c7b82 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sun, 9 Sep 2018 18:14:29 -0700 Subject: [PATCH 32/55] Added code to load reservations from CSV, not yet tested --- lib/book.rb | 2 +- lib/reservation.rb | 11 ++--------- spec/reservation_spec.rb | 31 +++++-------------------------- support/test_reservation_data.csv | 10 +++++++--- 4 files changed, 15 insertions(+), 39 deletions(-) diff --git a/lib/book.rb b/lib/book.rb index d8f1273e6..6b21da2f5 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -10,7 +10,7 @@ module HotBook # It includes support methods for determining availability. TEST_RESERVATION_FILENAME = "support/test_reservation_data.csv" -# TEST_BLOCK_FILENAME = "support/test_block_data.csv" +# RESERVATION_DATA_FILENAME = "data/reservation_data.csv" class Book require_relative "hotel.rb" diff --git a/lib/reservation.rb b/lib/reservation.rb index e83d9e0fd..0f4eed49b 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -9,14 +9,11 @@ class Reservation require_relative "daterange" attr_reader :daterange, :room_number, :room_rate, :notes - # RESERVATION_DATA_FILENAME = "data/reservation_data.csv" - def initialize(daterange:, room_number:, room_rate:, notes: nil) @daterange = daterange @room_number = room_number @room_rate = room_rate @notes = notes - # @csv_headers = ["start date", "end date", "room number", "room rate", "notes"] end def conflict?(daterange) @@ -50,6 +47,8 @@ def self.from_csv(filename) } return reservations end + end +end # Returns an array of hashes of all reservations # def self.all(filename) @@ -77,9 +76,3 @@ def self.from_csv(filename) # file << [self.daterange.start_date, self.daterange.end_date, # self.room_number, self.room_rate, self.notes] } # end - end -end -# -# daterange = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_8") -# HotBook::Reservation.new(daterange: daterange, room_number: "one", -# room_rate: 1.0, filename: RESERVATION_DATA_FILENAME) diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index fb7b66b60..b51d27241 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -18,7 +18,6 @@ let(:reservation) { HotBook::Reservation.new(daterange: daterange, room_number: "one", room_rate: 1.0) } - describe "initialize method" do it "will correctly calculate duration" do expect(reservation.duration).must_equal 2 @@ -33,36 +32,16 @@ end end -### Project Requirement: Code should raise StandardError when an invalid -### DateRange is provided - -##### Project Requirement: User can get the total cost for a given reservation -#### The last day of a reservation is the checkout day, so the guest should not -#### be charged for that night describe "cost method" do it "will return correct cost" do expect(reservation.cost).must_equal 2.0 end end - # describe "save_reservation method" do - # it "will add itself to CSV upon initialization" do - # before_data = CSV.open(TEST_FILENAME).map { |line| line } - # note_string = "This is the test Reservation" - # HotBook::Reservation.new(daterange: daterange, - # room_number: "one", - # room_rate: 1.0, - # notes: note_string) - # after_data = CSV.open(TEST_FILENAME).map { |line| line } - # expect(after_data.size).must_equal (before_data.size + 1) - # expect(after_data.last[4]).must_equal note_string - # end - # end - - # describe "self.all" do - # it "will return an array of hashes" do - # p HotBook::Reservation.all(TEST_FILENAME) - # end - # end + describe "csv loading" do + it "will correctly load lines of the CSV" do + + end + end end diff --git a/support/test_reservation_data.csv b/support/test_reservation_data.csv index 32329f468..74fe2052d 100644 --- a/support/test_reservation_data.csv +++ b/support/test_reservation_data.csv @@ -1,6 +1,10 @@ 2018-04-01,2018-04-06,1,200.0,"This is the first reservation" 2018-04-01,2018-04-06,2,200.0, -2018-04-01,2018-04-06,2,200.0, -2018-04-01,2018-04-06,3,200.0, -2018-04-01,2018-04-06,3,200.0, 2018-04-01,2018-04-06,3,200.0, +2018-04-06,2018-04-08,3,200.0, +2018-04-08,2018-04-10,3,200.0, +2018-04-10,2018-04-15,1,185.0,"This is a block 1 reservation" +2018-04-10,2018-04-15,2,185.0,"This is a block 1 reservation" +2018-04-10,2018-04-15,3,185.0,"This is a block 1 reservation" +2018-04-10,2018-04-15,4,185.0,"This is a block 1 reservation" +2018-04-10,2018-04-15,5,185.0,"This is a block 1 reservation" From b00e9180000416bace1086ab3a95ecf091a0049e Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sun, 9 Sep 2018 18:28:25 -0700 Subject: [PATCH 33/55] Better comments explaining range as overnights --- lib/book.rb | 4 ++-- lib/daterange.rb | 1 + lib/reservation.rb | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/book.rb b/lib/book.rb index 6b21da2f5..91eb56b74 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -88,8 +88,8 @@ def suggest_room(daterange) return public_avail_rooms.first end -# Returns an array of reservations - def list_reservations(date) +# Returns an array of reservations (EXCLUDING checkout day) + def list_by_nights(date) validate(:date, date) return reservations.select {|reservation| reservation.range.include? date} end diff --git a/lib/daterange.rb b/lib/daterange.rb index a5d4f33fb..dd436cb93 100644 --- a/lib/daterange.rb +++ b/lib/daterange.rb @@ -28,6 +28,7 @@ def conflict?(other) end end +# !!!!Range only includes overnights and EXCLUDES checkout day!!!! def to_range return (@start_date...@end_date) end diff --git a/lib/reservation.rb b/lib/reservation.rb index 0f4eed49b..5309618e9 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -28,6 +28,7 @@ def cost return duration * room_rate end +# !!!!Range only includes overnights and EXCLUDES checkout day!!!! def range return daterange.to_range end From 25a7649deec6403704bc17355dfcaa1215559e3f Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 00:57:50 -0700 Subject: [PATCH 34/55] Added CSV loading method, tests pass --- spec/reservation_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index b51d27241..71568a2de 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -1,6 +1,6 @@ require_relative "spec_helper" require "csv" -# TEST_FILENAME = "support/test_reservation.csv" +TEST_RESERVATION_FILENAME = "support/test_reservation_data.csv" # # First, overwrite the test CSV # CSV.open(TEST_FILENAME, "w") #["start date", "end date", "room number", "room rate"] } @@ -40,7 +40,11 @@ describe "csv loading" do it "will correctly load lines of the CSV" do - + blocknote = "This is a block 1 reservation" + reservations = HotBook::Reservation.from_csv(TEST_RESERVATION_FILENAME) + expect(reservations[5].room_number).must_equal "1" + expect(reservations.last.notes).must_equal blocknote + expect(reservations.first.daterange.start_date).must_equal Date.parse("apr_1") end end From 2157e3b5190a97a11990bab3c6e5ab7fc76720ee Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 01:13:43 -0700 Subject: [PATCH 35/55] Daterange now has conflict? method of its own --- lib/reservation.rb | 4 ++-- spec/reservation_spec.rb | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/reservation.rb b/lib/reservation.rb index 5309618e9..31e0fc0b3 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -16,8 +16,8 @@ def initialize(daterange:, room_number:, room_rate:, notes: nil) @notes = notes end - def conflict?(daterange) - return (self.daterange).conflict? daterange + def conflict?(other) + return daterange.conflict? other.daterange end def duration diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index 71568a2de..62dd956ba 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -1,16 +1,6 @@ require_relative "spec_helper" -require "csv" TEST_RESERVATION_FILENAME = "support/test_reservation_data.csv" -# # First, overwrite the test CSV -# CSV.open(TEST_FILENAME, "w") #["start date", "end date", "room number", "room rate"] } -# # Next, initialize a new Reservation, which stores itself in the CSV along with -# # a header row -# daterange = HotBook::DateRange.new(start_date: "apr_6", end_date: "apr_8") -# note_string = "This Reservation is instantiated above the spec tests" -# HotBook::Reservation.new(daterange: daterange, room_number: "one", -# room_rate: 1.0, notes: note_string) - ## Project Requirement: User can reserve a room for a given date range describe "HotBook::Reservation class" do let(:daterange) { HotBook::DateRange.new(start_date: "apr_6", @@ -18,6 +8,7 @@ let(:reservation) { HotBook::Reservation.new(daterange: daterange, room_number: "one", room_rate: 1.0) } + let(:load_reservations) { HotBook::Reservation.from_csv(TEST_RESERVATION_FILENAME) } describe "initialize method" do it "will correctly calculate duration" do expect(reservation.duration).must_equal 2 @@ -38,13 +29,22 @@ end end + describe "conflict? method" do + it "will correctly determine if there's a daterange conflict" do + thisres = load_reservations[1] + thatres = load_reservations[3] + anotherres = load_reservations[4] + expect(thisres.conflict?(thatres)).must_equal false + expect(thisres.conflict?(anotherres)).must_equal true + end + end + describe "csv loading" do it "will correctly load lines of the CSV" do blocknote = "This is a block 1 reservation" - reservations = HotBook::Reservation.from_csv(TEST_RESERVATION_FILENAME) - expect(reservations[5].room_number).must_equal "1" - expect(reservations.last.notes).must_equal blocknote - expect(reservations.first.daterange.start_date).must_equal Date.parse("apr_1") + expect(load_reservations[5].room_number).must_equal "1" + expect(load_reservations.last.notes).must_equal blocknote + expect(load_reservations.first.daterange.start_date).must_equal Date.parse("apr_1") end end From 712d117ddca4a446ec98cf5bb486734846557031 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 01:18:19 -0700 Subject: [PATCH 36/55] Reservation range bug fixed - no longer includes checkout date --- spec/reservation_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index 62dd956ba..1c8e03a8f 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -23,6 +23,15 @@ end end + describe "range method" do + it "range only includes overnights and excludes checkout day" do + range = load_reservations[0].range + checkout_date = Date.parse("apr_6") + expect(range).must_be_instance_of Range + expect(range.include? checkout_date).must_equal false + end + end + describe "cost method" do it "will return correct cost" do expect(reservation.cost).must_equal 2.0 From 6ec25ee169c76952ae40ea7e0b69e0c5975ff66a Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 01:42:49 -0700 Subject: [PATCH 37/55] private validate method checks all arguments --- spec/reservation_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index 1c8e03a8f..24f621827 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -1,5 +1,4 @@ require_relative "spec_helper" -TEST_RESERVATION_FILENAME = "support/test_reservation_data.csv" ## Project Requirement: User can reserve a room for a given date range describe "HotBook::Reservation class" do From f2868ab61d66761d849d3480cc7243cd50d4dad7 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 02:41:03 -0700 Subject: [PATCH 38/55] modified test reservation data --- spec/reservation_spec.rb | 4 ++-- support/test_reservation_data.csv | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index 24f621827..7bcfbb3e3 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -49,9 +49,9 @@ describe "csv loading" do it "will correctly load lines of the CSV" do - blocknote = "This is a block 1 reservation" + blocknote = "This is the fifth reservation" expect(load_reservations[5].room_number).must_equal "1" - expect(load_reservations.last.notes).must_equal blocknote + expect(load_reservations[5].notes).must_equal blocknote expect(load_reservations.first.daterange.start_date).must_equal Date.parse("apr_1") end end diff --git a/support/test_reservation_data.csv b/support/test_reservation_data.csv index 74fe2052d..caf2b6d58 100644 --- a/support/test_reservation_data.csv +++ b/support/test_reservation_data.csv @@ -1,10 +1,10 @@ 2018-04-01,2018-04-06,1,200.0,"This is the first reservation" -2018-04-01,2018-04-06,2,200.0, +2018-04-01,2018-04-06,2,200.0,"thisres" 2018-04-01,2018-04-06,3,200.0, -2018-04-06,2018-04-08,3,200.0, -2018-04-08,2018-04-10,3,200.0, -2018-04-10,2018-04-15,1,185.0,"This is a block 1 reservation" -2018-04-10,2018-04-15,2,185.0,"This is a block 1 reservation" -2018-04-10,2018-04-15,3,185.0,"This is a block 1 reservation" -2018-04-10,2018-04-15,4,185.0,"This is a block 1 reservation" -2018-04-10,2018-04-15,5,185.0,"This is a block 1 reservation" +2018-04-06,2018-04-08,3,200.0,"thatres" +2018-04-05,2018-04-06,4,200.0,"anotherres" +2018-04-10,2018-04-15,1,185.0,"This is the fifth reservation" +2018-04-10,2018-04-15,2,185.0, +2018-04-10,2018-04-15,3,185.0, +2018-04-10,2018-04-15,4,185.0, +2018-04-15,2018-04-30,20,200.0,"This is a public reservation" From b5fa6884e8fd62226808c5ecf073aa0b1dbd1c91 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 02:41:35 -0700 Subject: [PATCH 39/55] Added custom errors in errors.rb to HotBook module --- lib/book.rb | 40 ++++++++------- lib/errors.rb | 7 +++ spec/book_spec.rb | 123 ++++++++++++++++++++++++++-------------------- 3 files changed, 100 insertions(+), 70 deletions(-) create mode 100644 lib/errors.rb diff --git a/lib/book.rb b/lib/book.rb index 91eb56b74..bc9ef665d 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -16,6 +16,7 @@ class Book require_relative "hotel.rb" require_relative "reservation.rb" require_relative "block.rb" + require_relative "errors.rb" require "date" attr_reader :reservations, :hotel, :blocks @@ -30,16 +31,16 @@ def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new) end def new_reservation(daterange, room_number: suggest_room(daterange)) - room_number = room_number.upcase validate(:daterange, daterange) validate(:room_number, room_number) + room_number = room_number.upcase if room_taken?(daterange, room_number) - raise StandardError, "Room already has reservation on this daterange" + raise RoomIsTakenError, "Room is already reserved some time within this daterange" elsif room_blocked?(daterange, room_number) - raise StandardError, "This room has a bock conflict during this daterange" + raise RoomIsBlockedError, "This room conflicts with a Block during this daterange" end - room_rate = HotBook::Hotel.find_rate(room_number) + room_rate = hotel.find_rate(room_number) new_reservation = HotBook::Reservation.new(daterange: daterange, room_number: room_number, @@ -48,31 +49,34 @@ def new_reservation(daterange, room_number: suggest_room(daterange)) return new_reservation end - def new_block(daterange, rooms) + def new_block(daterange, rooms, discount_rate: 185.0) validate(:daterange, daterange) validate(:rooms, rooms) if rooms.size > 5 raise ArgumentError, "A Block cannot have more than 5 rooms" - elsif room_taken?(daterange, room_number) - raise StandardError, "Room already has reservation on this daterange" + end + rooms.each do |room_number| + if room_taken?(daterange, room_number) + raise RoomIsTakenError, "Room already has reservation during this daterange" + end end new_block = HotBook::Block.new(daterange: daterange, rooms: rooms, - room_rate: room_rate) + room_rate: discount_rate) # Default discount blocks << new_block return new_block end def new_block_reservation(block, room_number: block.available.first) - room_number = room_number.upcase validate(:room_number, room_number) validate(:block, block) + room_number = room_number.upcase # make sure the room number is part of the block unless block.rooms.include? room_number raise StandardError, "Room is not part of the given block" end unless block.available.include? room_number - raise StandardError, "Room already resurved during this block" + raise RoomIsTakenError, "Room already reserved during this block" end # Remove this room from its memory array of what's still available: block.disable(room_number) @@ -85,7 +89,7 @@ def new_block_reservation(block, room_number: block.available.first) def suggest_room(daterange) validate(:daterange, daterange) - return public_avail_rooms.first + return public_avail_rooms(daterange).first end # Returns an array of reservations (EXCLUDING checkout day) @@ -124,7 +128,7 @@ def room_blocked?(daterange, room_number) # Returns an array of reservations with a daterange conflict def conflicting_reservations(daterange) validate(:daterange, daterange) - return reservations.select { |reservation| reservation.conflict?(daterange) } + return reservations.select { |reservation| reservation.daterange.conflict?(daterange) } end # Returns an array of blocks with a daterange conflict @@ -139,19 +143,19 @@ def validate(type, var) case type when :date raise ArgumentError, "Invalid date - use Date.parse (expected Date, " \ - "not #{var.class})" unless var.is_a? Date + "not #{var.class})" unless var.is_a?(Date) when :room_number raise ArgumentError, "Invalid room number (expected String, " \ - "not #{var.class})" unless var.is_a? String + "not #{var.class})" unless var.is_a?(String) when :daterange raise ArgumentError, "Invalid daterange (expected HotBook::DateRange, " \ - "not #{var.class})" unless var.is_a? HotBook::DateRange + "not #{var.class})" unless var.is_a?(HotBook::DateRange) when :rooms - raise ArgumentError, "Invalid rooms (expected Array, " \ - "not #{var.class})" unless var.is_a? Array + raise ArgumentError, "Invalid rooms (expected Array of " \ + "Strings)" unless var.is_a?(Array) && var.first.is_a?(String) when :block raise ArgumentError, "Invalid block (expected HotBook::Block, " \ - "not #{var.class})" unless var.is_a? HotBook::Block + "not #{var.class})" unless var.is_a?(HotBook::Block) end end diff --git a/lib/errors.rb b/lib/errors.rb new file mode 100644 index 000000000..aa769e03f --- /dev/null +++ b/lib/errors.rb @@ -0,0 +1,7 @@ +module HotBook + class RoomIsTakenError < StandardError + end + + class RoomIsBlockedError < StandardError + end +end diff --git a/spec/book_spec.rb b/spec/book_spec.rb index d72630589..c3e040ffd 100644 --- a/spec/book_spec.rb +++ b/spec/book_spec.rb @@ -4,24 +4,14 @@ let(:hotel) { HotBook::Hotel.new } let(:book) { HotBook::Book.new(hotel) } let(:daterange) { HotBook::DateRange.new( - start_date: "apr_1", end_date: "apr_2") } + start_date: "apr_15", end_date: "apr_30") } let(:new_reservation) { book.new_reservation(daterange) } + let(:load_reservations) { hotel.reservations } + let(:block) { HotBook::Block.new(daterange, ["1", "2", "3", "4", "5"]) } + let(:rooms) { ["1", "2", "3", "4", "5"] } + let(:load_block) { book.new_block(daterange, rooms) } describe "new_reservation method" do - it "raises ArgError if the daterange isn't a DateRange" do - badrange = "some_string" - expect{book.new_reservation(badrange)}.must_raise ArgumentError - end - - it "raises ArgError if the room number isn't a String" do - bad_room_number = 99 - expect{book.new_reservation(daterange, room_number: bad_room_number)}.must_raise ArgumentError - end - - it "raises StandardError if there's no room by that number" do - bad_room_number = "not a room number" - expect{book.new_reservation(daterange, room_number: bad_room_number)}.must_raise StandardError - end it "creates a Reservation object and stores in @reservations" do expect(new_reservation).must_be_instance_of HotBook::Reservation @@ -29,53 +19,82 @@ 3.times { book.new_reservation(daterange) } expect((book.reservations).size).must_equal previous_size + 3 end - end - describe "suggested_room method" do - it "returns the correct room number" do - expect(book.suggested_room(daterange)).must_equal "4" + it "correctly raises error if room is already reserved during daterange" do + book + load_block + expect{book.new_reservation(daterange, room_number: "20")}.must_raise HotBook::RoomIsTakenError + expect{book.new_reservation(daterange, room_number: "1")}.must_raise HotBook::RoomIsBlockedError end - end - describe "default_rate method" do - it "does the right thing" do - expect(book.default_rate).must_equal 200.0 + it "correctly raises error if room is part of a block during daterange" do + end + end + # + # describe "suggested_room method" do + # it "returns the correct room number" do + # expect(book.suggested_room(daterange)).must_equal "4" + # end + # end + # + # describe "default_rate method" do + # it "does the right thing" do + # expect(book.default_rate).must_equal 200.0 + # end + # end ### PROJECT REQ: User can access the list of reservations for a specific date - describe "list_reservations" do - it "raises ArgError if the arg isn't a Date" do - baddate = "some_string" - expect{book.list_reservations(baddate)}.must_raise ArgumentError - end + # describe "list_reservations" do + # it "raises ArgError if the arg isn't a Date" do + # baddate = "some_string" + # expect{book.list_reservations(baddate)}.must_raise ArgumentError + # end + # + # it "returns an array of reservations " do + # note = "This is the first reservation" + # date = Date.parse("apr_6") + # list = book.list_reservations(date) + # expect(list).must_be_instance_of Array + # expect(list.first.notes).must_equal note + # end + # end - it "returns an array of reservations " do - note = "This is the first reservation" - date = Date.parse("apr_6") - list = book.list_reservations(date) - expect(list).must_be_instance_of Array - expect(list.first.notes).must_equal note - end - end + # describe "room_is_bookable? method" do + # it "correctly checks if a room has a reservation on that date" do + # room_number = "10" + # expect(book.room_is_bookable?(room_number, daterange)).must_equal true + # room_number = "2" + # expect{book.room_is_bookable?(room_number, daterange)}.must_raise StandardError + # end + # end - describe "room_is_bookable? method" do - it "correctly checks if a room has a reservation on that date" do - room_number = "10" - expect(book.room_is_bookable?(room_number, daterange)).must_equal true - room_number = "2" - expect{book.room_is_bookable?(room_number, daterange)}.must_raise StandardError - end - end + # describe "list_available_room_numbers method" do + # it "returns an array that's a subset (or equal to) all room numbers" do + # all = hotel.room_numbers + # list = book.list_available_room_numbers(daterange) + # expect(book.reservations.size).must_equal 6 + # expect((all - list).size).must_equal (all.size - list.size) + # expect(list.first).must_equal "4" + # expect(list.last).must_equal "20" + # end + # end - describe "list_available_room_numbers method" do - it "returns an array that's a subset (or equal to) all room numbers" do - all = hotel.room_numbers - list = book.list_available_room_numbers(daterange) - expect(book.reservations.size).must_equal 6 - expect((all - list).size).must_equal (all.size - list.size) - expect(list.first).must_equal "4" - expect(list.last).must_equal "20" + describe "validate(type, var) method" do + it "raises ArgError for invalid inputs" do + bad_range = nil + bad_room_number = nil + bad_date = nil + bad_rooms = [] + another_bad_rooms = nil + bad_block = nil + expect{book.new_reservation(bad_range)}.must_raise ArgumentError + expect{book.new_reservation(daterange, room_number: bad_room_number)}.must_raise ArgumentError + expect{book.list_by_nights(bad_date)}.must_raise ArgumentError + expect{book.new_block(daterange, bad_rooms)}.must_raise ArgumentError + expect{book.new_block(daterange, another_bad_rooms)}.must_raise ArgumentError + expect{book.new_block_reservation(bad_block, room_number: "2")}.must_raise ArgumentError end end end From 20e9c2f2502441261bf9434d84009f67c2d7eb33 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 03:28:05 -0700 Subject: [PATCH 40/55] Book class can now identify reservation and block conflicts --- spec/book_spec.rb | 119 ++++++++++++++++-------------- support/test_reservation_data.csv | 10 +-- 2 files changed, 70 insertions(+), 59 deletions(-) diff --git a/spec/book_spec.rb b/spec/book_spec.rb index c3e040ffd..bd638d4bd 100644 --- a/spec/book_spec.rb +++ b/spec/book_spec.rb @@ -10,76 +10,87 @@ let(:block) { HotBook::Block.new(daterange, ["1", "2", "3", "4", "5"]) } let(:rooms) { ["1", "2", "3", "4", "5"] } let(:load_block) { book.new_block(daterange, rooms) } + let(:date) {Date.parse("apr_15")} + let(:overlaprange) { HotBook::DateRange.new( start_date: "apr_08", + end_date: "apr_16") } - describe "new_reservation method" do + before do + book # test reservation data is loaded into @reservations + block = load_block # a single block, rooms 1-5, 4/15-4/30, is loaded into @blocks + block.disable("1") # This manually mimics what would happen if test reservations + block.disable("2") # with note "This is a block (private) reservation" + block.disable("3") # were created as block reservations + block.disable("4") + end + describe "new_reservation method" do it "creates a Reservation object and stores in @reservations" do expect(new_reservation).must_be_instance_of HotBook::Reservation - previous_size = book.reservations.size - 3.times { book.new_reservation(daterange) } - expect((book.reservations).size).must_equal previous_size + 3 + expect(book.reservations.size).must_equal 11 + 3.times {book.new_reservation(daterange) } + expect(book.reservations.size).must_equal 11 + 3 end - it "correctly raises error if room is already reserved during daterange" do - book - load_block - expect{book.new_reservation(daterange, room_number: "20")}.must_raise HotBook::RoomIsTakenError + it "raises unique errors if room is booked or blocked during daterange" do + expect{book.new_reservation(daterange, room_number: "6")}.must_raise HotBook::RoomIsTakenError expect{book.new_reservation(daterange, room_number: "1")}.must_raise HotBook::RoomIsBlockedError end + end - it "correctly raises error if room is part of a block during daterange" do + describe "suggest_room method" do + it "returns the first publicly available room number that isn't booked or blocked" do + expect(book.suggest_room(daterange)).must_equal "7" + end + end + describe "list_by_nights method" do + it "returns an array of reservations excluding checkout date" do + expect(book.list_by_nights(date).size).must_equal 1 + expect(book.list_by_nights(date)[0].room_number).must_equal "6" end + end + describe "public_avail_rooms method" do + it "returns an array of room numbers that aren't booked or blocked" do + list = %w(7 8 9 10 11 12 13 14 15 16 17 18 19 20) + expect(book.public_avail_rooms(overlaprange)).must_equal list + book.new_reservation(overlaprange) + list.shift + expect(book.public_avail_rooms(overlaprange)).must_equal list + end end - # - # describe "suggested_room method" do - # it "returns the correct room number" do - # expect(book.suggested_room(daterange)).must_equal "4" - # end - # end - # - # describe "default_rate method" do - # it "does the right thing" do - # expect(book.default_rate).must_equal 200.0 - # end - # end -### PROJECT REQ: User can access the list of reservations for a specific date - # describe "list_reservations" do - # it "raises ArgError if the arg isn't a Date" do - # baddate = "some_string" - # expect{book.list_reservations(baddate)}.must_raise ArgumentError - # end - # - # it "returns an array of reservations " do - # note = "This is the first reservation" - # date = Date.parse("apr_6") - # list = book.list_reservations(date) - # expect(list).must_be_instance_of Array - # expect(list.first.notes).must_equal note - # end - # end + describe "room_taken? method" do + it "given daterange, lists rezzies with conflict, " \ + "and tells you if the given room is in that list" do + expect(book.room_taken?(daterange, "6")).must_equal true + expect(book.room_taken?(daterange, "1")).must_equal false + end + end + + describe "room_blocked? method" do + it "given daterange, lists blocks with conflict, " \ + "and tells you if the given room is in that list" do + expect(book.room_blocked?(daterange, "1")).must_equal true + expect(book.room_blocked?(daterange, "6")).must_equal false + end + end - # describe "room_is_bookable? method" do - # it "correctly checks if a room has a reservation on that date" do - # room_number = "10" - # expect(book.room_is_bookable?(room_number, daterange)).must_equal true - # room_number = "2" - # expect{book.room_is_bookable?(room_number, daterange)}.must_raise StandardError - # end - # end + describe "conflicting_reservations method" do + it "returns an array of reservations with a daterange conflict" do + array = book.conflicting_reservations(daterange) + expect(array.size).must_equal 1 + expect(array[0]).must_be_instance_of HotBook::Reservation + end + end - # describe "list_available_room_numbers method" do - # it "returns an array that's a subset (or equal to) all room numbers" do - # all = hotel.room_numbers - # list = book.list_available_room_numbers(daterange) - # expect(book.reservations.size).must_equal 6 - # expect((all - list).size).must_equal (all.size - list.size) - # expect(list.first).must_equal "4" - # expect(list.last).must_equal "20" - # end - # end + describe "conflicting_blocks method" do + it "returns an array of blocks with a daterange conflict" do + array = book.conflicting_blocks(daterange) + expect(array.size).must_equal 1 + expect(array[0]).must_be_instance_of HotBook::Block + end + end describe "validate(type, var) method" do it "raises ArgError for invalid inputs" do diff --git a/support/test_reservation_data.csv b/support/test_reservation_data.csv index caf2b6d58..1cef15f5e 100644 --- a/support/test_reservation_data.csv +++ b/support/test_reservation_data.csv @@ -3,8 +3,8 @@ 2018-04-01,2018-04-06,3,200.0, 2018-04-06,2018-04-08,3,200.0,"thatres" 2018-04-05,2018-04-06,4,200.0,"anotherres" -2018-04-10,2018-04-15,1,185.0,"This is the fifth reservation" -2018-04-10,2018-04-15,2,185.0, -2018-04-10,2018-04-15,3,185.0, -2018-04-10,2018-04-15,4,185.0, -2018-04-15,2018-04-30,20,200.0,"This is a public reservation" +2018-04-10,2018-04-15,1,185.0,"This is a block (private) reservation" +2018-04-10,2018-04-15,2,185.0,"This is a block (private) reservation" +2018-04-10,2018-04-15,3,185.0,"This is a block (private) reservation" +2018-04-10,2018-04-15,4,185.0,"This is a block (private) reservation" +2018-04-15,2018-04-30,6,200.0,"This is a public reservation" From f7efff1d48499b1bfd29c87fd20daa6137562986 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 03:57:34 -0700 Subject: [PATCH 41/55] Added hotbook.rb file --- hotbook.rb | 23 +++++++++++++++++++++++ spec/spec_helper.rb | 8 +++----- 2 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 hotbook.rb diff --git a/hotbook.rb b/hotbook.rb new file mode 100644 index 000000000..2326b6e5c --- /dev/null +++ b/hotbook.rb @@ -0,0 +1,23 @@ +# gems the project needs +require "csv" +require "date" + +# Optional - for developer use +require "pry" +require "awesome_print" + +# project constants +TEST_RESERVATION_FILENAME = "support/test_reservation_data.csv" +# RESERVATION_DATA_FILENAME = "data/reservation_data.csv" + +# namespace module +module HotBook; +end + +# all of the classes that live in the module +require_relative "lib/block.rb" +require_relative "lib/book.rb" +require_relative "lib/daterange.rb" +require_relative "lib/errors.rb" +require_relative "lib/hotel.rb" +require_relative "lib/reservation.rb" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 86b62842e..ab16de102 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,11 +10,9 @@ require "date" require "csv" +TEST_RESERVATION_FILENAME = "support/test_reservation_data.csv" + # Add lib files here (require_relative "../") -require_relative "../lib/block.rb" -require_relative "../lib/book.rb" -require_relative "../lib/daterange.rb" -require_relative "../lib/hotel.rb" -require_relative "../lib/reservation.rb" +require_relative "../hotbook.rb" Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new From 65c20baebec2c1aeee134defb1dc708daa4a5f9b Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 04:11:04 -0700 Subject: [PATCH 42/55] Book class can make new block and check for conflicts --- lib/book.rb | 48 +++++++++-------------------------------------- spec/book_spec.rb | 35 +++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/lib/book.rb b/lib/book.rb index bc9ef665d..4235d8daa 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -1,7 +1,3 @@ -# Optional - for developer use -require "pry" -require "awesome_print" - module HotBook # The Book class is responsible for: # holding all reservations and blocks, @@ -9,15 +5,7 @@ module HotBook # making new reservations and blocks. # It includes support methods for determining availability. -TEST_RESERVATION_FILENAME = "support/test_reservation_data.csv" -# RESERVATION_DATA_FILENAME = "data/reservation_data.csv" - class Book - require_relative "hotel.rb" - require_relative "reservation.rb" - require_relative "block.rb" - require_relative "errors.rb" - require "date" attr_reader :reservations, :hotel, :blocks def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new) @@ -37,7 +25,7 @@ def new_reservation(daterange, room_number: suggest_room(daterange)) if room_taken?(daterange, room_number) raise RoomIsTakenError, "Room is already reserved some time within this daterange" elsif room_blocked?(daterange, room_number) - raise RoomIsBlockedError, "This room conflicts with a Block during this daterange" + raise RoomIsBlockedError, "Room is blocked some time within this daterange" end room_rate = hotel.find_rate(room_number) @@ -55,9 +43,12 @@ def new_block(daterange, rooms, discount_rate: 185.0) if rooms.size > 5 raise ArgumentError, "A Block cannot have more than 5 rooms" end + # Cannot overlap or conflict with existing reservation rooms.each do |room_number| if room_taken?(daterange, room_number) raise RoomIsTakenError, "Room already has reservation during this daterange" + elsif room_blocked?(daterange, room_number) + raise BlockConflictError, "A block already exists on a room during this daterange" end end new_block = HotBook::Block.new(daterange: daterange, @@ -72,10 +63,10 @@ def new_block_reservation(block, room_number: block.available.first) validate(:block, block) room_number = room_number.upcase # make sure the room number is part of the block - unless block.rooms.include? room_number + unless block.rooms.include?(room_number) raise StandardError, "Room is not part of the given block" end - unless block.available.include? room_number + unless block.available.include?(room_number) raise RoomIsTakenError, "Room already reserved during this block" end # Remove this room from its memory array of what's still available: @@ -95,7 +86,7 @@ def suggest_room(daterange) # Returns an array of reservations (EXCLUDING checkout day) def list_by_nights(date) validate(:date, date) - return reservations.select {|reservation| reservation.range.include? date} + return reservations.select {|reservation| reservation.range.include?(date) } end # Returns an array of room numbers that are publicly available during a daterange @@ -113,7 +104,7 @@ def room_taken?(daterange, room_number) validate(:daterange, daterange) validate(:room_number, room_number) a = conflicting_reservations(daterange).map { |reservation| reservation.room_number } - return a.include? room_number + return a.include?(room_number) end # Searches all block dateranges for any conflict with given daterange, @@ -122,7 +113,7 @@ def room_blocked?(daterange, room_number) validate(:daterange, daterange) validate(:room_number, room_number) b = conflicting_blocks(daterange).flat_map { |block| block.rooms } - return b.include? room_number + return b.include?(room_number) end # Returns an array of reservations with a daterange conflict @@ -161,24 +152,3 @@ def validate(type, var) end end - - - - # def load_reservations(filename) - # reservations = CSV.open(filename).map { |row| - # start_date = row[0] - # end_date = row[1] - # room_number = row[2] - # room_rate = row[3].to_f - # notes = row[4] - # - # HotBook::Reservation.new( - # daterange: HotBook::DateRange.new(start_date: start_date, - # end_date: end_date), - # room_number: room_number, room_rate: room_rate, notes: notes) - # } - # return reservations - # end - - # def save_reservations(filename) - # end diff --git a/spec/book_spec.rb b/spec/book_spec.rb index bd638d4bd..2eee91b00 100644 --- a/spec/book_spec.rb +++ b/spec/book_spec.rb @@ -15,11 +15,16 @@ end_date: "apr_16") } before do - book # test reservation data is loaded into @reservations - block = load_block # a single block, rooms 1-5, 4/15-4/30, is loaded into @blocks - block.disable("1") # This manually mimics what would happen if test reservations - block.disable("2") # with note "This is a block (private) reservation" - block.disable("3") # were created as block reservations + # Test reservation data is loaded into @reservations: + book + # A single block, rooms 1-5, 4/15-4/30, is loaded into @blocks: + block = load_block + + # This mimics what would happen if test reservations 6-9 were actually + # created as block reservations: + block.disable("1") + block.disable("2") + block.disable("3") block.disable("4") end @@ -43,6 +48,26 @@ end end + describe "new_block_reservation method" do + + end + + describe "new_block method" do + it "cannot overlap or conflict with an existing block" do + expect{book.new_block(daterange, rooms)}.must_raise HotBook::BlockConflictError + end + + it "cannot overlap or conflict with an existing reservation" do + expect{book.new_block(overlaprange, rooms)}.must_raise HotBook::RoomIsTakenError + expect{book.new_block(daterange, ["6"])}.must_raise HotBook::RoomIsTakenError + end + + it "cannot contain more than 5 rooms" do + bad_rooms = %w(1 2 3 4 5 6) + expect{book.new_block(daterange, bad_rooms)}.must_raise ArgumentError + end + end + describe "list_by_nights method" do it "returns an array of reservations excluding checkout date" do expect(book.list_by_nights(date).size).must_equal 1 From f81e93b17e8becf6fe7d077448ccc67d93760ea7 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 04:22:38 -0700 Subject: [PATCH 43/55] Book class can make new block reservations and check for conflicts --- lib/book.rb | 3 ++- spec/book_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/book.rb b/lib/book.rb index 4235d8daa..b7c244628 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -64,8 +64,9 @@ def new_block_reservation(block, room_number: block.available.first) room_number = room_number.upcase # make sure the room number is part of the block unless block.rooms.include?(room_number) - raise StandardError, "Room is not part of the given block" + raise ArgumentError, "Room is not part of the given block" end + # make sure the room is available unless block.available.include?(room_number) raise RoomIsTakenError, "Room already reserved during this block" end diff --git a/spec/book_spec.rb b/spec/book_spec.rb index 2eee91b00..f6317f55b 100644 --- a/spec/book_spec.rb +++ b/spec/book_spec.rb @@ -13,6 +13,7 @@ let(:date) {Date.parse("apr_15")} let(:overlaprange) { HotBook::DateRange.new( start_date: "apr_08", end_date: "apr_16") } + let(:current_block){book.blocks[0]} before do # Test reservation data is loaded into @reservations: @@ -50,6 +51,25 @@ describe "new_block_reservation method" do + it "will remove the room from block's memo array of what's still reservable" do + expect(current_block.available.size).must_equal 1 + book.new_block_reservation(current_block) + expect(current_block.available.size).must_equal 0 + end + + it "will raise an error if the room number given is not in the given block" do + expect{book.new_block_reservation(current_block, room_number: "20")}.must_raise ArgumentError + end + + it "will raise an error if the room is in the block, but already booked" do + expect{book.new_block_reservation(current_block, room_number: "1")}.must_raise HotBook::RoomIsTakenError + end + + it "will add the new reservation to the @reservations array" do + expect(book.reservations.last.room_number).must_equal "6" + book.new_block_reservation(current_block) + expect(book.reservations.last.room_number).must_equal "5" + end end describe "new_block method" do From fa642ea4941ac791f4d20498d50296a3265abbca Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 04:30:56 -0700 Subject: [PATCH 44/55] all current specs passing and 100% coverage --- hotbook.rb | 3 ++- lib/block.rb | 20 +++----------------- lib/book.rb | 2 +- lib/daterange.rb | 17 ++--------------- lib/errors.rb | 3 +++ lib/hotel.rb | 15 ++++++--------- lib/reservation.rb | 37 +++---------------------------------- spec/book_spec.rb | 1 - spec/hotel_spec.rb | 10 ++++++++-- spec/reservation_spec.rb | 4 ++-- spec/spec_helper.rb | 2 -- 11 files changed, 30 insertions(+), 84 deletions(-) diff --git a/hotbook.rb b/hotbook.rb index 2326b6e5c..7999187d9 100644 --- a/hotbook.rb +++ b/hotbook.rb @@ -7,8 +7,9 @@ require "awesome_print" # project constants +ROOM_NUMBERS_FILENAME = "data/room_numbers.csv" TEST_RESERVATION_FILENAME = "support/test_reservation_data.csv" -# RESERVATION_DATA_FILENAME = "data/reservation_data.csv" +RESERVATION_DATA_FILENAME = TEST_RESERVATION_FILENAME #"data/reservation_data.csv" # namespace module module HotBook; diff --git a/lib/block.rb b/lib/block.rb index 2d6c26445..c83011395 100644 --- a/lib/block.rb +++ b/lib/block.rb @@ -1,10 +1,5 @@ -# Optional - for developer use -require "pry" -require "awesome_print" - module HotBook class Block - require_relative "daterange" attr_reader :available, :rooms, :daterange, :room_rate def initialize(daterange:, rooms:, room_rate: 185.0) @@ -14,26 +9,17 @@ def initialize(daterange:, rooms:, room_rate: 185.0) @room_rate = room_rate end -# remove a room from @available once it's reserved +# Removes a room from @available once it's reserved def disable(query) room = available.find { |room| room == query } # guaranteed to return 1 return available.delete(room) # returns value of what it deleted end +# Does the block conflict with another block? +# Is this being used?? def conflict?(other) return daterange.conflict?(other) end end end - -# def self.from_csv(filename) -# # a block is a group of 1-5 reservations with the same daterange and unique -# # room numbers. (eventually, this could scale to >5 unique room numbers.) -# # parse the list into blocks -# blocked_reservations = Reservation.from_csv(filename) # returns array of Reservations -# # parse the -# # create new Block from groups of reservations -# # return an array of Blocks -# Block.new() -# end diff --git a/lib/book.rb b/lib/book.rb index b7c244628..ad592129b 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -14,7 +14,7 @@ def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new) # luckily, it's already in a class of its own, so it's ready # to do that (rather than hotel having methods for loading etc.) @hotel = hotel - @reservations = Reservation.from_csv(TEST_RESERVATION_FILENAME) + @reservations = Reservation.from_csv(RESERVATION_DATA_FILENAME) @blocks = [] # a list of Blocks end diff --git a/lib/daterange.rb b/lib/daterange.rb index dd436cb93..b8e8fade0 100644 --- a/lib/daterange.rb +++ b/lib/daterange.rb @@ -1,12 +1,7 @@ -# Optional - for developer use -require "pry" -require "awesome_print" - module HotBook # The DateRange class compares and does calculations on other DateRanges # Refer to Date gem docu to understand what date format to use (i.e. y-m-d) class DateRange - require "date" attr_reader :start_date, :end_date def initialize(start_date:, end_date:) @@ -20,6 +15,7 @@ def duration return (end_date - start_date).to_i end +# Does daterange conflict with another daterange? def conflict?(other) if start_date >= other.end_date || end_date <= other.start_date return false @@ -28,19 +24,10 @@ def conflict?(other) end end -# !!!!Range only includes overnights and EXCLUDES checkout day!!!! +# Range only includes overnights and EXCLUDES checkout day. def to_range return (@start_date...@end_date) end - - # def match?(other) - # if start_date == other.start_date && end_date == other.end_date - # return true - # else - # return false - # end - # end - end end diff --git a/lib/errors.rb b/lib/errors.rb index aa769e03f..aef9a3b73 100644 --- a/lib/errors.rb +++ b/lib/errors.rb @@ -4,4 +4,7 @@ class RoomIsTakenError < StandardError class RoomIsBlockedError < StandardError end + + class BlockConflictError < StandardError + end end diff --git a/lib/hotel.rb b/lib/hotel.rb index 4a8b6ca0b..e2de6d8d7 100644 --- a/lib/hotel.rb +++ b/lib/hotel.rb @@ -1,13 +1,7 @@ -# Optional - for developer use -require "pry" -require "awesome_print" - module HotBook # The Hotel class is responsible for knowing about rooms and all rooms class Hotel - require "csv" - ROOM_NUMBERS_FILENAME = "data/room_numbers.csv" attr_reader :room_rate, :room_numbers, :rooms def initialize(room_rate: 200.0, room_numbers: ROOM_NUMBERS_FILENAME) @@ -16,16 +10,19 @@ def initialize(room_rate: 200.0, room_numbers: ROOM_NUMBERS_FILENAME) # do i even need room hash? or just room numbers? @rooms = load_rooms # array of hashes # a "room" Hash ==== {room_number: "String", room_rate: 200.0} + # if @rooms == nil || @rooms == [] + # raise StandardError, "Hotel has no rooms (failed to initalize)" + # end end def load_room_numbers(filename) CSV.open(filename).flat_map{ |line| line.map { |row| row.upcase }} end -# refactor with load CSV of rooms in order to test - def self.find_rate(room_number) +# refactor with load CSV of rooms in order to truly test + def find_rate(room_number) room_number = room_number.upcase - room = rooms.select{|room| room[:room_number] == room_number} + room = rooms.find {|room| room[:room_number] == room_number} return room[:room_rate] end diff --git a/lib/reservation.rb b/lib/reservation.rb index 31e0fc0b3..5490a6ee0 100644 --- a/lib/reservation.rb +++ b/lib/reservation.rb @@ -1,12 +1,6 @@ -# Optional - for developer use -require "pry" -require "awesome_print" - module HotBook # The Reservation class is responsible for calculating reservation cost class Reservation - require "csv" - require_relative "daterange" attr_reader :daterange, :room_number, :room_rate, :notes def initialize(daterange:, room_number:, room_rate:, notes: nil) @@ -16,6 +10,8 @@ def initialize(daterange:, room_number:, room_rate:, notes: nil) @notes = notes end +# Does the reservation conflict with another reservation? +# Is this being used?? def conflict?(other) return daterange.conflict? other.daterange end @@ -28,7 +24,7 @@ def cost return duration * room_rate end -# !!!!Range only includes overnights and EXCLUDES checkout day!!!! +# Range only includes overnights and EXCLUDES checkout day. def range return daterange.to_range end @@ -50,30 +46,3 @@ def self.from_csv(filename) end end end - -# Returns an array of hashes of all reservations - # def self.all(filename) - # return data = CSV.open(filename, headers:true).map { |reservation| - # p reservation - # self.new(daterange: HotBook::DateRange.new(start_date: reservation["start date"], - # end_date: reservation["end date"]), - # room_number: reservation["room number"], - # room_rate: reservation["room rate"], - # notes: reservation["notes"] ) - # } - # end - - # def self.list_by_date(date) - # raise ArgumentError, "Invalid date - use Date.parse (expected Date, " \ - # "not #{date.class})" unless date.is_a? Date - # return self.all.select {|reservation| reservation.range.include? date} - # end - - # def append(filename) - # CSV.open(filename, "a+") { |file| - # if file.first == nil - # file << @csv_headers - # end - # file << [self.daterange.start_date, self.daterange.end_date, - # self.room_number, self.room_rate, self.notes] } - # end diff --git a/spec/book_spec.rb b/spec/book_spec.rb index f6317f55b..bc991e3bd 100644 --- a/spec/book_spec.rb +++ b/spec/book_spec.rb @@ -50,7 +50,6 @@ end describe "new_block_reservation method" do - it "will remove the room from block's memo array of what's still reservable" do expect(current_block.available.size).must_equal 1 book.new_block_reservation(current_block) diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index 7d3e6cf1f..0ba29079a 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -50,9 +50,15 @@ end # TODO: AFter the CSV load function is added - describe "self.find_rate method" do - it "Hotel class method can look up the correct room" do +# write both these tests +##### THIS ISN'T REALLY TESTING THAT MUCH + describe "find_rate method" do + it "Hotel object can look up the correct room" do + expect(hotel.find_rate("3")).must_equal 200.0 end + # + # it "handles edge case @rooms = nil" do + # end end end diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb index 7bcfbb3e3..d47b04320 100644 --- a/spec/reservation_spec.rb +++ b/spec/reservation_spec.rb @@ -27,7 +27,7 @@ range = load_reservations[0].range checkout_date = Date.parse("apr_6") expect(range).must_be_instance_of Range - expect(range.include? checkout_date).must_equal false + expect(range.include?(checkout_date)).must_equal false end end @@ -49,7 +49,7 @@ describe "csv loading" do it "will correctly load lines of the CSV" do - blocknote = "This is the fifth reservation" + blocknote = "This is a block (private) reservation" expect(load_reservations[5].room_number).must_equal "1" expect(load_reservations[5].notes).must_equal blocknote expect(load_reservations.first.daterange.start_date).must_equal Date.parse("apr_1") diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ab16de102..bf4c53755 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,8 +10,6 @@ require "date" require "csv" -TEST_RESERVATION_FILENAME = "support/test_reservation_data.csv" - # Add lib files here (require_relative "../") require_relative "../hotbook.rb" From 097f6b7f5c9e047d26be2a51ed82d5cd4eb18f85 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 04:57:38 -0700 Subject: [PATCH 45/55] Tested many new edge cases in Book.rb --- lib/book.rb | 12 ++++++++++-- lib/errors.rb | 3 +++ spec/book_spec.rb | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/book.rb b/lib/book.rb index ad592129b..98eab9a9a 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -59,8 +59,12 @@ def new_block(daterange, rooms, discount_rate: 185.0) end def new_block_reservation(block, room_number: block.available.first) - validate(:room_number, room_number) validate(:block, block) + # raise error if the block has no available reservations + if block.available == [] || nil + raise NoRoomsAvailableError, "This block is fully booked" + end + validate(:room_number, room_number) room_number = room_number.upcase # make sure the room number is part of the block unless block.rooms.include?(room_number) @@ -81,7 +85,11 @@ def new_block_reservation(block, room_number: block.available.first) def suggest_room(daterange) validate(:daterange, daterange) - return public_avail_rooms(daterange).first + available = public_avail_rooms(daterange) + if available == nil || available == [] + raise HotBook::NoRoomsAvailableError, "All rooms are booked during this daterange" + end + return available.first end # Returns an array of reservations (EXCLUDING checkout day) diff --git a/lib/errors.rb b/lib/errors.rb index aef9a3b73..4865dc4e5 100644 --- a/lib/errors.rb +++ b/lib/errors.rb @@ -7,4 +7,7 @@ class RoomIsBlockedError < StandardError class BlockConflictError < StandardError end + + class NoRoomsAvailableError < StandardError + end end diff --git a/spec/book_spec.rb b/spec/book_spec.rb index bc991e3bd..3010abd38 100644 --- a/spec/book_spec.rb +++ b/spec/book_spec.rb @@ -13,6 +13,8 @@ let(:date) {Date.parse("apr_15")} let(:overlaprange) { HotBook::DateRange.new( start_date: "apr_08", end_date: "apr_16") } + let(:shortrange) { HotBook::DateRange.new( start_date: "may_01", + end_date: "may_02") } let(:current_block){book.blocks[0]} before do @@ -49,6 +51,22 @@ end end + describe "edge case extravaganza: being super sure you can't overbook" do + it "new reservation case" do + expect{ 21.times { book.new_reservation(shortrange) } }.must_raise HotBook::NoRoomsAvailableError + expect{book.new_reservation(shortrange, room_number: "1")}.must_raise HotBook::RoomIsTakenError + end + + it "new block case" do + expect{2.times { book.new_block(shortrange, rooms)} }.must_raise HotBook::BlockConflictError + end + + it "new_block_reservation case" do + new_block = book.new_block(shortrange, rooms) + expect{ 6.times{ book.new_block_reservation(new_block) } }.must_raise HotBook::NoRoomsAvailableError + end + end + describe "new_block_reservation method" do it "will remove the room from block's memo array of what's still reservable" do expect(current_block.available.size).must_equal 1 From d9b08063d56fcfe03b8475b9f93e286adc8a9028 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 04:59:22 -0700 Subject: [PATCH 46/55] Deleted pseudocode.rb --- lib/book.rb | 4 ---- pseudocode.md | 32 -------------------------------- spec/hotel_spec.rb | 1 - 3 files changed, 37 deletions(-) delete mode 100644 pseudocode.md diff --git a/lib/book.rb b/lib/book.rb index 98eab9a9a..a1402bcbe 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -9,10 +9,6 @@ class Book attr_reader :reservations, :hotel, :blocks def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new) - # but what if hotel changes? it only updates @ initialize time... - # then it needs to change to be treated like Reservation class - # luckily, it's already in a class of its own, so it's ready - # to do that (rather than hotel having methods for loading etc.) @hotel = hotel @reservations = Reservation.from_csv(RESERVATION_DATA_FILENAME) @blocks = [] # a list of Blocks diff --git a/pseudocode.md b/pseudocode.md deleted file mode 100644 index bdbffb6a1..000000000 --- a/pseudocode.md +++ /dev/null @@ -1,32 +0,0 @@ -## Wave Three: Blocks of Rooms - -If you are not familiar with what a block of hotel rooms, here is a brief description: - -> A Block Booking refers to a group of rooms set aside for a specific group of customers for a set period of time. -> -> Room blocks are commonly created for large events like weddings or conventions. A number of rooms are set aside, and are made available for reservation by certain customers at a discounted rate. These rooms are not available to be reserved by the general public. - -### User Stories - -- As an administrator, I can create a block of rooms - - To create a block you need a date range, collection of rooms and a discounted room rate - - The collection of rooms should only include rooms that are available for the given date range - - If a room is set aside in a block, it is not available for reservation by the general public, nor can it be included in another block -- As an administrator, I can check whether a given block has any rooms available -- As an administrator, I can reserve a room from within a block of rooms - -is a reservation a specialized block? - -a block takes: daterange, room numbers - (potentially hashes) -a reservation has status: public, block, -a book has a new method for making a block reservation - -the new_reservation method has to check - - -### Constraints - -- A block can contain a maximum of 5 rooms -- When a room is reserved from a block of rooms, the reservation dates will always match the date range of the block -- All of the availability checking logic from Wave 2 should now respect room blocks as well as individual reservations diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index 0ba29079a..cfeac2352 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -60,5 +60,4 @@ # it "handles edge case @rooms = nil" do # end end - end From 9249d5f7a1ec3f58611f0e60138e2fd38bbe18cd Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 05:20:52 -0700 Subject: [PATCH 47/55] Notes added to refactors.txt --- lib/book.rb | 38 ++++++++++++++++++++++++-------------- refactors.txt | 25 ++++++------------------- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/lib/book.rb b/lib/book.rb index a1402bcbe..5e841f7ab 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -3,7 +3,7 @@ module HotBook # holding all reservations and blocks, # searching through them, and # making new reservations and blocks. -# It includes support methods for determining availability. +# It includes support methods for determining availability/conflicts. class Book attr_reader :reservations, :hotel, :blocks @@ -19,9 +19,11 @@ def new_reservation(daterange, room_number: suggest_room(daterange)) validate(:room_number, room_number) room_number = room_number.upcase if room_taken?(daterange, room_number) - raise RoomIsTakenError, "Room is already reserved some time within this daterange" + raise RoomIsTakenError, "Room is already reserved some time within "\ + "this daterange" elsif room_blocked?(daterange, room_number) - raise RoomIsBlockedError, "Room is blocked some time within this daterange" + raise RoomIsBlockedError, "Room is blocked some time within this "\ + "daterange" end room_rate = hotel.find_rate(room_number) @@ -42,14 +44,16 @@ def new_block(daterange, rooms, discount_rate: 185.0) # Cannot overlap or conflict with existing reservation rooms.each do |room_number| if room_taken?(daterange, room_number) - raise RoomIsTakenError, "Room already has reservation during this daterange" + raise RoomIsTakenError, "Room already has reservation during "\ + "this daterange" elsif room_blocked?(daterange, room_number) - raise BlockConflictError, "A block already exists on a room during this daterange" + raise BlockConflictError, "A block already exists on a room during " \ + "this daterange" end end new_block = HotBook::Block.new(daterange: daterange, rooms: rooms, - room_rate: discount_rate) # Default discount + room_rate: discount_rate) # Default blocks << new_block return new_block end @@ -79,11 +83,13 @@ def new_block_reservation(block, room_number: block.available.first) return new_reservation end +# Returns the first room in the array of available rooms def suggest_room(daterange) validate(:daterange, daterange) available = public_avail_rooms(daterange) if available == nil || available == [] - raise HotBook::NoRoomsAvailableError, "All rooms are booked during this daterange" + raise HotBook::NoRoomsAvailableError, "All rooms are booked " \ + "during this daterange" end return available.first end @@ -91,13 +97,15 @@ def suggest_room(daterange) # Returns an array of reservations (EXCLUDING checkout day) def list_by_nights(date) validate(:date, date) - return reservations.select {|reservation| reservation.range.include?(date) } + return reservations.select {|reservation| + reservation.range.include?(date) } end -# Returns an array of room numbers that are publicly available during a daterange +# Returns array of room numbers that are publicly available during a daterange def public_avail_rooms(daterange) validate(:daterange, daterange) - a = conflicting_reservations(daterange).map { |reservation| reservation.room_number } + a = conflicting_reservations(daterange).map { |reservation| + reservation.room_number } b = conflicting_blocks(daterange).flat_map { |block| block.rooms } available_rooms = hotel.room_numbers - a - b return available_rooms @@ -108,7 +116,8 @@ def public_avail_rooms(daterange) def room_taken?(daterange, room_number) validate(:daterange, daterange) validate(:room_number, room_number) - a = conflicting_reservations(daterange).map { |reservation| reservation.room_number } + a = conflicting_reservations(daterange).map { |reservation| + reservation.room_number } return a.include?(room_number) end @@ -124,7 +133,8 @@ def room_blocked?(daterange, room_number) # Returns an array of reservations with a daterange conflict def conflicting_reservations(daterange) validate(:daterange, daterange) - return reservations.select { |reservation| reservation.daterange.conflict?(daterange) } + return reservations.select { |reservation| + reservation.daterange.conflict?(daterange) } end # Returns an array of blocks with a daterange conflict @@ -144,8 +154,8 @@ def validate(type, var) raise ArgumentError, "Invalid room number (expected String, " \ "not #{var.class})" unless var.is_a?(String) when :daterange - raise ArgumentError, "Invalid daterange (expected HotBook::DateRange, " \ - "not #{var.class})" unless var.is_a?(HotBook::DateRange) + raise ArgumentError, "Invalid daterange (expected HotBook::DateRange," \ + " not #{var.class})" unless var.is_a?(HotBook::DateRange) when :rooms raise ArgumentError, "Invalid rooms (expected Array of " \ "Strings)" unless var.is_a?(Array) && var.first.is_a?(String) diff --git a/refactors.txt b/refactors.txt index 3b9bd1e1e..d6753fac9 100644 --- a/refactors.txt +++ b/refactors.txt @@ -1,26 +1,13 @@ # hotel/refactors.txt -Notes: -data file: -Room hash -Reservation hash +1. Create "rooms" CSV and load into hotel class into hash. Unless rooms have a lot more information than just room number and rate, they don't have to become a Rooms class object yet. The Hotel class is named Hotel rather than Rooms so as not to be confusing. There's a tradeoff there. +2. More consistency in terms of when keyword arguments vs positional arguments are used. -Other classes -DateRange (has behavior and attributes) +3. Hotel class needs to be able to add and remove rooms. -it was really easy to move my .from_csv loader from Book to Reservation, make -its a self method, and tests still passed. (which is suspicious, but hey.) +4. Block class could parse CSV block data (which will have an identical format as CSV reservation data) into Blocks--or, another approach is for CSV block data to be loaded by Reservations, and for Block class to parse an array of Reservations into Blocks. -why "hotel" stores "rooms": -hotel should be renamed rooms. -what if the room information changes? what if we add a room? +5. Learn about how better to structure classes if they load CSVs similarly. Like FarMar? -inheritance seems complicated. -i wanted to make choices within what I know how to do. -otherwise, it's a can of worms. - - -### TODO: Make sure you can't book the same reservation twice - -# assumption: that there won't be that many blocks +6. Come up with a more methodical way to do spec tests that more carefully ensures date/reservation/block conflicts are all caught as they should be. From 6addaa5abcfc59cf19cd40521082266f807aba9c Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 10:06:56 -0700 Subject: [PATCH 48/55] Added new reservation nominal case in book sepc --- spec/book_spec.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/book_spec.rb b/spec/book_spec.rb index 3010abd38..f666dd447 100644 --- a/spec/book_spec.rb +++ b/spec/book_spec.rb @@ -52,7 +52,12 @@ end describe "edge case extravaganza: being super sure you can't overbook" do - it "new reservation case" do + it "new reservation nominal case" do + 20.times{book.new_reservation(shortrange)} + expect(book.reservations.last.room_number).must_equal "20" + end + + it "new reservation edge case" do expect{ 21.times { book.new_reservation(shortrange) } }.must_raise HotBook::NoRoomsAvailableError expect{book.new_reservation(shortrange, room_number: "1")}.must_raise HotBook::RoomIsTakenError end From cf6fff86efe363351375a358a11adaaa1eff77f6 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 04:59:22 -0700 Subject: [PATCH 49/55] Deleted pseudocode.md --- lib/book.rb | 4 ---- pseudocode.md | 32 -------------------------------- spec/hotel_spec.rb | 1 - 3 files changed, 37 deletions(-) delete mode 100644 pseudocode.md diff --git a/lib/book.rb b/lib/book.rb index 98eab9a9a..a1402bcbe 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -9,10 +9,6 @@ class Book attr_reader :reservations, :hotel, :blocks def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new) - # but what if hotel changes? it only updates @ initialize time... - # then it needs to change to be treated like Reservation class - # luckily, it's already in a class of its own, so it's ready - # to do that (rather than hotel having methods for loading etc.) @hotel = hotel @reservations = Reservation.from_csv(RESERVATION_DATA_FILENAME) @blocks = [] # a list of Blocks diff --git a/pseudocode.md b/pseudocode.md deleted file mode 100644 index bdbffb6a1..000000000 --- a/pseudocode.md +++ /dev/null @@ -1,32 +0,0 @@ -## Wave Three: Blocks of Rooms - -If you are not familiar with what a block of hotel rooms, here is a brief description: - -> A Block Booking refers to a group of rooms set aside for a specific group of customers for a set period of time. -> -> Room blocks are commonly created for large events like weddings or conventions. A number of rooms are set aside, and are made available for reservation by certain customers at a discounted rate. These rooms are not available to be reserved by the general public. - -### User Stories - -- As an administrator, I can create a block of rooms - - To create a block you need a date range, collection of rooms and a discounted room rate - - The collection of rooms should only include rooms that are available for the given date range - - If a room is set aside in a block, it is not available for reservation by the general public, nor can it be included in another block -- As an administrator, I can check whether a given block has any rooms available -- As an administrator, I can reserve a room from within a block of rooms - -is a reservation a specialized block? - -a block takes: daterange, room numbers - (potentially hashes) -a reservation has status: public, block, -a book has a new method for making a block reservation - -the new_reservation method has to check - - -### Constraints - -- A block can contain a maximum of 5 rooms -- When a room is reserved from a block of rooms, the reservation dates will always match the date range of the block -- All of the availability checking logic from Wave 2 should now respect room blocks as well as individual reservations diff --git a/spec/hotel_spec.rb b/spec/hotel_spec.rb index 0ba29079a..cfeac2352 100644 --- a/spec/hotel_spec.rb +++ b/spec/hotel_spec.rb @@ -60,5 +60,4 @@ # it "handles edge case @rooms = nil" do # end end - end From 2ac71d81a740a8d5c253de8257bbb0ce2a30f237 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 05:20:52 -0700 Subject: [PATCH 50/55] Notes added to refactors.txt --- lib/book.rb | 38 ++++++++++++++++++++++++-------------- refactors.txt | 25 ++++++------------------- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/lib/book.rb b/lib/book.rb index a1402bcbe..5e841f7ab 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -3,7 +3,7 @@ module HotBook # holding all reservations and blocks, # searching through them, and # making new reservations and blocks. -# It includes support methods for determining availability. +# It includes support methods for determining availability/conflicts. class Book attr_reader :reservations, :hotel, :blocks @@ -19,9 +19,11 @@ def new_reservation(daterange, room_number: suggest_room(daterange)) validate(:room_number, room_number) room_number = room_number.upcase if room_taken?(daterange, room_number) - raise RoomIsTakenError, "Room is already reserved some time within this daterange" + raise RoomIsTakenError, "Room is already reserved some time within "\ + "this daterange" elsif room_blocked?(daterange, room_number) - raise RoomIsBlockedError, "Room is blocked some time within this daterange" + raise RoomIsBlockedError, "Room is blocked some time within this "\ + "daterange" end room_rate = hotel.find_rate(room_number) @@ -42,14 +44,16 @@ def new_block(daterange, rooms, discount_rate: 185.0) # Cannot overlap or conflict with existing reservation rooms.each do |room_number| if room_taken?(daterange, room_number) - raise RoomIsTakenError, "Room already has reservation during this daterange" + raise RoomIsTakenError, "Room already has reservation during "\ + "this daterange" elsif room_blocked?(daterange, room_number) - raise BlockConflictError, "A block already exists on a room during this daterange" + raise BlockConflictError, "A block already exists on a room during " \ + "this daterange" end end new_block = HotBook::Block.new(daterange: daterange, rooms: rooms, - room_rate: discount_rate) # Default discount + room_rate: discount_rate) # Default blocks << new_block return new_block end @@ -79,11 +83,13 @@ def new_block_reservation(block, room_number: block.available.first) return new_reservation end +# Returns the first room in the array of available rooms def suggest_room(daterange) validate(:daterange, daterange) available = public_avail_rooms(daterange) if available == nil || available == [] - raise HotBook::NoRoomsAvailableError, "All rooms are booked during this daterange" + raise HotBook::NoRoomsAvailableError, "All rooms are booked " \ + "during this daterange" end return available.first end @@ -91,13 +97,15 @@ def suggest_room(daterange) # Returns an array of reservations (EXCLUDING checkout day) def list_by_nights(date) validate(:date, date) - return reservations.select {|reservation| reservation.range.include?(date) } + return reservations.select {|reservation| + reservation.range.include?(date) } end -# Returns an array of room numbers that are publicly available during a daterange +# Returns array of room numbers that are publicly available during a daterange def public_avail_rooms(daterange) validate(:daterange, daterange) - a = conflicting_reservations(daterange).map { |reservation| reservation.room_number } + a = conflicting_reservations(daterange).map { |reservation| + reservation.room_number } b = conflicting_blocks(daterange).flat_map { |block| block.rooms } available_rooms = hotel.room_numbers - a - b return available_rooms @@ -108,7 +116,8 @@ def public_avail_rooms(daterange) def room_taken?(daterange, room_number) validate(:daterange, daterange) validate(:room_number, room_number) - a = conflicting_reservations(daterange).map { |reservation| reservation.room_number } + a = conflicting_reservations(daterange).map { |reservation| + reservation.room_number } return a.include?(room_number) end @@ -124,7 +133,8 @@ def room_blocked?(daterange, room_number) # Returns an array of reservations with a daterange conflict def conflicting_reservations(daterange) validate(:daterange, daterange) - return reservations.select { |reservation| reservation.daterange.conflict?(daterange) } + return reservations.select { |reservation| + reservation.daterange.conflict?(daterange) } end # Returns an array of blocks with a daterange conflict @@ -144,8 +154,8 @@ def validate(type, var) raise ArgumentError, "Invalid room number (expected String, " \ "not #{var.class})" unless var.is_a?(String) when :daterange - raise ArgumentError, "Invalid daterange (expected HotBook::DateRange, " \ - "not #{var.class})" unless var.is_a?(HotBook::DateRange) + raise ArgumentError, "Invalid daterange (expected HotBook::DateRange," \ + " not #{var.class})" unless var.is_a?(HotBook::DateRange) when :rooms raise ArgumentError, "Invalid rooms (expected Array of " \ "Strings)" unless var.is_a?(Array) && var.first.is_a?(String) diff --git a/refactors.txt b/refactors.txt index 3b9bd1e1e..d6753fac9 100644 --- a/refactors.txt +++ b/refactors.txt @@ -1,26 +1,13 @@ # hotel/refactors.txt -Notes: -data file: -Room hash -Reservation hash +1. Create "rooms" CSV and load into hotel class into hash. Unless rooms have a lot more information than just room number and rate, they don't have to become a Rooms class object yet. The Hotel class is named Hotel rather than Rooms so as not to be confusing. There's a tradeoff there. +2. More consistency in terms of when keyword arguments vs positional arguments are used. -Other classes -DateRange (has behavior and attributes) +3. Hotel class needs to be able to add and remove rooms. -it was really easy to move my .from_csv loader from Book to Reservation, make -its a self method, and tests still passed. (which is suspicious, but hey.) +4. Block class could parse CSV block data (which will have an identical format as CSV reservation data) into Blocks--or, another approach is for CSV block data to be loaded by Reservations, and for Block class to parse an array of Reservations into Blocks. -why "hotel" stores "rooms": -hotel should be renamed rooms. -what if the room information changes? what if we add a room? +5. Learn about how better to structure classes if they load CSVs similarly. Like FarMar? -inheritance seems complicated. -i wanted to make choices within what I know how to do. -otherwise, it's a can of worms. - - -### TODO: Make sure you can't book the same reservation twice - -# assumption: that there won't be that many blocks +6. Come up with a more methodical way to do spec tests that more carefully ensures date/reservation/block conflicts are all caught as they should be. From 2f55f2d56c93899c638ce3f038b73838b3927dce Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Mon, 10 Sep 2018 10:06:56 -0700 Subject: [PATCH 51/55] Added new reservation nominal case in book sepc --- spec/book_spec.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/book_spec.rb b/spec/book_spec.rb index 3010abd38..f666dd447 100644 --- a/spec/book_spec.rb +++ b/spec/book_spec.rb @@ -52,7 +52,12 @@ end describe "edge case extravaganza: being super sure you can't overbook" do - it "new reservation case" do + it "new reservation nominal case" do + 20.times{book.new_reservation(shortrange)} + expect(book.reservations.last.room_number).must_equal "20" + end + + it "new reservation edge case" do expect{ 21.times { book.new_reservation(shortrange) } }.must_raise HotBook::NoRoomsAvailableError expect{book.new_reservation(shortrange, room_number: "1")}.must_raise HotBook::RoomIsTakenError end From 3959abea322d143a8fe8c3effd3d871163ab1744 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Tue, 11 Sep 2018 22:18:50 -0700 Subject: [PATCH 52/55] Tagged a todo - inconsistency: daterange.conflict?(other) vs. block.conflict?(daterange) Pick one, and update both the class method and the method in book.rb that depends on it. --- lib/book.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/book.rb b/lib/book.rb index 5e841f7ab..e39a04fcb 100644 --- a/lib/book.rb +++ b/lib/book.rb @@ -137,6 +137,7 @@ def conflicting_reservations(daterange) reservation.daterange.conflict?(daterange) } end + #TODO: There's an inconsistency here-- daterange.conflict?(other) vs. block.conflict?(daterange)--PICK ONE! # Returns an array of blocks with a daterange conflict def conflicting_blocks(daterange) validate(:daterange, daterange) From ed6bead38a889096b1bd412a3a6553a8863c588b Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sat, 29 Sep 2018 20:14:55 -0700 Subject: [PATCH 53/55] Renamed Book class to BookingsManager --- hotbook.rb | 2 +- lib/{book.rb => bookingsmanager.rb} | 4 +- lib/daterange.rb | 1 + refactors.txt | 2 +- .../{book_spec.rb => bookingsmanager_spec.rb} | 92 +++++++++---------- 5 files changed, 51 insertions(+), 50 deletions(-) rename lib/{book.rb => bookingsmanager.rb} (98%) rename spec/{book_spec.rb => bookingsmanager_spec.rb} (51%) diff --git a/hotbook.rb b/hotbook.rb index 7999187d9..aeb542c30 100644 --- a/hotbook.rb +++ b/hotbook.rb @@ -17,7 +17,7 @@ module HotBook; # all of the classes that live in the module require_relative "lib/block.rb" -require_relative "lib/book.rb" +require_relative "lib/bookingsmanager.rb" require_relative "lib/daterange.rb" require_relative "lib/errors.rb" require_relative "lib/hotel.rb" diff --git a/lib/book.rb b/lib/bookingsmanager.rb similarity index 98% rename from lib/book.rb rename to lib/bookingsmanager.rb index 5e841f7ab..c16747c2d 100644 --- a/lib/book.rb +++ b/lib/bookingsmanager.rb @@ -1,11 +1,11 @@ module HotBook -# The Book class is responsible for: +# The BookingsManager class is responsible for: # holding all reservations and blocks, # searching through them, and # making new reservations and blocks. # It includes support methods for determining availability/conflicts. - class Book + class BookingsManager attr_reader :reservations, :hotel, :blocks def initialize(hotel) # expects a dependency injection (HotBook::Hotel.new) diff --git a/lib/daterange.rb b/lib/daterange.rb index b8e8fade0..1eb78f6d0 100644 --- a/lib/daterange.rb +++ b/lib/daterange.rb @@ -25,6 +25,7 @@ def conflict?(other) end # Range only includes overnights and EXCLUDES checkout day. +# should change this to "contains(date)" def to_range return (@start_date...@end_date) end diff --git a/refactors.txt b/refactors.txt index d6753fac9..e1f691452 100644 --- a/refactors.txt +++ b/refactors.txt @@ -1,4 +1,4 @@ -# hotel/refactors.txt +# hotbook/refactors.txt 1. Create "rooms" CSV and load into hotel class into hash. Unless rooms have a lot more information than just room number and rate, they don't have to become a Rooms class object yet. The Hotel class is named Hotel rather than Rooms so as not to be confusing. There's a tradeoff there. diff --git a/spec/book_spec.rb b/spec/bookingsmanager_spec.rb similarity index 51% rename from spec/book_spec.rb rename to spec/bookingsmanager_spec.rb index f666dd447..0896bd946 100644 --- a/spec/book_spec.rb +++ b/spec/bookingsmanager_spec.rb @@ -1,25 +1,25 @@ require_relative "spec_helper" -describe "HotBook::Book class" do +describe "HotBook::BookingsManager class" do let(:hotel) { HotBook::Hotel.new } - let(:book) { HotBook::Book.new(hotel) } + let(:bookingsmanager) { HotBook::BookingsManager.new(hotel) } let(:daterange) { HotBook::DateRange.new( start_date: "apr_15", end_date: "apr_30") } - let(:new_reservation) { book.new_reservation(daterange) } + let(:new_reservation) { bookingsmanager.new_reservation(daterange) } let(:load_reservations) { hotel.reservations } let(:block) { HotBook::Block.new(daterange, ["1", "2", "3", "4", "5"]) } let(:rooms) { ["1", "2", "3", "4", "5"] } - let(:load_block) { book.new_block(daterange, rooms) } + let(:load_block) { bookingsmanager.new_block(daterange, rooms) } let(:date) {Date.parse("apr_15")} let(:overlaprange) { HotBook::DateRange.new( start_date: "apr_08", end_date: "apr_16") } let(:shortrange) { HotBook::DateRange.new( start_date: "may_01", end_date: "may_02") } - let(:current_block){book.blocks[0]} + let(:current_block){bookingsmanager.blocks[0]} before do # Test reservation data is loaded into @reservations: - book + bookingsmanager # A single block, rooms 1-5, 4/15-4/30, is loaded into @blocks: block = load_block @@ -34,118 +34,118 @@ describe "new_reservation method" do it "creates a Reservation object and stores in @reservations" do expect(new_reservation).must_be_instance_of HotBook::Reservation - expect(book.reservations.size).must_equal 11 - 3.times {book.new_reservation(daterange) } - expect(book.reservations.size).must_equal 11 + 3 + expect(bookingsmanager.reservations.size).must_equal 11 + 3.times {bookingsmanager.new_reservation(daterange) } + expect(bookingsmanager.reservations.size).must_equal 11 + 3 end it "raises unique errors if room is booked or blocked during daterange" do - expect{book.new_reservation(daterange, room_number: "6")}.must_raise HotBook::RoomIsTakenError - expect{book.new_reservation(daterange, room_number: "1")}.must_raise HotBook::RoomIsBlockedError + expect{bookingsmanager.new_reservation(daterange, room_number: "6")}.must_raise HotBook::RoomIsTakenError + expect{bookingsmanager.new_reservation(daterange, room_number: "1")}.must_raise HotBook::RoomIsBlockedError end end describe "suggest_room method" do it "returns the first publicly available room number that isn't booked or blocked" do - expect(book.suggest_room(daterange)).must_equal "7" + expect(bookingsmanager.suggest_room(daterange)).must_equal "7" end end describe "edge case extravaganza: being super sure you can't overbook" do it "new reservation nominal case" do - 20.times{book.new_reservation(shortrange)} - expect(book.reservations.last.room_number).must_equal "20" + 20.times{bookingsmanager.new_reservation(shortrange)} + expect(bookingsmanager.reservations.last.room_number).must_equal "20" end it "new reservation edge case" do - expect{ 21.times { book.new_reservation(shortrange) } }.must_raise HotBook::NoRoomsAvailableError - expect{book.new_reservation(shortrange, room_number: "1")}.must_raise HotBook::RoomIsTakenError + expect{ 21.times { bookingsmanager.new_reservation(shortrange) } }.must_raise HotBook::NoRoomsAvailableError + expect{bookingsmanager.new_reservation(shortrange, room_number: "1")}.must_raise HotBook::RoomIsTakenError end it "new block case" do - expect{2.times { book.new_block(shortrange, rooms)} }.must_raise HotBook::BlockConflictError + expect{2.times { bookingsmanager.new_block(shortrange, rooms)} }.must_raise HotBook::BlockConflictError end it "new_block_reservation case" do - new_block = book.new_block(shortrange, rooms) - expect{ 6.times{ book.new_block_reservation(new_block) } }.must_raise HotBook::NoRoomsAvailableError + new_block = bookingsmanager.new_block(shortrange, rooms) + expect{ 6.times{ bookingsmanager.new_block_reservation(new_block) } }.must_raise HotBook::NoRoomsAvailableError end end describe "new_block_reservation method" do it "will remove the room from block's memo array of what's still reservable" do expect(current_block.available.size).must_equal 1 - book.new_block_reservation(current_block) + bookingsmanager.new_block_reservation(current_block) expect(current_block.available.size).must_equal 0 end it "will raise an error if the room number given is not in the given block" do - expect{book.new_block_reservation(current_block, room_number: "20")}.must_raise ArgumentError + expect{bookingsmanager.new_block_reservation(current_block, room_number: "20")}.must_raise ArgumentError end it "will raise an error if the room is in the block, but already booked" do - expect{book.new_block_reservation(current_block, room_number: "1")}.must_raise HotBook::RoomIsTakenError + expect{bookingsmanager.new_block_reservation(current_block, room_number: "1")}.must_raise HotBook::RoomIsTakenError end it "will add the new reservation to the @reservations array" do - expect(book.reservations.last.room_number).must_equal "6" - book.new_block_reservation(current_block) - expect(book.reservations.last.room_number).must_equal "5" + expect(bookingsmanager.reservations.last.room_number).must_equal "6" + bookingsmanager.new_block_reservation(current_block) + expect(bookingsmanager.reservations.last.room_number).must_equal "5" end end describe "new_block method" do it "cannot overlap or conflict with an existing block" do - expect{book.new_block(daterange, rooms)}.must_raise HotBook::BlockConflictError + expect{bookingsmanager.new_block(daterange, rooms)}.must_raise HotBook::BlockConflictError end it "cannot overlap or conflict with an existing reservation" do - expect{book.new_block(overlaprange, rooms)}.must_raise HotBook::RoomIsTakenError - expect{book.new_block(daterange, ["6"])}.must_raise HotBook::RoomIsTakenError + expect{bookingsmanager.new_block(overlaprange, rooms)}.must_raise HotBook::RoomIsTakenError + expect{bookingsmanager.new_block(daterange, ["6"])}.must_raise HotBook::RoomIsTakenError end it "cannot contain more than 5 rooms" do bad_rooms = %w(1 2 3 4 5 6) - expect{book.new_block(daterange, bad_rooms)}.must_raise ArgumentError + expect{bookingsmanager.new_block(daterange, bad_rooms)}.must_raise ArgumentError end end describe "list_by_nights method" do it "returns an array of reservations excluding checkout date" do - expect(book.list_by_nights(date).size).must_equal 1 - expect(book.list_by_nights(date)[0].room_number).must_equal "6" + expect(bookingsmanager.list_by_nights(date).size).must_equal 1 + expect(bookingsmanager.list_by_nights(date)[0].room_number).must_equal "6" end end describe "public_avail_rooms method" do it "returns an array of room numbers that aren't booked or blocked" do list = %w(7 8 9 10 11 12 13 14 15 16 17 18 19 20) - expect(book.public_avail_rooms(overlaprange)).must_equal list - book.new_reservation(overlaprange) + expect(bookingsmanager.public_avail_rooms(overlaprange)).must_equal list + bookingsmanager.new_reservation(overlaprange) list.shift - expect(book.public_avail_rooms(overlaprange)).must_equal list + expect(bookingsmanager.public_avail_rooms(overlaprange)).must_equal list end end describe "room_taken? method" do it "given daterange, lists rezzies with conflict, " \ "and tells you if the given room is in that list" do - expect(book.room_taken?(daterange, "6")).must_equal true - expect(book.room_taken?(daterange, "1")).must_equal false + expect(bookingsmanager.room_taken?(daterange, "6")).must_equal true + expect(bookingsmanager.room_taken?(daterange, "1")).must_equal false end end describe "room_blocked? method" do it "given daterange, lists blocks with conflict, " \ "and tells you if the given room is in that list" do - expect(book.room_blocked?(daterange, "1")).must_equal true - expect(book.room_blocked?(daterange, "6")).must_equal false + expect(bookingsmanager.room_blocked?(daterange, "1")).must_equal true + expect(bookingsmanager.room_blocked?(daterange, "6")).must_equal false end end describe "conflicting_reservations method" do it "returns an array of reservations with a daterange conflict" do - array = book.conflicting_reservations(daterange) + array = bookingsmanager.conflicting_reservations(daterange) expect(array.size).must_equal 1 expect(array[0]).must_be_instance_of HotBook::Reservation end @@ -153,7 +153,7 @@ describe "conflicting_blocks method" do it "returns an array of blocks with a daterange conflict" do - array = book.conflicting_blocks(daterange) + array = bookingsmanager.conflicting_blocks(daterange) expect(array.size).must_equal 1 expect(array[0]).must_be_instance_of HotBook::Block end @@ -167,12 +167,12 @@ bad_rooms = [] another_bad_rooms = nil bad_block = nil - expect{book.new_reservation(bad_range)}.must_raise ArgumentError - expect{book.new_reservation(daterange, room_number: bad_room_number)}.must_raise ArgumentError - expect{book.list_by_nights(bad_date)}.must_raise ArgumentError - expect{book.new_block(daterange, bad_rooms)}.must_raise ArgumentError - expect{book.new_block(daterange, another_bad_rooms)}.must_raise ArgumentError - expect{book.new_block_reservation(bad_block, room_number: "2")}.must_raise ArgumentError + expect{bookingsmanager.new_reservation(bad_range)}.must_raise ArgumentError + expect{bookingsmanager.new_reservation(daterange, room_number: bad_room_number)}.must_raise ArgumentError + expect{bookingsmanager.list_by_nights(bad_date)}.must_raise ArgumentError + expect{bookingsmanager.new_block(daterange, bad_rooms)}.must_raise ArgumentError + expect{bookingsmanager.new_block(daterange, another_bad_rooms)}.must_raise ArgumentError + expect{bookingsmanager.new_block_reservation(bad_block, room_number: "2")}.must_raise ArgumentError end end end From 64be84646f86b5f326f13e06c4eb3fdd175b9fb6 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sat, 29 Sep 2018 20:36:36 -0700 Subject: [PATCH 54/55] Added new items to refactors list --- refactors.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/refactors.txt b/refactors.txt index e1f691452..8d2708631 100644 --- a/refactors.txt +++ b/refactors.txt @@ -1,5 +1,17 @@ # hotbook/refactors.txt +1. Creating a block should not require user to make a list of rooms - make a helper method to find a list of rooms. + +2. Consider suggestion for block class to contain "reservations" array. + +3. Refactor/expand spec tests per suggestions. + +4. Could all classes inherit from a CSVLoader class? + + + + +## Previous refactors: 1. Create "rooms" CSV and load into hotel class into hash. Unless rooms have a lot more information than just room number and rate, they don't have to become a Rooms class object yet. The Hotel class is named Hotel rather than Rooms so as not to be confusing. There's a tradeoff there. 2. More consistency in terms of when keyword arguments vs positional arguments are used. From c884592e95d75e3c52708504f86c8cf15784b565 Mon Sep 17 00:00:00 2001 From: Sammi-Jo Lee Date: Sun, 30 Sep 2018 19:59:16 -0700 Subject: [PATCH 55/55] Added design-activity.md --- design-activity.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 design-activity.md diff --git a/design-activity.md b/design-activity.md new file mode 100644 index 000000000..929d8a07c --- /dev/null +++ b/design-activity.md @@ -0,0 +1,59 @@ +# Design Activity +https://github.com/Ada-Developers-Academy/textbook-curriculum/blob/master/02-intermediate-ruby/exercises/hotel-revisited.md + +## Prompts + +#### What classes does each implementation include? Are the lists the same? +Both implementations have the same three classes: `CartEntry`, `ShoppingCart`, and `Order`. + +#### Write down a sentence to describe each class. +| Class | A | B | +| :------------ | :------------- | :------------- | +| CartEntry | **State:** knows its unit price and its quantity. | **State:** knows its unit price and its quantity.

**Behavior:** can calculate its own price. | +| ShoppingCart | **State:** stores an array of CartEntries. | **State:** stores an array of CartEntries.

**Behavior:** can ask each CartEntry for its price, and then calculate the subtotal (price of all entries in the array). | +| Order | **State:** knows the SALES TAX constant, stores an instance of the ShoppingCart class.

**Behavior:** can calculate a CartEntries price, can calculate a ShoppingCart's price, and can assemble the two into a total price. | **State:** knows the SALES TAX constant, stores an instance of the ShoppingCart class.

**Behavior:** can ask the ShoppingCart for its price (subtotal), and then calculate the total price. | + +("stores" == "knows") + +#### How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper. + +The difference between A and B is that the Order class is concerned with *how* (A) versus *what* (B). + +In A, the Order class knows too much outside of its "jurisdiction" -- it knows *how* to calculate CartEntry's price and *how* to calculate ShoppingCart's price. In A, classes are tightly coupled. + +In B, the Order class asks CartEntry *what* its price is, asks ShoppingCart *what* its price is, and, from there, knows *how* to calculate a total price. In B, classes are loosely coupled. + +#### What **data** does each class store? How (if at all) does this differ between the two implementations? +Classes store the **State** described in the above table. + +#### What **methods** does each class have? How (if at all) does this differ between the two implementations? +Classes have methods for the **Behavior** described in the above table. + +#### 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`? + +A: the latter. B: the former. + + - Does `total_price` directly manipulate the instance variables of other classes? + +A: yes. B: no. + +#### If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify? + +In the real world, like on Zazzle.com or CafePress.com, items are likely to each have unique wholesale price brackets based on quantity (though I suppose it could be something like 10% off if you spend $100 or more). Going with the former, we'd do this by adding some data - probably a price_table of quantities and prices, either an array of arrays or an array of hashes. + +To modify A, CartEntry's unit_price has to be changed to price_table, and Order's total_price method has to be changed to include an enumerable method to look up a price by a quantity. + +To modify B, CartEntry's unit_price also has to be changed to price_table, and CartEntry's price method also has to be changed to look up a price by a quantity. + +B is easier to modify because changes in one class do not necessitate changes in another class. The person making the change doesn't have to hunt through the code to figure out effects of the change. The change is more proportional to the cost of change. There's less risk of far-off, hidden, unwanted effects of change. + +#### Which implementation better adheres to the single responsibility principle? + +I think B does, but I also think that classes in both A and B are *seemingly* single-responsibility. In both A and B, CartEntry has price and quantity, ShoppingCart has entries, and Order has a cart and a total price. + +However, I think B is the better answer because A's CartEntry can and should shift the responsibility of knowing about the instance variables of other classes from itself to the classes in question. + +#### Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled? + +B!