Skip to content

Commit 5150094

Browse files
committed
Add LocationPositionUpdater
This adds a class which handles updating the position of a location by fetching the information from the Ordnance Survey Places API. Jira-Issue: MAV-6379
1 parent 737570f commit 5150094

3 files changed

Lines changed: 188 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# frozen_string_literal: true
2+
3+
##
4+
# This class fetches the latitude and longitude of a location's address
5+
# using the +OrdnanceSurvey::PlacesAPI+ and stores it in the position column.
6+
class LocationPositionUpdater
7+
class MissingAddress < StandardError
8+
end
9+
10+
class NoResults < StandardError
11+
end
12+
13+
def initialize(location)
14+
@location = location
15+
end
16+
17+
attr_reader :location
18+
19+
def call
20+
raise MissingAddress unless location.has_address?
21+
22+
location.update!(position:)
23+
end
24+
25+
def self.call(...) = new(...).call
26+
27+
private_class_method :new
28+
29+
private
30+
31+
def full_address = location.address_parts.join(", ")
32+
33+
def position
34+
response = OrdnanceSurvey::PlacesAPI.find(full_address)
35+
36+
results = response[:results]
37+
raise NoResults if results.blank?
38+
39+
first_result = results.first
40+
41+
latitude = first_result.dig(:dpa, :lat)
42+
longitude = first_result.dig(:dpa, :lng)
43+
44+
raise NoResults if latitude.blank? || longitude.blank?
45+
46+
"POINT(#{longitude} #{latitude})"
47+
end
48+
end

spec/factories/locations.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@
5858
end
5959
end
6060

61+
trait :without_address do
62+
address_line_1 { nil }
63+
address_line_2 { nil }
64+
address_town { nil }
65+
address_postcode { nil }
66+
position { nil }
67+
end
68+
6169
factory :community_clinic do
6270
type { :community_clinic }
6371
name { "#{Faker::University.name} Clinic" }
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# frozen_string_literal: true
2+
3+
describe LocationPositionUpdater do
4+
describe "#call" do
5+
subject(:call) { described_class.call(location) }
6+
7+
let(:location) do
8+
create(
9+
:community_clinic,
10+
address_line_1: "1 High Street",
11+
address_town: "London",
12+
address_postcode: "SW1A 1AA",
13+
position: nil
14+
)
15+
end
16+
17+
context "when location has no address" do
18+
let(:location) { create(:community_clinic, :without_address) }
19+
20+
it "raises an error" do
21+
expect { call }.to raise_error(LocationPositionUpdater::MissingAddress)
22+
end
23+
24+
it "does not call the API" do
25+
expect(OrdnanceSurvey::PlacesAPI).not_to receive(:find)
26+
expect { call }.to raise_error(LocationPositionUpdater::MissingAddress)
27+
end
28+
29+
it "does not update the position" do
30+
expect { call }.to raise_error(
31+
LocationPositionUpdater::MissingAddress
32+
).and not_change(location, :position)
33+
end
34+
end
35+
36+
context "when API returns coordinates" do
37+
let(:response) do
38+
{
39+
header: {
40+
total_results: 1
41+
},
42+
results: [{ dpa: { lat: 51.5074, lng: -0.1278 } }]
43+
}
44+
end
45+
46+
before do
47+
allow(OrdnanceSurvey::PlacesAPI).to receive(:find).and_return(response)
48+
end
49+
50+
it "calls the API with the address" do
51+
expect(OrdnanceSurvey::PlacesAPI).to receive(:find).with(
52+
"1 High Street, London, SW1A 1AA"
53+
)
54+
call
55+
end
56+
57+
it "updates the location's position" do
58+
expect { call }.to change(location, :position).from(nil).to(
59+
an_instance_of(RGeo::Geographic::SphericalPointImpl)
60+
)
61+
expect(location.position.x).to eq(-0.1278)
62+
expect(location.position.y).to eq(51.5074)
63+
end
64+
end
65+
66+
context "when API returns no results" do
67+
let(:response) { { header: { total_results: 0 }, results: [] } }
68+
69+
before do
70+
allow(OrdnanceSurvey::PlacesAPI).to receive(:find).and_return(response)
71+
end
72+
73+
it "raises an error" do
74+
expect { call }.to raise_error(LocationPositionUpdater::NoResults)
75+
end
76+
end
77+
78+
context "when API returns a result without coordinates" do
79+
let(:response) do
80+
{ header: { total_results: 1 }, results: [{ dpa: {} }] }
81+
end
82+
83+
before do
84+
allow(OrdnanceSurvey::PlacesAPI).to receive(:find).and_return(response)
85+
end
86+
87+
it "raises an error" do
88+
expect { call }.to raise_error(LocationPositionUpdater::NoResults)
89+
end
90+
end
91+
92+
context "when location has partial address" do
93+
let(:location) do
94+
create(
95+
:community_clinic,
96+
address_line_1: "1 High Street",
97+
address_town: nil,
98+
address_postcode: "SW1A 1AA",
99+
position: nil
100+
)
101+
end
102+
103+
let(:response) do
104+
{
105+
header: {
106+
total_results: 1
107+
},
108+
results: [{ dpa: { lat: 51.5074, lng: -0.1278 } }]
109+
}
110+
end
111+
112+
before do
113+
allow(OrdnanceSurvey::PlacesAPI).to receive(:find).and_return(response)
114+
end
115+
116+
it "calls the API with partial address" do
117+
expect(OrdnanceSurvey::PlacesAPI).to receive(:find).with(
118+
"1 High Street, SW1A 1AA"
119+
)
120+
call
121+
end
122+
123+
it "updates the location position" do
124+
expect { call }.to change(location, :position).from(nil).to(
125+
an_instance_of(RGeo::Geographic::SphericalPointImpl)
126+
)
127+
expect(location.position.x).to eq(-0.1278)
128+
expect(location.position.y).to eq(51.5074)
129+
end
130+
end
131+
end
132+
end

0 commit comments

Comments
 (0)