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/design-activity.md b/design-activity.md new file mode 100644 index 000000000..4dda7273e --- /dev/null +++ b/design-activity.md @@ -0,0 +1,67 @@ +## Hotel Design Activity + +**What classes does each implementation include? Are the lists the same?** + +Both implementations have 3 classes by the same names: CartEntry, ShoppingCart, Order. + + +**Write down a sentence to describe each class.** + +CartEntry represents the addition of a given product to a cart, including its price and quantity. + +ShoppingCart represents a collection of everything that has been added to a cart. + +Order represents the total cost of everything in a given cart, including 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.** + +ShoppingCart contains each CartEntry. Order calculates the total of the ShoppingCart after sales tax. + + +**What data does each class store? How (if at all) does this differ between the two implementations?** + +CartEntry: both implementations store the prices and quantities of a given product. In implementation A, those values can be accessed outside of the class due to the attr_accessor whereas in Implementation B, they have to be used locally. + +ShoppingCart: both implementations have an array that stores each CartEntry object. In Implementation A, those entries can be accessed outside of the class. Implementation B also has a value for the sum of all entries in the cart. + +Order: both implementations store the sum/subtotal, or total price of a complete order (before sales tax). This is calculated differently in each implementation. Implementation A iterates through each price and quantity in @entries. Implementation B calls the #price method from ShoppingCart. + + +**What methods does each class have? How (if at all) does this differ between the two implementations?** + +CartEntry: both implementations have an initialize method. Implementation B also has a price method that returns the subtotal of one particular item. + +ShoppingCart: both have an initialize method. + +Order: both have an initialize method that creates a new instance of a ShoppingCart. Both also have a total_price method that returns the total of all cart entries plus sales tax. The difference is that the method in Implementation A handles all of the logic for "sum" whereas Implementation B calls the "price" method in the ShoppingCart class. + + +**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?** + +In Implementation A, the logic is retained in Order. In Implementation B, the logic relies on ShoppingCart, which is a lower level class. + + +**Does total_price directly manipulate the instance variables of other classes?** + +In Implementation A, total_price reads @entries from ShoppingCart and @unit_price and @quantity from CartEntry. In Implementation B, total_price does not read instance variables of any other class. + + +**If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify?** + +You would need to add some conditional logic that lowers the :unit_price based on :quantity. Implementation A is harder to modify because that logic would need to be called into account in the CartEntry class and Order class. Implementation B is easier to modify because you would only need to modify the CartEntry class. + + +**Which implementation better adheres to the single responsibility principle?** + +Implementation B. + + +**Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled?** + +Implementation B. + + +**changes to hotel** +I had a lot of logic regarding the date within reservation_hub. I can create a separate DateRange class to validate dates and create a date array. It would make my code better because I'd be able to call the DateRange class when booking both, room blocks and reservations. It would not only dry up my code, but also help me better adhere to single responsibility. diff --git a/lib/date_range.rb b/lib/date_range.rb new file mode 100644 index 000000000..ccaa02084 --- /dev/null +++ b/lib/date_range.rb @@ -0,0 +1,34 @@ +require 'date' + + +module Hotel + class DateRange + + def initialize(start_date, end_date) + + @start_date = start_date + @end_date = end_date + + validate_dates + + @date_range = create_date_array(start_date, end_date) + end + + + def validate_dates + raise ArgumentError.new("The end date must be after the start date") if @end_date <= @start_date + end + + + def create_date_array(start_date, end_date) + number_of_nights = (end_date - start_date).to_i + date_array = [] + number_of_nights.times do + date_array << start_date + start_date +=1 + end + return date_array + end + + end +end diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..a9f820972 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,22 @@ + + +module Hotel + class Reservation + attr_reader :room_id, :reservation_dates, :total_cost + + def initialize(reservation_dates, room_id) + + @reservation_dates = reservation_dates + @room_id = room_id + @total_cost = reservation_cost(reservation_dates) + + end + + def reservation_cost(reservation_dates) + nightly_cost = 200 + total_days = (reservation_dates.length).to_i + total_cost = total_days * nightly_cost + end + + end +end diff --git a/lib/reservation_hub.rb b/lib/reservation_hub.rb new file mode 100644 index 000000000..5ab1fcdce --- /dev/null +++ b/lib/reservation_hub.rb @@ -0,0 +1,119 @@ +require 'date' +require 'pry' + +require_relative 'reservation' +require_relative 'room_block' +require_relative 'date_range' + + +module Hotel + class ReservationHub + class NoRoomsAvailableError < StandardError; end + + attr_reader :reservations, :room_bookings, :room_blocks + + + def initialize + @reservations = [] + @room_bookings = {1 => [], 2 => [], 3 => [], 4 => [], 5 => [], 6 => [], 7 => [], 8 => [], 9 => [], 10 => [], 11 => [], 12 => [], 13 => [], 14 => [], 15 => [], 16 => [], 17 => [], 18 => [], 19 => [], 20 => []} + @room_blocks = [] + end + + + def add_reservation(start_date, end_date) + + dates = DateRange.new(start_date, end_date) + reservation_dates = dates.create_date_array(start_date, end_date) + + room_id = assign_room(reservation_dates) + + reservation = Reservation.new(reservation_dates, room_id) + + @reservations << reservation + + return reservation + end + + + def add_room_block(start_date, end_date, total_rooms) + + dates = DateRange.new(start_date, end_date) + reservation_dates = dates.create_date_array(start_date, end_date) + + room_ids = [] + + total_rooms.times do + room_id = assign_room(reservation_dates) + room_ids << room_id + end + + block_id = @room_blocks.length + 1 + + room_block = RoomBlock.new(reservation_dates, room_ids, block_id) + + @room_blocks << room_block + + return room_block + end + + + def check_available_rooms(reservation_dates) + + available_rooms = [] + + @room_bookings.each do |room, dates| + overlap = reservation_dates & dates + + if overlap.length == 0 + available_rooms << room + end + + end + return available_rooms + end + + + def assign_room(reservation_dates) + + available_rooms = check_available_rooms(reservation_dates) + + if available_rooms == nil + raise NoRoomsAvailableError, "No rooms are available." + end + + room_id = available_rooms[0] + + reservation_dates.each{|date| @room_bookings[room_id] << date} + + return room_id + end + + + def find_reservations(date) + + reservations_by_date = [] + index = 0 + + @reservations.each do + + if @reservations[index].reservation_dates.include?(date) + reservations_by_date << @reservations[index] + end + index +=1 + + end + return reservations_by_date + end + + + def find_room_block(id) + + @room_blocks.each do |room_block| + if room_block.block_id == id + return room_block + end + end + end + + end +end diff --git a/lib/room_block.rb b/lib/room_block.rb new file mode 100644 index 000000000..e3c684a99 --- /dev/null +++ b/lib/room_block.rb @@ -0,0 +1,16 @@ + + +module Hotel + class RoomBlock + attr_reader :reservation_dates, :room_ids, :block_id + + def initialize(reservation_dates, room_ids, block_id) + + @reservation_dates = reservation_dates + @room_ids = room_ids + @block_id = block_id + + end + + end +end diff --git a/refactors.txt b/refactors.txt new file mode 100644 index 000000000..90ae534b0 --- /dev/null +++ b/refactors.txt @@ -0,0 +1,5 @@ +**the name "room_bookings" seems a bit misleading. I used it to include both, reservations and room blocks for each room. For each room, I probably would have been better off creating a nested hash. One key/value pair would be confirmed_reservations => [] and one would be room_blocks => []. + +**I would have changed the method for adding reservations so it could also be used to add a block reservation as well. The current method assigns rooms, which is a step that wouldn't be necessary if a user entered in a block id (it already would have been handled). + +**I feel like some of my methods in reservation hub are currently doing a lot. I think the overall code would benefit by breaking some of those methods up into smaller steps. diff --git a/spec/date_range_spec.rb b/spec/date_range_spec.rb new file mode 100644 index 000000000..c01e86d5d --- /dev/null +++ b/spec/date_range_spec.rb @@ -0,0 +1,52 @@ +require_relative 'spec_helper' +require 'pry' + +describe "DateRange class" do + + describe "validate" do + + it "must throw an argument error if the end date is before the start date" do + + start_date = Date.new(2015, 03, 05) + end_date = Date.new(2015, 03, 01) + + room_id = 4 + + expect {Hotel::DateRange.new(start_date, end_date)}.must_raise ArgumentError + + end + end + + + describe "create date array" do + + before do + start_date = Date.new(2018,01,06) + end_date = Date.new(2018,01,10) + + dates = Hotel::DateRange.new(start_date, end_date) + + @date_array = dates.create_date_array(start_date, end_date) + + @test_dates = [] + + until start_date == end_date + @test_dates << start_date + start_date +=1 + end + + end + + it "returns an array of all dates" do + expect(@date_array).must_be_kind_of Array + expect(@date_array.length).must_equal @test_dates.length + end + + it "includes all dates in the reservation, not including the end date" do + expect(@date_array).wont_include @end_date + expect(@date_array).must_equal @test_dates + + end + end + +end diff --git a/spec/reservation_hub_spec.rb b/spec/reservation_hub_spec.rb new file mode 100644 index 000000000..c8ac88807 --- /dev/null +++ b/spec/reservation_hub_spec.rb @@ -0,0 +1,230 @@ +require_relative 'spec_helper' +require 'pry' + +describe "Reservation Hub class" do + + before do + @reservation_hub = Hotel::ReservationHub.new + end + + + describe "Reservation Hub initialization" do + + it "is an instance of Reservation Hub" do + + expect(@reservation_hub).must_be_kind_of Hotel::ReservationHub + + end + + it "can access all rooms in the hotel" do + + expect(@reservation_hub.room_bookings.keys.length).must_equal 20 + + end + + it "must initialize with an empty array of reservations" do + + expect(@reservation_hub.reservations).must_be_kind_of Array + expect(@reservation_hub.reservations.length).must_equal 0 + + end + + it "must initialize with a hash and corresponding empty arrays for each room" do + + expect(@reservation_hub.room_bookings).must_be_kind_of Hash + expect(@reservation_hub.room_bookings.length).must_equal 20 + expect(@reservation_hub.room_bookings[1]).must_equal [] + + end + + it "must be able to access all reservations and room bookings" do + + expect(@reservation_hub).must_respond_to :reservations + expect(@reservation_hub).must_respond_to :room_bookings + + end + + it "raises an error if the start date is after the end date" do + + start_date = Date.new(2018,01,05) + end_date = Date.new(2018,01,02) + expect{@reservation_hub.add_reservation(start_date, end_date)}.must_raise ArgumentError + + end + end + + + describe "add reservation" do + before do + start_date1 = Date.new(2018,01,03) + end_date1 = Date.new(2018,01,06) + + start_date2 = Date.new(2018,04,06) + end_date2 = Date.new(2018,04,11) + + @reservation1 = @reservation_hub.add_reservation(start_date1, end_date1) + + @reservation2 = @reservation_hub.add_reservation(start_date2, end_date2) + + end + + it "returns a new reservation" do + + expect(@reservation1).must_be_kind_of Hotel::Reservation + expect(@reservation2).must_be_kind_of Hotel::Reservation + + end + + it "adds a new reservation to array of all reservations" do + + expect(@reservation_hub.reservations.length).must_equal 2 + + end + end + + + describe "add room block" do + + before do + start_date = Date.new(2018,01,03) + end_date = Date.new(2018,01,06) + total_rooms = 3 + @room_block = @reservation_hub.add_room_block(start_date, end_date, total_rooms) + end + + it "returns a new room block" do + expect(@room_block).must_be_kind_of Hotel::RoomBlock + end + + it "adds a new room block to an array of all room blocks" do + expect(@reservation_hub.room_blocks.length).must_equal 1 + end + + it "increments the block id by 1 for every new room block" do + + start_date = Date.new(2018,02,04) + end_date = Date.new(2018,02,15) + total_rooms = 2 + + @room_block2 = @reservation_hub.add_room_block(start_date, end_date, total_rooms) + + expect(@room_block2.block_id).must_equal 2 + end + end + + + describe "check available rooms method" do + + it "returns an array of 20 rooms upon initialization" do + + reservation_dates = [1,2] + + available_rooms = @reservation_hub.check_available_rooms(reservation_dates) + + expect(available_rooms.length).must_equal 20 + expect(available_rooms).must_be_kind_of Array + end + + it "doesn't include rooms that have already been booked" do + + start_date = 1 + end_date = 3 + + @reservation1 = @reservation_hub.add_reservation(start_date, end_date) + + @reservation2 = @reservation_hub.add_reservation(start_date, end_date) + + @reservation_dates = [1,2] + + + available_rooms = @reservation_hub.check_available_rooms(@reservation_dates) + + expect(available_rooms.length).must_equal 18 + + end + end + + + describe "assign room" do + + before do + @reservation_dates = [1,2,3] + end + + it "will assign the first value in the array to the reservation" do + + room_id = @reservation_hub.assign_room(@reservation_dates) + expect(room_id).must_equal 1 + + end + + it "won't duplicate room ids if two reservations share the same dates" do + + reservation_dates = [1,2,3] + room_id = @reservation_hub.assign_room(reservation_dates) + room_id2 = @reservation_hub.assign_room(reservation_dates) + expect(room_id).must_equal 1 + expect(room_id2).must_equal 2 + + end + + it "raises an error if the hotel is fully booked" do + + start_date = Date.new(2018,01,05) + end_date = Date.new(2018,01,06) + + 20.times do + @reservation_hub.add_reservation(start_date, end_date) + end + + expect{@reservation_hub.add_reservation(start_date, end_date)}.must_raise StandardError + end + end + + + describe "find reservation" do + + before do + @start_date = Date.new(2018,01,03) + @end_date = Date.new(2018,01,06) + + @reservation_hub = Hotel::ReservationHub.new + reservation1 = @reservation_hub.add_reservation(@start_date, @end_date) + end + + it "returns an array of reservations" do + + date = Date.new(2017,01,01) + reservations = @reservation_hub.find_reservations(date) + + expect(reservations).must_be_kind_of Array + + expect(@reservation_hub.reservations[0]).must_be_kind_of Hotel::Reservation + + end + + it "returns a reservation if its start date is the parameter" do + + reservations = @reservation_hub.find_reservations(@start_date) + + expect(reservations.length).must_equal 1 + end + + it "returns a reservation if one of the middle dates is a parameter" do + date = @start_date +1 + + reservations = @reservation_hub.find_reservations(date) + + expect(reservations.length).must_equal 1 + + end + + it "does not include a reservation if its end date is a parameter" do + + reservation = @reservation_hub.find_reservations(@end_date) + + expect(reservation.length).must_equal 0 + end + end + +end diff --git a/spec/reservation_spec.rb b/spec/reservation_spec.rb new file mode 100644 index 000000000..b58011082 --- /dev/null +++ b/spec/reservation_spec.rb @@ -0,0 +1,41 @@ +require_relative 'spec_helper' +require 'pry' + +describe "Reservation class" do + + describe "Reservation initialization" do + + before do + date_range = [1,2,3] + @reservation = Hotel::Reservation.new(date_range, 4) + end + + it "is an instance of Reservation" do + expect(@reservation).must_be_kind_of Hotel::Reservation + end + + it "is initialized with a date range, room id and total cost" do + expect(@reservation).must_respond_to :reservation_dates + expect(@reservation.reservation_dates).must_be_kind_of Array + + expect(@reservation).must_respond_to :room_id + expect(@reservation.room_id).must_be_kind_of Integer + + expect(@reservation).must_respond_to :total_cost + expect(@reservation.total_cost).must_be_kind_of Integer + + end + end + + + describe "reservation cost" do + it "returns the total amount of the new reservation" do + + date_range = [1,2,3,4] + reservation = Hotel::Reservation.new(date_range, 3) + + expect(reservation.reservation_cost(date_range)).must_equal 800 + end + end + +end diff --git a/spec/room_block_spec.rb b/spec/room_block_spec.rb new file mode 100644 index 000000000..e402c8a9d --- /dev/null +++ b/spec/room_block_spec.rb @@ -0,0 +1,37 @@ +require_relative 'spec_helper' +require 'pry' + +describe "Room Block class" do + + describe "room block initialization" do + + before do + reservation_dates = [1,2,3] + room_ids = [4,5,6] + block_id = 1 + + @room_block = Hotel::RoomBlock.new(reservation_dates, room_ids, block_id) + end + + it "is an instance of a Room Block" do + expect(@room_block).must_be_kind_of Hotel::RoomBlock + end + + it "is initialized with a date range, room ids and block id" do + + expect(@room_block).must_respond_to :reservation_dates + expect(@room_block.reservation_dates).must_be_kind_of Array + + expect(@room_block).must_respond_to :room_ids + expect(@room_block.room_ids).must_be_kind_of Array + + expect(@room_block).must_respond_to :block_id + expect(@room_block.block_id).must_be_kind_of Integer + + end + + + + + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4d1e3fdc8..909ea9b6b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,14 @@ +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/reservation' +require_relative '../lib/reservation_hub' +require_relative '../lib/room_block' +require_relative '../lib/date_range'