This repository has been archived by the owner on Mar 27, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 283
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
311 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
class GeocoderJob < ActiveJob::Base | ||
queue_as :geocode | ||
|
||
class GeocodingError < StandardError; end | ||
|
||
SLEEP_MULTIPLIER = 3 | ||
MAX_SLEEP_TIME = 12.hours | ||
MAX_FAILURE_COUNT = 2 | ||
|
||
def perform(site, model_type, model_id) | ||
@delay = 1 | ||
@error_count = 0 | ||
ActiveRecord::Base.connection_pool.with_connection do | ||
Site.with_current(site) do | ||
klass = model_type.constantize | ||
klass.with_advisory_lock('geocode') do # only one at a time | ||
model = klass.find(model_id) | ||
geocode_model(model) | ||
end | ||
end | ||
end | ||
sleep @delay unless Rails.env.test? # delay next GeocoderJob as a simple rate limit | ||
end | ||
|
||
def geocode_model(model) | ||
model.geocode | ||
rescue Geocoder::OverQueryLimitError | ||
over_query_limit_error(model) | ||
retry | ||
rescue Geocoder::Error, Timeout::Error => e | ||
general_error(model, e.message) | ||
retry | ||
else | ||
model.dont_geocode = true | ||
model.save(validate: false) | ||
end | ||
|
||
def over_query_limit_error(model) | ||
@delay *= SLEEP_MULTIPLIER | ||
if @delay > MAX_SLEEP_TIME | ||
fail GeocodingError, | ||
'Over geocoder rate limit for #{model.class.name} #{model.id}. Giving up.' | ||
else | ||
Rails.logger.warn( | ||
"Over geocoder rate limit for #{model.class.name} #{model.id}. " \ | ||
"Sleeping for #{@delay} second(s)" | ||
) | ||
sleep @delay | ||
end | ||
end | ||
|
||
def general_error(model, message) | ||
@error_count += 1 | ||
if @error_count > MAX_FAILURE_COUNT | ||
fail GeocodingError, | ||
"Error geocoding for #{model.class.name} #{model.id}: #{message}. " \ | ||
"This is error number #{@error_count}. Giving up." | ||
else | ||
Rails.logger.warn( | ||
"Error geocoding for #{model.class.name} #{model.id}: #{message}. " \ | ||
"This is error number #{@error_count}. Sleeping for 5 seconds." | ||
) | ||
sleep 5 | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
Geocoder.configure( | ||
lookup: :google | ||
lookup: :google, | ||
always_raise: :all | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require Rails.root.join('lib/sucker_punch/enqueue_at_monkey_patch') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
module ActiveJob | ||
module QueueAdapters | ||
class SuckerPunchAdapter | ||
# TODO in Rails 5 this is an INSTANCE method -- not a class method | ||
def self.enqueue_at(job, timestamp) | ||
wait = timestamp.to_i - Time.now.to_i | ||
JobWrapper.new.async.later(wait, job.serialize) | ||
end | ||
|
||
class JobWrapper | ||
def later(sec, data) | ||
after(sec) { perform(data) } | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
require_relative '../rails_helper' | ||
|
||
describe GeocoderJob do | ||
let(:family) do | ||
FactoryGirl.create( | ||
:family, | ||
address1: '650 S. Peoria', | ||
city: 'Tulsa', | ||
state: 'OK', | ||
zip: '74120', | ||
country: 'US', | ||
dont_geocode: true | ||
) | ||
end | ||
|
||
describe '#perform' do | ||
context 'given a proper response from the geocoding service' do | ||
before do | ||
Geocoder::Lookup::Test.add_stub( | ||
'650 S. Peoria, Tulsa, OK, 74120, US', [ | ||
{ | ||
'latitude' => 36.151305, | ||
'longitude' => -95.975393, | ||
'address' => 'Tulsa, OK, USA', | ||
'state' => 'Oklahoma', | ||
'state_code' => 'OK', | ||
'country' => 'United States', | ||
'country_code' => 'US' | ||
} | ||
] | ||
) | ||
subject.perform(Site.current, 'Family', family.id) | ||
end | ||
|
||
it 'updates the latitude and longitude' do | ||
expect(family.reload.attributes).to include( | ||
'latitude' => within(0.00001).of(36.151305), | ||
'longitude' => within(0.00001).of(-95.975393) | ||
) | ||
end | ||
end | ||
|
||
context 'given a timeout error from the geocoding service' do | ||
before do | ||
allow(subject).to receive(:sleep) | ||
call_count = 0 | ||
allow(Geocoder).to receive(:search) do | ||
call_count += 1 | ||
if call_count < 3 | ||
fail Geocoder::OverQueryLimitError | ||
else | ||
[double('result', latitude: 36.151305, longitude: -95.975393)] | ||
end | ||
end | ||
subject.perform(Site.current, 'Family', family.id) | ||
end | ||
|
||
it 'sleeps for a time before trying again' do | ||
expect(subject).to have_received(:sleep).with(3) | ||
expect(subject).to have_received(:sleep).with(9) | ||
expect(Geocoder).to have_received(:search).exactly(3).times | ||
end | ||
|
||
it 'updates the latitude and longitude' do | ||
expect(family.reload.attributes).to include( | ||
'latitude' => within(0.00001).of(36.151305), | ||
'longitude' => within(0.00001).of(-95.975393) | ||
) | ||
end | ||
end | ||
|
||
context 'given a general error from the geocoding service' do | ||
before do | ||
allow(subject).to receive(:sleep) | ||
call_count = 0 | ||
allow(Geocoder).to receive(:search) do | ||
call_count += 1 | ||
if call_count <= error_count | ||
fail Geocoder::RequestDenied | ||
else | ||
[double('result', latitude: 36.151305, longitude: -95.975393)] | ||
end | ||
end | ||
end | ||
|
||
context 'the error happens twice' do | ||
let(:error_count) { 2 } | ||
|
||
before do | ||
subject.perform(Site.current, 'Family', family.id) | ||
end | ||
|
||
it 'sleeps twice before trying again' do | ||
expect(subject).to have_received(:sleep).with(5).twice | ||
expect(Geocoder).to have_received(:search).exactly(3).times | ||
end | ||
|
||
it 'updates the latitude and longitude' do | ||
expect(family.reload.attributes).to include( | ||
'latitude' => within(0.00001).of(36.151305), | ||
'longitude' => within(0.00001).of(-95.975393) | ||
) | ||
end | ||
end | ||
|
||
context 'the error happens three times' do | ||
let(:error_count) { 3 } | ||
|
||
it 'fails' do | ||
expect { | ||
subject.perform(Site.current, 'Family', family.id) | ||
}.to raise_error(GeocoderJob::GeocodingError) | ||
end | ||
|
||
it 'does not update the latitude and longitude' do | ||
expect(family.reload.attributes).to include( | ||
'latitude' => nil, | ||
'longitude' => nil | ||
) | ||
end | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.