diff --git a/Rakefile b/Rakefile new file mode 100644 index 000000000..50cabef64 --- /dev/null +++ b/Rakefile @@ -0,0 +1,13 @@ +require 'rake/testtask' + +Rake::TestTask.new do |t| + t.libs = ["lib"] + t.warning = true + t.test_files = FileList['specs/*_spec.rb'] + puts "" +end + +task default: :test + + +puts diff --git a/design-activity.md b/design-activity.md new file mode 100644 index 000000000..f17b508c8 --- /dev/null +++ b/design-activity.md @@ -0,0 +1,44 @@ +What classes does each implementation include? Are the lists the same? + - Each implementation has the same class names: + 1. CartEntry + 2. ShoppingCart + 3. Order + - The lists, what I interpret as list of classes, is the same. + +Write down a sentence to describe each class. + 1. CartEntry - Responsible for creating entries for the cart + 2. ShoppingCart - responsible for making carts that can hold entries and their prices + 3. Order - calculates the shopping carts' entries total price + +How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper. + - Order relies on ShoppingCart and ShoppingCart relies on CartEntry + +What data does each class store? How (if at all) does this differ between the two implementations? + - From what I can tell, each implementation's classes store the same thing. ShoppingCart = entries and order = shopping cart instances. + +What methods does each class have? How (if at all) does this differ between the two implementations? + + +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? + - Imp A delegated to both ShoppingCart and CartEntry by utilizing their instance variables + - Imp B did not, it was wrapped up nicely so that only messages were sent not responsibilities. + +Does total_price directly manipulate the instance variables of other classes? + - Imp A yes. it manipulates CartEntry#unit_price and CartEntry#quantity + - Imp B does not. + +If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify? + - Imp B is easier to change than Imp A. We would add the logic to CartEntry#price + +Which implementation better adheres to the single responsibility principle? + - Imp B + +Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled? + - Imp B + +____________________________________ + +My Hotel + +Hotel::Admin#add_reservation_to_block method was tightly coupled to Block class per Dan's comment. So I refactored the code so only messages were sent between the two classes and those loosely coupled them. For example, I added the instance reservation directly to Block#reservation_array and this was a poor idea. diff --git a/lib/Admin.rb b/lib/Admin.rb new file mode 100644 index 000000000..c30e2d343 --- /dev/null +++ b/lib/Admin.rb @@ -0,0 +1,122 @@ +require_relative 'reservation' +require_relative 'block' +require 'pry' +require 'date' + +module Hotel + class Admin + + attr_reader :blocks, :room_nums, :reservations_array + + def initialize + @blocks = [] + @room_nums = [] + 20.times do |i| + @room_nums << (i + 1) + end + + # @reservations_array1 = [] + + end # end initialize + + + + def list_of_rooms + return Array.new(room_nums) + end + + + # I do not remember why I had this method. Kept it for future reference. + # def add_block(room_num_array, check_in, check_out, block_id, discount_percent: 0) + # @blocks << Hotel::Block.new(room_num_array, check_in, check_out, block_id, discount_percent: 0) + # end + + # Wave 2 requirement. Add reservation only no block + # Kept this code because wave 2 requires it. As for instructor feedback provided after first submition. Please see the create_block_by_date method on line 87'ish + def add_reservation(room_selection, check_in, check_out) + # self.add_block() + # self.add_reservation_to_block() + @reservations << Hotel::Reservation.new(room_selection, check_in, check_out) + end + + + def list_reservations(date) + rez_by_date = [] + @blocks.each do |block| + if block.block_date_range.include(date) + block.reservations_array.each do |reservation| + rez_by_date << reservation + end + end + end + return rez_by_date + end + + def list_vacancies(check_in, check_out) + available_rooms = list_of_rooms + + date_range = DateRange.new(check_in, check_out) + + + @blocks.each do |block| + if block.block_date_range.overlap?(date_range) + block.room_num_array.each do |room_num| + available_rooms.delete(room_num) + end + end + end + + return available_rooms + end + + # creates block with available room for a given date range. Dan, I read you original feedback about the discount_percent passing through to calc price and ran out of time for testing. Now I worked on it for a while and I was not able to solve it. I will ask Charles for help. + def create_block_by_date(rooms_per_block, check_in, check_out, block_id, discount_percent: 0.0) + while self.list_vacancies(check_in, check_out).empty? + raise StandardError("No more vacancies!") + end + + room_num_array = self.list_vacancies(check_in, check_out).take(rooms_per_block).to_a + + # take one of the rooms from the room_num_array to make a reservation + room_num = room_num_array.first + + new_block = Hotel::Block.new(room_num_array, check_in, check_out, block_id, discount_percent: 0.0) + + new_block.make_reservation(room_num, check_in, check_out, discount_percent: 0) + + @blocks << new_block + end + + def find_block(id) + @blocks.each do |block| + if block.block_id == id + return block + end + end + end + + + # Dan I saw your original feedback about the below method not being necessary. My thought at the time was single responsibility for this method and then utiling it in the find_rooms_from_block method. If my thinking is incorrect, please let me know. + def list_available_blocked_rooms(id) + #find the block. + # access its assigned rooms + return self.find_block(id).room_num_array + end + + + def find_rooms_from_block(id, num_rooms_to_reserve) + return self.list_available_blocked_rooms(id).take(num_rooms_to_reserve) + end + + + def add_reservation_to_block(id, num_rooms_to_reserve, check_in, check_out, discount_percent: 0) + block = self.find_block(id) + num_rooms_to_reserve = self.find_rooms_from_block(id, num_rooms_to_reserve) + + num_rooms_to_reserve.length.times do |room_num| + # block.reservations_array << Hotel::Reservation.new(room_num, check_in, check_out, discount_percent: 0) + block.make_reservation(room_num, check_in, check_out, discount_percent: 0) + end + end + end # end class +end # end module diff --git a/lib/Block.rb b/lib/Block.rb new file mode 100644 index 000000000..8f982a335 --- /dev/null +++ b/lib/Block.rb @@ -0,0 +1,40 @@ +require 'date' + +require_relative 'daterange' + +module Hotel + class Block + COST_PER_NIGHT = 200.00 + + attr_reader :room_num_array, :block_date_range, :discount_percent, :block_id, :reservations_array + + def initialize(room_num_array, check_in, check_out, block_id, discount_percent: 0) + + + @room_num_array = room_num_array + @block_date_range = DateRange.new(check_in, check_out) + # what to automate block_id and was stumped on implementation. So I opted for manuel entry + @block_id = block_id + @discount_percent = discount_percent + + + @reservations_array = [] + end + + + + def total_cost + full_price = (@block_date_range.total_num_of_nights) * COST_PER_NIGHT + discount = full_price * (discount_percent/100.0) + return total_cost = full_price - discount + end + + + def make_reservation(room_num, check_in, check_out, discount_percent: 0) + reservations_array << Hotel::Reservation.new(room_num, check_in, check_out, discount_percent: 0) + end + + + + end # end class +end #end module diff --git a/lib/DateRange.rb b/lib/DateRange.rb new file mode 100644 index 000000000..b94713b54 --- /dev/null +++ b/lib/DateRange.rb @@ -0,0 +1,35 @@ + + +module Hotel + + class DateRange + + attr_reader :check_in, :check_out + + def initialize(check_in, check_out) + raise ArgumentError.new("Please enter a Check Out date that comes after Check In date.") if check_in >= check_out + + @check_in = check_in + @check_out = check_out + end + + + def include(date) + if date >= @check_in && date <= @check_out + return true + end + end + + def overlap?(other) + return !(other.check_out <= @check_in || other.check_in >= @check_out) + end + + def total_num_of_nights + return check_out - check_in + end + + + + + end +end diff --git a/lib/Reservation.rb b/lib/Reservation.rb new file mode 100644 index 000000000..8755bfe58 --- /dev/null +++ b/lib/Reservation.rb @@ -0,0 +1,34 @@ +require 'date' + +require_relative 'daterange' + +module Hotel + class Reservation + COST_PER_NIGHT = 200.00 + + attr_reader :total_cost, :room_num, :date_range + + def initialize(room_num, check_in, check_out, discount_percent: 0) + + # @reservation_array = [] + @total_cost = 0 + @room_num = room_num + @date_range = DateRange.new(check_in, check_out) + end + + # def total_cost + # full_price = (@block_date_range_array.length - 1) * COST_PER_NIGHT + # discount = full_price * (discount_percent/100.0) + # return total_cost = full_price - discount + # end + + def total_cost + full_price = (@date_range.total_num_of_nights) * COST_PER_NIGHT + discount = full_price * (discount_percent/100.0) + return total_cost = full_price - discount + end + + + + end # end class +end #end module diff --git a/specs/admin_spec.rb b/specs/admin_spec.rb new file mode 100644 index 000000000..495474636 --- /dev/null +++ b/specs/admin_spec.rb @@ -0,0 +1,253 @@ +require_relative '../specs/spec_helper' + +=begin +Each room should have a room number. +- pass in the room number to the room object. +- to automate making all 20 rooms we need a times loop from 1-20. +- set an iterative variable to 1. +=end + +# Hotel +# Admin +# Reservations + + + + +describe "Admin Class" do + + before do + + @admin = Hotel::Admin.new + + rooms_per_block = 4 + rooms_per_block.must_be_kind_of Integer + + check_in = Date.new(2017,2,3) + check_in.must_be_instance_of Date + + check_out = Date.new(2017, 2,7) + check_in.must_be_instance_of Date + + block_id = 1 + + @new_block = @admin.create_block_by_date(rooms_per_block, check_in, check_out, block_id, discount_percent: 0.0) + + # @admin.add_reservation_to_block(1, 1, check_in, check_out, discount_percent: 0) + + + end + + describe "initialize" do + it "@blocks returns blocks array" do + @admin.blocks.must_be_kind_of Array + end + + it "@room_nums returns an array" do + @admin.room_nums.must_be_kind_of Array + end + + it "@room_nums is between 1-20" do + # normative classes + @admin.room_nums.first.must_equal 1 + @admin.room_nums.last.must_equal 20 + + # edge classes + + end + + describe "List_of_rooms method" do + it "returns of all rooms" do + @admin.room_nums.must_be_kind_of Array + @admin.list_of_rooms.must_be_kind_of Array + + # normative classes + @admin.list_of_rooms.first.must_equal 1 + @admin.list_of_rooms.last.must_equal 20 + + # edge classes + end + end + end # end initialize + + describe "add_block" do + it "thing that is added is a block" do + @admin.blocks[0].must_be_instance_of Hotel::Block + @admin.blocks[-1].must_be_instance_of Hotel::Block + end + + it "verify block is added to array" do + @admin.blocks.length.must_equal 1 + end + end + + describe "list_reservations tests" do + it "list test" do + date = Date.new(2017, 2, 4) + date.must_be_instance_of Date + + test_ob = @admin.add_reservation_to_block(1,1, Date.new(2017,2,2), Date.new(2017,2,7), discount_percent: 0) + + @admin.list_reservations(date).must_be_instance_of Array + @admin.list_reservations(date).first.must_be_instance_of Hotel::Reservation + @admin.list_reservations(date).last.must_be_instance_of Hotel::Reservation + + # edge cases + date = Date.new(2017, 2, 9) + date.must_be_instance_of Date + + end + end + + + it "list_vacancies" do + check_in = Date.new(2017, 2, 4) + check_in.must_be_instance_of Date + + check_out = Date.new(2017, 2, 5) + check_in.must_be_instance_of Date + + @admin.list_vacancies(check_in, check_out).must_be_instance_of Array + # @admin.list_vacancies(check_in, check_out)[0].must_be_kind_of Integer + @admin.list_vacancies(check_in, check_out)[-1].must_be_kind_of Integer + + # edge cases + + # I read original feedback "What if there are no reservations? (it should return a list of all the rooms)" and was not able to figure it out. Will follow up for clarification soon. + # @admin.list_vacancies(Date.new(2017,1,1), Date.new(2017,1,3)).must_equal[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20] + + @admin.list_vacancies(check_in, check_out).must_equal [5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20] + + @admin.create_block_by_date(16, check_in, check_out, 2) + + @admin.list_vacancies(check_in, check_out).must_equal [] + + + + + end + + + + describe "create block by date range" do + it "validates input" do + rooms_per_block = 4 + rooms_per_block.must_be_kind_of Integer + + check_in = Date.new(2017, 2, 4) + check_in.must_be_instance_of Date + + check_out = Date.new(2017, 2, 5) + check_in.must_be_instance_of Date + + block_id = 2 + block_id.must_be_kind_of Integer + + @admin.create_block_by_date(rooms_per_block, check_in, check_out,block_id) + + @admin.must_respond_to :list_vacancies + + @admin.blocks.length.must_equal 2 + end + + it "automatically makes one reservation for each block" do + @admin.blocks.length.must_equal 1 + + @admin.blocks.first.reservations_array.length.must_equal 1 + end + + it "Raise an error for no more available rooms" do + proc { Hotel::Admin.create_block_by_date(rooms_per_block, check_in, check_out, block_id)}.must_raise StandardError + end + + + describe "find_block" do + it "takes integer id" do + id = 1 + @admin.find_block(id).must_be_kind_of Hotel::Block + + @admin.find_block(id).must_be_kind_of Hotel::Block + + @admin.find_block(id).wont_be_kind_of String + + + id = 0 + @admin.find_block(id).wont_be_kind_of Hotel::Block + end + + it "returns block object" do + @admin.find_block(1).must_be_instance_of Hotel::Block + end + end + + describe "list_available_blocked_rooms" do + it "returns an array with room numbers" do + @admin.list_available_blocked_rooms(1).must_be_instance_of Array + @admin.list_available_blocked_rooms(1).first.must_be_kind_of Integer + @admin.list_available_blocked_rooms(1).last.must_be_kind_of Integer + + + #edge cases + + + end + end + + describe "find_rooms_from_block" do + it "returns array of a specific block's rooms" do + @admin.find_rooms_from_block(1, 4).must_be_kind_of Array + @admin.find_rooms_from_block(1, 4).first.must_equal 1 + @admin.find_rooms_from_block(1, 4).last.must_equal 4 + end + end + + describe "add_reservation_to_block" do + it "reservation is added to array" do + + check_in = Date.new(2017,2,3) + check_out = Date.new(2017, 2,7) + + @admin.add_reservation_to_block(1, 1, check_in, check_out, discount_percent: 0) + + # One reservation was already in the reservations_array so total is now 2 + @admin.blocks.first.reservations_array.length.must_equal 2 + + @admin.blocks.first.reservations_array.first.must_be_instance_of Hotel::Reservation + + @admin.blocks.first.reservations_array.last.must_be_instance_of Hotel::Reservation + end + end + end +end # end admin class + + +=begin + +Reservations +Block +Discount +Reservations +Room number +dates +Date +Price +Name + +=end + +=begin + +Block.new([rooms], [dates], discount) + +[] + +Block +Discount (float) +Reservations (array) +Room number +dates +Date +Price +Name + +=end diff --git a/specs/block_spec.rb b/specs/block_spec.rb new file mode 100644 index 000000000..10f23aff1 --- /dev/null +++ b/specs/block_spec.rb @@ -0,0 +1,55 @@ +# require_relative '../specs/spec_helper' +# +# test_ob = Hotel::Block.new +# +# test_ob.add_reservation_to_block + +describe "Block tests" do + before do + @block = Hotel::Block.new([1,2,3,4], Date.new(2017,2,2), Date.new(2017,2, 7),1) + @block.make_reservation(1, Date.new(2017,2,2), Date.new(2017,2, 7), discount_percent: 0) + end + + describe "initialize" do + it "Returns a block object" do + + @block.must_be_instance_of Hotel::Block + end + end # initialize + + it "adds to reservations_array" do + @block.reservations_array.must_be_instance_of Array + @block.reservations_array.length.must_equal 1 + end + + describe "total_cost method" do + it "calculating both full and discount price" do + @block.total_cost.must_equal 1000 + + Hotel::Block.new([1,2,3,4], Date.new(2017,2,2), Date.new(2017,2, 7), 1, discount_percent: 30.0).total_cost.must_equal 700 + + end + end + +end # block tests + + + + + + + + + + + + + + + + + + + + +puts diff --git a/specs/date_range_spec.rb b/specs/date_range_spec.rb new file mode 100644 index 000000000..0169460b0 --- /dev/null +++ b/specs/date_range_spec.rb @@ -0,0 +1,89 @@ +require_relative '../specs/spec_helper' + +=begin +- the date range class will be instanciated in the Admin class. +1. initialize the method with a start date and and end date. +2. Does the range of dates have a reservation? +3. + + + +=end + +describe "DateRange class" do + before do + check_in = Date.new(2017,2,3) + check_out = Date.new(2017, 2,7) + @date_range = Hotel::DateRange.new(check_in, check_out) + end + describe "initialize" do + + it "Takes a start date object" do + check_in = Date.new(2017,2,3) + check_out = Date.new(2017, 2,7) + Hotel::DateRange.new(check_in, check_out).must_respond_to :check_in + end + + it "Takes an end date object" do + check_in = Date.new(2017,2,3) + check_out = Date.new(2017, 2,7) + Hotel::DateRange.new(check_in, check_out).must_respond_to :check_out + end + + + it "Raises an error for invalid date ranges" do + check_in = Date.new(2017, 2, 3) + check_out = Date.new(2015, 2, 7) + proc{Hotel::DateRange.new(check_in, check_out)}.must_raise ArgumentError + end + + it "Returns a DateRange object" do + @date_range.must_be_instance_of Hotel::DateRange + end + + end # end initialize + + describe "over lap method" do + it "returns true if other date range overlaps self" do + check_in = Date.new(2017,2,2) + check_out = check_in + 2 + + other = Hotel::DateRange.new(check_in, check_out) + + @date_range.overlap?(other).must_equal true + end + end + + describe "total_num_of_nights" do + it "calc is correct" do + @date_range.total_num_of_nights.must_equal 4 + + + + + + + + + # TODO: Add tests for include method. + + + + + + + + + + + + + + + + # interesting test case is check in and check out are same day. + end + end + + +end # end date range class diff --git a/specs/reservation_spec.rb b/specs/reservation_spec.rb new file mode 100644 index 000000000..182ce577a --- /dev/null +++ b/specs/reservation_spec.rb @@ -0,0 +1,27 @@ +require_relative '../specs/spec_helper' + + +describe "reservations" do + + before do + room_num = 1 + check_in = Date.new(2017,2,3) + check_out = Date.new(2017, 2,7) + @reservation = Hotel::Reservation.new(room_num, check_in, check_out) + end + describe "initialize" do + it "Returns a Reservation object" do + @reservation.must_be_instance_of Hotel::Reservation + end + end + + xdescribe "total_cost method" do + it "calculating both full and discount price" do + @reservation.total_cost.must_equal 1000 + + Hotel::Reservation.new(2, Date.new(2017,2,2), Date.new(2017,2, 7), 1, discount_percent: 30.0).total_cost.must_equal 700 + + end + end + +end diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb new file mode 100644 index 000000000..062fd3414 --- /dev/null +++ b/specs/spec_helper.rb @@ -0,0 +1,24 @@ +require 'simplecov' +SimpleCov.start + +# specs/spec_helper.rb +require 'minitest' +require 'minitest/autorun' +require 'minitest/reporters' +require 'minitest/pride' +require 'minitest/skip_dsl' + + + +# Require any classes +# ex require_relative 'lib/foo.rb' + +require_relative '../lib/daterange' +require_relative '../lib/admin' +require_relative '../lib/reservation' +require_relative '../lib/block' + + + +# +# Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new diff --git a/unused-work/Room.rb b/unused-work/Room.rb new file mode 100644 index 000000000..ca2d4fb8c --- /dev/null +++ b/unused-work/Room.rb @@ -0,0 +1,29 @@ +=begin +- Room class makes ojects of room. +- Its attributes are: +- room number + + +- Each room should have a room number. + - pass in the room number to the room object. + - to automate making all 20 rooms we need a times loop from 1-20. + - set an iterative variable to 1. + - errors for room numbers not on the sequence 1-20 + - errors for reservation status not being either + + + +=end + + +module Hotel + class Room + + attr_reader :room_num, :reservation_status + + def initialize(room_num, reservation_status) + @room_num = room_num + @reservation_status = reservation_status + end + end +end diff --git a/unused-work/date-range-spec.rb b/unused-work/date-range-spec.rb new file mode 100644 index 000000000..f1397bb53 --- /dev/null +++ b/unused-work/date-range-spec.rb @@ -0,0 +1,17 @@ +it "include? method returns True" do + # Boundry cases + date = Date.new(2017,2,3) + @date_range.include?(date).must_equal true + + date = Date.new(2017,2, 6) + @date_range.include?(date).must_equal true + + # edge cases + date = Date.new(2017,2, 2) + @date_range.include?(date).must_equal false + + date = Date.new(2017,2,7) + @date_range.include?(date).must_equal false + + +end diff --git a/unused-work/room_spec.rb b/unused-work/room_spec.rb new file mode 100644 index 000000000..a49b15dfb --- /dev/null +++ b/unused-work/room_spec.rb @@ -0,0 +1,86 @@ +require_relative '../specs/spec_helper' + +=begin + + +- Room class makes ojects of room. +- Its attributes are: +- room number: each room must be 1-20 + +The hotel has 20 rooms, and they are numbered 1 through 20 +- Every room is identical, and a room always costs $200/night +- The last day of a reservation is the checkout day, so the guest should not be charged for that night +- For this wave, any room can be reserved at any time, and you don't need to check whether reservations conflict with each other (this will come in wave 2!) +- Your code should raise an error when an invalid date range is provided +- A reservation is allowed start on the same day that another reservation for the same room ends +- Your code should raise an exception when asked to reserve a room that is not available + + +- Each room should have a room number. +- pass in the room number to the room object. +- to automate making all 20 rooms we need a times loop from 1-20. +- set an iterative variable to 1. +- errors for room numbers not on the sequence 1-20 +- errors for reservation status not being either "reserved" or "vacant" + + +=end + +describe "Room Class" do + before do + @room = Hotel::Room + end + + describe "initialize" do + it "Takes strings for both room number and reservation status" do + room_num = "1" + reservation_status = "Reserved" + + room_num.must_be_instance_of String + reservation_status.must_be_instance_of String + + end + + it "Raises an error for room number not being in range 1 -20" do + room_num = "#" + reservation_status = "Reserved" + proc { + Hotel::Room.new(room_num, reservation_status) + }.must_raise ArgumentError + + room_num = "21" + proc{ + @room.new(room_num, reservation_status) + }.must_raise ArgumentError + end + + it "Raises an error for reservation status not being either 'Reserved' or 'vacant'" do + room_num = "20" + reservation_status = "Rainbow" + + proc { + @room.new(room_num, reservation_status) + }.must_raise ArgumentError + + reservation_status = "123" + proc { + @room.new(room_num, reservation_status) + }.must_raise ArgumentError + + reservation_status = "!@#" + proc{ + @room.new(room_num, reservation_status) + }.must_raise ArgumentError + + end + + it "Returns an instance of Room class" do + room_num = "1" + reservation_status = "Reserved" + + @room.new(room_num, reservation_status).must_be_instance_of Hotel::Room + + end + end # end initialize + +end # end room class