diff --git a/lib/spatial_features/has_spatial_features/queued_spatial_processing.rb b/lib/spatial_features/has_spatial_features/queued_spatial_processing.rb index c3027be..4fc2a30 100644 --- a/lib/spatial_features/has_spatial_features/queued_spatial_processing.rb +++ b/lib/spatial_features/has_spatial_features/queued_spatial_processing.rb @@ -3,6 +3,28 @@ module QueuedSpatialProcessing extend ActiveSupport::Concern mattr_accessor :priority_offset, default: 0 # Offsets the queued priority of spatial tasks. Lower numbers run with higher priority + class_methods do + # Records whose most recent feature import failed. `->>` yields NULL for a missing + # key, so a record that has never been imported is not included. + def with_failed_feature_updates + where("spatial_processing_status_cache->>'update_features!' = 'failure'") + end + + # Re-import everything whose last attempt failed. Nothing else retries these, so a + # record broken by a defect stays broken after the defect is fixed unless something + # sweeps it up. + # + # Queued rather than run inline, so a caller (a deploy migration, a console) doesn't + # block on reimporting shapefiles, and so `SpatialProcessingJob`'s callbacks maintain + # `spatial_processing_status_cache` — calling `#update_features!` directly bypasses + # them and leaves a record flagged as failed even when the retry succeeded. + def retry_failed_feature_updates!(**options) + with_failed_feature_updates.find_each do |record| + record.delay_update_features!(**options) + end + end + end + def self.update_cached_status(record, method_name, state) return unless record.has_attribute?(:spatial_processing_status_cache) diff --git a/lib/spatial_features/version.rb b/lib/spatial_features/version.rb index 51669ab..0d9b80a 100644 --- a/lib/spatial_features/version.rb +++ b/lib/spatial_features/version.rb @@ -1,3 +1,3 @@ module SpatialFeatures - VERSION = "3.11.0" + VERSION = "3.11.1" end diff --git a/spec/lib/spatial_features/has_spatial_features/queued_spatial_processing_spec.rb b/spec/lib/spatial_features/has_spatial_features/queued_spatial_processing_spec.rb index a76da39..1e1398c 100644 --- a/spec/lib/spatial_features/has_spatial_features/queued_spatial_processing_spec.rb +++ b/spec/lib/spatial_features/has_spatial_features/queued_spatial_processing_spec.rb @@ -43,6 +43,37 @@ end end + describe '::retry_failed_feature_updates!', delayed_job: false do + let(:klass) { new_dummy_class(:spatial_processing_status_cache => :jsonb) } + + def status!(record, state) + SpatialFeatures::QueuedSpatialProcessing.update_cached_status(record, 'update_features!', state) + end + + it 'queues a feature update for each record whose last import failed' do + failed = klass.create.tap {|record| status!(record, 'failure') } + + expect { klass.retry_failed_feature_updates! } + .to change { failed.spatial_processing_jobs('update_features!').count } + .by(1) + end + + it 'ignores records that succeeded, and records never imported at all' do + klass.create.tap {|record| status!(record, 'success') } + klass.create # no import attempted, so no key in the cache at all + + expect { klass.retry_failed_feature_updates! }.not_to change { Delayed::Job.count } + end + + it 'passes options through to the queued job' do + klass.create.tap {|record| status!(record, 'failure') } + + klass.retry_failed_feature_updates!(:priority => 10) + + expect(Delayed::Job.last.priority).to eq(10) + end + end + describe '#clear_feature_update_error_status' do let(:klass) { new_dummy_class(:spatial_processing_status_cache => :jsonb) }