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/design-activity.md b/design-activity.md new file mode 100644 index 000000000..eca0fe74b --- /dev/null +++ b/design-activity.md @@ -0,0 +1,48 @@ +What classes does each implementation include? Are the lists the same? + Each implementation contains CartEntry, ShoppingCart and Order. (The lists are the same) + +Write down a sentence to describe each class. + CartEntry: one or more of the same item that has been added to the cart. + ShoppingCart: a collection of all CartEntries with an additional function to calculate total price. + Order: a container class for ShoppingCart that also calculates total price 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 and Order have a 1:1 relationship. + ShoppingCart has many CartEntries. + +What data does each class store? How (if at all) does this differ between the two implementations? + CartEntry: stores unit_price and quantity in both implementations. + ShoppingCart: + A: stores an array of CartEntries. + B: stores an array of CartEntries, has a function to return price. + Order: + A: stores total_price, calculated by asking each CartEntry for price data. + B: stores total_price by asking ShoppingCart for price. + +What methods does each class have? How (if at all) does this differ between the two implementations? + CartEntry: + ShoppingCart: + 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? + A: retained in Order + B: delegated to lower level classes +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? + A: Harder to modify. We would need to add additional logic to Order, to instruct it which price to apply for each CartEntry depending on quantity. We would also need to make an additional variable for CartEntry to store bulk price. + B: Easier to modify. We could add some logic to Cart's price function. We would also need to make an additional variable for CartEntry to store bulk price. + +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 + +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. + This is not listed in my refactor ideas (which were mainly extra features, not improvements of existing features), but: the available_rooms function within Hotel contains logic that should be the responsibility of the Room class. Right now, Hotel is determining which rooms are available by accessing their instance variables of reservations and blocks, but ideally it would ask each room whether it's available and let the room make that determination by searching its own instance variables. I've edited the code to make that change by adding an is_available function to Room, and removing the overreaching logic from Hotel. + + (A similar change could lighten up a few other functions within Hotel as well, such as reservations_by_date.) \ No newline at end of file diff --git a/hotel.notes.rtf b/hotel.notes.rtf new file mode 100644 index 000000000..b3caffb72 --- /dev/null +++ b/hotel.notes.rtf @@ -0,0 +1,237 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf600 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fswiss\fcharset0 Helvetica-Bold;\f2\fnil\fcharset0 HelveticaNeue; +\f3\fnil\fcharset0 LucidaGrande;\f4\fnil\fcharset0 HelveticaNeue-Bold;\f5\fnil\fcharset0 LucidaGrande-Bold; +} +{\colortbl;\red255\green255\blue255;\red27\green31\blue34;\red255\green255\blue255;} +{\*\expandedcolortbl;;\cssrgb\c14118\c16078\c18039;\cssrgb\c100000\c100000\c100000;} +{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{circle\}}{\leveltext\leveltemplateid1\'01\uc0\u9702 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1} +{\list\listtemplateid2\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{circle\}}{\leveltext\leveltemplateid101\'01\uc0\u9702 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid2} +{\list\listtemplateid3\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid201\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid3} +{\list\listtemplateid4\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{circle\}}{\leveltext\leveltemplateid301\'01\uc0\u9702 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{hyphen\}}{\leveltext\leveltemplateid302\'01\uc0\u8259 ;}{\levelnumbers;}\fi-360\li1440\lin1440 }{\listname ;}\listid4} +{\list\listtemplateid5\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{circle\}}{\leveltext\leveltemplateid401\'01\uc0\u9702 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{hyphen\}}{\leveltext\leveltemplateid402\'01\uc0\u8259 ;}{\levelnumbers;}\fi-360\li1440\lin1440 }{\listname ;}\listid5} +{\list\listtemplateid6\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{circle\}}{\leveltext\leveltemplateid501\'01\uc0\u9702 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{hyphen\}}{\leveltext\leveltemplateid502\'01\uc0\u8259 ;}{\levelnumbers;}\fi-360\li1440\lin1440 }{\listname ;}\listid6} +{\list\listtemplateid7\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{circle\}}{\leveltext\leveltemplateid601\'01\uc0\u9702 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{hyphen\}}{\leveltext\leveltemplateid602\'01\uc0\u8259 ;}{\levelnumbers;}\fi-360\li1440\lin1440 }{\listname ;}\listid7} +{\list\listtemplateid8\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{circle\}}{\leveltext\leveltemplateid701\'01\uc0\u9702 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{hyphen\}}{\leveltext\leveltemplateid702\'01\uc0\u8259 ;}{\levelnumbers;}\fi-360\li1440\lin1440 }{\listname ;}\listid8} +{\list\listtemplateid9\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{circle\}}{\leveltext\leveltemplateid801\'01\uc0\u9702 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{square\}}{\leveltext\leveltemplateid802\'01\uc0\u9642 ;}{\levelnumbers;}\fi-360\li1440\lin1440 }{\listname ;}\listid9} +{\list\listtemplateid10\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{circle\}}{\leveltext\leveltemplateid901\'01\uc0\u9702 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid10}} +{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}{\listoverride\listid3\listoverridecount0\ls3}{\listoverride\listid4\listoverridecount0\ls4}{\listoverride\listid5\listoverridecount0\ls5}{\listoverride\listid6\listoverridecount0\ls6}{\listoverride\listid7\listoverridecount0\ls7}{\listoverride\listid8\listoverridecount0\ls8}{\listoverride\listid9\listoverridecount0\ls9}{\listoverride\listid10\listoverridecount0\ls10}} +\margl1440\margr1440\vieww10800\viewh8400\viewkind0 +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 + +\f0\fs24 \cf0 HOTEL NOTES\ +\ + +\f1\b Wave One: +\f0\b0 \ +\ +\pard\pardeftab720\partightenfactor0 + +\f2 \cf2 \cb3 \expnd0\expndtw0\kerning0 +As a user of the hotel system...\cb1 \ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls1\ilvl0\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext +\f3 \uc0\u9702 +\f2 } +\f4\b \expnd0\expndtw0\kerning0 +I can access the list of all of the rooms in the hotel: Hotel +\f2\b0 \cb1 \ +\ls1\ilvl0\cb3 \kerning1\expnd0\expndtw0 {\listtext +\f3 \uc0\u9702 +\f2 } +\f4\b \expnd0\expndtw0\kerning0 +I can make a reservation of a room for a given date range: Reservation\cb1 \ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls1\ilvl0\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext +\f5 \uc0\u9702 +\f4 }\expnd0\expndtw0\kerning0 +I can access the list of reservations for a specific date, so that I can track reservations by date: Hotel +\f2\b0 \cb1 \ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls1\ilvl0\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext +\f3 \uc0\u9702 +\f2 } +\f4\b \expnd0\expndtw0\kerning0 +I can get the total cost for a given reservation: Reservation +\f2\b0 \cb1 \ +\ls1\ilvl0\cb3 \kerning1\expnd0\expndtw0 {\listtext +\f3 \uc0\u9702 +\f2 } +\f4\b \expnd0\expndtw0\kerning0 +I want exception raised when an invalid date range is provided, so that I can't make a reservation for an invalid date range: Reservation\ +\pard\tx720\pardeftab720\partightenfactor0 +\cf2 \ +Notes:\ +\ + +\f2\b0 Reservation class and methods stored in reservation.rb. I will make a new reservation from the hotel file (like trip in trip dispatcher). \ +For wave 1, I assign a room in reservation. I will move this logic to Hotel in wave 2.\ +Editing reservation to store both a date range and a start_date and end_date \ +\ + +\f4\b WAVE TWO: +\f2\b0 \ +\ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls2\ilvl0\cf2 \kerning1\expnd0\expndtw0 {\listtext +\f3 \uc0\u9702 +\f2 } +\f4\b \expnd0\expndtw0\kerning0 +I can view a list of rooms that are not reserved for a given date range, so that I can see all available rooms for that day +\f2\b0 \cb1 \ +\ls2\ilvl0\cb3 \kerning1\expnd0\expndtw0 {\listtext +\f3 \uc0\u9702 +\f2 } +\f4\b \expnd0\expndtw0\kerning0 +I can get a reservation of a room for a given date range, and that room will not be part of any other reservation overlapping that date range +\f2\b0 \cb1 \ +\ls2\ilvl0\cb3 \kerning1\expnd0\expndtw0 {\listtext +\f3 \uc0\u9702 +\f4\b } +\f2\b0 \expnd0\expndtw0\kerning0 +I want an exception raised if I try to reserve a room during a date range when all rooms are reserved, so that I cannot make two reservations for the same room that overlap by date\ +\pard\tx720\pardeftab720\partightenfactor0 + +\f4\b \cf2 \ + +\f2\b0 Working on enumerating through a date range. Thoughts from date class:\ +\ +Until start_date == end_date\ +Check for avail\ +start_date = start_date.next_day\ +End \ +\ +Try to use each on date range? It works!\ +\ +Or make dates into array. Each room\'92s reservation must not include each of the dates in the array \ +\ +Final answer: GREP. Whoa I love grep.\ +\ +Thoughts on date range:\ +I will use this object in: \ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls3\ilvl0\cf2 \kerning1\expnd0\expndtw0 {\listtext \uc0\u8226 }\expnd0\expndtw0\kerning0 +reservation (a reservation is kinda just a date-range that stores a room number rn)\ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls3\ilvl0 +\f4\b \cf2 \kerning1\expnd0\expndtw0 {\listtext \uc0\u8226 } +\f2\b0 block\ +\ls3\ilvl0 +\f4\b {\listtext \uc0\u8226 } +\f2\b0 hotel.available_rooms \ +\pard\tx720\pardeftab720\partightenfactor0 + +\f4\b \cf2 \expnd0\expndtw0\kerning0 +\ + +\f2\b0 Fighting with grep: current function wording \'93return an array of rooms for which zero existing reservations produce an overlap array of more than zero days with my requested dates\'94\ +When grep detects no overlap, it return an empty array\ +\'93Return an array of rooms for which zero existing reservations produce an overlap array of more than zero dates with my requested dates\'94\ +\ +Problem: available_rooms is returning all rooms no matter what\ +Solution: you need to specify the range attribute of the date_range object, dummy\ +\ + +\f4\b WAVE 3 +\f2\b0 \ +\ +\pard\pardeftab720\partightenfactor0 +\cf2 As a user of the hotel system,\cb1 \ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls4\ilvl0\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext +\f3 \uc0\u9702 +\f2 } +\f4\b \expnd0\expndtw0\kerning0 +I can create a Hotel Block if I give a date range, collection of rooms, and a discounted room rate +\f2\b0 \ +\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0 +\ls4\ilvl1\cf2 \kerning1\expnd0\expndtw0 {\listtext \uc0\u8259 }new block.rb class, parameters date range, number of rooms, room rate\ +{\listtext \uc0\u8259 }make_block function in hotel\ +{\listtext \uc0\u8259 }adjust room rate \ +{\listtext \uc0\u8259 }store array of blocks for each room\expnd0\expndtw0\kerning0 +\ +\pard\tx720\pardeftab720\partightenfactor0 +\cf2 \cb1 \ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls5\ilvl0 +\f4\b \cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext +\f5 \uc0\u9702 +\f4 }\expnd0\expndtw0\kerning0 +I want an exception raised if I try to create a Hotel Block and at least one of the rooms is unavailable for the given date range +\f2\b0 \ +\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0 +\ls5\ilvl1\cf2 \kerning1\expnd0\expndtw0 {\listtext \uc0\u8259 }happens within make_block function within hotel.rb\expnd0\expndtw0\kerning0 +\ +\pard\tx720\pardeftab720\partightenfactor0 +\cf2 \cb1 \ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls6\ilvl0\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext +\f3 \uc0\u9702 +\f2 } +\f4\b \expnd0\expndtw0\kerning0 +Given a specific date, and that a room is set aside in a hotel block for that specific date, I cannot reserve that specific room for that specific date, because it is unavailable +\f2\b0 \ +\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0 +\ls6\ilvl1\cf2 \kerning1\expnd0\expndtw0 {\listtext \uc0\u8259 }add to available_rooms\expnd0\expndtw0\kerning0 +\ +\pard\tx720\pardeftab720\partightenfactor0 + +\f4\b \cf2 \cb1 \ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls7\ilvl0\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext +\f5 \uc0\u9702 +\f4 }\expnd0\expndtw0\kerning0 +Given a specific date, and that a room is set aside in a hotel block for that specific date, I cannot create another hotel block that includes that specific room for that specific date, because it is unavailable +\f2\b0 \ +\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0 +\ls7\ilvl1\cf2 \kerning1\expnd0\expndtw0 {\listtext \uc0\u8259 }available_rooms\expnd0\expndtw0\kerning0 +\ +\pard\tx720\pardeftab720\partightenfactor0 +\cf2 \cb1 \ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls8\ilvl0\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext +\f3 \uc0\u9702 +\f4\b } +\f2\b0 \expnd0\expndtw0\kerning0 +I can check whether a given block has any rooms available\ +\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0 +\ls8\ilvl1\cf2 \kerning1\expnd0\expndtw0 {\listtext \uc0\u8259 }new function within block\expnd0\expndtw0\kerning0 +\ +\pard\tx720\pardeftab720\partightenfactor0 +\cf2 \cb1 \ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls9\ilvl0\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext +\f5\b \uc0\u9702 +\f4 } +\f2\b0 \expnd0\expndtw0\kerning0 +I can reserve a specific room from a hotel block\cb1 \ +\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0 +\ls9\ilvl1\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext +\f5\b \uc0\u9642 +\f4 } +\f2\b0 \expnd0\expndtw0\kerning0 +I can only reserve that room from a hotel block for the full duration of the block\ +\ls9\ilvl1\kerning1\expnd0\expndtw0 {\listtext +\f5\b \uc0\u9642 +\f4 } +\f2\b0 should happen within make_block_reservation\expnd0\expndtw0\kerning0 +\ +\pard\tx720\tx1440\pardeftab720\partightenfactor0 +\cf2 \cb1 \ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\ls10\ilvl0\cf2 \cb3 \kerning1\expnd0\expndtw0 {\listtext +\f3 \uc0\u9702 +\f2 } +\f4\b \expnd0\expndtw0\kerning0 +I can see a reservation made from a hotel block from the list of reservations for that date (see wave 1 requirements) +\f2\b0 <\'97should be nbd, block_res will not be different from res. Optional block_name parameter.\ +\pard\tx720\pardeftab720\partightenfactor0 + +\f4\b \cf2 \ +Refactor ideas:\ +Eliminate past reservations\ +Allow a reservation to be edited\ +Track reservation by name, maybe in a hash\ +Make promo codes, discount rates\ +\ +\ +} \ No newline at end of file diff --git a/lib/block.rb b/lib/block.rb new file mode 100644 index 000000000..6159df4c3 --- /dev/null +++ b/lib/block.rb @@ -0,0 +1,20 @@ +class Block + attr_reader :date_range, :name, :room_quantity, :cost + attr_accessor :room_nums + + def initialize(block_range, block_name, room_quantity, cost) + @date_range = block_range + @name = block_name + @room_quantity = room_quantity + @room_nums = [] + @cost = cost + end + + def total_cost + return @cost * (date_range.end_date - date_range.start_date) + end + + def unbooked_rooms_count + return @room_nums.length + end +end \ No newline at end of file diff --git a/lib/daterange.rb b/lib/daterange.rb new file mode 100644 index 000000000..f9c27fcab --- /dev/null +++ b/lib/daterange.rb @@ -0,0 +1,14 @@ +require 'date' + +class DateRange + attr_reader :start_date, :end_date, :range + + def initialize(start_year, start_month, start_day, end_year, end_month, end_day) + @start_date = Date.new(start_year, start_month, start_day) + @end_date = Date.new(end_year, end_month, end_day) + @range = (@start_date...@end_date) + if @end_date <= @start_date + raise ArgumentError, "End date must be later than start date." + end + end +end \ No newline at end of file diff --git a/lib/hotel.rb b/lib/hotel.rb new file mode 100644 index 000000000..83f0a08b0 --- /dev/null +++ b/lib/hotel.rb @@ -0,0 +1,92 @@ +require_relative 'room' +require_relative 'reservation' +require_relative 'daterange' +require_relative 'block' + +class Hotel + attr_reader :rooms, :blocks + + def initialize(rooms) + @rooms = [] + @blocks = [] + rooms.times do |i| + @rooms << Room.new(i + 1) + end + end + + def list_rooms + @rooms.each do |curr_room| + puts "Room Number #{curr_room.room_num}" + end + end + + def make_reservation(start_year, start_month, start_day, end_year, end_month, end_day) + res_range = DateRange.new(start_year, start_month, start_day, end_year, end_month, end_day) + new_reservation = Reservation.new(res_range) + potential_rooms = available_rooms(res_range) + if potential_rooms.length == 0 + raise ArgumentError, "No vacancy" + end + new_reservation.room_num = potential_rooms.sample.room_num + @rooms[new_reservation.room_num - 1].reservations << new_reservation + return new_reservation + end + + #reserve a room within a previously scheduled block + def make_block_reservation(start_year, start_month, start_day, end_year, end_month, end_day, input_name) + res_range = DateRange.new(start_year, start_month, start_day, end_year, end_month, end_day) + block = blocks.find {|block| block.name == input_name} + if block == nil + raise ArgumentError, "No block under that name was found in the hotel system." + end + if block.date_range.range != res_range.range + raise ArgumentError, "The requested booking dates do not match the dates specified by the block." + end + new_block_reservation = Reservation.new(res_range, input_name) + new_block_reservation.room_num = block.room_nums.shift + @rooms[new_block_reservation.room_num - 1].reservations << new_block_reservation + return new_block_reservation + end + + #create a new block of multiple rooms + def make_block(start_year, start_month, start_day, end_year, end_month, end_day, block_name, room_quantity, cost) + block_range = DateRange.new(start_year, start_month, start_day, end_year, end_month, end_day) + block = Block.new(block_range, block_name, room_quantity, cost) + if @blocks.any? { |block| block.name == block_name && block.date_range.range == block_range.range } + raise ArgumentError, "A block with identical name and dates already exists at this hotel. Please choose a different name or dates." + end + block_rooms = available_rooms(block_range).sample(room_quantity) + if block_rooms.length < room_quantity + raise ArgumentError, "The hotel does not have a sufficient number of empty rooms to book this block." + end + block_rooms.each do |block_room| + block.room_nums << block_room.room_num + @rooms[block_room.room_num - 1].blocks << block + end + @blocks << block + return block + end + + #shows which rooms are neither reserved nor blocked on a given day, require date_range + def available_rooms(req_range) + available_rooms_array = [] + rooms.each do |curr_room| + if curr_room.is_available(req_range) + available_rooms_array << curr_room + end + end + return available_rooms_array + end + + #returns all reservations for a given date + def reservations_by_date(year, month, day) + requested_date = Date.new(year, month, day) + reservations_by_date = [] + @rooms.each do |room| + if valid_date_res = room.reservations.find {|res|res.date_range.range.include?(requested_date)} + reservations_by_date << valid_date_res + end + end + return reservations_by_date + end +end diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..a03c6abbf --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,13 @@ +class Reservation + attr_reader :date_range, :block_name + attr_accessor :room_num + + def initialize(res_range, block_name = nil) + @date_range = res_range + @block_name = block_name + end + + def total_cost + return 200 * (date_range.end_date - date_range.start_date) + end +end \ No newline at end of file diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..2228e4d08 --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,14 @@ +class Room + attr_reader :room_num + attr_accessor :reservations, :blocks + + def initialize(room_num) + @room_num = room_num + @reservations = [] + @blocks = [] + end + + def is_available(req_range) + reservations.all? {|res| res.date_range.range.grep(req_range.range).length == 0 } && blocks.all? {|block| block.date_range.range.grep(req_range.range).length == 0 } + end +end \ No newline at end of file diff --git a/refactors.txt b/refactors.txt new file mode 100644 index 000000000..9b51428d3 --- /dev/null +++ b/refactors.txt @@ -0,0 +1,8 @@ +Changes I could make to hotel: + +-Edit reservation to include name, promo codes/discounts, details like check-out time, additional charges +-Track reservations in a hash using name as a key, to make look up faster +-Figure out how to eliminate reservations from past dates, so the system doesn't become overloaded as time progresses. +-Allow more aspects of a reservation to be edited. (change stay duration, for example) +-Eliminate or reduce dependencies as I understand them more + diff --git a/test/block_test.rb b/test/block_test.rb new file mode 100644 index 000000000..34155c156 --- /dev/null +++ b/test/block_test.rb @@ -0,0 +1,37 @@ +require_relative 'test_helper' + +describe "Block class" do + describe "Block instantiation" do + before do + @date_range = DateRange.new(2019, 7, 6, 2019, 7, 9) + @block = Block.new(@date_range, "name", 1, 100) + end + + it "is an instance of Block" do + expect(@block).must_be_kind_of Block + end + end + + describe "total cost" do + before do + @date_range = DateRange.new(2019, 7, 6, 2019, 7, 9) + @block = Block.new(@date_range, "name", 1, 100) + end + + it "can calculate total cost" do + expect(@block.total_cost).must_equal 300 + end + end + + describe "available_rooms" do + before do + @hotel = Hotel.new(4) + @block = @hotel.make_block(2019, 7, 6, 2019, 7, 9, "name", 3, 100) + end + + it "can return a list of available rooms" do + expect(@block.room_nums.length).must_equal @block.room_quantity + end + + end +end \ No newline at end of file diff --git a/test/daterange_test.rb b/test/daterange_test.rb new file mode 100644 index 000000000..c474a260b --- /dev/null +++ b/test/daterange_test.rb @@ -0,0 +1,29 @@ +require_relative 'test_helper' + +describe "DateRange class" do + describe "DateRange instantiation" do + before do + @date_range = DateRange.new(2019, 7, 6, 2019, 7, 9) + end + + it "is an instance of DateRange" do + expect(@date_range).must_be_kind_of DateRange + end + + it "includes a start date" do + expect(@date_range.start_date).must_be_kind_of Date + end + + it "includes an end date" do + expect(@date_range.end_date).must_be_kind_of Date + end + + it "does not include last day of range" do + expect(@date_range.range).wont_include @date_range.end_date + end + + it "raises an ArgumentError if end date is before start date" do + expect{DateRange.new(2019, 7, 6, 2019, 7, 1)}.must_raise ArgumentError + end + end +end \ No newline at end of file diff --git a/test/hotel_test.rb b/test/hotel_test.rb new file mode 100644 index 000000000..db6022cab --- /dev/null +++ b/test/hotel_test.rb @@ -0,0 +1,165 @@ +require_relative 'test_helper' + +describe "Hotel class" do + describe "Hotel instantiation" do + before do + @hotel = Hotel.new(4) + @res = @hotel.make_reservation(2019, 9, 1, 2019, 9, 5) + end + + it "is an instance of Hotel" do + expect(@hotel).must_be_kind_of Hotel + end + + it "has an array of rooms equalling the 'rooms' initialization parameter" do + expect(@hotel.rooms.length).must_equal 4 + end + end + + describe "make_reservation" do + before do + @hotel = Hotel.new(4) + @res = @hotel.make_reservation(2019, 9, 1, 2019, 9, 5) + end + + it "can create a reservation" do + expect(@res).must_be_kind_of Reservation + end + + it "adds reservation to the reservation array for the correct room" do + expect(@hotel.rooms[@res.room_num - 1].reservations.length).must_equal 1 + end + + it "returns an error if the user tries to book when the hotel is full" do + 3.times do + @hotel.make_reservation(2019, 9, 1, 2019, 9, 5) + end + expect {@hotel.make_reservation(2019, 9, 1, 2019, 9, 5)}.must_raise ArgumentError + end + end + + describe "make_reservation conflicting date edge cases" do + before do + @hotel = Hotel.new(1) + @res = @hotel.make_reservation(2019, 9, 1, 2019, 9, 5) + end + + it "will allow an adjacent previous booking" do + expect(@hotel.make_reservation(2019, 8, 1, 2019, 9, 1)).must_be_kind_of Reservation + end + + it "will allow an adjacent subsequent booking" do + expect(@hotel.make_reservation(2019, 9, 5, 2019, 9, 7)).must_be_kind_of Reservation + end + + it "will not allow a booking surrounding an existing reservation" do + expect{@hotel.make_reservation(2019, 8, 1, 2019, 10, 1)}.must_raise ArgumentError + end + + it "will not allow a booking within an existing reservation" do + expect{@hotel.make_reservation(2019, 9, 2, 2019, 9, 3)}.must_raise ArgumentError + end + + it "will not allow a partially overlapping booking" do + expect{@hotel.make_reservation(2019, 8, 27, 2019, 9, 3)}.must_raise ArgumentError + end + end + + describe "make_block" do + before do + @hotel = Hotel.new(4) + @block = @hotel.make_block(2019, 9, 1, 2019, 9, 5, "name", 1, 100) + end + + it "can create a block" do + expect(@block).must_be_kind_of Block + end + + it "correctly adds a block to the blocks array under the correct room when booking a single room" do + expect(@hotel.rooms[@block.room_nums[0] - 1].blocks.length).must_equal 1 + end + + it "correctly adds a block to the blocks array under multiple rooms when blocking multiple rooms" do + block2 = @hotel.make_block(2019, 9, 1, 2019, 9, 5, "zora", 3, 100) + @hotel.rooms.each do |room| + if block2.room_nums.include?(room.room_num) + expect(room.blocks[0]).must_equal block2 + end + end + end + + describe "make_block_reservation" do + before do + @hotel = Hotel.new(4) + @block = @hotel.make_block(2019, 9, 1, 2019, 9, 5, "zadie", 3, 100) + @blocked_room_nums = @block.room_nums.dup + @block_res = @hotel.make_block_reservation(2019, 9, 1, 2019, 9, 5, "zadie") + end + + it "can create a reservation" do + expect(@block_res).must_be_kind_of Reservation + end + + it "adds reservation to the reservation array for a room included in the block" do + expect(@hotel.rooms[@block_res.room_num - 1].reservations.length).must_equal 1 + expect(@hotel.rooms[@block_res.room_num - 1].reservations[0]).must_equal @block_res + expect(@blocked_room_nums).must_include @block_res.room_num + end + + it "returns an error if the user tries to book for different dates than the block specifies" do + expect {@hotel.make_block_reservation(2019, 9, 1, 2019, 9, 3, "zadie")}.must_raise ArgumentError + end + + it "return an error if the user tries to book a block under a name not found in the blocks array" do + expect {@hotel.make_block_reservation(2019, 9, 1, 2019, 9, 3, "nora")}.must_raise ArgumentError + end + + it "returns an error if the user tries to book a block containing a name and date range identical to a block already in the system" do + expect {@hotel.make_block(2019, 9, 1, 2019, 9, 5, "zadie", 1, 100)}.must_raise ArgumentError + end + + it "returns an error if the user tries to book more rooms than the hotel has available" do + expect {@hotel.make_block(2019, 9, 1, 2019, 9, 5, "diff_name", 4, 100)}.must_raise ArgumentError + end + + it "adds block to the hotel's list of blocks" do + expect(@hotel.blocks[0]).must_equal @block + end + end + + describe "available_rooms" do + before do + @hotel = Hotel.new(4) + @res = @hotel.make_reservation(2019, 9, 1, 2019, 9, 5) + @date_range = DateRange.new(2019, 9, 2, 2019, 9, 3) + end + + it "decreases list of available rooms for a date conflicting with a single reservation by 1" do + expect(@hotel.available_rooms(@date_range).length).must_equal 3 + end + + it "does not list previously reserved room as available for a subsequent reservation" do + available_room_nums = @hotel.available_rooms(@date_range).map do |room| + room.room_num + end + expect(available_room_nums).wont_include @res.room_num + end + + it "does not list previously blocked rooms as available for a subsequent reservation not within a block" do + @hotel.make_block(2019, 9, 1, 2019, 9, 5, "name", 2, 100) + expect(@hotel.available_rooms(@date_range).length).must_equal 1 + end + end + + describe "reservations_by_date" do + before do + @hotel = Hotel.new(4) + @res = @hotel.make_reservation(2019, 9, 1, 2019, 9, 5) + end + + it "returns a list of reservations for a given date" do + expect(@hotel.reservations_by_date(2019, 9, 2).length).must_equal 1 + end + end + end +end \ No newline at end of file diff --git a/test/reservation_test.rb b/test/reservation_test.rb new file mode 100644 index 000000000..02ace99ed --- /dev/null +++ b/test/reservation_test.rb @@ -0,0 +1,19 @@ +require_relative 'test_helper' + +describe "Reservation class" do + describe "Reservation instantiation" do + before do + @date_range = DateRange.new(2019, 7, 6, 2019, 7, 9) + @res = Reservation.new(@date_range) + end + + it "is an instance of Reservation" do + expect(@res).must_be_kind_of Reservation + end + + it "can calculate total cost" do + expect(@res.total_cost).must_equal 600 + end + + end +end \ No newline at end of file diff --git a/test/room_test.rb b/test/room_test.rb new file mode 100644 index 000000000..5ef3d036f --- /dev/null +++ b/test/room_test.rb @@ -0,0 +1,17 @@ +require_relative 'test_helper' + +describe "Room class" do + describe "Room instantiation" do + before do + @room = Room.new(4) + end + + it "is an instance of Room" do + expect(@room).must_be_kind_of Room + end + + it "can return room number" do + expect(@room.room_num).must_equal 4 + end + end +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index c3a7695cf..85696c399 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,8 +1,18 @@ -# Add simplecov +require 'simplecov' + +SimpleCov.start do + add_filter 'test/' +end + require "minitest" require "minitest/autorun" require "minitest/reporters" +require "minitest/pride" Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new -# require_relative your lib files here! +require_relative "../lib/room" +require_relative "../lib/hotel" +require_relative "../lib/reservation" +require_relative "../lib/block" +require_relative "../lib/daterange" \ No newline at end of file