diff --git a/Gemfile b/Gemfile index 63ffa5acd..769f5f038 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'http://rubygems.org' -ruby '2.5.5' +ruby '2.6.5' gem 'rake' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..1e34c6def --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,34 @@ +GEM + remote: http://rubygems.org/ + specs: + ansi (1.5.0) + awesome_print (1.8.0) + builder (3.2.4) + csv (3.1.2) + minitest (5.14.0) + minitest-reporters (1.4.2) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + minitest-skip (0.0.1) + minitest (~> 5.0) + rake (13.0.1) + ruby-progressbar (1.10.1) + +PLATFORMS + ruby + +DEPENDENCIES + awesome_print + csv + minitest + minitest-reporters + minitest-skip + rake + +RUBY VERSION + ruby 2.6.5p114 + +BUNDLED WITH + 1.17.2 diff --git a/lib/csv_record.rb b/lib/csv_record.rb index 96319ab87..40a47f8e1 100644 --- a/lib/csv_record.rb +++ b/lib/csv_record.rb @@ -1,3 +1,4 @@ +require 'time' require 'csv' module RideShare @@ -8,9 +9,7 @@ def initialize(id) self.class.validate_id(id) @id = id end - - # Takes either full_path or directory and optional file_name - # Default file name matches class name + def self.load_all(full_path: nil, directory: nil, file_name: nil) full_path ||= build_path(directory, file_name) diff --git a/lib/driver.rb b/lib/driver.rb new file mode 100644 index 000000000..183986949 --- /dev/null +++ b/lib/driver.rb @@ -0,0 +1,93 @@ +require_relative 'csv_record' + +module RideShare + class Driver < CsvRecord + attr_reader :name, :vin, :trips + attr_accessor :status + + def initialize(id:, name:, vin:, status: :AVAILABLE, trips: nil) + super(id) + @name = name + @vin = vin + @status = status + @trips = trips || [] + + if vin.length != 17 + raise ArgumentError.new("VIN length has to be 17") + end + + driver_status = [:AVAILABLE, :UNAVAILABLE] + if !driver_status.include?(status) + raise ArgumentError.new("Driver status invalid.") + end + + end + + def add_trip(trip) + @trips << trip + end + + def ongoing_trips + ongoing_trip = [] + self.trips.each do |trip| + if trip.end_time == nil + ongoing_trip << trip + end + end + + return ongoing_trip + end + + def average_rating + rating = 0 + trips.each do |trip| + if trip.rating == nil + rating += 0 + else + rating += trip.rating + end + end + + if trips.length == 0 + return 0 + end + + return rating.to_f/trips.length + end + + def total_revenue + earnings = 0.0 + + trips.each do |trip| + if trip.cost <= 1.65 + earnings += 0 + else + earnings += (trip.cost * 0.80) + end + end + + return earnings.to_f.round(2) + end + + def status_to_unavailable + self.status = :UNAVAILABLE + end + + def get_new_trip(trip) + self.add_trip(trip) + self.status_to_unavailable + end + + private + + def self.from_csv(record) + return new( + id: record[:id], + name: record[:name], + vin: record[:vin], + status: record[:status].to_sym, + trips: record[:trips] + ) + end + end +end \ No newline at end of file diff --git a/lib/passenger.rb b/lib/passenger.rb index afd13d73b..bae8027f9 100644 --- a/lib/passenger.rb +++ b/lib/passenger.rb @@ -1,3 +1,5 @@ +require 'time' + require_relative 'csv_record' module RideShare @@ -9,13 +11,38 @@ def initialize(id:, name:, phone_number:, trips: nil) @name = name @phone_number = phone_number - @trips = trips || [] + @trips = trips || [] end def add_trip(trip) @trips << trip end + def net_expenditures + total_money = 0 + @trips.each do |trip| + if trip == nil + return 0 + else + total_money += trip.cost + end + end + return total_money + end + + + def total_time_spent + if @trips.empty? == true + return 0 + else + time_duration = (@trips).map do |trip| + trip.time_difference + end + + return time_duration.sum + end + end + private def self.from_csv(record) diff --git a/lib/trip.rb b/lib/trip.rb index 87ce496e9..4ef6939fc 100644 --- a/lib/trip.rb +++ b/lib/trip.rb @@ -1,67 +1,101 @@ require 'csv' +require 'time' require_relative 'csv_record' module RideShare class Trip < CsvRecord - attr_reader :id, :passenger, :passenger_id, :start_time, :end_time, :cost, :rating + attr_reader :id, :passenger, :passenger_id, :start_time, :end_time, :cost, :rating, :driver_id, :driver def initialize( id:, passenger: nil, passenger_id: nil, - start_time:, - end_time:, + start_time: Time.now, + end_time: nil, cost: nil, - rating: + rating: nil, + driver_id: nil, + driver: nil ) super(id) + @start_time = start_time + @end_time = end_time + @cost = cost + @rating = rating if passenger @passenger = passenger @passenger_id = passenger.id - elsif passenger_id @passenger_id = passenger_id - else raise ArgumentError, 'Passenger or passenger_id is required' end - @start_time = start_time - @end_time = end_time - @cost = cost - @rating = rating + if driver + @driver = driver + @driver_id = driver.id + elsif driver_id + @driver_id = driver_id + else + raise ArgumentError, 'Driver or driver_id is required' + end - if @rating > 5 || @rating < 1 + if start_time.class != Time + raise ArgumentError.new("Value is not a Time object.") + end + + if @rating == nil + @rating = rating + elsif @rating > 5 || @rating < 1 raise ArgumentError.new("Invalid rating #{@rating}") end + + unless @end_time == nil + if @end_time < @start_time + raise ArgumentError.new('End time cannot be before start time.') + end + end end + def time_difference + if @start_time == nil + raise ArgumentError.new("Start time cannot be nil.") + end + + return @end_time - @start_time + end + + def inspect - # Prevent infinite loop when puts-ing a Trip - # trip contains a passenger contains a trip contains a passenger... "#<#{self.class.name}:0x#{self.object_id.to_s(16)} " + "ID=#{id.inspect} " + "PassengerID=#{passenger&.id.inspect}>" end - def connect(passenger) + def connect_passenger(passenger) @passenger = passenger passenger.add_trip(self) end + def connect_driver(driver) + @driver = driver + driver.add_trip(self) + end + private def self.from_csv(record) - return self.new( + return new( id: record[:id], passenger_id: record[:passenger_id], - start_time: record[:start_time], - end_time: record[:end_time], + start_time: Time.parse(record[:start_time]), + end_time: Time.parse(record[:end_time]), cost: record[:cost], - rating: record[:rating] + rating: record[:rating], + driver_id: record[:driver_id] ) end end -end +end \ No newline at end of file diff --git a/lib/trip_dispatcher.rb b/lib/trip_dispatcher.rb index 5130849f8..034488bb4 100644 --- a/lib/trip_dispatcher.rb +++ b/lib/trip_dispatcher.rb @@ -3,38 +3,81 @@ require_relative 'passenger' require_relative 'trip' +require_relative 'driver' module RideShare class TripDispatcher attr_reader :drivers, :passengers, :trips - + def initialize(directory: './support') + @drivers = Driver.load_all(directory: directory) @passengers = Passenger.load_all(directory: directory) @trips = Trip.load_all(directory: directory) connect_trips end - + def find_passenger(id) Passenger.validate_id(id) return @passengers.find { |passenger| passenger.id == id } end - + + def find_driver(id) + Driver.validate_id(id) + return @drivers.find { |driver| driver.id == id} + end + + def inspect - # Make puts output more useful return "#<#{self.class.name}:0x#{object_id.to_s(16)} \ - #{trips.count} trips, \ - #{drivers.count} drivers, \ - #{passengers.count} passengers>" + #{trips.count} trips, \ + #{drivers.count} drivers, \ + #{passengers.count} passengers>" end - + + def find_available_driver + available_drivers = [] + drivers.each do |driver| + if driver.status = :AVAILABLE && driver.ongoing_trips.length == 0 + available_drivers << driver + end + end + + if available_drivers.empty? + raise ArgumentError.new "There are no available drivers at this time" + end + + return available_drivers.shuffle[0] + end + + def start_trip(driver:, passenger:) + current_time = Time.new + new_id = (trips.last.id + 1) + return Trip.new(id: new_id, passenger: passenger, driver: driver, start_time: current_time) + end + + def request_trip(passenger_id) + available_driver = self.find_available_driver + passenger = self.find_passenger(passenger_id) + + new_trip = self.start_trip(driver: available_driver, passenger: passenger) + + available_driver.get_new_trip(new_trip) + passenger.add_trip(new_trip) + trips << new_trip + + return new_trip + end + + private - + def connect_trips @trips.each do |trip| passenger = find_passenger(trip.passenger_id) - trip.connect(passenger) + trip.connect_passenger(passenger) + trip.connect_driver(find_driver(trip.driver_id)) end - + return trips end end diff --git a/test/driver_test.rb b/test/driver_test.rb index 4e6076ec2..48b5541c8 100644 --- a/test/driver_test.rb +++ b/test/driver_test.rb @@ -1,6 +1,6 @@ require_relative 'test_helper' -xdescribe "Driver class" do +describe "Driver class" do describe "Driver instantiation" do before do @driver = RideShare::Driver.new( @@ -76,6 +76,12 @@ expect(@driver.trips).must_include @trip expect(@driver.trips.length).must_equal previous + 1 end + + it "gives accurate trip information" do + @driver.add_trip(@trip) + + expect (@driver.trips[@driver.trips.length - 1]).must_be_instance_of RideShare::Trip + end end describe "average_rating method" do @@ -129,8 +135,48 @@ expect(@driver.average_rating).must_be_close_to (5.0 + 1.0) / 2.0, 0.01 end end + + describe "total_revenue method" do + it "calculates total revenue" do + @driver = RideShare::Driver.new( + id: 54, + name: "Rogers Bartell IV", + vin: "1C9EVBRM0YBC564DZ" + ) + trip1 = RideShare::Trip.new( + id: 8, + driver: @driver, + passenger_id: 3, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 9), + rating: 1, + cost: 0 + ) + @driver.add_trip(trip1) + + trip2 = RideShare::Trip.new( + id: 8, + driver: @driver, + passenger_id: 3, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 9), + rating: 1, + cost: 12 + ) + @driver.add_trip(trip2) + + expect(@driver.total_revenue).must_equal 9.6 + end - describe "total_revenue" do - # You add tests for the total_revenue method + it "returns zero if no driven trips" do + driver = RideShare::Driver.new( + id: 54, + name: "Rogers Bartell IV", + vin: "1C9EVBRM0YBC564DZ" + ) + + expect(driver.total_revenue).must_equal 0 + end end end + diff --git a/test/passenger_test.rb b/test/passenger_test.rb index eb3a631df..c9b4957a3 100644 --- a/test/passenger_test.rb +++ b/test/passenger_test.rb @@ -37,22 +37,44 @@ describe "trips property" do before do - # TODO: you'll need to add a driver at some point here. @passenger = RideShare::Passenger.new( id: 9, name: "Merl Glover III", phone_number: "1-602-620-2330 x3723", trips: [] ) - trip = RideShare::Trip.new( + @driver = RideShare::Driver.new( + id: 54, + name: "Rogers Bartell IV", + vin: "1C9EVBRM0YBC564DZ", + status: :AVAILABLE, + trips: [1] + ) + trip1 = RideShare::Trip.new( + id: 8, + passenger: @passenger, + start_time: Time.new(2016, 8, 8, 13, 39 , 0), + end_time: Time.new(2016, 8, 8, 13, 50, 0), + rating: 5, + cost: 5, + driver_id: @driver.id, + driver: nil + ) + + @passenger.add_trip(trip1) + + trip2 = RideShare::Trip.new( id: 8, passenger: @passenger, - start_time: Time.new(2016, 8, 8), - end_time: Time.new(2016, 8, 9), - rating: 5 + start_time: Time.new(2016, 8, 8, 16, 30, 0), + end_time: Time.new(2016, 8, 8, 16, 40, 0), + rating: 5, + cost: 10, + driver_id: @driver.id, + driver: nil ) - @passenger.add_trip(trip) + @passenger.add_trip(trip2) end it "each item in array is a Trip instance" do @@ -66,9 +88,29 @@ expect(trip.passenger.id).must_equal 9 end end - end - describe "net_expenditures" do - # You add tests for the net_expenditures method + it "calculates total costs" do + @passenger.trips.each do |trip| + expect(@passenger.net_expenditures).must_equal 15 + end + end + + it "calculates time spent" do + @passenger.trips.each do |trip| + expect(@passenger.total_time_spent).must_equal 1260 + end + end + + it "handles no trips for expenditures and time spent" do + @passenger = RideShare::Passenger.new( + id: 9, + name: "Merl Glover III", + phone_number: "1-602-620-2330 x3723", + trips: [] + ) + expect(@passenger.net_expenditures).must_equal 0 + expect(@passenger.total_time_spent).must_equal 0 + end + end -end +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index bdb9bf352..082369790 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -12,4 +12,4 @@ require_relative '../lib/passenger' require_relative '../lib/trip' require_relative '../lib/trip_dispatcher' -# require_relative '../lib/driver' +require_relative '../lib/driver' diff --git a/test/trip_dispatch_test.rb b/test/trip_dispatch_test.rb index 21f4457b7..edc4d1048 100644 --- a/test/trip_dispatch_test.rb +++ b/test/trip_dispatch_test.rb @@ -23,11 +23,9 @@ def build_test_dispatcher expect(dispatcher.trips).must_be_kind_of Array expect(dispatcher.passengers).must_be_kind_of Array - # expect(dispatcher.drivers).must_be_kind_of Array end it "loads the development data by default" do - # Count lines in the file, subtract 1 for headers trip_count = %x{wc -l 'support/trips.csv'}.split(' ').first.to_i - 1 dispatcher = RideShare::TripDispatcher.new @@ -67,6 +65,36 @@ def build_test_dispatcher expect(last_passenger.id).must_equal 8 end + it "adds the trip to the passenger's list of trips" do + @passenger = RideShare::Passenger.new( + id: 9, + name: "Merl Glover III", + phone_number: "1-602-620-2330 x3723", + trips: [] + ) + @driver = RideShare::Driver.new( + id: 54, + name: "Rogers Bartell IV", + vin: "1C9EVBRM0YBC564DZ", + status: :AVAILABLE, + trips: [1] + ) + trip1 = RideShare::Trip.new( + id: 8, + passenger: @passenger, + start_time: Time.new(2016, 8, 8, 13, 39 , 0), + end_time: Time.new(2016, 8, 8, 13, 50, 0), + rating: 5, + cost: 5, + driver_id: @driver.id, + driver: nil + ) + + @passenger.add_trip(trip1) + + expect(@passenger.trips).must_include(trip1) + end + it "connects trips and passengers" do dispatcher = build_test_dispatcher dispatcher.trips.each do |trip| @@ -78,8 +106,7 @@ def build_test_dispatcher end end - # TODO: un-skip for Wave 2 - xdescribe "drivers" do + describe "drivers" do describe "find_driver method" do before do @dispatcher = build_test_dispatcher @@ -93,6 +120,19 @@ def build_test_dispatcher driver = @dispatcher.find_driver(2) expect(driver).must_be_kind_of RideShare::Driver end + + it "finds available driver" do + driver = @dispatcher.find_driver(3) + expect(driver.status).must_equal :AVAILABLE + end + + it "changes driver status to UNAVAILABLE when new trip added" do + dispatcher = build_test_dispatcher + + trip1 = dispatcher.request_trip(1) + expect(trip1.driver.status).must_equal :UNAVAILABLE + end + end describe "Driver & Trip loader methods" do @@ -120,6 +160,20 @@ def build_test_dispatcher expect(trip.driver.trips).must_include trip end end + + it "adds new trip to collection of all Trips" do + dispatcher = build_test_dispatcher + trip1 = dispatcher.request_trip(1) + + expect(dispatcher.trips).must_include(trip1) + end + + it "returns new_trip" do + dispatcher = build_test_dispatcher + trip1 = dispatcher.request_trip(1) + + expect(trip1).must_be_kind_of RideShare::Trip + end end end end diff --git a/test/trip_test.rb b/test/trip_test.rb index 2063e28a7..0f226dcd5 100644 --- a/test/trip_test.rb +++ b/test/trip_test.rb @@ -15,11 +15,76 @@ start_time: start_time, end_time: end_time, cost: 23.45, - rating: 3 + rating: 3, + driver: RideShare::Driver.new( + id: 54, + name: "Rogers Bartell IV", + vin: "1C9EVBRM0YBC564DZ", + status: :AVAILABLE, + trips: nil + ) } @trip = RideShare::Trip.new(@trip_data) end + it "accurately finds time length of ride" do + start_time = "2018-12-27 02:39:05 -0800" + end_time = "2018-12-27 03:38:08 -0800" + + difference = Time.parse(end_time) - Time.parse(start_time) + + + expect(difference).must_equal(3543.0) + end + + it "raises error when end_time or start_time is not Time object" do + test_trip = { + id: 8, + passenger: RideShare::Passenger.new( + id: 1, + name: "Ada", + phone_number: "412-432-7640" + ), + start_time: "2018-12-27 03:38:08 -0800", + end_time: "2018-12-27 02:39:05 -0800", + cost: 23.45, + rating: 3, + driver: RideShare::Driver.new( + id: 54, + name: "Rogers Bartell IV", + vin: "1C9EVBRM0YBC564DZ", + status: :AVAILABLE, + trips: [1, 2, 3] + ) + } + + expect {RideShare::Trip.new(test_trip)}.must_raise ArgumentError + end + + it "raises error when end_time is less than start_time" do + test_trip = { + id: 8, + passenger: RideShare::Passenger.new( + id: 1, + name: "Ada", + phone_number: "412-432-7640" + ), + start_time: Time.parse("2018-12-27 03:38:08 -0800"), + end_time: Time.parse("2018-12-27 02:39:05 -0800"), + cost: 23.45, + rating: 3, + driver: RideShare::Driver.new( + id: 54, + name: "Rogers Bartell IV", + vin: "1C9EVBRM0YBC564DZ", + status: :AVAILABLE, + trips: [1] + ) + } + + expect {RideShare::Trip.new(test_trip)}.must_raise ArgumentError + end + it "is an instance of Trip" do expect(@trip).must_be_kind_of RideShare::Trip end @@ -29,17 +94,14 @@ end it "stores an instance of driver" do - skip # Unskip after wave 2 expect(@trip.driver).must_be_kind_of RideShare::Driver end it "raises an error for an invalid rating" do [-3, 0, 6].each do |rating| @trip_data[:rating] = rating - expect do - RideShare::Trip.new(@trip_data) - end.must_raise ArgumentError + expect {RideShare::Trip.new(@trip_data)}.must_raise ArgumentError end end end -end +end \ No newline at end of file diff --git a/time_math.rb b/time_math.rb new file mode 100644 index 000000000..a85809866 --- /dev/null +++ b/time_math.rb @@ -0,0 +1,16 @@ +require 'time' + +def time_difference(time_a, time_b) + difference = Time.parse(time_b) - Time.parse(time_a) + + if difference > 0 + return difference + else + return 24 * 3600 + difference + end +end + +end_time = "2018-12-27 03:38:08 -0800" +start_time = "2018-12-27 02:39:05 -0800" + +puts time_difference(start_time, end_time) \ No newline at end of file