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/design-activity.md b/design-activity.md new file mode 100644 index 000000000..246e9ab4d --- /dev/null +++ b/design-activity.md @@ -0,0 +1,30 @@ +## Evaluating Responsibility + +* What classes does each implementation include? Are the lists the same? | Each has the same classes, CartEntry, ShoppingCart, and Order, but the methods have been reorganized in the second implementation. +* Write down a sentence to describe each class. | + * CartEntry handles an item added, taking in as parameters its unit price and quantity. It also calculates the price of that CartEntry instance. + * ShoppingCart manages the list of entries and price of all entries combined. + * Order stores the constant SALES_TAX and totals the sum of the ShoppingCart plus Sales tax. +* How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper. + * Order totals the cost of the ShoppingCart which has a collection of instances of CartEntry. +* What data does each class store? How (if at all) does this differ between the two implementations? + * CartEntry stores the instance variables unit_price and quantity, which are only used in CartEntry. In the second implementation, it does not have an attribute accessor. + * ShoppingCart stores the instance variable entries which is a collection of CartEntry instances. In the second implementation, it does not have an attribute accessor. + * Order stores the constant SALES_TAX. It stores the instance of ShoppingCart. +* What methods does each class have? How (if at all) does this differ between the two implementations? + * Each has an initialize, but they no longer as attribute reader/writers. The Order total_price method no longer calls instance variables from other classes. It only calls an instance variable of ShoppingCart, which was initialized in Order. +* 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? + * Computing the price is retained in Order. + * Does total_price directly manipulate the instance variables of other classes? + * No, only those directly called in Order. +* If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify? + * This would be easier to change in Implementation B because we would only need to change what is passed into CartEntry unit price and/or quantity. +* Which implementation better adheres to the single responsibility principle? + * Implementation B better adheres to single responsibility because each class only operates on and calculates its own instance variables. +* Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled? + * Implementation B is more loosely coupled. It has less instances of directly calling/naming an instance variable or attribute from other classes or variables. + +## Revisiting Hotel +Identify one place in your Hotel project where a class takes on multiple roles, or directly modifies the attributes of another class. Describe in design-activity.md what changes you would need to make to improve this design, and how the resulting design would be an improvement. +BookingManager class takes on multiple roles, some of which are acting on (or creating) attributes of class Reservation. I would need to move several of the functions from BookingManager to Reservation. Making this change would allow my code to more so follow Single Responsibility. diff --git a/lib/booking_manager.rb b/lib/booking_manager.rb new file mode 100644 index 000000000..8ec0d3be5 --- /dev/null +++ b/lib/booking_manager.rb @@ -0,0 +1,187 @@ +require_relative 'room' +require_relative 'reservation' +# require 'pry' + +module Hotel + class BookingManager + attr_accessor :rooms, :reservations, :room_calendar + + def initialize(number_rooms) + @rooms = populate_room_list(number_rooms) #(20) + @reservations = make_reservation_list + @room_calendar = make_room_calendar(number_rooms) + end + + # Create list of rooms as list of room Instances + def populate_room_list(number_rooms) + rooms = [] + num = 1 + + number_rooms.times do |room| + room = Room.new(num) + # + rooms << room + num += 1 + end + + return rooms + end + + + # Create array to store all of reservations + def make_reservation_list + reservations = [] + return reservations + end + + + # Create list of rooms with reserved dates. + def make_room_calendar(number) + @room_calendar = {} + + @rooms.each do |room| + dates_reserved = {} + @room_calendar[room] = dates_reserved + end + + return @room_calendar + end + + + # Method to add a reservation to list of reservations + # def add_reservation(reservation) #combine with add_reservation_to_calendar? + # @reservations << reservation + # end + + + # Add reservation date range to hash of room reserved dates + def add_reservation_to_calendar(reservation) + date = reservation.start_date + @reservations << reservation + + reservation.number_nights.times do + @room_calendar[reservation.room][date] = reservation # dependency + date += 1 + end + +# binding.pry + return @room_calendar + end + + + # Check if date range given is valid - start must be before end + def check_dates(start_date, end_date) + if start_date > end_date + raise ArgumentError.new "Invalid date range. Start date must be before end date, both in format of 'Month dd, yyyy'. " + end + end + + + # Create array of all dates from start date to end date + def determine_date_range(start_date, end_date) + check_dates(start_date, end_date) + + res_start_date = Date.parse(start_date) + res_end_date = Date.parse(end_date) + + date_range = [] + search_date = res_start_date + + until search_date > res_end_date + date_range << search_date + search_date += 1 + end + return date_range + end + + # Method to list all reservation instances + def list_reservations + return @reservations + end + + + # Method to list all rooms in hotel + def list_rooms + return @rooms + end + + # # Method to get total cost of reservation + # def get_reservation_cost(nights, cost_per_night) + # total_cost = nights * cost_per_night + # return total_cost + # end + + + # Return array reservations with matching date from room calendar hash + def find_reservations_on_date(date, calendar) + search_date = Date.parse(date) + found_reservations = [] + + calendar.each do |room, info| + info.each do |date, reservation| + if date == search_date + found_reservations << reservation + else + next + end + end + end + # Add return message for no reservations found? + return found_reservations + end + + def find_vacancies_on_date(date, calendar) + search_date = Date.parse(date) + found_vacancies = [] + + calendar.each do |room, info| + if info == nil + found_vacancies << room + else + info.any? {|date, reservation| date == search_date}? next : found_vacancies << room + end + end + + return found_vacancies + #.empty? ? no_vacancies_message: found_vacancies + end + + + def find_vacancies_in_date_range(start_date, end_date) + check_dates(start_date, end_date) + date_range = determine_date_range(start_date, end_date) + rooms_available_in_date_range = [] + + @room_calendar.each do |room, reserved_dates| + if reserved_dates.empty? + rooms_available_in_date_range << room + else + if (reserved_dates.keys && date_range).length > 0 + next + else + rooms_available_in_date_range << room + end + end + end + + return rooms_available_in_date_range + end + + + # def no_vacancies_message + # return "There are no vacancies for the given date range." + # end + + + def reserve_available_room(guest_name, start_date, end_date) + check_dates(start_date, end_date) + available_rooms = find_vacancies_in_date_range(start_date, end_date) + + new_reservation = Reservation.new(available_rooms.first, guest_name: guest_name, start_date: start_date, end_date: end_date) + # add_reservation(new_reservation) + add_reservation_to_calendar(new_reservation) + return new_reservation #unneeded? + end + + end +end diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..971b452b9 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,29 @@ +module Hotel + class Reservation + attr_reader :room, :number_nights + attr_accessor :guest_name, :start_date, :end_date, :cost_per_night + + def initialize(room, guest_name:, start_date:, end_date:, cost_per_night: 200.00) + @room = room + @guest_name = guest_name + check_dates(start_date, end_date) + @start_date = Date.parse(start_date) + @end_date = Date.parse(end_date) + @cost_per_night = cost_per_night + @number_nights = (@end_date - @start_date).to_i + end + + def check_dates(start_date, end_date) + if start_date > end_date + raise ArgumentError.new "Invalid date range. Start date must be before end date, both in format of 'Month dd, yyyy'. " + end + end + + # # Method to get total cost of reservation + def get_reservation_cost + total_cost = @number_nights * @cost_per_night + return total_cost + end + + end +end diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..1a962b5c5 --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,18 @@ +module Hotel + class Room + attr_reader :number + + def initialize(number) + @number = number # should this be a string ? + end + + # def check_availability(start_date, end_date) + # # Do..something with dates. + # # if date of check is within range + # # return boolean + # availability = true + # return availability + # end # of check_availability + + end # of class Room +end # of module Hotel diff --git a/refactors.txt b/refactors.txt new file mode 100644 index 000000000..4a5c155de --- /dev/null +++ b/refactors.txt @@ -0,0 +1,8 @@ +Possible changes to Hotel: +- Reduce dependency of method for making Reservation on reserve instance variables? +- Split some of Hotel into smaller methods (like reserve_available_room) +- Combine methods that have overlapping but contradictory options? Combine or link. (ex: @reservations & @room_calendar) +- Change name for BookingManager instance(s) in tests? Sometimes listed as "hotel", other times "manager", etc. Change all to BookingSystem? +- Check more of edge cases +- Too many variables with 'reservation' in title? +- Add check that rooms are not overwritten diff --git a/spec/booking_manager_spec.rb b/spec/booking_manager_spec.rb new file mode 100644 index 000000000..04fec26b0 --- /dev/null +++ b/spec/booking_manager_spec.rb @@ -0,0 +1,248 @@ +require_relative 'spec_helper' + +describe "BookingManager class" do + describe "Initializer" do + it "creates an instance of BookingManager" do + manager = Hotel::BookingManager.new(10) + expect(manager).must_be_kind_of Hotel::BookingManager + end + + it "creates the proper structures for instance variables instantiated" do + hotel_rooms = Hotel::BookingManager.new(20) + + expect(hotel_rooms.rooms).must_be_kind_of Array + expect(hotel_rooms.reservations).must_be_kind_of Array + end + end + + + describe "populate_room_list method" do + it "creates an array of Room instances" do + new_rooms = Hotel::BookingManager.new(10) + expect(new_rooms.populate_room_list(10)).must_be_kind_of Array + expect(new_rooms.populate_room_list(10).first).must_be_instance_of Hotel::Room + expect(new_rooms.populate_room_list(10).last).must_be_instance_of Hotel::Room + end + + it "assigns numbers to rooms consecutively as instantiated" do + x = 15 + hotel_rooms = Hotel::BookingManager.new(x) + first_room = hotel_rooms.populate_room_list(x).first + last_room = hotel_rooms.populate_room_list(x).last + + expect(first_room.number).must_equal 1 + expect(last_room.number).must_equal x + end + + it "returns array of rooms with length equal to parameter passed into constructor" do + x = 12 + hotel_rooms = Hotel::BookingManager.new(x) + expect(hotel_rooms.populate_room_list(x).length).must_equal x + end + end + + + describe "make_reservation_list method" do + it "returns a value equal to @reservations at instantiation" do + hotel_rooms = Hotel::BookingManager.new(10) + expect(hotel_rooms.reservations).must_equal hotel_rooms.make_reservation_list + end + + it "returns an empty array as the list of reservations" do + hotel_rooms = Hotel::BookingManager.new(5) + expect(hotel_rooms.make_reservation_list).must_be_empty + end + end + + describe "list_reservations method" do + it "returns an array" do + hotel_rooms = Hotel::BookingManager.new(7) + expect(hotel_rooms.list_reservations).must_be_kind_of Array + end + end + + describe "list_rooms method" do + it "returns an array of Room instances" do + hotel_rooms = Hotel::BookingManager.new(5) + expect(hotel_rooms.list_rooms).must_be_kind_of Array + expect(hotel_rooms.list_rooms.first).must_be_instance_of Hotel::Room + end + + it "returns an array with the given number of rooms" do + x = 5 + hotel_rooms = Hotel::BookingManager.new(x) + expect(hotel_rooms.list_rooms.length).must_equal x + end + end + + describe "add_reservation_to_calendar method" do + before do + @hotel_rooms = Hotel::BookingManager.new(5) + end + + it "increases the length of the reservations array by one" do + old_number = @hotel_rooms.reservations.length + room = @hotel_rooms.rooms[2] + new_booking = Hotel::Reservation.new(room, guest_name: "Polly Pocket", start_date: "May 10, 2018", end_date: "May 12, 2018") + + @hotel_rooms.add_reservation_to_calendar(new_booking) + expect(@hotel_rooms.reservations.length).must_equal old_number + 1 + end + + it "adds new instance of Reservation to reservations array" do + room = @hotel_rooms.rooms[1] + another_booking = Hotel::Reservation.new(room, guest_name: "Polly Pocket", start_date: "May 10, 2018", end_date: "May 12, 2018") + @hotel_rooms.add_reservation_to_calendar(another_booking) + + expect(@hotel_rooms.reservations.last).must_be_instance_of Hotel::Reservation + expect(@hotel_rooms.reservations.last.guest_name).must_equal "Polly Pocket" + end + + it "creates a hash with room instance as key and dates as hash" do + hotel = Hotel::BookingManager.new(3) + # room = Hotel::Room.new(3) + room = hotel.rooms.first + another_booking = Hotel::Reservation.new(room, guest_name: "Kim Possible", start_date: "June 11, 2018", end_date: "June 14, 2018") + + hotel.add_reservation_to_calendar(another_booking) + expect(hotel.add_reservation_to_calendar(another_booking)).must_be_kind_of Hash + expect(hotel.room_calendar[room]).must_include (Date.parse("June 11, 2018")) + expect(hotel.room_calendar[room].length).must_equal another_booking.number_nights + end + end + + # describe "get_reservation_cost method" do + # it "returns the total cost of the reservation" do + # hotel = Hotel::BookingManager.new(3) + # room = hotel.rooms[3] + # booking = Hotel::Reservation.new(room, guest_name: "Tony Tonson", start_date: "June 10, 2018", end_date: "June 12, 2018") + # + # expect(hotel.get_reservation_cost(booking.cost_per_night, booking.number_nights)).must_be_close_to 400 + # end + # end + + describe "find_reservations_on_date method" do + it "returns all reservations on the desired date" do + hotel = Hotel::BookingManager.new(5) + room1 = hotel.rooms[0] + room2 = hotel.rooms[1] + room3 = hotel.rooms[2] + booking1 = Hotel::Reservation.new(room1, guest_name: "Marsha Daze", start_date: "June 10, 2018", end_date: "June 12, 2018") + booking2 = Hotel::Reservation.new(room2, guest_name: "Jessie Jade", start_date: "June 10, 2018", end_date: "June 14, 2018") + booking3 = Hotel::Reservation.new(room3, guest_name: "Daria Jade", start_date: "June 14, 2018", end_date: "June 16, 2018") + hotel.add_reservation_to_calendar(booking1) + hotel.add_reservation_to_calendar(booking2) + hotel.add_reservation_to_calendar(booking3) + + expect(hotel.find_reservations_on_date("June 11, 2018", hotel.room_calendar).length).must_equal 2 + end + end + + describe "find_vacancies_on_date method" do + before do + @hotel = Hotel::BookingManager.new(5) + room1 = @hotel.rooms[0] + room2 = @hotel.rooms[1] + @room3 = @hotel.rooms[2] + booking1 = Hotel::Reservation.new(room1, guest_name: "Tonya Blaze", start_date: "June 10, 2018", end_date: "June 12, 2018") + booking2 = Hotel::Reservation.new(room2, guest_name: "Jane James", start_date: "June 10, 2018", end_date: "June 14, 2018") + booking3 = Hotel::Reservation.new(@room3, guest_name: "Jessie Jade", start_date: "June 14, 2018", end_date: "June 16, 2018") + @hotel.add_reservation_to_calendar(booking1) + @hotel.add_reservation_to_calendar(booking2) + @hotel.add_reservation_to_calendar(booking3) + end + + it "returns array of rooms without reservations on given date" do + expect(@hotel.find_vacancies_on_date("June 10, 2018", @hotel.room_calendar).length).must_equal 3 + expect(@hotel.find_vacancies_on_date("June 17, 2018", @hotel.room_calendar).length).must_equal 5 + end + + # it "returns message of no vacancies when all rooms are reserved" do + # room4 = @hotel.rooms[3] + # room5 = @hotel.rooms[4] + # + # booking4 = Hotel::Reservation.new(@room3, guest_name: "Lady Day", start_date: "June 10, 2018", end_date: "June 11, 2018") + # booking5 = Hotel::Reservation.new(room4, guest_name: "Horis Who", start_date: "June 08, 2018", end_date: "June 14, 2018") + # booking6 = Hotel::Reservation.new(room5, guest_name: "Dorian Damian", start_date: "June 06, 2018", end_date: "June 11, 2018") + # + # @hotel.add_reservation_to_calendar(booking4) + # @hotel.add_reservation_to_calendar(booking5) + # @hotel.add_reservation_to_calendar(booking6) + # + # expect(@hotel.find_vacancies_on_date("June 10, 2018", @hotel.room_calendar)).must_be_kind_of String + # end + end + + describe "reserve_available_room method" do + before do + @hotel = Hotel::BookingManager.new(5) + room4 = @hotel.rooms[3] + room5 = @hotel.rooms[4] + + booking5 = Hotel::Reservation.new(room4, guest_name: "Horis Who", start_date: "June 08, 2018", end_date: "June 14, 2018") + booking6 = Hotel::Reservation.new(room5, guest_name: "Dorian Damian", start_date: "June 06, 2018", end_date: "June 11, 2018") + + @hotel.add_reservation_to_calendar(booking5) + @hotel.add_reservation_to_calendar(booking6) + + @number_prior_reservations = @hotel.reservations.length + @hotel.reserve_available_room("Donna Foster", "January 02, 2019", "January 08, 2019") + end + + # it "only allows reservations for rooms/dates not already reserved" do + # + # end + + it "reserves available room for given date range" do + expect(@hotel.reservations.length).must_equal @number_prior_reservations + 1 + end + + it "iterates through rooms to find available room" do + room2 = @hotel.rooms[1] + booking2 = Hotel::Reservation.new(room2, guest_name: "Joe Jeans", start_date: "June 08, 2018", end_date: "June 14, 2018") + @hotel.add_reservation_to_calendar(booking2) + number_reserved = @hotel.reservations.length + + @hotel.reserve_available_room("Lisel Wallace", "June 08, 2018", "June 13, 2018") + # expect(@hotel.reservations).must_include "Lisel Wallace" + expect(@hotel.reservations.length).must_equal number_reserved + 1 + end + end + + describe "determine_date_range method" do + before do + @bookingsystem = Hotel::BookingManager.new(5) + @range = @bookingsystem.determine_date_range("July 10, 2000", "July 15, 2000") + end + + it "creates array of Date instances" do + expect(@range.length).must_equal 6 + expect(@range.first).must_be_instance_of Date + end + + it "includes the correct dates" do + expect(@range.first.to_s).must_match /2000-07-10/ + expect(@range.last.to_s).must_match /2000-07-15/ + end + end + + describe "find_vacancies_in_date_range method" do + before do + @bookingsystem = Hotel::BookingManager.new(5) + room = @bookingsystem.rooms[0] + booking = Hotel::Reservation.new(room, guest_name: "Mary Poppins", start_date: "June 08, 2018", end_date: "June 14, 2018") + @bookingsystem.add_reservation_to_calendar(booking) + @found_vacancies = @bookingsystem.find_vacancies_in_date_range("June 10, 2018", "June 12, 2018") + end + + it "returns the correct number of vacant rooms" do + expect(@found_vacancies.length).must_equal 4 + end + + it "returns an array of Room instances" do + expect(@found_vacancies.first).must_be_instance_of Hotel::Room + expect(@found_vacancies.last).must_be_instance_of Hotel::Room + end + + end +end diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb new file mode 100644 index 000000000..410ebfffe --- /dev/null +++ b/spec/reservation_spec.rb @@ -0,0 +1,34 @@ +require_relative 'spec_helper' + +describe "Reservation class" do + describe "initialize method" do + before do + @manager = Hotel::BookingManager.new(5) + @room = @manager.rooms.first + end + + it "creates a new instance of Reservation" do + new_booking = Hotel::Reservation.new(@room, guest_name: "Tina Fey", start_date: "June 11, 2018", end_date: "June 14, 2018") + expect(new_booking).must_be_instance_of Hotel::Reservation + end # of new reservation is new instance it + + it "raises an ArgumentError for an invalid date range" do + proc { + booking = Hotel::Reservation.new(@room, guest_name: "Tina Fey", start_date: "June 11, 2018", end_date: "June 10, 2018") + }.must_raise ArgumentError + #proc {booking.check_dates(booking.start_date, booking.end_date)}.must_raise ArgumentError + end # of invalid date range ArgumentError + end # of initialize method + + + describe "get_reservation_cost method" do + it "returns the total cost of the reservation" do + hotel = Hotel::BookingManager.new(5) + room = hotel.rooms.first + booking = Hotel::Reservation.new(room, guest_name: "Ilona Illari", start_date: "June 10, 2018", end_date: "June 12, 2018") + + expect(booking.get_reservation_cost).must_be_close_to 400 + end + end + +end # of class Reservation diff --git a/spec/room_spec.rb b/spec/room_spec.rb new file mode 100644 index 000000000..d07365568 --- /dev/null +++ b/spec/room_spec.rb @@ -0,0 +1,11 @@ +require_relative 'spec_helper' + +describe "Room class" do + describe "Initializer" do + it "is an instance of Room" do + number = 2 + @room = Hotel::Room.new(number) + expect(@room).must_be_kind_of Hotel::Room + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4d1e3fdc8..717c478a4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,17 @@ +require 'simplecov' +SimpleCov.start + require 'minitest' require 'minitest/autorun' require 'minitest/reporters' -# Add simplecov +require 'date' + Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new # Require_relative your lib files here! +require_relative '../lib/room.rb' +require_relative '../lib/booking_manager.rb' +require_relative '../lib/reservation.rb' + +# require_relative '..'