Skip to content

Commit 8498f38

Browse files
Refactor
1 parent b8b8858 commit 8498f38

11 files changed

+117
-68
lines changed

app/controllers/api/v1/upcoming_rides_controller.rb

+1-7
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,7 @@
44
module Api
55
# API::V1 module for version 1 of the API
66
module V1
7-
# UpcomingRidesController for the API
8-
# This controller is responsible for returning upcoming rides for a driver
9-
# It includes the UpcomingRides module
10-
# It includes the Pagy::Backend module for pagination
11-
# It inherits from ApplicationController
12-
# It has an index method
13-
# It has a private driver_params method
7+
# This controller is responsible for returning the upcoming rides for a driver
148
class UpcomingRidesController < ApplicationController
159
include UpcomingRides
1610
include Pagy::Backend

app/models/address.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
# Adress model is used to store the address of the address.
3+
# Adress model is used to store valid formatted addresses.
44
class Address < ApplicationRecord
55
has_many :home_addresses, class_name: "Driver", foreign_key: "home_address_id"
66
has_many :start_addresses, class_name: "Ride", foreign_key: "start_address_id"

app/models/driver.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
# Driver model is used to store the driver's name and address.
3+
# Driver model is used to store the driver's address.
44
class Driver < ApplicationRecord
55
belongs_to :home_address, class_name: "Address"
66
end

app/models/ride.rb

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
# A ride has a start address and a destination address.
4+
# The destination address must be different from the start address.
45
class Ride < ApplicationRecord
56
belongs_to :start_address, class_name: "Address"
67
belongs_to :destination_address, class_name: "Address"

app/services/google_directions_api_client.rb

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
# frozen_string_literal: true
22

3+
# DirectionsAPIError is a custom error class that is raised when there is an error fetching
4+
# directions from the Google Directions API
35
class DirectionsAPIError < StandardError; end
46

7+
# GoogleDirectionsApiClient is a service class that fetches directions from the Google Directions
8+
# API
59
class GoogleDirectionsApiClient
610
BASE_URL = "https://maps.googleapis.com/maps/api/directions/json"
711

812
METERS_IN_A_MILE = 1609.34
9-
SECONDS_IN_A_MINUTE = 60
13+
SECONDS_IN_A_MINUTE = 60.0
14+
15+
GOOGLE_DIRECTIONS_API_ERROR = "Google Directions API Error"
1016

1117
def initialize(api_key)
1218
@api_key = api_key
@@ -17,7 +23,7 @@ def get_directions(origin, destination)
1723

1824
response = send_request(url)
1925

20-
check_response(response)
26+
check_response!(response)
2127

2228
parse_response(response)
2329
end
@@ -34,9 +40,9 @@ def send_request(url)
3440
raise DirectionsAPIError, "Unable to fetch directions. Please try again."
3541
end
3642

37-
def check_response(response)
38-
raise DirectionsAPIError, "Not OK" unless response["status"] == "OK"
39-
raise DirectionsAPIError, "Google Directioons API error" if response["error_message"].present?
43+
def check_response!(response)
44+
raise DirectionsAPIError, GOOGLE_DIRECTIONS_API_ERROR unless response["status"] == "OK"
45+
raise DirectionsAPIError, GOOGLE_DIRECTIONS_API_ERROR if response["error_message"].present?
4046
end
4147

4248
def parse_response(response)

db/seeds.rb

+15-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@
1818
Driver.find_or_create_by!(id: "e76885d9-dc50-4616-830e-cd24beefd7d9",
1919
home_address: Address.all.sample)
2020

