diff --git a/.gitignore b/.gitignore index 5e1422c9c..7a01977d4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /test/tmp/ /test/version_tmp/ /tmp/ +/design.md # Used by dotenv library to load environment variables. # .env diff --git a/Rakefile b/Rakefile new file mode 100644 index 000000000..deb52f2cd --- /dev/null +++ b/Rakefile @@ -0,0 +1,9 @@ +require 'rake/testtask' + +Rake::TestTask.new do |t| + t.libs = ["lib"] + t.warning = true + t.test_files = FileList['specs/*_spec.rb'] +end + +task default: :test diff --git a/design-activity.md b/design-activity.md new file mode 100644 index 000000000..8d9c42089 --- /dev/null +++ b/design-activity.md @@ -0,0 +1,55 @@ +* What classes does each implementation include? Are the lists the same? + + a. Both implementation A and implementation B have three classes: CartEntry, ShoppingCart, and Order. + +* Write down a sentence to describe each class. + + a. CartEntry class - In both implementations, this class initializes two instance variable unit price and quantity. In implementation B the CartEntry class also contains a price method that calculates price by multiplying unit price and quantity. + + b. ShoppingCart class- In implementation A, ShoppingCart is simply a collection of entries. In implementation B, ShoppingCart is a collection of entries and calculates the price for each entry. + + c. Order class - Order creates a new instance of `@cart`, sets a constant variable for sales tax, and calculates the total price of the cart. Implementation A calculates the entry total and multiplies it by the sales tax while implementation B simply sets the subtotal to `@cart.price` and multiplies the 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. + + a. It seems that Order knows about ShoppingCart and ShoppingCart should know about CartEntry (this is assuming that each CartEntry gets pushed on to the `@entries` variable). + +* What data does each class store? How (if at all) does this differ between the two implementations? + + a. CartEntry stores `@unit_price` and `@quantity`, ShoppingCart stores `@entries`, Order stores `@cart` and `SALES_TAX`. The data is the same across both implementations. + +* What methods does each class have? How (if at all) does this differ between the two implementations? + + a. Both implementations have a total_price method but implementation B also has two price methods. In implementation B, a price in calculated for each cart entry, the shopping cart, and the entire order. In implementation A, price is only calculated once in the Order 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? + + a. implementation A - logic to compute price is all retained in the Order class. + b. implementation B - logic to compute price is delegated to the "lower level" classes, ShoppingCart and CartEntry. + + * Does total_price directly manipulate the instance variables of other classes? + a. implementation A - Yes + b. implementation 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. To make this modification, you would have to add a bulk discount to the method that calculates price. Implementation B is easier to modify because you can add the bulk discount to the CartEntry price method. + +* Which implementation better adheres to the single responsibility principle? + + a. Implementation B + +* Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled? + + a. Implementation B + + +# Revisiting Hotel + +Describe in `design-activity.md` what changes you would need to make to improve this design, and how why the resulting design would be an improvement. + + * In the DateRange class, I was calculating number_of_nights in using aq instance variable and making use of that instance variable in the Reservation Class to return the price in the total_price method. The change I made here was change number_of_nights from an instance variable to a instance method and I called on that instance method in the total_price method in the Reservation class. + + * In the view_available_rooms method in the Hotel class, I got rid of the available_room_nums instance variable and made available_rooms a local variable that references the instance variable rooms. This helped to get rid of some redundancy and dry up my code. With this change, I was also able to reference the view_available_rooms method inside of the reserve_room method. I also removed the reserved_room_nums instance variable and made reserved_rooms into a local variable to dry up the code. diff --git a/lib/block.rb b/lib/block.rb new file mode 100644 index 000000000..1a62e3881 --- /dev/null +++ b/lib/block.rb @@ -0,0 +1,29 @@ +module Hotel + class Block + attr_reader :date_range, :block_rooms, :discount, :hotel + def initialize + @hotel = Hotel.new + @block_rooms = [] + @discount = 0.2 + end + + # currently broken/incomplete + def create_block(rooms,check_in,check_out) + rooms = [] + # check what rooms are available + available_for_block = hotel.view_available_rooms(check_in,check_out) + # push 5 available room onto block_rooms + # ap "printing available:#{available_for_block}" + available_for_block.each do |room| + if rooms.length < 5 + rooms << room + end + end + # rooms.each do |room| + # hotel.reserve_room(room_id, check_in, check_out) + # end + # ap "printing rooms :#{rooms}" + end + + end +end diff --git a/lib/date_range.rb b/lib/date_range.rb new file mode 100644 index 000000000..ba98b73dd --- /dev/null +++ b/lib/date_range.rb @@ -0,0 +1,41 @@ +require 'date' +require 'awesome_print' +require 'pry' + +module Hotel + class DateRange + attr_reader :check_in, :check_out + def initialize(check_in, check_out) + @check_in = Date.parse(check_in) + @check_out = Date.parse(check_out) + # @number_of_nights = (@check_out - @check_in).to_i + unless @check_in > Date.today + raise ArgumentError.new("That date has passed.") + end + unless @check_in < @check_out + raise ArgumentError.new("Check in date must be before Check out date.") + end + end # end of initialize + + def overlap?(start_date, end_date) + # comparing one instance of date to another + if (self.check_in < Date.parse(end_date)) && (self.check_out >= Date.parse(start_date)) + return true + else + return false + end + end + + def number_of_nights + # changed number_of_nights from an intance variable to an instance method + return (@check_out - @check_in).to_i + end + + def return_date_range + date_range = "#{check_in} to #{check_out}" + # Hotel::HotelClass.reservations << date_range + puts date_range + return date_range + end # end of return_date_range + end # end of DateRange Clas +end # end of Module diff --git a/lib/hotel.rb b/lib/hotel.rb new file mode 100644 index 000000000..d86c2aeb4 --- /dev/null +++ b/lib/hotel.rb @@ -0,0 +1,69 @@ +require_relative 'reservation' + +module Hotel + class Hotel + attr_reader :rooms, :reservations, :reserved + def initialize + # access the list of all of the rooms in the hotel + @rooms = (1..20).to_a + @reservations = [] + @reserved = [] + end + + def view_available_rooms(check_in, check_out) + # view a list of rooms that are not reserved for a given date range + # loop thru reservations.date_range to see if it include the date_range passed in, if not return room_ids + + available_rooms = @rooms + reserved_rooms = [] + + reservations.each do |reservation| + if reservation.date_range.overlap?(check_in, check_out) + reserved << reservation + end + end + + reserved.each do |reserved_room| + reserved_rooms << reserved_room.room_id + end + + available_rooms -= reserved_rooms + return available_rooms + end + + def reserve_room(room_id, check_in, check_out) + unless @rooms.include?(room_id) + raise ArgumentError.new("That room does not exist") + end + + unless view_available_rooms(check_in, check_out).include?(room_id) + raise ArgumentError.new("Room #{room_id} is not available for those dates") + end + + new_reservation = Reservation.new(room_id, check_in, check_out) + reservations << new_reservation + + return new_reservation + end + + def access_reservations(date) + # access the list of reservations for a specific date + # loop over @reservations.date_range + # if date_range includes passed in date + # return reservations + + valid_reservations = [] + + reservations.each do |reservation| + # broken + # the if statement seems to be always returning false and what is being returned from reservations are + # therefore nothing is being pushed on valid_reservations + if (reservation.date_range.check_in..reservation.date_range.check_out).include?(date) + valid_reservations << reservation + end + end + return valid_reservations + end + + end +end diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..14bcabfc9 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,16 @@ +module Hotel + class Reservation + attr_reader :date_range, :room_id, :cost_per_night + def initialize(room_id,check_in,check_out) + # @id = id + @date_range = DateRange.new(check_in,check_out) + @room_id = room_id + @cost_per_night = 200 + end + + def get_total + # get the total cost for a given reservation + return date_range.number_of_nights * @cost_per_night + end + end +end diff --git a/specs/block_spec.rb b/specs/block_spec.rb new file mode 100644 index 000000000..42467a7d7 --- /dev/null +++ b/specs/block_spec.rb @@ -0,0 +1,42 @@ +require_relative 'spec_helper' + +describe "Block class" do + before do + @block_test = Hotel::Block.new#("2017-11-13", "2017-11-15") + @hotel_test = Hotel::Hotel.new + end + describe "Initialize Block Class" do + it "Block.new is an instance of Hotel module" do + @block_test.must_be_instance_of Hotel::Block + end + it "Responds to date_range variable" do + @block_test.must_respond_to :date_range + @block_test.block_rooms.must_be_kind_of Array + end + it "Responds to block_rooms variable" do + @block_test.must_respond_to :block_rooms + end + it "Responds to discount variable" do + @block_test.must_respond_to :discount + end + it "Responds to hotel variable" do + @block_test.must_respond_to :hotel + end + end + describe "Create Block method" do + it "Returns an array" do + hotel_test2 = Hotel::Hotel.new + hotel_test2.reserve_room(2,"2017-11-15", "2017-11-18") + hotel_test2.reserve_room(3,"2017-11-15", "2017-11-18") + hotel_test2.reserve_room(1,"2017-11-15", "2017-11-18") + ap hotel_test2.view_available_rooms("2017-11-15", "2017-11-18") + @block_test.create_block(2,"2017-11-15", "2017-11-17").must_be_kind_of Array + # @block_test.block_rooms.length.must_equal 5 + end + it "Contains 5 rooms" do + skip + @block_test.create_block([4,5,6,7,8],"2017-11-15", "2017-11-17") + @block_test.block_rooms.length.must_equal 5 + end + end +end # end of Block class describe diff --git a/specs/date_range_spec.rb b/specs/date_range_spec.rb new file mode 100644 index 000000000..13bfde612 --- /dev/null +++ b/specs/date_range_spec.rb @@ -0,0 +1,46 @@ +require_relative 'spec_helper' + +describe "DateRange Class" do + before do + @date_range_test = Hotel::DateRange.new("2017-11-12", "2017-11-15") + end + describe "Initialize DateRange Class" do + it "DateRange.new is an instance of Hotel module" do + @date_range_test.must_be_instance_of Hotel::DateRange + end + it "Responds to check_in variable" do + @date_range_test.must_respond_to :check_in + end + it "Responds to check_out variable" do + @date_range_test.must_respond_to :check_out + end + it "Responds to number_of_nights variable" do + @date_range_test.must_respond_to :number_of_nights + end + it "Equals number_of_nights" do + @date_range_test.number_of_nights.must_equal 3 + end + it "raises an ArgumentError if check_in date has passed" do + proc { + Hotel::DateRange.new("2017-9-1", "2017-9-12") + }.must_raise ArgumentError + end + it "raises an ArgumentError if check_out date is before check_in date" do + proc { + Hotel::DateRange.new("2017-11-15", "2017-11-12") + }.must_raise ArgumentError + end + end # end of describe Initialize DateRange Class + describe "Overlap? method" do + it "Checks for overlapping dates" do + @date_range_test = Hotel::DateRange.new("2017-11-12", "2017-11-15") + @date_range_test.overlap?("2017-11-13", "2017-11-14").must_equal true + @date_range_test.overlap?("2017-11-16", "2017-11-17").must_equal false + end + end + describe "Return_date_range method" do + it "Returns to a string" do + @date_range_test.return_date_range.must_be_kind_of String + end + end # end of describe return_date_range method +end # end of describe DateRange class diff --git a/specs/hotel_spec.rb b/specs/hotel_spec.rb new file mode 100644 index 000000000..9461ef7ea --- /dev/null +++ b/specs/hotel_spec.rb @@ -0,0 +1,62 @@ +require_relative 'spec_helper' +require 'awesome_print' + +describe "Hotel" do + before do + @hotel_test = Hotel::Hotel.new + end + describe "Initialize Hotel Class" do + it "Hotel.new is an instance of Hotel module" do + @hotel_test.must_be_instance_of Hotel::Hotel + end + it "Responds to rooms variable" do + @hotel_test.must_respond_to :rooms + end + it "Rooms variable is an array" do + @hotel_test.rooms.must_be_kind_of Array + end + it "Rooms variable has 20 rooms" do + @hotel_test.rooms.length.must_equal 20 + end + end + + describe "Reserve_room method" do + it "Responds to Hotel::Reservation" do + @hotel_test.reserve_room(3,"2017-11-12", "2017-11-15") + @hotel_test.reservations[0].must_be_instance_of Hotel::Reservation + end + it "Raises an error if asked to reserve an unavailable room " do + @hotel_test.reserve_room(3,"2017-11-12", "2017-11-15") + proc { + @hotel_test.reserve_room(3,"2017-11-13", "2017-11-15") + }.must_raise ArgumentError + end + it "Raises an error if room does not exist" do + proc { + @hotel_test.reserve_room(23,"2017-11-13", "2017-11-15") + }.must_raise ArgumentError + end + end + describe "Access_reservations method" do + it "Responds to Hotel" do + @hotel_test.must_respond_to :reservations + end + it "Is an Array" do + @hotel_test.reserve_room(3,"2017-11-12", "2017-11-15") + @hotel_test.access_reservations("2017-11-12").must_be_kind_of Array + end + it "Has one element" do + @hotel_test.reserve_room(20,"2017-11-12", "2017-11-15") + @hotel_test.reserve_room(20,"2017-11-16", "2017-11-20") + skip + @hotel_test.access_reservations("2017-11-12").length.must_equal 1 + end + end + describe "View_available_rooms method" do + it "Return a list of available rooms for a given date range" do + @hotel_test.reserve_room(19,"2017-11-15", "2017-11-18") + @hotel_test.view_available_rooms("2017-11-15", "2017-11-18").must_be_kind_of Array + @hotel_test.view_available_rooms("2017-11-15", "2017-11-18").wont_include 19 + end + end +end diff --git a/specs/reservation_spec.rb b/specs/reservation_spec.rb new file mode 100644 index 000000000..4fac24d60 --- /dev/null +++ b/specs/reservation_spec.rb @@ -0,0 +1,26 @@ +require_relative 'spec_helper' + +describe "Reservation class" do + before do + @reservation_test = Hotel::Reservation.new(2, "2017-11-12","2017-11-15") + + end + describe "Initialize Reservation class" do + it "Reservation.new is an instance of Hotel module" do + @reservation_test.must_be_instance_of Hotel::Reservation + end + it "Responds to date_range variable" do + @reservation_test.must_respond_to :date_range + end + it "Responds to cost_per_night variable" do + @reservation_test.must_respond_to :cost_per_night + end + end + describe "Get_total method" do + it "Calulates total" do + total = @reservation_test.get_total + total.must_equal 600 + end + end + +end # end describe Reservation class diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb new file mode 100644 index 000000000..24c54dcc2 --- /dev/null +++ b/specs/spec_helper.rb @@ -0,0 +1,19 @@ +# specs/spec_helper.rb +require 'simplecov' +SimpleCov.start +require 'minitest' +require 'minitest/autorun' +require 'minitest/reporters' +require 'minitest/pride' +require 'minitest/skip_dsl' +require 'awesome_print' +# require 'pry' + + +# Require any classes +require_relative '../lib/hotel' +require_relative '../lib/reservation' +require_relative '../lib/date_range' +require_relative '../lib/block' + +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new