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/README.md b/README.md index 07dc42bcf..a47a514c8 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,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 +139,27 @@ 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. + + +Revisiting Hotel +Now that we've got you thinking about design, spend some time to revisit the code you wrote for the Hotel project. For each class in your program, ask yourself the following questions: + +What is this class's responsibility? +You should be able to describe it in a single sentence. +Is this class responsible for exactly one thing? +Does this class take on any responsibility that should be delegated to "lower level" classes? +Is there code in other classes that directly manipulates this class's instance variables? +You might recall writing a file called refactor.txt. Take a look at the refactor plans that you wrote, and consider the following: + +How easy is it to follow your own instructions? +Do these refactors improve the clarity of your code? +Do you still agree with your previous assesment, or could your refactor be further improved? +Activity +Based on the answers to each set of the above questions, identify one place in your Hotel project where a class takes on multiple roles, or directly modifies the attributes of another class. Describe in design-activity.md what changes you would need to make to improve this design, and how the resulting design would be an improvement. + +If you need inspiration, remember that the reference implementation exists. + +Then make the changes! Don't forget to take advantage of all the tests you wrote - if they're well structured, they should quickly inform you when your refactoring breaks something. + +Once you're satisfied, git commit your changes and then push them to GitHub. This will automatically update your pull request. diff --git a/design-activity.md b/design-activity.md new file mode 100644 index 000000000..f7c95bc5e --- /dev/null +++ b/design-activity.md @@ -0,0 +1,48 @@ +1. The two implementations have the same classes but their utilization and functionality of the class are different. +2. Implementation A: + - Cart Entry: stores data for individual cart entries + - Shopping Cart: stores a collection of instances of cart entries + - Order: creates and calculates full order details + Implementation B: + - CartEntry: stores data for individual entry and calculates price + - ShoppingCart: stores collection of instances of entries and calculates sum of a cart. + - Order: calculates total order cost including Tax and stores instances of Cart +3. In Implementation A - the classes are heavily dependent upon one another. The Order class depends on the ShoppingCart class and does all of the work. Any changes done in one of the lower classes changes the Order class. + +Implementation B - the classes are still dependent upon each other but are more loosely coupled because each of the classes includes a specific class method that will calculate price. A change in one of the lower classes has less of an event on the Order class. + +4. Data - in both classes each class stores the same data except implementatin B also calculates out price per entry and shopping Cart in addition to just storing quantity and unit price. + +5. Implementation A - The order class stores all the methods and functionality that this program would need to accomplish. It creates an instance of the cart and then calculates it's total cost. + +Implentation B - Each of the classes is able to calculate the cost/price associated with its specific object. This makes the classes more independent of each other. + +6. I think it makes sense to delegate price computation to lower classes and still have total computed in order. Each class is responsible for knowing the information respective to the instances and objects it creates. In implementation A, total_price manipulates instances of the other classes to produce a total. In implementation B, total_price does not directly manipulate instances of other classes but instead calls upon them to use their own methods to give it what it needs. + +7. To include a discount for bulk buying, it would be much easier to implement this new feature in Impmentation B. The variable and corresponding method change could be added to the lower level classes and the class Order would be non the wiser to the change but still deliver what the program needs to accomplish. + +8. Both Implementations mostly stick to the single responsibility principles except for Implementation A's Order class. Implementation A stores data about a cart and calculates its total cost but is also responsible for calculating the costs of the instances in the two other classes. + +9. ImplementationB is more loosely coupled. + +-- + +Hotel +- Reservation manager + - manages/creates relationships between rooms and reservations + - Room factory can be moved to the room class. + - There is no code in the lower classes that would directly influence instance variables in this class. + +- Reservation + - holds all pertinent information to a specific instance of reservation + - refactor reservation to be more generic and usable for block rooms. + +- Room + - holds all pertinent information to a specific room + - can take on the factory of rooms from ReservationManager Class + +- Block + - manages information specific to a block. + - remove reservation responsibility from block class + +My refactors doc was clear but a little bit scant. I think I could've been more thorough and thoughtful in creating it. But combined with the design activities and instructor feedback, I feel like I have a clear idea of what changes need to be made. diff --git a/lib/DateRange.rb b/lib/DateRange.rb new file mode 100644 index 000000000..7412137ec --- /dev/null +++ b/lib/DateRange.rb @@ -0,0 +1,14 @@ +# module Hotel +# class DateRange +# def initialize(check_in, check_out) +# @check_in = Date.parse(check_in) +# @check_out = Date.parse(check_out) +# end +# +# def overlap +# end +# +# def include +# end +# +# def number_of_nights diff --git a/lib/block.rb b/lib/block.rb new file mode 100644 index 000000000..ef434d4b3 --- /dev/null +++ b/lib/block.rb @@ -0,0 +1,31 @@ +require 'pry' + + +module Hotel + class Block + + attr_reader :rooms, :reservations, :date_range, :check_in, :check_out, :discount_rate, :id + + def initialize(number, check_in:, check_out:, number_of_rooms:1, discount_rate: 0.8) + @id = number + @check_in = Date.parse(check_in) + @check_out = Date.parse(check_out) + @date_range = (@check_in...@check_out) + @rooms = [] #if i change this to rooms can i use available rooms on it? + @discount_rate = discount_rate + @reservations = [] + #change this to standard error / rescue + if @check_out != nil + if @check_out <= @check_in + raise ArgumentError, "Invalid date range" + end + end + end + + # def available_rooms_in_block(check_in, check_out) + # available_rooms = self.rooms.select {|room| room.is_booked?(check_in, check_out) == false} + # #loop through Reservations @match dates on the reservations#reject dates that match + # return available_rooms #array + # end + end +end diff --git a/lib/refactors.txt b/lib/refactors.txt new file mode 100644 index 000000000..57b020957 --- /dev/null +++ b/lib/refactors.txt @@ -0,0 +1,6 @@ +Refactors for Hotel +- change reserve method so it can be used for block reseration +hve it pull rooms from a vairable- that can be plugged in i.e. +available rooms method for any kind of reservation and rooms on hold for block reservations +- figure out how to handle blocked rooms that are not reserved yet and only allow certain people to reserve, m +maybe need to supply block id? diff --git a/lib/reservations.rb b/lib/reservations.rb new file mode 100644 index 000000000..8b6bb80af --- /dev/null +++ b/lib/reservations.rb @@ -0,0 +1,50 @@ + +# require 'Date' + +module Hotel + COST_OF_ROOM = 200 + class Reservation + attr_reader :id, :check_in, :check_out, :rooms, :total_cost, :date_range, :discount_rate + + + + def initialize(id_num, number_of_rooms:1, check_in:, check_out:, discount_rate: 1, block_id:'') + @id = id_num + @block_id = block_id + @check_in = Date.parse(check_in) + @check_out = Date.parse(check_out) + @date_range = (@check_in...@check_out) + @rooms = [] + @discount_rate = discount_rate + #change this to standard error / rescue + if @check_out != nil + if @check_out <= @check_in + raise ArgumentError, "Invalid date range" + end + end + end + + def find_reservation(date) + date = Date.parse(date) + return self.date_range.include? date + end + + def overlaps?(check_in, check_out) + existing_check_in = self.check_in + existing_check_out = self.check_out + if existing_check_in <= check_out && existing_check_in >= check_in + return true + elsif check_in <= existing_check_out && existing_check_out <= check_out + return true + else + return false + end + end + + def total_cost + total_cost = self.date_range.count * self.rooms.length * COST_OF_ROOM * self.discount_rate + return total_cost + end + + end +end diff --git a/lib/reservations_manager.rb b/lib/reservations_manager.rb new file mode 100644 index 000000000..d3f80ccaa --- /dev/null +++ b/lib/reservations_manager.rb @@ -0,0 +1,119 @@ +require 'pry' +# require 'Date' + +require_relative 'room' +require_relative 'reservations' +require_relative 'block' +#reservations manager +module Hotel + class ReservationManager + + attr_reader :rooms, :reservations + + def initialize(number_of_rooms) + @reservations = [] + @blocks = [] + @rooms = [] + #move this factory over to room class + (1..number_of_rooms).each do |number| + @rooms << Room.new(number) + end + end + + def find_room(id) #what is this method used for? + @rooms.each do |room| + if room.id == id + return room + end + end + end + + def find_block(block_id) + @blocks.each do |block| + if block.id == id + return block + end + end + end + + def booked_reservations(date) + return @reservations.select {|reservation| reservation.find_reservation(date) == true} + end + + def reserve_room(check_in, check_out, number_of_rooms: 1, block_id:'') + new_reservation = Reservation.new(@reservations.length + 1, number_of_rooms: number_of_rooms, check_in: check_in, check_out: check_out, block_id:block_id) + assigned_rooms = available_rooms(check_in, check_out, block_id:block_id).last(number_of_rooms) + assigned_rooms.each do |room| + new_reservation.rooms << room.id + find_room(room.id).reservations << new_reservation + if block_id == Integer + find_block(block_id).reservation << new_reservation + end + end + @reservations << new_reservation + return new_reservation + end + + def available_rooms(check_in, check_out, block_id:'') + if block_id == '' + available_rooms = self.rooms.select {|room| room.is_booked?(check_in, check_out) == false} + elsif block_id == Integer + available_rooms = find_block(block_id).rooms.select {|room| room.is_booked?(check_in, check_out) == false} + end + #loop through Reservations @match dates on the reservations#reject dates that match + return available_rooms #array + end + + def create_block(check_in, check_out, number_of_rooms: , discount_rate: 0.8) + block = Block.new(@blocks.length + 1, check_in: check_in, check_out: check_out, number_of_rooms: number_of_rooms, discount_rate: discount_rate) + rooms_to_hold = available_rooms(check_in, check_out).first(number_of_rooms) + rooms_to_hold.each do |room| + block.rooms << room.id + find_room(room.id).reservations << block# - this needs to be changed + end + @blocks << block + return block + end + +# def reserve_block(check_in, check_out, number_of_rooms: , block_id: @blocks.last.id) +# block_reservation = Reservation.new(@reservations.length + 1, check_in: check_in, check_out: check_out) +# #refer to blok - create find block based on id return blocked rooms +# available_rooms() check_out).last(number_of_rooms) +# assigned_rooms.each do |room| +# new_reservation.rooms << room.id +# find_room(room.id).reservations << new_reservation +# end +# @reservations << block_reservation +# return block_reservation +# end + end + + #how to account for multiple rooms for a rservation within a block_reservation + + # add additional room to reservation + + # def reserve_block(number_of_rooms, check_in, check_out, discount_rate: 0.80) + # while available_rooms(check_in, check_out).length > number_of_rooms + # block_reservation = Reservation.new(@reservations.length + 1, check_in: check_in, check_out: check_out, discount_rate: discount_rate) + # assigned_rooms = available_rooms(check_in, check_out).first(number_of_rooms) + # assigned_rooms.each do |room| + # block_reservation.rooms << room.id + # find_room(room.id).reservations << block_reservation + # end + # end + # @reservations << block_reservation + # return block_reservation + # end + + + + # def @reservations.all + # return @reservations + # end + #create instance of reservation + #find available room, assign room to reservation id + #ask room - do you have availability + + + +end diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..b012c9eea --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,53 @@ +require 'pry' +# require 'Date' + +module Hotel + class Room + attr_reader :id + attr_accessor :reservations + + def initialize(id_num) + @id = id_num + @reservations = [] + #bookings - both res/block + #define room as physical space + end + + def self.make_rooms(number_of_rooms) + rooms = [] + (1..number_of_rooms).each do |number| + rooms << Room.new(number) + end + return rooms + end + + #create helper methods that return boolean values - in reservmgr - create loop methods that take helper method booleans and creates an array + # def self.find_room(id) + # if room.id == id + # return room + # end + # end + # + # def find_reservation(date) + # date = Date.parse(date) + # return self.date_range.include? date + # end + + + def is_booked?(check_in, check_out) + check_in = Date.parse(check_in) + check_out = Date.parse(check_out) + check_dates = check_in...check_out + return false if self.reservations.length == 0 + self.reservations.each do |reservation| + if reservation.date_range.cover?(check_dates) + return true + elsif reservation.overlaps?(check_in, check_out) + return true + end + end + return false + end + + end +end diff --git a/spec/block_spec.rb b/spec/block_spec.rb new file mode 100644 index 000000000..e69de29bb diff --git a/spec/reservations_manager_spec.rb b/spec/reservations_manager_spec.rb new file mode 100644 index 000000000..98beb20c4 --- /dev/null +++ b/spec/reservations_manager_spec.rb @@ -0,0 +1,123 @@ +require_relative 'spec_helper' +require 'pry' +# require 'Date' + +describe "ReservationManager" do + before do + @hotel_ada = Hotel::ReservationManager.new(20) + @dateA = '2018-08-09' + @dateB = '2018-08-15' + @dateC = '2018-08-20' + @dateD = '2018-08-23' + @dateE ='2018-08-25' + @dateF = '2018-08-27' + @test_date = '2018-08-20' + + @new_reservation1 = @hotel_ada.reserve_room(@dateA, @dateC) + @new_reservation2 = @hotel_ada.reserve_room(@dateB, @dateD) + @new_reservation3 = @hotel_ada.reserve_room(@dateE, @dateF) + @new_reservation4 = @hotel_ada.reserve_room(@dateA, @dateD) + @new_reservation5 = @hotel_ada.reserve_room(@dateC, @dateD) + end + + describe "Initialize ReservationsManager" do + it "is an instance of Hotel::ReservationsManager" do + expect(@hotel_ada).must_be_instance_of Hotel::ReservationManager + expect(@hotel_ada.reservations).must_be_kind_of Array + end + + it "raise an argumenterror for invalid date range" do + expect { + @hotel_ada.reserve_room(@dateD, @dateC) + }.must_raise ArgumentError + expect { + @hotel_ada.reserve_room(@dateD, @dateD) + }.must_raise ArgumentError + end + end + + describe "Access all rooms" do + it "returns a list of all rooms" do + expect(@hotel_ada.rooms.count).must_equal 20 + expect(@hotel_ada.rooms).must_be_kind_of Array + expect(@hotel_ada.rooms[0]).must_be_instance_of Hotel::Room + end + end + + + describe "reserve_room ReservationManager" do + it "can reserve a room for a given date range" do + expect(@new_reservation1).must_be_instance_of Hotel::Reservation + #binding.pry + expect(@new_reservation1.id).must_equal 1 + expect(@new_reservation5.id).must_equal 5 + expect(@new_reservation1.check_in).must_be_instance_of Date + end + + it "adds room id to the reservation" do + expect(@new_reservation1.rooms).must_be_kind_of Array + expect(@new_reservation1.rooms[0]).must_equal 20 + expect(@new_reservation5.rooms[0]).must_equal 17 # llook for room with availability conflict + end + + it "adds the reservation to the instance of room" do + expect(@hotel_ada.rooms[0].reservations).must_be_kind_of Array + expect(@hotel_ada.rooms[19].reservations[0]).must_be_instance_of Hotel:: Reservation + expect(@hotel_ada.rooms[19].reservations[0].id).must_equal 1 + end + end + + describe "it can list all reservations for a given date" do + + it "returns a list of rooms for given date" do + expect(@hotel_ada.booked_reservations(@test_date)).must_be_kind_of Array + #binding.pry + expect(@hotel_ada.booked_reservations(@test_date)[0]).must_be_instance_of Hotel::Reservation + expect(@hotel_ada.booked_reservations(@test_date).length).must_equal 3 + end + end + + describe "Wave 2 - Return list of available rooms for given date range" do + it "returns a list of availble rooms for given date" do + expect(@hotel_ada.available_rooms('2018-08-20', '2018-08-23')).must_be_kind_of Array + #binding.pry + expect(@hotel_ada.available_rooms('2018-08-20', '2018-08-23').length).must_equal 16 + expect(@hotel_ada.available_rooms('2018-08-20', '2018-08-23')[0]).must_be_kind_of Hotel::Room + #expect(@hotel_ada.available_rooms("08.23.2018", "08.25.2018")[0].id).must_equal #rooom ID + end + end + + describe "Wave 3 - Reserve Block rooms" do + before do + @many_rooms_test = @hotel_ada.reserve_room('2018-08-20', '2018-08-23', number_of_rooms: 3) + @block = @hotel_ada.create_block('2018-08-20', '2018-08-23', number_of_rooms: 10, discount_rate: 0.8) + @block_reservation = @hotel_ada.reserve_room('2018-08-20', '2018-08-23', number_of_rooms: 2, block_id: 1) #:block_id + end + + it "allows you to reserve multiple rooms on one reservation" do + expect(@many_rooms_test).must_be_instance_of Hotel::Reservation + expect(@many_rooms_test.id).must_equal 6 + expect(@many_rooms_test.rooms.length).must_equal 3 + expect(@many_rooms_test.total_cost).must_equal 1800 + end + + it "can block a given amount of rooms over a date range" do + expect(@block).must_be_instance_of Hotel::Block + expect(@block.reservations).must_be_kind_of Array + expect(@block.rooms).must_be_kind_of Array + expect(@block.rooms[0]).must_be_kind_of Integer + expect(@block.rooms.length).must_equal 10 + expect(@block.date_range.count).must_equal 3 + end + + it "allows you to make multiple reservation in a block of rooms" do + + expect(@block.reservations[0]).must_be_instance_of Hotel::Reservation + end + end + +end + + + +# describe "List all rooms " diff --git a/spec/reservations_spec.rb b/spec/reservations_spec.rb new file mode 100644 index 000000000..d07825ccf --- /dev/null +++ b/spec/reservations_spec.rb @@ -0,0 +1,29 @@ +require_relative 'spec_helper' +require 'pry' + +describe "Reservation" do + describe "Initializes an instance of reservation and its instance methods" do + before do + @hotel_ada = Hotel::ReservationManager.new(20) + @new_reservation = @hotel_ada.reserve_room("2018-08-23", "2018-08-25") + @test_reservation = @hotel_ada.reserve_room("2018-09-18", "2018-09-20") + end + + it "creates an instance of Reservation" do + expect(@test_reservation).must_be_instance_of Hotel::Reservation + expect(@test_reservation.rooms).must_be_kind_of Array + expect(@test_reservation.check_in).must_be_instance_of Date + expect(@test_reservation.check_in.day).must_equal 18 + expect(@test_reservation.date_range.count).must_equal 2 + end + + it "checks to see if there are any reservations for a given date" do + expect(@test_reservation.find_reservation("2018-09-19")).must_equal true + end + + it "calulates total cost for a reservation" do +#binding.pry + expect(@test_reservation.total_cost).must_equal 400 + end + end +end diff --git a/spec/room_spec.rb b/spec/room_spec.rb new file mode 100644 index 000000000..eb2958f52 --- /dev/null +++ b/spec/room_spec.rb @@ -0,0 +1,18 @@ +require_relative 'spec_helper' +require 'pry' + +describe "Room Class" do + describe "Initialize Room" do + it "is an instance of Hotel::Room" do + + hotel_room = Hotel::Room.new(1) + expect(hotel_room).must_be_instance_of Hotel::Room + expect(hotel_room.reservations).must_be_kind_of Array + end + end + # wave 2 describe "is_available method" do + # it "checks to see which rooms are available for a given date range" do + # expect(hotel_room.is_available(check_in: '08-23-2018', check_out: '08-25-2018')).must_be_kind_of Array + # + # end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4d1e3fdc8..9431529e5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,15 @@ +require 'simplecov' +SimpleCov.start require 'minitest' require 'minitest/autorun' require 'minitest/reporters' + + # Add simplecov Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new # Require_relative your lib files here! +require_relative '../lib/reservations_manager.rb' +require_relative '../lib/room.rb' +require_relative '../lib/reservations.rb'