Skip to content

Commit

Permalink
Add functionality to get route duration for commuting the rides from …
Browse files Browse the repository at this point in the history
…address
  • Loading branch information
mikeheft committed Jul 4, 2024
1 parent 72ebebd commit 13afe16
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 4 deletions.
6 changes: 5 additions & 1 deletion app/models/driver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ class Driver < ApplicationRecord
class_name: "DriverAddress",
dependent: :destroy,
inverse_of: :driver
has_one :current_address, through: :current_driver_address, source: :driver, dependent: :destroy
has_one :current_address, through: :current_driver_address, source: :address, dependent: :destroy

validates :first_name, :last_name, presence: true

def origin_place_id
current_address.place_id
end
end
25 changes: 25 additions & 0 deletions lib/rides/commands/get_commute_duration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Rides
module Commands
# Computes the duration of the commute for the ride.
# This is used in the ranking for the rides that the driver will
# choose from.
# Returns a list of objects where the origin is the driver's current address(home)
class GetCommuteDuration < BaseCommand
def call(rides:, driver:)
commute_rides = convert_rides(rides, driver)
GetRoutesData.call(rides: commute_rides)
end

# Converts the Driver's current home address and
# the Ride#from_address into structs that can be used to
# obtain the route information
private def convert_rides(rides, driver)
rides.each_with_object([]) do |ride, acc|
acc << OpenStruct.new(origin_place_id: driver.origin_place_id, destination_place_id: ride.origin_place_id)
end
end
end
end
end
66 changes: 66 additions & 0 deletions lib/rides/commands/get_routes_data.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

module Rides
module Commands
# Makes a request to the Google API to obtain the route information
class GetRoutesData < BaseCommand
DIRECTIONS_API_URL = "https://routes.googleapis.com/distanceMatrix/v2:computeRouteMatrix"
DEFAULT_HEADERS = {
"X-Goog-FieldMask" => "originIndex,destinationIndex,status,condition,distanceMeters,duration",
"X-goog-api-key" => ENV["GOOGLE_API_KEY"],
"Content-Type" => "application/json"
}.freeze

def call(rides:)
data = get_direction_data_for_ride(rides)
results(data, rides)
end

# Returns a list of objects, with attributes of
# @param[:distance_in_meters] = Integer
# @param[:duration] = String, e.g., "577s"
# Duration is in seconds
private def results(data, rides)
# The response keeps the array positioning on the return. Since we're getting a matrix
# of routes back, we only want the ones where we explicitly have a 'Ride'. This means that
# we want the computations where the indicies match.
data = data.select { _1[:originIndex] == _1[:destinationIndex] }
data = transform_keys!(data)

data.map.with_index { OpenStruct.new(ride: rides[_2], **_1) }
end

private def connection
@connection ||= Client::Request.connection(
url: DIRECTIONS_API_URL,
headers: DEFAULT_HEADERS
)
end

private def get_direction_data_for_ride(rides)
body = build_request_body(rides)

response = connection.post(
DIRECTIONS_API_URL,
body.merge(routingPreference: "TRAFFIC_AWARE", travelMode: "DRIVE")
)

JSON.parse(response.body, symbolize_names: true)
end

private def build_request_body(rides)
rides.each_with_object({}) do |ride, acc|
acc[:origins] ||= []
acc[:destinations] ||= []

acc[:origins] << { waypoint: { placeId: ride.origin_place_id } }
acc[:destinations] << { waypoint: { placeId: ride.destination_place_id } }
end
end

private def transform_keys!(data)
data.map { |d| d.transform_keys { |k| k.to_s.underscore.to_sym } }
end
end
end
end
2 changes: 2 additions & 0 deletions spec/factories/drivers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
factory :driver do
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }

association :current_address, factory: :address
end
end
23 changes: 23 additions & 0 deletions spec/lib/rides/commands/get_commute_duration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

RSpec.describe Rides::Commands::GetCommuteDuration do
it "gets data for an address" do
VCR.use_cassette("first_commute") do
from_address = create(
:address, :with_out_place_id, line_1: "4705 Weitzel Street", city: "Timnath", state: "CO",
zip_code: "80547"
)
to_address = create(
:address, :with_out_place_id, line_1: "151 N College Ave", city: "Fort Collins", state: "CO",
zip_code: "80524"
)
create_list(:ride, 2, from_address:, to_address:)
driver = create(:driver, current_address: from_address)
rides = Ride.selectable
data = described_class.call(rides:, driver:)

expect(data.length).to eq(2)
expect(data.all? { _1.duration == "577s" })
end
end
end
22 changes: 22 additions & 0 deletions spec/lib/rides/commands/get_routes_data_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

RSpec.describe Rides::Commands::GetRoutesData do
it "gets data for an address" do
VCR.use_cassette("first_ride_directions") do
from_address = create(
:address, :with_out_place_id, line_1: "711 Oval Drive", city: "Fort Collins", state: "CO",
zip_code: "80521"
)
to_address = create(
:address, :with_out_place_id, line_1: "151 N College Ave", city: "Fort Collins", state: "CO",
zip_code: "80524"
)
create_list(:ride, 2, from_address:, to_address:)
rides = Ride.selectable
data = described_class.call(rides:)

expect(data.length).to eq(2)
expect(data.all? { _1.distance_in_meters == 3105 && _1.duration == "577s" })
end
end
end
7 changes: 4 additions & 3 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
VCR.configure do |config|
config.cassette_library_dir = "spec/cassettes"
config.hook_into :webmock
config.default_cassette_options = {
match_requests_on: %i[method uri]
}
config.debug_logger = File.open("log/vcr_debug.log", "w")
# config.default_cassette_options = {
# match_requests_on: %i[method uri]
# }
end
# Add additional requires below this line. Rails is not loaded until this point!

Expand Down

0 comments on commit 13afe16

Please sign in to comment.