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..a029ef031 --- /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 + 2.1.4 diff --git a/lib/csv_record.rb b/lib/csv_record.rb index 96319ab87..7a7152604 100644 --- a/lib/csv_record.rb +++ b/lib/csv_record.rb @@ -1,4 +1,5 @@ require 'csv' +require 'time' module RideShare class CsvRecord diff --git a/lib/driver.rb b/lib/driver.rb new file mode 100644 index 000000000..8a709b9e1 --- /dev/null +++ b/lib/driver.rb @@ -0,0 +1,77 @@ +require_relative 'csv_record' + +module RideShare + class Driver < CsvRecord + + attr_reader :id, :name, :vin, :status, :trips + + def initialize(id: nil, name: nil, vin:, status: :AVAILABLE, trips: nil) + super(id) + + @name = name + @vin = vin + @status = status + @trips = trips || [] + + unless @status == :AVAILABLE || @status == :UNAVAILABLE + raise ArgumentError.new("#{@status} is not a valid status.") + end + + unless @vin.split(//).length == 17 + raise ArgumentError.new("#{@vin} is not a valid VIN number.") + end + + end + + def add_trip(trip) + unless trip.end_time == nil + @trips << trip + end + end + + def average_rating + ratings = [] + if @trips.length == 0 + return 0 + end + + @trips.each do |trip| + unless trip.end_time == nil + ratings << trip.rating + end + end + + return (ratings.sum.to_f / ratings.length.to_f) + end + + def total_revenue + total_revenue = 0 + total_revenue = @trips.sum { + |trip| unless trip.cost == nil + trip.cost + end + } + return total_revenue + end + + def change_driver_status + if @status == :AVAILABLE + @status = :UNAVAILABLE + else + @status = :AVAILABLE + end + end + + private + + def self.from_csv(record) + return new( + id: record[:id], + name: record[:name], + vin: record[:vin], + status: record[:status].to_sym + ) + end + + end +end \ No newline at end of file diff --git a/lib/passenger.rb b/lib/passenger.rb index afd13d73b..6f1243204 100644 --- a/lib/passenger.rb +++ b/lib/passenger.rb @@ -4,7 +4,7 @@ module RideShare class Passenger < CsvRecord attr_reader :name, :phone_number, :trips - def initialize(id:, name:, phone_number:, trips: nil) + def initialize(id:, name:, phone_number:, trips: []) super(id) @name = name @@ -16,6 +16,25 @@ def add_trip(trip) @trips << trip end + def net_expenditures + total_cost = 0 + total_cost = @trips.sum { + |trip| unless trip.cost == nil + trip.cost + end + } + return total_cost + end + + def total_time_spent + total_time = @trips.sum { + |trip| unless trip.end_time == nil + trip.duration + end + } + return total_time + end + private def self.from_csv(record) diff --git a/lib/trip.rb b/lib/trip.rb index 87ce496e9..c5e155c72 100644 --- a/lib/trip.rb +++ b/lib/trip.rb @@ -1,15 +1,19 @@ require 'csv' require_relative 'csv_record' +require_relative 'driver' +require_relative 'passenger' 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, + driver_id: nil, + driver: nil, start_time:, end_time:, cost: nil, @@ -17,6 +21,17 @@ def initialize( ) super(id) + 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 passenger @passenger = passenger @passenger_id = passenger.id @@ -33,9 +48,18 @@ def initialize( @cost = cost @rating = rating - if @rating > 5 || @rating < 1 + valid_ratings = [1, 2, 3, 4, 5, nil] + + unless valid_ratings.include?(@rating) raise ArgumentError.new("Invalid rating #{@rating}") end + + if end_time != nil + if @end_time - @start_time < 0 + raise ArgumentError.new("End time: #{@end_time} cannot be less than start time: #{@start_time}") + end + end + end def inspect @@ -46,9 +70,15 @@ def inspect "PassengerID=#{passenger&.id.inspect}>" end - def connect(passenger) + def connect(passenger, driver) @passenger = passenger + @driver = driver passenger.add_trip(self) + driver.add_trip(self) + end + + def duration + return @end_time - @start_time end private @@ -57,8 +87,9 @@ def self.from_csv(record) return self.new( id: record[:id], passenger_id: record[:passenger_id], - start_time: record[:start_time], - end_time: record[:end_time], + driver_id: record[:driver_id], + start_time: Time.parse(record[:start_time]), + end_time: Time.parse(record[:end_time]), cost: record[:cost], rating: record[:rating] ) diff --git a/lib/trip_dispatcher.rb b/lib/trip_dispatcher.rb index 5130849f8..569a6d79f 100644 --- a/lib/trip_dispatcher.rb +++ b/lib/trip_dispatcher.rb @@ -3,6 +3,7 @@ require_relative 'passenger' require_relative 'trip' +require_relative 'driver' module RideShare class TripDispatcher @@ -11,6 +12,7 @@ class TripDispatcher def initialize(directory: './support') @passengers = Passenger.load_all(directory: directory) @trips = Trip.load_all(directory: directory) + @drivers = Driver.load_all(directory: directory) connect_trips end @@ -19,6 +21,11 @@ def find_passenger(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)} \ @@ -27,12 +34,56 @@ def inspect #{passengers.count} passengers>" end + def request_trip(passenger_id) + passenger = self.find_passenger(passenger_id) + trip_id = 601 + driver = find_driver_for_trip + + trip = RideShare::Trip.new( + id: trip_id, + passenger: passenger, + passenger_id: passenger_id, + start_time: Time.now, + end_time: nil, + cost: nil, + rating: nil, + driver_id: driver.id, + driver: driver + ) + driver.change_driver_status + driver.add_trip(trip) + passenger.add_trip(trip) + @trips << trip + trip_id += 1 + return trip + end + + def find_driver_for_trip + + available_drivers = self.drivers.select { |driver| driver.status == :AVAILABLE} + + if available_drivers.length == 0 + raise ArgumentError.new("There are no available drivers!") + end + + available_drivers.each do |driver| + if driver.trips.length == 0 + return driver + end + end + + available_drivers.sort! { |driver| driver.trips[-1].end_time} + + return available_drivers[-1] + end + private def connect_trips @trips.each do |trip| passenger = find_passenger(trip.passenger_id) - trip.connect(passenger) + driver = find_driver(trip.driver_id) + trip.connect(passenger, driver) end return trips diff --git a/test/driver_test.rb b/test/driver_test.rb index 4e6076ec2..75828c4eb 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( @@ -131,6 +131,40 @@ end describe "total_revenue" do - # You add tests for the total_revenue method + before do + @driver = RideShare::Driver.new( + id: 54, + name: "Rogers Bartell IV", + vin: "1C9EVBRM0YBC564DZ" + ) + + trip = RideShare::Trip.new( + id: 8, + driver: @driver, + passenger_id: 3, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 8), + cost: 2, + rating: 5 + ) + + trip2 = RideShare::Trip.new( + id: 8, + driver: @driver, + passenger_id: 3, + start_time: Time.new(2016, 8, 11), + end_time: Time.new(2016, 8, 12), + cost: 13, + rating: 5 + ) + + @driver.add_trip(trip) + @driver.add_trip(trip2) + end + + it "Will return the total revenue for the driver" do + # You add tests for the total_revenue method + expect (@driver.total_revenue).must_equal 15 + end end end diff --git a/test/passenger_test.rb b/test/passenger_test.rb index eb3a631df..109b53e12 100644 --- a/test/passenger_test.rb +++ b/test/passenger_test.rb @@ -49,7 +49,9 @@ passenger: @passenger, start_time: Time.new(2016, 8, 8), end_time: Time.new(2016, 8, 9), - rating: 5 + cost: 8.3, + rating: 5, + driver_id: 222 ) @passenger.add_trip(trip) @@ -68,7 +70,44 @@ end end - describe "net_expenditures" do + describe "passenger methods" 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", + ) + trip = RideShare::Trip.new( + id: 8, + passenger: @passenger, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 9), + cost: 8.3, + rating: 5, + driver_id: 222 + ) + trip_two = RideShare::Trip.new( + id: 8, + passenger: @passenger, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 9), + cost: 5.2, + rating: 5, + driver_id: 222 + ) + + @passenger.add_trip(trip) + @passenger.add_trip(trip_two) + end + # You add tests for the net_expenditures method + it "will return the total cost for all trips made by the passenger" do + expect(@passenger.net_expenditures).must_equal 13.5 + end + + it "will return the total number of seconds spent in rides by the passenger" do + expect(@passenger.total_time_spent).must_equal 172800 + end end end 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..72ee1de3f 100644 --- a/test/trip_dispatch_test.rb +++ b/test/trip_dispatch_test.rb @@ -79,7 +79,7 @@ def build_test_dispatcher end # TODO: un-skip for Wave 2 - xdescribe "drivers" do + describe "drivers" do describe "find_driver method" do before do @dispatcher = build_test_dispatcher @@ -121,5 +121,63 @@ def build_test_dispatcher end end end + + describe "Request Trip method tests" do + before do + @dispatcher = build_test_dispatcher + end + + it "will create the trip properly" do + expect(@dispatcher.request_trip(4)).must_be_instance_of RideShare::Trip + end + + it "will update the lists for the selected passenger and driver" do + passenger = @dispatcher.find_passenger(4) + @dispatcher.request_trip(4) + expect(passenger.trips.length).must_equal 2 + end + + it "will select an available driver" do + selected_driver = nil + + available_drivers = @dispatcher.drivers.select { |driver| driver.status == :AVAILABLE} + + available_drivers.each do |driver| + if driver.trips.length == 0 + selected_driver = driver + end + end + + if selected_driver == nil + available_drivers.sort! { |driver| driver.trips[-1].end_time} + selected_driver = available_drivers[-1] + end + + @dispatcher.request_trip(2) + + expect(@dispatcher.trips[-1].driver).must_equal selected_driver + end + + it "will return an error if there are no available drivers" do + + expect { + 3.times do |i| + @dispatcher.request_trip(i + 1) + end + }.must_raise ArgumentError + end + + it "will select the driver with no trips completed first" do + @dispatcher.request_trip(4) + expect (@dispatcher.trips[-1].driver_id).must_equal 3 + end + + it "will select the driver who completed a trip the longest ago if there are no drivers with zero trips completed" do + 2.times do |i| + @dispatcher.request_trip(i + 1) + end + expect (@dispatcher.trips[-1].driver_id).must_equal 2 + end + end end -end +end \ No newline at end of file diff --git a/test/trip_test.rb b/test/trip_test.rb index 2063e28a7..820956688 100644 --- a/test/trip_test.rb +++ b/test/trip_test.rb @@ -15,7 +15,8 @@ start_time: start_time, end_time: end_time, cost: 23.45, - rating: 3 + rating: 3, + driver_id: 222 } @trip = RideShare::Trip.new(@trip_data) end @@ -41,5 +42,49 @@ end.must_raise ArgumentError end end + + it "raises an error for an end time that is earlier than the start time" do + start_time = Time.parse("2010-10-31 12:00") + end_time = Time.parse("2010-10-31 11:00") + @trip_data = { + id: 8, + passenger: RideShare::Passenger.new( + id: 1, + name: "Ada", + phone_number: "412-432-7640" + ), + start_time: start_time, + end_time: end_time, + cost: 23.45, + rating: 3, + driver_id: 222 + } + expect{@trip = RideShare::Trip.new(@trip_data)}.must_raise ArgumentError + end + end + + describe "trip methods" do + + it "will return the duration of the trip in second with method .duration" do + start_time = Time.parse("2010-10-31 11:59") + end_time = Time.parse("2010-10-31 12:00") + trip_data = { + id: 8, + passenger: RideShare::Passenger.new( + id: 1, + name: "Ada", + phone_number: "412-432-7640" + ), + start_time: start_time, + end_time: end_time, + cost: 23.45, + rating: 3, + driver_id: 222 + } + + trip = RideShare::Trip.new(trip_data) + + expect(trip.duration).must_equal 60.0 + end end end