diff --git a/lib/driver.rb b/lib/driver.rb new file mode 100644 index 000000000..b50ebd303 --- /dev/null +++ b/lib/driver.rb @@ -0,0 +1,68 @@ +require 'csv' +require 'time' + +require_relative 'csv_record' + +module RideShare + class Driver < CsvRecord + attr_reader :id, :name, :vin, :status, :trips + + def initialize(id:, name:, vin:, status: :AVAILABLE, trips: []) + raise ArgumentError.new("Invalid VIN length") unless vin.length == 17 + raise ArgumentError.new("Invalid status") unless [:AVAILABLE, :UNAVAILABLE].include?(status.to_sym) + + super(id) + + @name = name + @vin = vin + @status = status.to_sym + @trips = trips + end + + def add_trip(trip) + @trips << trip + end + + def average_rating + all_ratings = @trips.map { |trip| trip.rating } + + if all_ratings.length == 0 + return 0 + else + average = all_ratings.compact.sum.to_f / all_ratings.compact.length + return average + end + + end + + def total_revenue + all_cost = @trips.map do |trip| + case trip.cost + when nil + nil + when (0...1.65) + 0 + else + (trip.cost - 1.65) * 0.8 + end + end + + return all_cost.compact.sum + end + + def make_unavailable + @status = :UNAVAILABLE + end + + private + + def self.from_csv(record) + return new( + id: record[:id], + name: record[:name], + vin: record[:vin], + status: record[:status] + ) + end + end +end diff --git a/lib/passenger.rb b/lib/passenger.rb index 12ec25982..2541a6a32 100644 --- a/lib/passenger.rb +++ b/lib/passenger.rb @@ -1,4 +1,5 @@ require_relative 'csv_record' +#require_relative 'trip' module RideShare class Passenger < CsvRecord @@ -16,6 +17,17 @@ def add_trip(trip) @trips << trip end + def net_expenditures + all_cost = @trips.map { |trip| trip.cost} + return all_cost.compact.sum + end + + def total_time_spent + all_times = @trips.map {|trip| trip.duration} + return all_times.compact.sum + end + + private def self.from_csv(record) diff --git a/lib/trip.rb b/lib/trip.rb index f59464dae..c9a681e83 100644 --- a/lib/trip.rb +++ b/lib/trip.rb @@ -1,22 +1,37 @@ require 'csv' +require 'time' require_relative 'csv_record' +require_relative 'driver' module RideShare class Trip < CsvRecord - attr_reader :id, :passenger, :passenger_id, :start_time, :end_time, :cost, :rating + attr_reader :id, :driver, :driver_id, :passenger, :passenger_id, :start_time, :end_time, :cost, :rating def initialize( id:, + driver: nil, + driver_id: nil, passenger: nil, passenger_id: nil, start_time:, - end_time:, + end_time: nil, cost: nil, - rating: + rating: nil ) 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,8 +48,16 @@ def initialize( @cost = cost @rating = rating - if @rating > 5 || @rating < 1 - raise ArgumentError.new("Invalid rating #{@rating}") + unless @rating == nil + if @rating > 5 || @rating < 1 + raise ArgumentError.new("Invalid rating #{@rating}") + end + end + + unless end_time == nil + if end_time < start_time + raise ArgumentError.new("End time cannot be before start time.") + end end end @@ -43,6 +66,7 @@ def inspect # trip contains a passenger contains a trip contains a passenger... "#<#{self.class.name}:0x#{self.object_id.to_s(16)} " + "id=#{id.inspect} " + + "driver_id=#{driver&.id.inspect} " + "passenger_id=#{passenger&.id.inspect} " + "start_time=#{start_time} " + "end_time=#{end_time} " + @@ -50,9 +74,15 @@ def inspect "rating=#{rating}>" end - def connect(passenger) + def connect(passenger, driver) @passenger = passenger + @driver = driver passenger.add_trip(self) + driver.add_trip(self) + end + + def duration + @end_time == nil ? nil : (@end_time - @start_time) end private @@ -60,9 +90,10 @@ def connect(passenger) def self.from_csv(record) return self.new( id: record[:id], + driver_id: record[:driver_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] ) diff --git a/lib/trip_dispatcher.rb b/lib/trip_dispatcher.rb index 5130849f8..2af3e73d0 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,50 @@ def inspect #{passengers.count} passengers>" end + def available_driver + available_drivers = [] + @drivers.each do |driver| + if driver.status == :AVAILABLE + available_drivers << driver + end + end + return available_drivers + end + + def trip_id_generator + return @trips.last.id.to_i + 1 + end + + def request_trip(passenger_id) + driver = available_driver[0] + passenger = find_passenger(passenger_id) + + if driver == nil + raise ArgumentError.new("No available drivers") + end + + new_trip = Trip.new( + id: trip_id_generator, + driver_id: driver.id, + passenger_id: passenger_id, + start_time: Time.now, + ) + driver.make_unavailable + + new_trip.connect(passenger, driver) + + @trips << new_trip + + return new_trip + 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..6d0517f06 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( @@ -128,9 +128,70 @@ expect(@driver.average_rating).must_be_close_to (5.0 + 1.0) / 2.0, 0.01 end + + it"ignores trips in progress" do + trip = RideShare::Trip.new( + id: 8, + driver: @driver, + passenger_id: 3, + start_time: Time.now, + end_time: nil, + rating: nil + ) + @driver.add_trip(trip) + expect(@driver.average_rating).must_equal 5 + end 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: 11.65, + rating: 5 + ) + @driver.add_trip(trip) + end + + it "calculates accurate revenue" do + expect(@driver.total_revenue).must_be_close_to 8.0 + end + + it "returns 0 for trips less than 1.65" do + trip = RideShare::Trip.new( + id:29, + driver: @driver, + passenger_id: 94, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 8), + cost: 1.29, + rating: 4 + ) + expect(@driver.total_revenue).must_be_close_to 8 + end + + it "ignores trips in progress" do + trip = RideShare::Trip.new( + id: 9, + driver: @driver, + passenger_id: 2, + start_time: Time.new(2016, 8, 8), + end_time: nil, + cost: nil, + rating: nil + ) + @driver.add_trip(trip) + expect(@driver.total_revenue).must_equal 8.0 + end end end diff --git a/test/passenger_test.rb b/test/passenger_test.rb index eb3a631df..faf6e5c39 100644 --- a/test/passenger_test.rb +++ b/test/passenger_test.rb @@ -46,6 +46,7 @@ ) trip = RideShare::Trip.new( id: 8, + driver_id: 1, passenger: @passenger, start_time: Time.new(2016, 8, 8), end_time: Time.new(2016, 8, 9), @@ -69,6 +70,120 @@ end describe "net_expenditures" do - # You add tests for the net_expenditures method + before do + @passenger = RideShare::Passenger.new( + id: 9, + name: "Merl Glover III", + phone_number: "1-602-620-2330 x3723", + trips: [] + ) + trip = RideShare::Trip.new( + id: 8, + driver_id: 2, + passenger: @passenger, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 9), + cost: 24.5, + rating: 5 + ) + @passenger.add_trip(trip) + + end + + it "returns a numeric" do + expect(@passenger.net_expenditures).must_be_kind_of Numeric + end + + it "returns accurate net expenditure" do + trip = RideShare::Trip.new( + id: 8, + driver_id: 3, + passenger: @passenger, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 9), + cost: 39, + rating: 5 + ) + @passenger.add_trip(trip) + expect(@passenger.net_expenditures).must_equal 63.5 + end + + it "ignores any trips in progress" do + trip = RideShare::Trip.new( + id: 8, + driver_id: 3, + passenger: @passenger, + start_time: Time.new(2016, 8, 8), + end_time: nil, + cost: nil, + rating: nil + ) + @passenger.add_trip(trip) + expect(@passenger.net_expenditures).must_equal 24.5 + end + + it "returns 0 for nil trips" do + @passenger = RideShare::Passenger.new( + id: 12, + name: "Merl Glover IV", + phone_number: "1-602-620-2330 x3723", + trips: [] + ) + expect(@passenger.net_expenditures).must_equal 0 + end end + + describe "total_time_spent" do + before do + @passenger = RideShare::Passenger.new( + id: 9, + name: "Merl Glover III", + phone_number: "1-602-620-2330 x3723", + trips: [] + ) + trip = RideShare::Trip.new( + id: 8, + driver_id: 3, + passenger: @passenger, + start_time: Time.new(2016, 8, 8, 12), + end_time: Time.new(2016, 8, 8, 12, 10, 30), + cost: 24.5, + rating: 5 + ) + @passenger.add_trip(trip) + end + + it "returns a float" do + expect(@passenger.total_time_spent).must_be_instance_of Float + end + + it "returns accurate result" do + expect(@passenger.total_time_spent).must_equal 630 + end + + it "returns 0 for nil trips" do + @passenger = RideShare::Passenger.new( + id: 12, + name: "Merl Glover IV", + phone_number: "1-602-620-2330 x3723", + trips: [] + ) + expect(@passenger.total_time_spent).must_equal 0 + end + + it "ignores trips in progress" do + trip = RideShare::Trip.new( + id: 11, + driver_id: 9, + passenger: @passenger, + start_time: Time.new(2016, 8, 8, 12), + end_time: nil, + cost: nil, + rating: nil + ) + @passenger.add_trip(trip) + expect(@passenger.total_time_spent).must_equal 630 + 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..f2aac7cac 100644 --- a/test/trip_dispatch_test.rb +++ b/test/trip_dispatch_test.rb @@ -78,8 +78,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 @@ -121,5 +120,54 @@ def build_test_dispatcher end end end + + describe "request trip" do + before do + @dispatcher = build_test_dispatcher + end + it "returns an instance of Trip" do + request = @dispatcher.request_trip(3) + expect(request).must_be_instance_of RideShare::Trip + end + + it "updates passenger trip list" do + passenger_trip_initial = @dispatcher.passengers.first.trips.length + + @dispatcher.request_trip(1) + passenger_trip_final = @dispatcher.passengers.first.trips.length + + expect(passenger_trip_final).must_equal passenger_trip_initial + 1 + end + + it "updates driver trip list" do + driver_trip_initial = @dispatcher.available_driver.first.trips.length + + new_trip = @dispatcher.request_trip(1) + driver_trip_final = new_trip.driver.trips.length + + expect(driver_trip_final).must_equal driver_trip_initial + 1 + end + + it "uses an available driver and makes unavailable" do + driver_before_request = @dispatcher.available_driver.first + + expect(driver_before_request.status).must_equal :AVAILABLE + + new_trip = @dispatcher.request_trip(4) + driver_after_request = new_trip.driver + + expect(driver_after_request.id).must_equal driver_before_request.id + expect(driver_after_request.status).must_equal :UNAVAILABLE + end + + it "will raise Argument Error when no drivers available" do + @dispatcher.request_trip(4) + @dispatcher.request_trip(4) + + expect { @dispatcher.request_trip(4) }.must_raise ArgumentError + + end + + end end end diff --git a/test/trip_test.rb b/test/trip_test.rb index 2063e28a7..8b5de48e1 100644 --- a/test/trip_test.rb +++ b/test/trip_test.rb @@ -7,6 +7,12 @@ end_time = start_time + 25 * 60 # 25 minutes @trip_data = { id: 8, + driver: RideShare::Driver.new( + id: 31, + name: "Mackenzie", + vin: '12345678901234567', + status: :AVAILABLE + ), passenger: RideShare::Passenger.new( id: 1, name: "Ada", @@ -29,10 +35,13 @@ end it "stores an instance of driver" do - skip # Unskip after wave 2 expect(@trip.driver).must_be_kind_of RideShare::Driver end + it "stores end_time as an instance of Time" do + expect(@trip.end_time).must_be_instance_of Time + end + it "raises an error for an invalid rating" do [-3, 0, 6].each do |rating| @trip_data[:rating] = rating @@ -41,5 +50,36 @@ end.must_raise ArgumentError end end + + it "raises an error for end times before start times" do + @trip_data[:end_time] = @trip_data[:start_time] - 25 * 60 + expect do + RideShare::Trip.new(@trip_data) + end.must_raise ArgumentError + end + end + + describe "duration" do + before do + start_time = Time.now + end_time = start_time + 10 * 60 # 10 minutes + @trip_data = { + id: 8, + driver_id: 4, + 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 + } + @trip = RideShare::Trip.new(@trip_data) + end + it "returns accurate number of seconds" do + expect(@trip.duration).must_equal 600 + end end end