diff --git a/README.md b/README.md index 846b7732b..b58d05518 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,9 @@ Remember: Your job is to only build the classes that store information and handl #### User Stories - As a user of the hotel system... - - I can access the list of all of the rooms in the hotel + - I can access the list of all of the rooms in the hotel + GIVEN the user is a front desk hotel worker + THEN create a method that allows them to get a list of all rooms in the hotel - I can make a reservation of a room for a given date range - I can access the list of reservations for a specific date, so that I can track reservations by date - I can get the total cost for a given reservation diff --git a/design-activity.md b/design-activity.md new file mode 100644 index 000000000..8cf38578e --- /dev/null +++ b/design-activity.md @@ -0,0 +1,93 @@ + +-What classes does each implementation include? Are the lists the same? + + Each implementation has the exact same classes and the lists are exactly the same, but each one uses the classes completely differently. + +-Write down a sentence to describe each class? + + Implementation A: + + Class CartEntry: Allows you to set and get each cart entry price and entry quantity with an accessor method. + + Class ShoppingCart: Lets you get and set shopping cart list into an array. + + Class Order: It calculates total price of shopping cart by creating a Shopping Cart object and setting it to @cart instance variable. + + Implementation B: + + Class CartEntry: It initializes passed in unit price and quantity and then has a method to calculate the price for that cart entry. + + Class ShoppingCart: It initializes a shopping cart with an array of entries and has a method to calculate total cost for the shopping cart by calling on price method for each entry from class CartEntry. + + Class Order: Initializes @cart with a ShoppingCart object with has total price for shopping and adds in sales tax with the total price method. + +-How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper. + + Implementation A: + + In this implementation class Order depends on ShoppingCart Class to retrieve an array of product entries. Upon initialization Class Order instantiates a Shopping Cart object to get the shopping cart and stores them in @cart. To calculate total price it loops through ShoppingCart array and calls on each entries for it's price and quantity which is available for each cart entry from Class CartEntry. + + Implementation B: + + Unlike the first implementation each cart entries price is calculated in Class CartEntry. Then ShoppingCart class holds an array of entries and calculates price for the entire Shopping cart. Lastly, order creates an instance of ShoppingCart class and calculates the total price by adding on tax. + +-What data does each class store? How (if at all) does this differ between the two implementations? + + Implementation A: + + class CartEntry: It stores unit price and quantity of an entry. + + class ShoppingCart: Stores and array of entries. + + Class Order: calculates total price and includes taxes, but doesn't store anything. + + Implementation B: + + Class CartEntry: It stores unit_price and quantity for each entry. + + Class ShoppingCart: It stores and array of entries. + + Class Order: it doesn't store anything it calculates total price to include sales tax. + + Comparing both: + They don't differ in their storage of data. They pretty much store the same type of information in each class.They differ in the way they depend on each-other for different things. + +-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? + + Implementation A: It is retained in Order. In this implementation the total_price method does everything. + + Implementation B: In this implementation is delated to lower classes and it just calculates the sales Tax in the method. + +-Does total_price directly manipulate the instance variables of other classes? + + In implementation A it does manipulate the instance variables of other classes because it goes through each and every since entry and calls for the unit price and quantity and multiplies them together. In implementation B it simply calls on Shopping cart for the price and adds on the sales Tax. It never touches any of the other classes instance variables. + +-If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify? + + You would have to change the class CartEntry to include this feature, but it would be really hard to change it in Implementation A because you would have to also change the order Class and not just CartEntry Class. + +-Which implementation better adheres to the single responsibility principle? + + Implementation B, better adheres the single responsibility principle because each class is responsible for exactly one thing. + +-Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled? + + +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? + + + + DateRange: creates a date range given a start date and end-date and validates it, it also checks for overlap, and calculates total number of nights stayed. + + Reservation: using DateRange object it tally's the cost for a particular room. + + Concierge: Is in charge of making reservations for the first available given DateRange. + + \ No newline at end of file diff --git a/design-scaffolding-notes.md b/design-scaffolding-notes.md new file mode 100644 index 000000000..141f71585 --- /dev/null +++ b/design-scaffolding-notes.md @@ -0,0 +1,38 @@ +# Hotel Design Scaffolding + +## Purpose + +This scaffolding is intended for students who are feeling overwhelmed by the open-ended nature of the Hotel project. Its goal is to answer some of the initial questions about how project files should be laid out, so that students can focus on designing the object interactions and complex Ruby logic that are the core learning goals of the project. The hope is to do so without removing too much of the interesting design work. + +This document and the associated code is intended to be student-facing - if you have a student you think would benefit from this, send them a link! + +### What it includes + +- Three class stubs, `HotelController`, `Reservation` and `DateRange` +- Stubs for public methods of each class from waves 1 and 2, as described in the user stories +- "Interface" tests for each class method that invoke it with the right parameters and verify the return type +- Full test stubs for the `DateRange` class + +### What it does not include + +- Opinions about how classes should interact or data should be stored +- Opinions about whether there should be a `Room` class, or whether it should know about `Reservation`s +- Private helper methods to keep code organized + +Students should feel free to modify any code as they see fit, including changing method signatures, adding new classes and methods, reordering things, not looking at the `DateRange` tests because they want to give it a shot on their own, etc. + +## How to use this code + +Design scaffolding code lives on the `design-scaffolding` branch. + +You can use this code either as inspiration, or as a starting point. If using it as an inspiration, it follows our standard project layout, with product code under `lib/` and tests under `spec/`. + +If you choose to use the code on this branch as a starting point, follow these steps to start your project: + +``` +$ git clone +$ cd hotel +$ git merge origin/design-scaffolding +``` + +You can try to merge in the design scaffolding after you've started, but you'll probably end up with merge conflicts. See an instructor if you're not able to resolve them yourself. diff --git a/lib/concierge.rb b/lib/concierge.rb new file mode 100644 index 000000000..4a81c9e16 --- /dev/null +++ b/lib/concierge.rb @@ -0,0 +1,54 @@ +module Hotel + class Concierge + attr_reader :rooms, :reservations + + # Wave 1 + def initialize(rooms = (1..20).to_a) + @rooms = rooms + @reservations = [] + end + + def reserve_room(start_date, end_date) + rooms = available_rooms(start_date, end_date) + if rooms.empty? + raise StandardError.new "No rooms available #{start_date} through #{end_date} " + end + res = Reservation.new(start_date, end_date, rooms.first) + # if res.start_date.instance_of?(Date) false && res.end_date.instance_of?(Date) false + # raise StandardError.new "Invalid date" + # end + @reservations << res + # start_date and end_date should be instances of Date + return res + end + + def find_reservations(date) + specific_date_reservations = [] + @reservations.each do |reservation| + if reservation.date_range.include?(date) + specific_date_reservations << reservation + end + end + return specific_date_reservations + end + + # Wave 2 + def available_rooms(start_date, end_date) + conflicting = [] + @reservations.each do |reservation| + if reservation.date_range.overlap?(DateRange.new(start_date, end_date)) + conflicting << reservation.room + end + end + open_rooms = [] + rooms.each do |room| + unless conflicting.include?(room) + open_rooms << room + end + end + return open_rooms + end + end +end + + \ No newline at end of file diff --git a/lib/date_range.rb b/lib/date_range.rb new file mode 100644 index 000000000..84f2971af --- /dev/null +++ b/lib/date_range.rb @@ -0,0 +1,40 @@ +module Hotel + class DateRange + attr_accessor :start_date, :end_date + + def initialize(start_date, end_date) + self.class.validate_date(start_date, end_date) + @start_date = start_date + @end_date = end_date + end + + def self.validate_date(start_date, end_date) + number_of_days = (end_date - start_date).to_i + puts number_of_days + + if start_date.nil? || end_date.nil? + raise ArgumentError.new('Invalid no Start and/or End Date given') + elsif start_date == end_date + raise ArgumentError.new('Invalid Start and/or End Date') + elsif number_of_days < 0 + raise ArgumentError.new('Invalid, end date cant be before start date') + end + end + + #finish this: + def overlap?(other_range) + if self.start_date == other_range.start_date && self.end_date == other_range.end_date + return true + elsif other_range.start_date >= self.start_date && other_range.end_date >= end_date + return true + else + return false + end + end + + def nights + total_nights = (end_date - start_date).to_i + return total_nights + end + end +end \ No newline at end of file diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..284dc9124 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,17 @@ +module Hotel + class Reservation + attr_reader :date_range, :room + + #I don't need to have start and end date in initialize becuase that would be bad design. + #start and end dates are already saved in date-range. + def initialize(start_date, end_date, room) + @date_range = DateRange.new(start_date, end_date) + @room = room + end + + def cost + total_cost = date_range.nights * 200 + return total_cost + end + end +end diff --git a/test/concierge_test.rb b/test/concierge_test.rb new file mode 100644 index 000000000..599ac17f9 --- /dev/null +++ b/test/concierge_test.rb @@ -0,0 +1,46 @@ +require_relative "test_helper" + +describe Hotel::Concierge do + #What does @concierge do in this test at beginning? I don't understand is it creating a new + #Concierge obj + before do + @concierge = Hotel::Concierge.new + @date = Date.parse("2020-08-04") + end + describe "Wave 1" do + describe "rooms" do + it "returns a list of rooms" do + rooms = @concierge.rooms + expect(rooms).must_be_kind_of Array + end + end + describe "reserve_room" do + it "takes two Date objects and returns a Reservation" do + start_date = @date + end_date = start_date + 3 + @reservation = @concierge.reserve_room(start_date, end_date) + expect(@reservation).must_be_kind_of Hotel::Reservation + end + end + describe "find_reservations" do + it "takes a Date and returns a list of Reservations" do + reservation_list = @concierge.find_reservations(@date) + + expect(reservation_list).must_be_kind_of Array + reservation_list.each do |res| + res.must_be_kind_of Reservation + end + end + end + end + describe "Wave 2" do + describe "available_rooms" do + it "takes two dates and returns a list" do + start_date = @date + end_date = start_date + 3 + room_list = @concierge.available_rooms(start_date, end_date) + expect(room_list).must_be_kind_of Array + end + end + end +end diff --git a/test/date_range_test.rb b/test/date_range_test.rb new file mode 100644 index 000000000..780430206 --- /dev/null +++ b/test/date_range_test.rb @@ -0,0 +1,137 @@ +require_relative "test_helper" + +describe Hotel::DateRange do + describe "Constructor" do + it "Can be initialized with two dates" do + start_date = Date.new(2017, 01, 01) + end_date = start_date + 3 + range = Hotel::DateRange.new(start_date, end_date) + expect(range.start_date).must_equal start_date + expect(range.end_date).must_equal end_date + end + + it "is an an error for negative-length ranges" do + start_date = Date.new(2017, 01, 01) + end_date = Date.new(2016,01,01) + expect { Hotel::DateRange.new(start_date, end_date) }.must_raise ArgumentError + end + + it "is an error to create a 0-length range" do + start_date = Date.new(2017, 01, 01) + end_date = Date.new(2017, 01, 01) + expect {Hotel::DateRange.new(start_date, end_date)}.must_raise ArgumentError + end + end + + describe "overlap?" do + before do + start_date = Date.new(2017, 01, 01) + end_date = start_date + 3 + @range = Hotel::DateRange.new(start_date, end_date) + end + + it "returns true for the same range" do + start_date = @range.start_date + end_date = @range.end_date + test_range = Hotel::DateRange.new(start_date, end_date) + expect(@range.overlap?(test_range)).must_equal true + end + + it "returns false for range starting on the end_date date" do + start_date = @range.end_date + end_date = start_date + 2 + test_range = Hotel::DateRange.new(start_date, end_date) + expect(@range.overlap?(test_range)).must_equal false + end + + +#This test is not working fix it.!!! Not sure if it works at this point. + it "returns true for a range that overlaps with start date inside" do + start_date = @range.start_date + 1 + #changed the > sign to less than becuase I want start date in w/in the range + expect(start_date).must_be < @range.end_date + end_date = @range.end_date + 3 + expect(end_date).must_be > @range.end_date + # I don't know if I need tests range here : do I if it was created in before statement + test_range = Hotel::DateRange.new(start_date, end_date) + expect(@range.overlap?(test_range)).must_equal true + end + + #Test that I added to check end date inside range + it "returns true for a range that overlaps with the end date inside" do + start_date = @range.start_date - 1 + expect(start_date).must_be < @range.start_date + end_date = @range.end_date - 1 + expect(end_date).must_be < @range.end_date + test_range = Hotel::DateRange.new(start_date, end_date) + expect(@range.overlap?(test_range)).must_equal true + end + + xit "returns false for a range ending on the start_date date" do + start_date = @range.start_date - 3 + expect(start_date).must_be < @range.start_date + end_date = @range.start_date + expect(end_date).must_equal @range.start_date + test_range = Hotel::DateRange.new(start_date, end_date) + expect(@range.overlap?(test_range)).must_equal false + end + + xit "returns false for a range completely before" do + start_date = @range.start_date - 7 + end_date = @range.start_date - 5 + test_range = Hotel::DateRange.new(start_date, end_date) + expect(@range.overlap?(test_range)).must_equal false + end + + xit "returns false for a date completely after" do + start_date = @range.end_date + 10 + end_date = @range.end_date + 15 + test_range = Hotel::DateRange.new(start_date, end_date) + expect(@range.overlap?(test_range)).must_equal false + end + end + + xdescribe "include?" do + + xit "returns true for a contained range" do + start_date = @range.start_date + end_date = @range.end_date + test_range = Hotel::DateRange.new(start_date, end_date) + expect(@range.include?(test_range.start_date)).must_equal true + expect(@range.include?(test_range.end_date)).must_equal true + end + + xit "returns true for a range that overlaps after @range start_date " do + start_date = Date.new(2017,12,28) + end_date = Date.new(2017,01, 02) + test_range = Hotel::DateRange.new(start_date, end_date) + expect(@range.include?(test_range.end_date)).must_equal true + end + + xit "returns true for a range that overlaps in the back" do + start_date = Date.new(2017 ,12 ,28) + end_date = Date.new(2017,03 ,02) + test_range = Hotel::DateRange.new(start_date, end_date) + expect(@range.include?(test_range.start_date)).must_equal true + end + + it "returns false if the date is clearly out" do + end + + it "returns true for dates in the range" do + end + + it "returns false for the end_date date" do + end + end + + xdescribe "nights" do + start_date = Date.new(2017, 01, 01) + end_date = start_date + 3 + @range = Hotel::DateRange.new(start_date, end_date) + + it "returns the correct number of nights" do + expect(@range.end_date - @range.start_date).must_equal 3 + end + end +end diff --git a/test/reservation_test.rb b/test/reservation_test.rb new file mode 100644 index 000000000..ec4204774 --- /dev/null +++ b/test/reservation_test.rb @@ -0,0 +1,12 @@ +require_relative "test_helper" + +describe Hotel::Reservation do + describe "cost" do + it "returns a number" do + start_date = Date.new(2017, 01, 01) + end_date = start_date + 3 + reservation = Hotel::Reservation.new(start_date, end_date, nil) + expect(reservation.cost).must_be_kind_of Numeric + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index c3a7695cf..77807c93a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,8 +1,17 @@ -# Add simplecov +require 'simplecov' +SimpleCov.start do + add_filter 'test/' # Tests should not be checked for coverage. +end require "minitest" require "minitest/autorun" require "minitest/reporters" +require "minitest/skip_dsl" + +require "date" Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new -# require_relative your lib files here! +# Require_relative your lib files here! +require_relative "../lib/concierge.rb" +require_relative "../lib/reservation.rb" +require_relative "../lib/date_range.rb"