21-
100.times do
22-
Ride.find_or_create_by(start_address: Address.all.sample, destination_address: Address.all.sample)
23-
end
21+
Ride.find_or_create_by(start_address: Address.first, destination_address: Address.second)
22+
Ride.find_or_create_by(start_address: Address.first, destination_address: Address.third)
23+
Ride.find_or_create_by(start_address: Address.first, destination_address: Address.fourth)
24+
25+
Ride.find_or_create_by(start_address: Address.second, destination_address: Address.first)
26+
Ride.find_or_create_by(start_address: Address.second, destination_address: Address.third)
27+
Ride.find_or_create_by(start_address: Address.second, destination_address: Address.fourth)
28+
29+
Ride.find_or_create_by(start_address: Address.third, destination_address: Address.first)
30+
Ride.find_or_create_by(start_address: Address.third, destination_address: Address.second)
31+
Ride.find_or_create_by(start_address: Address.third, destination_address: Address.fourth)
32+
33+
Ride.find_or_create_by(start_address: Address.fourth, destination_address: Address.first)
34+
Ride.find_or_create_by(start_address: Address.fourth, destination_address: Address.second)
35+
Ride.find_or_create_by(start_address: Address.fourth, destination_address: Address.third)

lib/upcoming_rides.rb

+14-12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
# Description: This module is used to get the upcoming rides from the database.
44
module UpcomingRides
5+
# The CACHE_EXPIRATION constant is used to set the cache expiration time for the ride's
6+
# directions.
7+
CACHE_EXPIRATION = 5.0.minutes
8+
59
# The upcoming_rides method takes in a driver and returns the upcoming rides from the database.
610
# It uses the Ride.where method to get the upcoming rides from the database.
711
# It uses the map method to map the rides to a new array with the ride's attributes and additional
@@ -36,7 +40,8 @@ def sort_upcoming_rides(upcoming_rides)
3640
def get_directions(start_address, destination_address)
3741
cache_key = "ride/#{start_address.id}-#{destination_address.id}/directions"
3842

39-
Rails.cache.fetch(cache_key, expires_in: 5.minutes) do # Look to see if can call fetch_all
43+
Rails.cache.fetch(cache_key, expires_in: CACHE_EXPIRATION) do
44+
# Consider dependency injection
4045
google_directions_api_client = GoogleDirectionsApiClient.new(
4146
Rails.application.credentials.google_api_key
4247
)
@@ -50,44 +55,41 @@ def get_directions(start_address, destination_address)
5055
# ride's attributes and additional attributes such as the driver's home address, the commute
5156
# distance and duration, the ride distance and duration, the ride earnings, and the score.
5257
def add_ride_attributes(driver, ride)
53-
# Rename data (to ride_data?) - testable
54-
data = data(driver.home_address, ride.start_address, ride.destination_address)
58+
data = get_data(driver.home_address, ride.start_address, ride.destination_address)
5559

5660
ride.attributes.merge(data)
5761
end
5862

59-
# Add unit tests
6063
def ride_earnings(ride_distance, ride_duration)
6164
# The ride earnings is how much the driver earns by driving the ride. It takes into account
6265
# both the amount of time the ride is expected to take and the distance. For the purposes of
6366
# this exercise, it is calculated as:
6467
# $12 + $1.50 per mile beyond 5 miles + (ride duration) * $0.70 per minute beyond 15 minutes
6568

66-
12 + ([ride_distance - 5, 0].max * 1.5) + ([ride_duration - 15, 0].max * 0.7)
69+
12.0 + ([ride_distance - 5.0, 0.0].max * 1.5) + ([ride_duration - 15.0, 0.0].max * 0.7)
6770
end
6871

6972
# The score is a metric that takes into account the ride earnings, the commute duration, and the
7073
# ride duration. It is calculated as the ride earnings divided by the sum of the commute duration
7174
# and the ride duration.
72-
# Add unit test
7375
def score(ride_earnings, commute_duration, ride_duration)
7476
ride_earnings / (commute_duration + ride_duration)
7577
end
7678

