diff --git a/.gitignore b/.gitignore index 5e1422c9c..085efd0a6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /test/tmp/ /test/version_tmp/ /tmp/ +/junk/ # Used by dotenv library to load environment variables. # .env diff --git a/Guardfile b/Guardfile index 6760f9177..fa59fc3ef 100644 --- a/Guardfile +++ b/Guardfile @@ -1,4 +1,4 @@ -guard :minitest, bundler: false, rubygems: false do +guard :minitest, bundler: false, autorun: false, rubygems: false do # with Minitest::Spec watch(%r{^spec/(.*)_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } diff --git a/data/all_hotel_rooms.csv b/data/all_hotel_rooms.csv new file mode 100644 index 000000000..c51ed7db9 --- /dev/null +++ b/data/all_hotel_rooms.csv @@ -0,0 +1,11 @@ +room_number,cost,status,reserved_dates +1,200,available,[] +2,200,available,[] +3,200,available,[] +4,200,available,[] +5,200,available,[] +6,200,available,[] +7,200,available,[] +8,200,available,[] +9,200,available,[] +10,200,available,[] diff --git a/data/reservations.csv b/data/reservations.csv new file mode 100644 index 000000000..14a18c447 --- /dev/null +++ b/data/reservations.csv @@ -0,0 +1 @@ +room_number,cost,status,check_in,check_out diff --git a/design-activity.md b/design-activity.md new file mode 100644 index 000000000..e9b3d7853 --- /dev/null +++ b/design-activity.md @@ -0,0 +1,68 @@ +# Activity: Evaluating Responsibility + +## Prompts +Once you have read through the above code, add a file to your Hotel project called design-activity.md. This file will be submitted with the rest of this exercise. In that file, please respond to the following prompts: + +### What classes does each implementation include? Are the lists the same? +Write down a sentence to describe each class. + Each implementation has: CartEntry, ShoppingCart, and Order classes. + CartEntry = the price and quantity are stored as instance variables upon initialization. Implemetation B's version also has the instance method price which returns the product of the unit price and quantity. + ShoppingCart = an empty array (instance variable entries) is created upon initialization. For implementation B, it also has the instance method 'price', which sums the price of each entry in the array @entries. + Order = the Order creates a new instance of ShoppingCart upon initialization. Both implementations have instance method 'total_price', however in implenentation A, the sum is calculated by using the instance variable entries in the ShoppingCart class with the instance variable cart in the Order class. In implementation B, the method stays within the class, using Order's instance varialbe cart only. + + + + +### 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 - the classes are interwoven, attr_accessor is used to interweave the classes together. The Order class is reliant on the CartEntry and ShoppingCart classes for calculating total price. + + Implementation B - the classes are more independent from each other, the Order class is reliant only on information from the ShoppingCart method and only because a new ShoppingCart is initialized with its creation. + + + +### What data does each class store? How (if at all) does this differ between the two implementations? What methods does each class have? How (if at all) does this differ between the two implementations? + In implementation B, all classes not only store information but are responsible for calculations for each instance of itself. In implementation A, however, Order is responsible for everyone. + + +### 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? +Does total_price directly manipulate the instance variables of other classes? + In implementation B, it is delegated to lower level classes, in A it directly manipulates the other classes' instance vars. + + +### If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify? +Which implementation better adheres to the single responsibility principle? + Implementation B isn't as tightly woven as A. Since all classes are responsible for their own thing and have their own methods that perform calculations, it is much easier to create a class or other edit. As opposed to A which must be fully changed from top-bottom in order to add that functionality. + + +### Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled? + Implementation B + + +Once you've responded to the prompts, git add design-activity.md and git commit! + +**** + +Now that we've got you thinking about design, spend some time to revisit the code you wrote for the 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? +You might recall writing a file called refactor.txt. Take a look at the refactor plans that you wrote, and consider the following: + +How easy is it to follow your own instructions? +Do these refactors improve the clarity of your code? +Do you still agree with your previous assesment, or could your refactor be further improved? +**** + +### 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. + Changed a bunch of stuff, remove CSV stuff since it didn't work out, change some methods and clearly separate module from class. Moved module methods to Concierge class, but I think the program as a whole needs a rehaul. + + +If you need inspiration, remember that the reference implementation exists. + +Then make the changes! Don't forget to take advantage of all the tests you wrote - if they're well structured, they should quickly inform you when your refactoring breaks something. + +Once you're satisfied, git commit your changes and then push them to GitHub. This will automatically update your pull request. diff --git a/junk/admin.rb b/junk/admin.rb new file mode 100644 index 000000000..97c7eca3b --- /dev/null +++ b/junk/admin.rb @@ -0,0 +1,62 @@ +require 'awesome_print' +require 'date' +#concierge can VIEW reservations +#concierge can RESERVE reservation +#concierge can ACCESS receipt for completed stay +#concierge can ACCESS hotel room list = yes +#can datevar.strftime("%b %d, %Y") + +module Lodging #namespace/container + #similar to the main.rb of other projects + class Concierge #small hotel + attr_accessor :room_hash + + def initialize(room_count) + Lodging.create_rooms(room_count) #creating rooms upon initialization + end + + def all_rooms + room_hash = [] + Room.show_list.each do |room| + room_info = {} + # room = "Room #{room.room_number} costs $#{room.cost} a night and is #{room.status}." + room_info[:room_number] = room.room_number + room_info[:price] = room.cost + room_info[:status] = room.status + + room_hash << room_info + end + return room_hash + end + + + def new_reservation(check_in, check_out, block = 1) + raise ArgumentError if Date.parse(check_out) < Date.parse(check_in) + # + # status = block > 1 ? :hold : :unavailable #hold if block, unavailable if single reservation + + # block.times do + book_this = Lodging.reservation(all_rooms, status) + book_this[:check_in] = Date.parse(check_in) + book_this[:check_out] = Date.parse(check_out) + ap book_this + # end + # + # @room_hash.find do |room| + # room[:room_number] == book_this[:room_number] + # # book_this[:status] = :unavailable + # end + + # booked[:status] = :unavailable + + end + + def search_reservation(date) + + + end + + end + + +end diff --git a/junk/admin_spec.rb b/junk/admin_spec.rb new file mode 100644 index 000000000..ad14d77f3 --- /dev/null +++ b/junk/admin_spec.rb @@ -0,0 +1,45 @@ +require_relative 'spec_helper' + +describe 'Concierge class' do + before(:each) do + Lodging::Room.class_variable_set(:@@room_list, []) + end + + let(:concierge) { + Lodging::Concierge.new(10) + } + + describe 'all_rooms method' do + it 'shows all rooms in hotel' do + # Lodging.create_rooms(10) + + room_list = concierge.all_rooms + + expect(room_list.any? Hash).must_equal true + expect(room_list.length).must_equal 10 + end + + end + + describe 'new reservation method' do + let(:new_booking) { + concierge.new_reservation('201876', '201878') + } + + it 'errors if invalid date is entered' do + expect{concierge.new_reservation('201876', '201778')}.must_raise ArgumentError + end + + # it 'creates a check_in and check_out dates for room' do + # ap concierge + # + # end + + end + + describe 'search reservation' do + it 'can find a reservation based on date' do + + end + end +end diff --git a/junk/reservation.rb b/junk/reservation.rb new file mode 100644 index 000000000..0c26d6fd3 --- /dev/null +++ b/junk/reservation.rb @@ -0,0 +1,42 @@ +require 'awesome_print' +require 'date' +require 'pry' +#reservation CREATES reservations +#reservation TOTALS costs of stay + +module Lodging + + def self.room_status(input) #check room_status + avail = input.find do |room| + room[:status] == :available #returns first instance it finds of available room + end + + return avail + end + + # module Reservation + + def self.reservation(input) #switches status from avail to unavail + room = Lodging.room_status(input) + + room[:status] = status + + Lodging::Room.status_change(room[:room_number], status) + + room[:start_date] = nil + room[:end_date] = nil + # check status, if available + # takes date range + # switches status to unavailable + end + # end + + def self.receipt #calculates total cost of stay + #counts days in range + #multiply count by + #can date var -= 1 to get day before + + end + + +end diff --git a/junk/reservation_spec.rb b/junk/reservation_spec.rb new file mode 100644 index 000000000..682c78ae5 --- /dev/null +++ b/junk/reservation_spec.rb @@ -0,0 +1,60 @@ +require_relative 'spec_helper' + +describe 'Lodging Reservation methods' do + before(:each) do + Lodging::Room.class_variable_set(:@@room_list, []) + end + + let(:concierge){ + Lodging::Concierge.new(15) + } + + describe 'check status' do + before do + Lodging.create_rooms(5) + end + + describe 'room status' do + it 'errors if no available rooms' do + room = [{room_number: 2, status: :unavailable, cost: 200}] + + ap room + + expect(Lodging.room_status(room)).must_raise ArgumentError + + end + + end + + it 'returns one available room if no error' do + avail = Lodging.room_status(concierge.all_rooms) + # ap avail + + expect(avail).must_be_instance_of Hash + expect(avail[:status]).must_equal :available + + end + end + + # describe 'reservation' do + # it 'switches room status to unavailable' do + # + # end + # + # it 'switches status of room instance to unavailable' do + # + # end + # + # end + + # describe 'reciept' do + # it 'calculates room total based on room cost per night' do + # + # end + # + # it 'does not calculate last date of reservation in total' do + # + # end + # end + +end diff --git a/lib/.keep b/lib/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/lib/concierge.rb b/lib/concierge.rb new file mode 100644 index 000000000..ae15fec48 --- /dev/null +++ b/lib/concierge.rb @@ -0,0 +1,166 @@ +require 'awesome_print' +require 'date' +require 'csv' +require 'pry' + +require_relative 'room' +#concierge can ACCESS list of all rooms in hotel = yes +#concierge can RESERVE reservation for date range = yes +#concierge can RESERVE new res. on last day of prev. one +#concierge can ACCESS list of reservations on date = yes +#concierge can ACCESS receipt for completed stay = yes +#receipt does NOT INCLUDE last day = yes +#can view rooms NOT RESERVED on date = yes +#can block reservation = yes +#datevar.strftime("%b %d, %Y") - date output + +module Lodging + + class Concierge + + attr_accessor :rooms_info + + def initialize(room_count) + Concierge.create_rooms(room_count) #creating rooms upon initialization + + rooms = CSV.read('data/all_hotel_rooms.csv', headers: true, header_converters: :symbol).map do |room| #converting string headers to symbols for hash + room[:reserved_dates] = [] #converting string '[]' to empty array [] + room.to_h + end + @rooms_info = rooms + end + + def all_rooms + return @rooms_info + end + + def new_reservation(check_in, check_out) #makes new reservation based on two dates + + raise ArgumentError if Date.parse(check_out) < Date.parse(check_in) #checkout cannot be earlier than check in + + book_this = Concierge.available_room(@rooms_info, check_in) #returns one available room + + reserved_dates = Concierge.create_date_range(check_in, check_out) #parses range for date, into array + + update_room = @rooms_info.find do |room| + room[:room_number] == book_this[:room_number] + end #finds room to be reserved + + update_room[:status] = "unavailable" #changes status of said room in the array of hashes as 'unavailable' + update_room[:reserved_dates] += reserved_dates #all reserved dates are added under previous dates + end + + def new_block(check_in, check_out, block = 1, discount = 0.15) #makes new reservation based on two dates + + raise ArgumentError if Date.parse(check_out) < Date.parse(check_in) #checkout cannot be earlier than check in + + reserved_dates = Concierge.create_date_range(check_in, check_out) #parses range for date, into array + + block.times do + hold_this = Concierge.available_room(@rooms_info, check_in) #returns one available room + + update_room = @rooms_info.find do |room| + room[:room_number] == hold_this[:room_number] + end #finds room to be reserved + + new_cost = (update_room[:cost].to_f) - (update_room[:cost].to_f * discount) + + update_room[:cost] = new_cost.round(2).to_s + update_room[:status] = "hold" #changes status of said room in the array of hashes as 'unavailable' + update_room[:reserved_dates] += reserved_dates #all reserved dates are added under previous dates + ap update_room + end + end + + def block_reserve(check_in, check_out) + block_dates = Concierge.create_date_range(check_in, check_out) #parses range for date, into array + held_room = @rooms_info.find do |room| + room[:status] == 'hold' && room[:reserved_dates] == block_dates + end #finds room to be reserved + + raise ArgumentError if held_room == nil + + held_room[:status] = 'unavailable' #changes status to unavailable aka reserved + end + + def reserved_rooms(date) #see list of reserved rooms by date + + raise ArgumentError if !Date.parse(date).instance_of? Date + + search_date = Date.parse(date) + + reserved_rooms = @rooms_info.find_all do |room| + room if room[:reserved_dates] + end + + reserved_rooms.select! do |room| + room[:reserved_dates].include? search_date + end + + return reserved_rooms + + end + + def view_avail(date) + + raise ArgumentError if !Date.parse(date).instance_of? Date + + search_date = Date.parse(date) + + avail_rooms = @rooms_info.find_all do |room| + room if room[:status] == 'available' + end + + avail_rooms = @rooms_info.reject! do |room| + room[:reserved_dates].include? search_date + end + + return avail_rooms + end + + def receipt(room_number) #date step method to determine day count + check_out_room = @rooms_info.find do |room| + room_number.to_s == room[:room_number] + end + + cost = check_out_room[:cost].to_f + total_days = check_out_room[:reserved_dates].length - 1 + + return Concierge.total_owed(total_days, cost) + + end + + #creates multiple rooms at once, and assigns room number + def self.create_rooms(room_count, cost = 200) #creates new rooms, assigns room no. + raise ArgumentError if !room_count.is_a? Integer + i = 1 + room_count.times do + Room.new(i, cost) + i += 1 + end + end + + def self.available_room(input, check_in) #check room_status + avail = input.find do |room| + room[:reserved_dates].last == Date.parse(check_in) || room[:status] == "available" #returns first instance it finds of available room + end + + raise ArgumentError if avail == false + + return avail + end + + def self.total_owed(multiplier, price) #date step method to determine day count + total = multiplier.to_f * price.to_f + return total.round(2) + end + + def self.create_date_range(date1, date2) + return (Date.parse(date1)..Date.parse(date2)).to_a + end + + + end + + end +end diff --git a/lib/lodging.rb b/lib/lodging.rb new file mode 100644 index 000000000..2662f1d7b --- /dev/null +++ b/lib/lodging.rb @@ -0,0 +1,5 @@ +require 'awesome_print' + +module Lodging + +end diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..2e47266d2 --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,26 @@ +require 'awesome_print' +#room should CREATE ROOMS = yes +#room should STORE room information = yes +#room should STORE room status = yes +#room should STORE room costs = yes +#room should STORE all_hotel_rooms = yes + +module Lodging + + class Room + attr_reader :room_number, :status, :cost + + def initialize(room_number, cost = 200) + @room_number = room_number + @cost = cost + @status = :available + + room = [@room_number, @cost, @status, Array.new] + + CSV.open('data/all_hotel_rooms.csv', 'a+') do |row| + row << room + end + end + + end +end diff --git a/refactors.txt b/refactors.txt new file mode 100644 index 000000000..421a29913 --- /dev/null +++ b/refactors.txt @@ -0,0 +1,5 @@ +I would refactor mostly the concierge class. It is bigger than I want. I wanted it to be more of an 'viewing' sort of class but it ends up being more of an 'action' class. I want to move a lot of methods out from there. + +I tried to use CSV for my data, but none of the data was appending to my reservations file. I think if that worked, my concierge class would have been more the way I wanted it to be. It would rely more on the module methods than its own methods. + +I want to move around a lot of stuff and make it much more organized. diff --git a/spec/concierge_spec.rb b/spec/concierge_spec.rb new file mode 100644 index 000000000..2893a5ed4 --- /dev/null +++ b/spec/concierge_spec.rb @@ -0,0 +1,170 @@ +require_relative 'spec_helper' + +describe 'Lodging::Concierge class' do + #reset CSV data + before(:each) do + CSV.open('data/all_hotel_rooms.csv', 'w', headers: true) do |csv| + csv << ['room_number','cost','status', 'reserved_dates'] + end + + CSV.open('data/reservations.csv', 'w', headers: true) do |csv| + csv << ['room_number','cost','status','check_in','check_out'] + end + end + + let(:concierge){ + Lodging::Concierge.new(10) + } + + describe 'all rooms method' do + it 'can view data in all_hotel_rooms csv file' do + + expect(concierge.all_rooms).must_be_instance_of Array + expect(concierge.all_rooms.any? Hash).must_equal true + expect(concierge.all_rooms.length).must_equal 10 + + end + end + + describe 'new reservation method' do + + it 'creates new reservations' do + concierge.all_rooms + + concierge.new_reservation('Sept 9, 2018', 'Sept 12, 2018') + + first = concierge.all_rooms[0][:reserved_dates].first + last = concierge.all_rooms[0][:reserved_dates].last + status = concierge.all_rooms[0][:status] + + expect(status).must_equal 'unavailable' + expect(first).must_equal Date.parse('Sept 9, 2018') + expect(last).must_equal Date.parse('Sept 12, 2018') + + end + + it 'errors if invalid date is entered' do + expect{concierge.new_reservation('August 7, 2019', 'August 28, 2018')}.must_raise ArgumentError + end + + it 'can reserve room on last day of previous res' do + tiny_hotel = Lodging::Concierge.new(1) + + + tiny_hotel.new_reservation('Sept 9, 2018', 'Sept 12, 2018') + tiny_hotel.new_reservation('Sept 12, 2018', 'Sept 14, 2018') + tiny_hotel.all_rooms + + expect(tiny_hotel.all_rooms[0][:reserved_dates].length).must_equal 7 + + end + + end + + describe 'reserved rooms method' do + it 'errors if date argument not passed' do + concierge.new_reservation('Sept 9, 2018', 'Sept 12, 2018') + concierge.new_reservation('Sept 13, 2018', 'Sept 20, 2018') + + expect{concierge.reserved_rooms('forty-two')}.must_raise ArgumentError + expect{concierge.reserved_rooms(4)}.must_raise TypeError + expect{concierge.reserved_rooms('66')}.must_raise ArgumentError + + end + + it 'returns list of reservations that have date' do + concierge.new_reservation('Sept 9, 2018', 'Sept 12, 2018') #room 1 + concierge.new_reservation('Sept 13, 2018', 'Sept 20, 2018') #room 2 + concierge.new_reservation('Sept 10, 2018', 'Sept 20, 2018') #room 3 + concierge.new_reservation('Sept 6, 2018', 'Sept 13, 2018') #room 4 + + sept_12 = concierge.reserved_rooms('Sept 12, 2018') + + expect(sept_12.length).must_equal 3 + expect(sept_12[0][:room_number]).must_equal '1' + expect(sept_12[1][:room_number]).must_equal '3' + expect(sept_12[2][:room_number]).must_equal '4' + + end + + end + + describe 'new_block' do + before do + concierge.new_block('Dec 1, 2018', 'Dec 15, 2018', 5) + end + + it 'creates multiple holds for several rooms' do + + + first = concierge.all_rooms.first + fifth = concierge.all_rooms[4] + last = concierge.all_rooms.last + + expect(first[:status]).must_equal 'hold' + expect(fifth[:status]).must_equal 'hold' + expect(last[:status]).must_equal 'available' + + + end + + it 'updates pricing of each room to discounted rate' do + first = concierge.all_rooms.first + fifth = concierge.all_rooms[4] + last = concierge.all_rooms.last + + expect(first[:cost]).must_equal '170.0' + expect(fifth[:cost]).must_equal '170.0' + expect(last[:cost]).must_equal '200' + + end + + end + + describe 'block_reserve' do + before do + concierge.new_block('Dec 1, 2018', 'Dec 15, 2018', 5) + end + + it 'reserves from hotel block' do + concierge.block_reserve('Dec 1, 2018', 'Dec 15, 2018') + + reserved = concierge.all_rooms[0] + + expect(reserved[:status]).must_equal 'unavailable' + end + + it 'raises ArgumentError for different block dates' do + expect{concierge.block_reserve('Dec 12, 2018', 'Dec 15, 2018')}.must_raise ArgumentError + end + end + + + describe 'view avail method' do + it 'returns list of available rooms for date specified' do + concierge.new_reservation('Sept 9, 2018', 'Sept 12, 2018') #room 1 + concierge.new_reservation('Sept 13, 2018', 'Sept 20, 2018') #room 2 + concierge.new_reservation('Sept 10, 2018', 'Sept 20, 2018') #room 3 + concierge.new_reservation('Sept 6, 2018', 'Sept 13, 2018') #room 4 + + sept_12 = concierge.view_avail('Sept 12, 2018') + + expect(sept_12.length).must_equal 7 + expect(sept_12.first[:room_number]).must_equal '2' + expect(sept_12.first[:status]).must_equal 'unavailable' #unavailable starting 13/09 + expect(sept_12.last[:room_number]).must_equal '10' + + + end + + end + + describe 'receipt' do + it 'calculates total cost for stay' do + concierge.new_reservation('Sept 9, 2018', 'Sept 12, 2018') #room 1 + + concierge.receipt(1) + end + end + +end diff --git a/spec/lodging_spec.rb b/spec/lodging_spec.rb new file mode 100644 index 000000000..a1c766853 --- /dev/null +++ b/spec/lodging_spec.rb @@ -0,0 +1 @@ +#ALL LODGING METHODS TESTED IN CLASS SPECS diff --git a/spec/room_spec.rb b/spec/room_spec.rb new file mode 100644 index 000000000..ad7ee47bc --- /dev/null +++ b/spec/room_spec.rb @@ -0,0 +1,44 @@ +require_relative 'spec_helper' + +describe 'room class' do + #to clear CSV file before each test + before(:each) do + CSV.open('data/all_hotel_rooms.csv', 'w', headers: true) do |csv| + csv << ['room_number','cost','status', 'reserved_dates'] + end + + CSV.open('data/reservations.csv', 'w', headers: true) do |csv| + csv << ['room_number','cost','status','check_in','check_out'] + end + end + + describe 'initialize' do + it 'creates a new instance of room' do + new_room = Lodging::Room.new(3, 250) + + expect(new_room).must_be_instance_of Lodging::Room + expect(new_room.room_number).must_equal 3 + expect(new_room.cost).must_equal 250 + expect(new_room.status).must_equal :available + end + end + + it 'adds each new instance of room into csv file' do + Lodging.create_rooms(5) + + rooms = CSV.read('data/all_hotel_rooms.csv', headers: true) + + expect(rooms).must_be_instance_of CSV::Table + expect(rooms.length).must_equal 5 + end + + describe 'available room' do + + it 'returns CSV of available room' do + Lodging.create_rooms(5) + + expect(Lodging::Room.available_room).must_be_instance_of CSV + end + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4d1e3fdc8..783edf820 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,15 @@ +#### +require 'simplecov' #for autotesting w. guard +SimpleCov.start +#### require 'minitest' require 'minitest/autorun' require 'minitest/reporters' -# Add simplecov Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new # Require_relative your lib files here! +# require_relative '../lib/block' +require_relative '../lib/lodging' +require_relative '../lib/concierge' +require_relative '../lib/room'