diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..189eb029e Binary files /dev/null and b/.DS_Store differ 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..6391a6d83 --- /dev/null +++ b/design-activity.md @@ -0,0 +1,70 @@ +Q: What classes does each implementation include? Are the lists the same? + +A: Both implementation have the same classes names, but classes itself are different. Those classes are: + * CartEntry + * ShoppingCArt + * Order + +Q: Write down a sentence to describe each class. + +A: + * Implementation A - CartEntry: It initializes one product that have two instances variables quantity and unit_price. + + * Implementation B - CartEntry: It initialize a product with two instances variables, unit_price and quantity. It calculates the total price of the product. + + * Implementation A - ShoppingCart: It creates an empty array to place all the products. + + * B - ShoppingCart: It initializes an empty array to place all the products and calculate the total price for all products without taxes. + + * A - Order: It creates a new instance of ShoppingCart. It calculates the total price per product and it sums the total price for all products including taxes. + + * B - Order: Creates a new instance of ShoppingCart and add the taxes to the total price. + +Q: How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper. + +A: Implementation A: The Order class uses CartEntry and ShoppingCart classes. It creates a new instance of ShoppingCart that is call cart. For each element in cart calculate the total price, using the instance variables of the CartEntry class. Sum all this result and add the taxes. + +Implementation B: CartEntry calculates the price per entry, the ShoppingCart uses this prices to calculate the total price of all products without taxes. Finally Order class creates an instance of ShoppingCart, it adds the taxes to the total price (calculates in ShoppingCart). This way ShoppingCart is relate to CartEntry and Order is relate to ShoppingCart. + +Q: 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 that is an instance of ShoppingCart and sales tax. The classes in both implementations store the same data. + +Q: What methods does each class have? How (if at all) does this differ between the two implementations? + +A: +* CartEntry has the same initialize method in both implementations. In implementation B, there is also a price method that multiplies unit_price by quantity returning the total price per product. + +* ShoppingCart has the same initialize method in both implementations. In implementation B there is a price method that returns the sum of the total value for each element in the entries array. + +* Order class has the same initialize method in both implementations. The implementation A has a total_price method that calculates the total price per product in cart and add all this values. Later it adds the sales taxes to get the total price. In the implementation B total_price calls the price method from the ShoppingCart class to calculate the total value without taxes, then returns the total value of the order with taxes. + +Q: 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: It is better to have price in lower level classes because otherwise all the logic is going to be store at the Order Class and this is not following the single responsibility principle. + +Does total_price directly manipulate the instance variables of other classes? + +A: In implementation A it manipulates unit_price and quantity from CartEntry class. It also iterates over the entries arrays from ShoppingCart class. + +Implementation B doesn't manipulate directly the instance variables of other classes. + +Q: If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify? + +We need to have some code that checks if for a given quantity of a product we can apply the discount, this should be inside the CartEntry class. + +It is easier to change the implementation B because we just need to change the CartEntry class, at the other hand with the implementation A we need to change CartEntry class and the method total_price inside Order class. + +Q: Which implementation better adheres to the single responsibility principle? + +A: The implementation B, because CartEntry takes care just for single products, ShoppingCart takes care of products inside entries and Order takes care of the order in general. While in implementation A, Order takes care of the single products, items in entries and the order in general. + +Revisiting Hotel + +When I did hotel the first time I paid special attention to the single responsibility of a class. Now that I'm looking again this project, I think that the DateRange, Block and Reservation classes satisfies the requirements of a good design. + +The Admin class is a big class that manages the reservations and store the information about the rooms. I wanted to crate a new class call Rooms. This class would create the hash with rooms using the create_rooms method. But when I was making the changes, I noticed that if I do this, then Admin class would modify the instance variables of Rooms class, so I decided not to modify Admin class. + +The change that I did was to put attr_reader or attr_accessor to instance variables, how Metiz books recommends. diff --git a/lib/admin.rb b/lib/admin.rb new file mode 100644 index 000000000..a20a3bb2c --- /dev/null +++ b/lib/admin.rb @@ -0,0 +1,93 @@ + +module Hotel + class Admin + attr_reader :list_reservations, :rooms + + def initialize + @rooms = {} + @list_reservations = [] + create_rooms + end + + def add_reservation(checkin, checkout) + dates_to_reserve = Hotel::DateRange.new(checkin, checkout) + rooms.each do |room_number, dates| + if is_room_avaliable?(room_number, dates_to_reserve) + rooms[room_number] << dates_to_reserve + make_reservation = Hotel::Reservation.new(checkin, checkout, room_number) + list_reservations << make_reservation + return make_reservation + end #if + end #each + raise ArgumentError.new( "There are not avaliable rooms for that date range") + end + + def reservations_per_day(date) + list_per_day = [] + list_reservations.each do |reservation| + if reservation.days_range.include?(date) + list_per_day << reservation + end #if + end #each + return list_per_day + end #method + + def create_rooms + 20.times do |i| + rooms["#{i + 1}"] = [] + end + end + + def is_room_avaliable?(room_number, dates_to_reserve) + avaliable = true + + rooms[room_number].each do |busy_dates| + if dates_to_reserve.overlap?(busy_dates) + avaliable = false + end#if + end + return avaliable + end + + def avaliable_rooms_daterange(checkin, checkout) + avaliable_rooms = [] + date_range = Hotel::DateRange.new(checkin,checkout) + @rooms.each do |room_number, dates| + if is_room_avaliable?(room_number, date_range) + avaliable_rooms << room_number + end + end + return avaliable_rooms + end + + def can_create_block?(checkin, checkout, number_of_rooms) + if number_of_rooms > 5 + raise ArgumentError.new("There is no possible to create a block qith more of 5 rooms") + else + if avaliable_rooms_daterange(checkin, checkout).length >= number_of_rooms + return true + else + return false + end + end + end + + def create_block (checkin, checkout, number_of_rooms, price) + rooms_in_block = [] + if can_create_block?(checkin, checkout, number_of_rooms) + number_of_rooms.times do + reserve = add_reservation(checkin, checkout) + rooms_in_block << reserve.room_number + end + end + + Hotel::Block.new(checkin, checkout, rooms_in_block, price) + + end + + + + + end #class + +end #module diff --git a/lib/block-admin.rb b/lib/block-admin.rb new file mode 100644 index 000000000..ba300542c --- /dev/null +++ b/lib/block-admin.rb @@ -0,0 +1,25 @@ +# require_relative '../lib/admin.rb' +# module Hotel +# class BlockAdmin +# #def initialize +# #@rooms = Hotel::Admin.rooms +# #@list_reservations = Hotel::Admin.list_reservations +# #end +# +# #def can_create_block? +# #end +# def can_create_block?(checkin, checkout, number_of_rooms) +# if number_of_rooms > 5 +# raise ArgumentError.new("There is no possible to create a block qith more of 5 rooms") +# else +# if avaliable_rooms_daterange(checkin, checkout).length >= number_of_rooms +# return true +# else +# return false +# end +# end +# end +# +# +# end#class +# end #module diff --git a/lib/block.rb b/lib/block.rb new file mode 100644 index 000000000..bed443bd7 --- /dev/null +++ b/lib/block.rb @@ -0,0 +1,37 @@ +module Hotel + class Block + attr_reader :price_per_night, :days_range, :rooms_set + + def initialize(checkin, checkout, rooms_set, price) + @price_per_night = price + @days_range = Hotel::DateRange.new(checkin, checkout) + @rooms_set = rooms_set + end + + def totalcost_oneroom + price_per_night * days_range.stay_length + end + + def room_avaliable_block? + rooms_set.length > 0 ? true : false + end + + def reserve + if room_avaliable_block? + rooms_set.delete_at(0) + return rooms_set + else + raise ArgumentError.new("There is not more rooms avaliables in the given block") + end + end + + + + + + + + + + end #class +end #module diff --git a/lib/daterange.rb b/lib/daterange.rb new file mode 100644 index 000000000..85f1f7150 --- /dev/null +++ b/lib/daterange.rb @@ -0,0 +1,42 @@ + +module Hotel + class DateRange + attr_reader :checkin, :checkout + + def initialize(day1, day2) + @checkin = day1 + @checkout = day2 + valid_date? + end + + def valid_date? + if checkin < checkout + true + else + raise ArgumentError.new("No valid input") + end + end + + def stay_length + (checkout - checkin).to_i + end + + def include?(date) + if checkin <= date && checkout > date + return true + else + return false + end + end + + def overlap?(other) + if other.checkin <= self.checkin && other.checkout > self.checkin + return true + elsif other.checkin >= self.checkin && other.checkin < self.checkout + return true + else + return false + end + end + end +end #module diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..72a1a6913 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,22 @@ +module Hotel + class Reservation + attr_reader :checkin, :checkout, :days_range, :room_number, :price_per_night + def initialize(checkin, checkout, room_number) + @price_per_night = 200 + @days_range = Hotel::DateRange.new(checkin, checkout) + @room_number = room_number + end + + def totalcost + price_per_night * days_range.stay_length + end + + # def room_avaliables(@days_range) + # + # end + + + + + end #class Reservation +end diff --git a/lib/rooms.rb b/lib/rooms.rb new file mode 100644 index 000000000..a86aad648 --- /dev/null +++ b/lib/rooms.rb @@ -0,0 +1,19 @@ +# module Hotel +# class Rooms +# +# attr_accessor :rooms +# +# def initialize +# @rooms = {} +# create_rooms +# end +# +# def create_rooms +# 20.times do |i| +# @rooms["#{i + 1}"] = [] +# end +# end +# +# end #class +# +# end #module diff --git a/specs/admin_spec.rb b/specs/admin_spec.rb new file mode 100644 index 000000000..b076bf2a5 --- /dev/null +++ b/specs/admin_spec.rb @@ -0,0 +1,169 @@ +require_relative 'spec_helper.rb' +require 'pry' + +describe 'Admin class' do + before do + @administrator = Hotel::Admin.new + end + + describe 'Initialize' do + it "Can be create" do + @administrator.must_be_instance_of Hotel::Admin + end + + it "Create list of Reservation" do + @administrator.list_reservations.must_equal [] + end + + it "Create a hash of rooms" do + @administrator.rooms.must_be_instance_of Hash + end + + it "Length of rooms hash is 20" do + @administrator.rooms.length.must_equal 20 + end + end + + describe "Creates a new reservation" do + before do + @day1 = Date.new(2017, 8, 29) + @day2 = Date.new(2017, 9, 3) + end + + it "Creates areservation when it is possible" do + @administrator.add_reservation(@day1,@day2).must_be_instance_of Hotel::Reservation + end + + it "Stores the new reservations in the list_reservations" do + day3 = Date.new(2017,12,7) + day4 = Date.new(2017,12,12) + day5 = Date.new(2017,12,24) + @administrator.add_reservation(@day1,@day2) + @administrator.add_reservation(day3,day4) + @administrator.add_reservation(day4,day5) + @administrator.list_reservations.length.must_equal 3 + end + + it "Modify the rooms hash" do + day3 = Date.new(2017,12,7) + day4 = Date.new(2017,12,12) + day5 = Date.new(2017,12,24) + @administrator.add_reservation(@day1,@day2) + @administrator.add_reservation(day3,day4) + @administrator.add_reservation(day4,day5) + @administrator.rooms["1"].length.must_equal 3 + @administrator.add_reservation(day4,day5) + @administrator.rooms["2"].length.must_equal 1 + @administrator.rooms["3"].length.must_equal 0 + end + + it "doesn't create a new reservation if there is not rooms" do + 20.times do + @administrator.add_reservation(@day1,@day2) + end + proc{@administrator.add_reservation(@day1,@day2)}.must_raise ArgumentError + end + + + + end + + describe "reservations_per_day" do + before do + @date1 = Date.new(2018,01,25) + @date2 = Date.new(2018,02,22) + @date3 = Date.new(2018,01,31) + @date4 = Date.new(2018,02,05) + @date5 = Date.new(2018,01,29) + #@administrator = Hotel::Admin.new + @administrator.add_reservation(@date1, @date3) + @administrator.add_reservation(@date1, @date4) + + #terminar de hacer las reservaciones para los test + + end + + it 'Returns an array ' do + @administrator.reservations_per_day(@date1).must_be_instance_of Array + end + + it 'Returns an array of reservations instances' do + @administrator.reservations_per_day(@date1)[0].must_be_instance_of Hotel::Reservation + end + + it 'Returns all the reservations per day, when is not empty' do + @administrator.reservations_per_day(@date1).length.must_equal 2 + end + + it 'Returns empty array if that day there is no reservations' do + @administrator.reservations_per_day(@date2).must_equal [] + end + end + + describe "avaliable_rooms_daterange" do + before do + @day1 = Date.new(2017,12, 13) + @day2 = Date.new(2017,12, 19) + @day3 = Date.new(2017,12,7) + @day4 = Date.new(2017,12,12) + @day5 = Date.new(2017,12,24) + @day6 = Date.new(2017,12,14) + @day7 = Date.new(2017,12,16) + @all_rooms = [] + 20.times do |i| + @all_rooms << "#{i+1}" + end + end + + it "returns all the rooms if there is 0 reservations for that date range" do + @administrator.avaliable_rooms_daterange(@day1, @day2).must_equal @all_rooms + end + + it "returns the rigth number of rooms avaliables" do + 3.times do + @administrator.add_reservation(@day1, @day2) + end + @administrator.add_reservation(@day3,@day4) + @administrator.add_reservation(@day1,@day5) + @administrator.avaliable_rooms_daterange(@day6,@day7).must_equal @all_rooms[4..19] + end + end + + describe 'can create block?' do + before do + @checkin = Date.new(2017,11,5) + @checkout = Date.new(2017,11,9) + end + it 'raises an error if there is the number of rooms to reserve is bigger than 5' do + proc{@administrator.can_create_block?(@checkin, @checkout, 7)}.must_raise ArgumentError + end + + it 'returns true if it is possible to create the block' do + @administrator.can_create_block?(@checkin, @checkout, 4).must_equal true + end + + it 'returns fakse if it is not possible to create block' do + 17.times do + @administrator.add_reservation(@checkin, @checkout) + end + @administrator.can_create_block?(@checkin, @checkout,4).must_equal false + end + end + + describe "create_block" do + before do + @checkin = Date.new(2017,11,5) + @checkout = Date.new(2017,11,9) + end + it "if it is rooms avalibles creates the block" do + @administrator.create_block(@checkin, @checkout, 4, 150).must_be_instance_of Hotel::Block + end + + it "change the rooms" do + @administrator.create_block(@checkin, @checkout, 4, 150) + @administrator.list_reservations.length.must_equal 4 + end + + end + +end#describe class diff --git a/specs/block-admin_spec.rb b/specs/block-admin_spec.rb new file mode 100644 index 000000000..467609465 --- /dev/null +++ b/specs/block-admin_spec.rb @@ -0,0 +1,27 @@ +# require_relative 'spec_helper.rb' +# +# describe 'BlockAdmin' do +# before do +# @adminblock = Hotel::BlockAdmin.new +# @admin = Hotel::Admin.new +# end +# # describe 'initialize' do +# # it 'can be initialize' do +# # Hotel::BlockAdmin.new.must_be_instance_of Hotel::BlockAdmin +# # end +# # end +# +# describe 'can create block?' do +# before do +# @checkin = Date.new(2017,11,5) +# @checkout = Date.new(2017,11,9) +# end +# it 'raises an error if there is the number of rooms to reserve is bigger than 5' do +# proc{@adminblock.can_create_block?(@checkin, @checkout, 7)}.must_raise ArgumentError +# end +# +# it 'returns true if it is possible to create the block' do +# @adminblock.can_create_block?(@checkin, @checkout, 4).must_equal true +# end +# end +# end diff --git a/specs/block_spec.rb b/specs/block_spec.rb new file mode 100644 index 000000000..a4c17bf58 --- /dev/null +++ b/specs/block_spec.rb @@ -0,0 +1,50 @@ +require_relative 'spec_helper' + + +describe 'Block class' do + before do + @checkin = Date.new(2018,03,05) + @checkout = Date.new(2018,03,10) + @rooms_set = [17, 18, 19, 10] + @price = 150 + @blocknew = Hotel::Block.new(@checkin, @checkout, @rooms_set, @price) + end + + describe 'initialize' do + it 'can be initialize' do + @blocknew.must_be_instance_of Hotel::Block + end + end#initialize + + describe 'totalcost' do + it 'get the right price for the date range' do + @blocknew.totalcost_oneroom.must_equal 750 + end + end + + describe 'reserve' do + it 'change the rooms_set when you make a reservation' do + @blocknew.reserve.must_equal [18, 19, 10] + @blocknew.reserve.must_equal [19, 10] + end + + it 'raises argument error if there is not more rooms in block' do + 4.times do + @blocknew.reserve + end + proc{@blocknew.reserve}.must_raise ArgumentError + end + end + + describe 'room_avaliable_block' do + it 'returns true if there is rooms avaliables' do + @blocknew.room_avaliable_block?.must_equal true + end + it 'returns false if there is not rooms avaliables' do + 4.times do + @blocknew.reserve + end + @blocknew.room_avaliable_block?.must_equal false + end + end +end diff --git a/specs/daterange_spec.rb b/specs/daterange_spec.rb new file mode 100644 index 000000000..7fa98a52f --- /dev/null +++ b/specs/daterange_spec.rb @@ -0,0 +1,100 @@ +require_relative 'spec_helper.rb' + +describe 'DateRange class' do + describe 'initialize' do + before do + @day1 = Date.new(2017,8,3) + @day2 = Date.new(2017,8,6) + end + + it "Responds to checkin checkout" do + newdate = Hotel::DateRange.new(@day1 ,@day2) + newdate.checkin.must_equal @day1 + newdate.checkout.must_equal @day2 + end + + it "Can be initialize if the dates are right" do + Hotel::DateRange.new( @day1, @day2).must_be_instance_of Hotel::DateRange + end + + it "Raises error if checkout is before checkin" do + proc {Hotel::DateRange.new(@day2, @day1)}.must_raise ArgumentError + end + + it "Raises an error if checkin and checkout are equal" do + proc{Hotel::DateRange.new(@day1, @day1)}.must_raise ArgumentError + end + + end#describe initialize + + + describe "stay_length" do + before do + @day1 = Date.new(2017,8,3) + @day2 = Date.new(2017,8,4) + end + it "Should give the right amount of days" do + newdate = Hotel::DateRange.new(@day1, @day2) + newdate.stay_length.must_equal 1 + end + end + + describe 'include?' do + before do + @checkin = Date.new(2017,9,10) + @checkout = Date.new(2017,9,13) + @newdaterange = Hotel::DateRange.new(@checkin, @checkout) + end + + it 'returns true, if the date is include' do + date = Date.new(2017,9,11) + @newdaterange.include?(date).must_equal true + end + + it 'returns true if the date is the checkin date' do + @newdaterange.include?(@checkin).must_equal true + end + + it 'returns false for the checkout date' do + @newdaterange.include?(@checkout).must_equal false + end + + it 'returns false for a date out of range' do + date = Date.new(2017,9,15) + @newdaterange.include?(date).must_equal false + end + + end + + describe 'overlap?' do + before do + @day1 = Date.new(2017,12, 13) + @day2 = Date.new(2017,12, 19) + @dr1 = Hotel::DateRange.new(@day1, @day2) + @day3 = Date.new(2017,12,7) + @day4 = Date.new(2017,12,12) + @day5 = Date.new(2017,12,24) + @day6 = Date.new(2017,12,14) + @day7 = Date.new(2017,12,16) + @dr2 = Hotel::DateRange.new(@day3,@day4) + @dr3 = Hotel::DateRange.new(@day2,@day5) + @dr4 = Hotel::DateRange.new(@day6,@day7) + @dr5 = Hotel::DateRange.new(@day6,@day5) + @dr6 = Hotel::DateRange.new(@day3,@day5) + @dr7 = Hotel::DateRange.new(@day3,@day6) + end + + it "returns false if the intervals are disconect" do + @dr1.overlap?(@dr2).must_equal false + @dr1.overlap?(@dr3).must_equal false + end + + it "returns true if the dates ranges overlap" do + @dr1.overlap?(@dr4).must_equal true + @dr1.overlap?(@dr5).must_equal true + @dr1.overlap?(@dr6).must_equal true + @dr1.overlap?(@dr7).must_equal true + end + end#overlap + +end#big describe diff --git a/specs/reservation_spec.rb b/specs/reservation_spec.rb new file mode 100644 index 000000000..2a8372048 --- /dev/null +++ b/specs/reservation_spec.rb @@ -0,0 +1,28 @@ +require_relative 'spec_helper.rb' + +describe 'Registration class' do + describe 'initialize'do + it 'Can be initialize' do + checkin = Date.new(2017,9,10) + checkout = Date.new(2017,9,13) + room_number = 1 + Hotel::Reservation.new(checkin, checkout, room_number).must_be_instance_of Hotel::Reservation + end + + it "doesn't initialize wrong dates" do + checkin = Date.new(2017,9,10) + room_number = 1 + proc{Hotel::Reservation.new(checkin, checkin, room_number)}.must_raise ArgumentError + end + end + + describe 'total price' do + it 'calcualate the total price' do + checkin = Date.new(2017,9,10) + checkout = Date.new(2017,9,13) + room_number = 1 + newreservation = Hotel::Reservation.new(checkin, checkout, room_number) + newreservation.totalcost.must_equal 600 + end + end +end#describe Registratioin class diff --git a/specs/rooms_spec.rb b/specs/rooms_spec.rb new file mode 100644 index 000000000..9cbd7c0c3 --- /dev/null +++ b/specs/rooms_spec.rb @@ -0,0 +1,24 @@ +# require_relative 'spec_helper.rb' +# +# describe "Rooms class" do +# +# describe "Initialize" do +# +# before do +# @rooms = Hotel::Rooms.new +# end +# +# it 'Can be created' do +# Hotel::Rooms.new.must_be_instance_of Hotel::Rooms +# end +# +# it "Create a hash of rooms" do +# @rooms.rooms.must_be_instance_of Hash +# end +# +# it "Length of rooms hash is 20" do +# @rooms.rooms.length.must_equal 20 +# end +# +# end +# end diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb new file mode 100644 index 000000000..671e12742 --- /dev/null +++ b/specs/spec_helper.rb @@ -0,0 +1,14 @@ +require 'simplecov' +SimpleCov.start +require 'minitest/autorun' +require 'minitest/reporters' +require 'date' +require_relative '../lib/daterange.rb' +require_relative '../lib/reservation.rb' +require_relative '../lib/admin.rb' +require_relative '../lib/block.rb' +# require_relative '../lib/block-admin.rb' +# require_relative '../lib/rooms.rb' + + +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new