77-
# The data method takes in the driver's home address, the start address, and the destination
79+
# The get_data method takes in the driver's home address, the start address, and the destination
7880
# address, and returns a hash with the commute distance and duration, the ride distance and
7981
# duration, the ride earnings, and the score.
8082
# It uses the get_directions method to get the commute distance and duration and the ride distance
8183
# and duration, and the ride_earnings method to get the ride earnings.
8284
# It uses the score method to get the score.
8385
# It returns the hash with the data.
84-
# The data method is used by the add_ride_attributes method to add the ride's attributes and
86+
# The get_data method is used by the add_ride_attributes method to add the ride's attributes and
8587
# additional attributes to the ride.
86-
# The data method is also used by the upcoming_rides method to get the upcoming rides from the
88+
# The get_data method is also used by the upcoming_rides method to get the upcoming rides from the
8789
# database.
88-
# The data method is also used by the add_ride_attributes method to add the ride's attributes and
89-
# additional attributes to the ride.
90-
def data(home_address, start_address, destination_address)
90+
# The get_data method is also used by the add_ride_attributes method to add the ride's attributes
91+
# and additional attributes to the ride.
92+
def get_data(home_address, start_address, destination_address)
9193
commute_distance, commute_duration = get_directions(home_address, start_address)
9294
ride_distance, ride_duration = get_directions(start_address, destination_address)
9395
ride_earnings = ride_earnings(ride_distance, ride_duration)

spec/lib/upcoming_rides_spec.rb

+33-37
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
# frozen_string_literal: true
22

33
require "rails_helper"
4-
require_relative "../../lib/upcoming_rides"
5-
6-
# Create a class to include the UpcomingRides module for testing
7-
class UpcomingRidesForTesting
8-
include UpcomingRides
9-
end
104

115
RSpec.describe UpcomingRides do
126
include described_class
@@ -16,6 +10,7 @@ class UpcomingRidesForTesting
1610
instance_double(Ride, start_address: Faker::Address.street_address,
1711
destination_address: Faker::Address.street_address)
1812
end
13+
1914
let(:headers) do
2015
{
2116
"Accept" => "*/*",
@@ -40,11 +35,11 @@ class UpcomingRidesForTesting
4035
end
4136

4237
describe '#sort_upcoming_rides' do
43-
xit 'sorts the upcoming rides by the score in descending order' do
38+
it 'sorts the upcoming rides by the score in descending order' do
4439
# Create some sample rides with scores
45-
ride1 = instance_double(Ride, score: 5)
46-
ride2 = instance_double(Ride, score: 7)
47-
ride3 = instance_double(Ride, score: 3)
40+
ride1 = { score: 5 }
41+
ride2 = { score: 7 }
42+
ride3 = { score: 3 }
4843

4944
# Expect the sort_upcoming_rides method to return the rides sorted by score
5045
expect(sort_upcoming_rides([ride1, ride2, ride3])).to eq([ride2, ride1, ride3])
@@ -108,18 +103,18 @@ class UpcomingRidesForTesting
108103

109104
describe '#add_ride_attributes' do
110105
it 'returns a hash with ride attributes and additional attributes' do
111-
# Stub the data method to return sample data
112-
allow(self).to receive(:data).and_return({
113-
home_address: '123 Main St',
114-
start_address: '456 Elm St',
115-
commute_distance: 5.0,
116-
commute_duration: 10.0,
117-
destination_address: '789 Oak St',
118-
ride_distance: 8.0,
119-
ride_duration: 15.0,
120-
ride_earnings: 20.0,
121-
score: 0.5
122-
})
106+
# Stub the get_data method to return sample data
107+
allow(self).to receive(:get_data).and_return({
108+
home_address: '123 Main St',
109+
start_address: '456 Elm St',
110+
commute_distance: 5.0,
111+
commute_duration: 10.0,
112+
destination_address: '789 Oak St',
113+
ride_distance: 8.0,
114+
ride_duration: 15.0,
115+
ride_earnings: 20.0,
116+
score: 0.5
117+
})
123118

124119
# Stub the ride attributes to be merged
125120
allow(ride).to receive(:attributes).and_return({ id: 1, name: 'Ride 1' })
@@ -163,7 +158,7 @@ class UpcomingRidesForTesting
163158
end
164159
end
165160

166-
describe '#data' do
161+
describe '#get_data' do
167162
it 'returns a hash with data including commute distance, duration, ride earnings, and score' do
168163
home_address = instance_double(Address, id: Faker::Internet.uuid,
169164
address: "1588 E Thompson Blvd")
@@ -172,6 +167,9 @@ class UpcomingRidesForTesting
172167
destination_address = instance_double(Address, id: Faker::Internet.uuid,
173168
address: "2112 E Thompson Blvd")
174169

170+
# expect_any_instance_of(GoogleDirectionsApiClient).to receive(:get_directions).with(home_address.address, start_address.address).and_return([5.0, 10.0])
171+
# expect_any_instance_of(GoogleDirectionsApiClient).to receive(:get_directions).with(start_address.address, destination_address.address).and_return([5.0, 10.0])
172+
175173
stub_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=1588 E Thompson Blvd&destination=2112 E Thompson Blvd&key=#{Rails.application.credentials.google_api_key}")
176174
.with(
177175
headers: {
@@ -232,20 +230,18 @@ class UpcomingRidesForTesting
232230
"status" : "OK"
233231
}', headers: {})
234232

235-
puts data(home_address, start_address, destination_address).inspect
236-
237-
# Expect the data method to return a hash with the correct data
238-
expect(data(home_address, start_address, destination_address)).to eq({
239-
home_address: home_address,
240-
start_address: start_address,
241-
commute_distance: 5.0,
242-
commute_duration: 10.0,
243-
destination_address: destination_address,
244-
ride_distance: 5.0,
245-
ride_duration: 10.0,
246-
ride_earnings: 12.0,
247-
score: 0.6
248-
})
233+
# Expect the get_data method to return a hash with the correct data
234+
expect(get_data(home_address, start_address, destination_address)).to eq({
235+
home_address: home_address,
236+
start_address: start_address,
237+
commute_distance: 5.0,
238+
commute_duration: 10.0,
239+
destination_address: destination_address,
240+
ride_distance: 5.0,
241+
ride_duration: 10.0,
242+
ride_earnings: 12.0,
243+
score: 0.6
244+
})
249245
end
250246
end
251247
end

spec/models/driver_spec.rb

+13
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,17 @@
99
expect(association.macro).to eq(:belongs_to)
1010
end
1111
end
12+
13+
describe "validations" do
14+
let(:driver) { described_class.new(home_address: build(:address)) }
15+
16+
it "is valid with valid attributes" do
17+
expect(driver.valid?).to be true
18+
end
19+
20+
it "is not valid without a home_address" do
21+
driver.home_address = nil
22+
expect(driver.valid?).not_to be true
23+
end
24+
end
1225
end

spec/models/ride_spec.rb

+25
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,29 @@
1616
expect(association.options[:class_name]).to eq("Address")
1717
end
1818
end
19+
20+
describe "validations" do
21+
let(:ride) do
22+
described_class.new(start_address: build(:address), destination_address: build(:address))
23+
end
24+
25+
it "is valid with valid attributes" do
26+
expect(ride.valid?).to be true
27+
end
28+
29+
it "is not valid without a start_address" do
30+
ride.start_address = nil
31+
expect(ride.valid?).not_to be true
32+
end
33+
34+
it "is not valid without a destination_address" do
35+
ride.destination_address = nil
36+
expect(ride.valid?).not_to be true
37+
end
38+
39+
it "is not valid if the destination_address is the same as the start_address" do
40+
ride.destination_address = ride.start_address
41+
expect(ride.valid?).not_to be true
42+
end
43+
end
1944
end

spec/services/google_directions_api_client_spec.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
RSpec.describe GoogleDirectionsApiClient do
66
subject(:google_directions_api_client) do
7-
described_class.new(Rails.application.credentials.google_api_key)
7+
described_class.new("GOOGLE_DIRECTIONS_API_KEY")
88
end
99

1010
let(:headers) do
@@ -17,7 +17,7 @@
1717

1818
describe '#get_directions' do
1919
it 'returns the distance and duration in miles and minutes' do
20-
stub_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=1588%20E%20Thompson%20Blvd&destination=2112%20E%20Thompson%20Blvd&key=#{Rails.application.credentials.google_api_key}")
20+
stub_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=1588%20E%20Thompson%20Blvd&destination=2112%20E%20Thompson%20Blvd&key=GOOGLE_DIRECTIONS_API_KEY")
2121
.with(
2222
headers: headers
2323
)

0 commit comments

Comments
 (0)