Jobs in queue: <%= queue.size %>
diff --git a/app/views/settings/background_jobs/new.html.erb b/app/views/settings/background_jobs/new.html.erb
deleted file mode 100644
index 35410be2..00000000
--- a/app/views/settings/background_jobs/new.html.erb
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
New background job
-
- <%= render "form", settings_background_job: @settings_background_job %>
-
- <%= link_to "Back to background jobs", settings_background_jobs_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
-
diff --git a/app/views/settings/background_jobs/show.html.erb b/app/views/settings/background_jobs/show.html.erb
deleted file mode 100644
index c0c7369c..00000000
--- a/app/views/settings/background_jobs/show.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- <% if notice.present? %>
-
<%= notice %>
- <% end %>
-
- <%= render @settings_background_job %>
-
- <%= link_to "Edit this background job", edit_settings_background_job_path(@settings_background_job), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
- <%= link_to "Back to background jobs", settings_background_jobs_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
-
- <%= button_to "Destroy this background job", @settings_background_job, method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
-
-
-
diff --git a/config/initializers/geocoder.rb b/config/initializers/geocoder.rb
index 0fc44fbb..baa9cf53 100644
--- a/config/initializers/geocoder.rb
+++ b/config/initializers/geocoder.rb
@@ -1,4 +1,5 @@
-# config/initializers/geocoder.rb
+# frozen_string_literal: true
+
Geocoder.configure(
# geocoding service request timeout, in seconds (default 3):
# timeout: 5,
@@ -9,7 +10,7 @@ Geocoder.configure(
# caching (see Caching section below for details):
cache: Redis.new,
cache_options: {
- expiration: 1.day, # Defaults to `nil`
+ expiration: 1.day # Defaults to `nil`
# prefix: "another_key:" # Defaults to `geocoder:`
}
)
diff --git a/config/routes.rb b/config/routes.rb
index 185c5a1e..9ff93595 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -9,7 +9,7 @@ Rails.application.routes.draw do
resources :settings, only: :index
namespace :settings do
- resources :background_jobs
+ resources :background_jobs, only: %i[index create destroy]
resources :users, only: :create
end
diff --git a/db/migrate/20240712141303_add_geodata_to_points.rb b/db/migrate/20240712141303_add_geodata_to_points.rb
new file mode 100644
index 00000000..9d791c5d
--- /dev/null
+++ b/db/migrate/20240712141303_add_geodata_to_points.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class AddGeodataToPoints < ActiveRecord::Migration[7.1]
+ def change
+ add_column :points, :geodata, :jsonb, null: false, default: {}
+ add_index :points, :geodata, using: :gin
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a56b2bb7..19c7f624 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.1].define(version: 2024_07_03_105734) do
+ActiveRecord::Schema[7.1].define(version: 2024_07_12_141303) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -109,12 +109,14 @@ ActiveRecord::Schema[7.1].define(version: 2024_07_03_105734) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "user_id"
+ t.jsonb "geodata", default: {}, null: false
t.index ["altitude"], name: "index_points_on_altitude"
t.index ["battery"], name: "index_points_on_battery"
t.index ["battery_status"], name: "index_points_on_battery_status"
t.index ["city"], name: "index_points_on_city"
t.index ["connection"], name: "index_points_on_connection"
t.index ["country"], name: "index_points_on_country"
+ t.index ["geodata"], name: "index_points_on_geodata", using: :gin
t.index ["import_id"], name: "index_points_on_import_id"
t.index ["latitude", "longitude"], name: "index_points_on_latitude_and_longitude"
t.index ["timestamp"], name: "index_points_on_timestamp"
diff --git a/spec/factories/points.rb b/spec/factories/points.rb
index f66d3983..de39b166 100644
--- a/spec/factories/points.rb
+++ b/spec/factories/points.rb
@@ -25,5 +25,6 @@ FactoryBot.define do
import_id { '' }
city { nil }
country { nil }
+ user
end
end
diff --git a/spec/jobs/enqueue_reverse_geocoding_job_spec.rb b/spec/jobs/enqueue_reverse_geocoding_job_spec.rb
index 89ca2307..25d6c3cd 100644
--- a/spec/jobs/enqueue_reverse_geocoding_job_spec.rb
+++ b/spec/jobs/enqueue_reverse_geocoding_job_spec.rb
@@ -1,5 +1,14 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe EnqueueReverseGeocodingJob, type: :job do
- pending "add some examples to (or delete) #{__FILE__}"
+ let(:job_name) { 'start_reverse_geocoding' }
+ let(:user_id) { 1 }
+
+ it 'calls job creation service' do
+ expect(Jobs::Create).to receive(:new).with(job_name, user_id).and_return(double(call: nil))
+
+ EnqueueReverseGeocodingJob.perform_now(job_name, user_id)
+ end
end
diff --git a/spec/jobs/reverse_geocoding_job_spec.rb b/spec/jobs/reverse_geocoding_job_spec.rb
index d16a5383..38a4d4d4 100644
--- a/spec/jobs/reverse_geocoding_job_spec.rb
+++ b/spec/jobs/reverse_geocoding_job_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe ReverseGeocodingJob, type: :job do
@@ -17,44 +19,27 @@ RSpec.describe ReverseGeocodingJob, type: :job do
expect { perform }.not_to(change { point.reload.city })
end
- it 'does not call Geocoder' do
+ it 'does not call ReverseGeocoding::FetchData' do
+ allow(ReverseGeocoding::FetchData).to receive(:new).and_call_original
+
perform
- expect(Geocoder).not_to have_received(:search)
+ expect(ReverseGeocoding::FetchData).not_to have_received(:new)
end
end
context 'when REVERSE_GEOCODING_ENABLED is true' do
before { stub_const('REVERSE_GEOCODING_ENABLED', true) }
- it 'updates point with city and country' do
- expect { perform }.to change { point.reload.city }.from(nil)
- end
+ let(:stubbed_geocoder) { OpenStruct.new(data: { city: 'City', country: 'Country' }) }
it 'calls Geocoder' do
+ allow(Geocoder).to receive(:search).and_return([stubbed_geocoder])
+ allow(ReverseGeocoding::FetchData).to receive(:new).and_call_original
+
perform
- expect(Geocoder).to have_received(:search).with([point.latitude, point.longitude])
- end
-
- context 'when point has city and country' do
- let(:point) { create(:point, city: 'City', country: 'Country') }
-
- before do
- allow(Geocoder).to receive(:search).and_return(
- [double(city: 'Another city', country: 'Some country')]
- )
- end
-
- it 'does not update point' do
- expect { perform }.not_to change { point.reload.city }
- end
-
- it 'does not call Geocoder' do
- perform
-
- expect(Geocoder).not_to have_received(:search)
- end
+ expect(ReverseGeocoding::FetchData).to have_received(:new).with(point.id)
end
end
end
diff --git a/spec/models/point_spec.rb b/spec/models/point_spec.rb
index 17bc8bc0..3129f600 100644
--- a/spec/models/point_spec.rb
+++ b/spec/models/point_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
RSpec.describe Point, type: :model do
describe 'associations' do
it { is_expected.to belong_to(:import).optional }
- it { is_expected.to belong_to(:user).optional }
+ it { is_expected.to belong_to(:user) }
end
describe 'validations' do
@@ -13,4 +13,24 @@ RSpec.describe Point, type: :model do
it { is_expected.to validate_presence_of(:longitude) }
it { is_expected.to validate_presence_of(:timestamp) }
end
+
+ describe 'scopes' do
+ describe '.reverse_geocoded' do
+ let(:point) { create(:point, country: 'Country', city: 'City') }
+ let(:point_without_address) { create(:point, city: nil, country: nil) }
+
+ it 'returns points with reverse geocoded address' do
+ expect(described_class.reverse_geocoded).to eq([point])
+ end
+ end
+
+ describe '.not_reverse_geocoded' do
+ let(:point) { create(:point, country: 'Country', city: 'City') }
+ let(:point_without_address) { create(:point, city: nil, country: nil) }
+
+ it 'returns points without reverse geocoded address' do
+ expect(described_class.not_reverse_geocoded).to eq([point_without_address])
+ end
+ end
+ end
end
diff --git a/spec/requests/settings/background_jobs_spec.rb b/spec/requests/settings/background_jobs_spec.rb
index 1572b5b6..8eef2501 100644
--- a/spec/requests/settings/background_jobs_spec.rb
+++ b/spec/requests/settings/background_jobs_spec.rb
@@ -1,135 +1,69 @@
+# frozen_string_literal: true
+
require 'rails_helper'
-# This spec was generated by rspec-rails when you ran the scaffold generator.
-# It demonstrates how one might use RSpec to test the controller code that
-# was generated by Rails when you ran the scaffold generator.
-#
-# It assumes that the implementation code is generated by the rails scaffold
-# generator. If you are using any extension libraries to generate different
-# controller code, this generated spec may or may not pass.
-#
-# It only uses APIs available in rails and/or rspec-rails. There are a number
-# of tools you can use to make these specs even more expressive, but we're
-# sticking to rails and rspec-rails APIs to keep things simple and stable.
+RSpec.describe '/settings/background_jobs', type: :request do
+ before do
+ stub_request(:any, 'https://api.github.com/repos/Freika/dawarich/tags')
+ .to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {})
+ end
-RSpec.describe "/settings/background_jobs", type: :request do
-
- # This should return the minimal set of attributes required to create a valid
- # Settings::BackgroundJob. As you add validations to Settings::BackgroundJob, be sure to
- # adjust the attributes here as well.
- let(:valid_attributes) {
- skip("Add a hash of attributes valid for your model")
- }
-
- let(:invalid_attributes) {
- skip("Add a hash of attributes invalid for your model")
- }
-
- describe "GET /index" do
- it "renders a successful response" do
- Settings::BackgroundJob.create! valid_attributes
+ context 'when user is not authenticated' do
+ it 'redirects to sign in page' do
get settings_background_jobs_url
- expect(response).to be_successful
+
+ expect(response).to redirect_to(new_user_session_url)
end
end
- describe "GET /show" do
- it "renders a successful response" do
- background_job = Settings::BackgroundJob.create! valid_attributes
- get settings_background_job_url(background_job)
- expect(response).to be_successful
+ context 'when user is authenticated' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in user
end
- end
- describe "GET /new" do
- it "renders a successful response" do
- get new_settings_background_job_url
- expect(response).to be_successful
- end
- end
+ describe 'GET /index' do
+ it 'renders a successful response' do
+ get settings_background_jobs_url
- describe "GET /edit" do
- it "renders a successful response" do
- background_job = Settings::BackgroundJob.create! valid_attributes
- get edit_settings_background_job_url(background_job)
- expect(response).to be_successful
- end
- end
-
- describe "POST /create" do
- context "with valid parameters" do
- it "creates a new Settings::BackgroundJob" do
- expect {
- post settings_background_jobs_url, params: { settings_background_job: valid_attributes }
- }.to change(Settings::BackgroundJob, :count).by(1)
- end
-
- it "redirects to the created settings_background_job" do
- post settings_background_jobs_url, params: { settings_background_job: valid_attributes }
- expect(response).to redirect_to(settings_background_job_url(Settings::BackgroundJob.last))
+ expect(response).to be_successful
end
end
- context "with invalid parameters" do
- it "does not create a new Settings::BackgroundJob" do
- expect {
- post settings_background_jobs_url, params: { settings_background_job: invalid_attributes }
- }.to change(Settings::BackgroundJob, :count).by(0)
- end
+ describe 'POST /create' do
+ let(:params) { { job_name: 'start_reverse_geocoding' } }
-
- it "renders a response with 422 status (i.e. to display the 'new' template)" do
- post settings_background_jobs_url, params: { settings_background_job: invalid_attributes }
- expect(response).to have_http_status(:unprocessable_entity)
- end
-
- end
- end
+ context 'with valid parameters' do
+ it 'enqueues a new job' do
+ expect do
+ post settings_background_jobs_url, params:
+ end.to have_enqueued_job(EnqueueReverseGeocodingJob)
+ end
- describe "PATCH /update" do
- context "with valid parameters" do
- let(:new_attributes) {
- skip("Add a hash of attributes valid for your model")
- }
+ it 'redirects to the created settings_background_job' do
+ post(settings_background_jobs_url, params:)
- it "updates the requested settings_background_job" do
- background_job = Settings::BackgroundJob.create! valid_attributes
- patch settings_background_job_url(background_job), params: { settings_background_job: new_attributes }
- background_job.reload
- skip("Add assertions for updated state")
- end
-
- it "redirects to the settings_background_job" do
- background_job = Settings::BackgroundJob.create! valid_attributes
- patch settings_background_job_url(background_job), params: { settings_background_job: new_attributes }
- background_job.reload
- expect(response).to redirect_to(settings_background_job_url(background_job))
+ expect(response).to redirect_to(settings_background_jobs_url)
+ end
end
end
- context "with invalid parameters" do
-
- it "renders a response with 422 status (i.e. to display the 'edit' template)" do
- background_job = Settings::BackgroundJob.create! valid_attributes
- patch settings_background_job_url(background_job), params: { settings_background_job: invalid_attributes }
- expect(response).to have_http_status(:unprocessable_entity)
+ describe 'DELETE /destroy' do
+ it 'clears the Sidekiq queue' do
+ queue = instance_double(Sidekiq::Queue)
+ allow(Sidekiq::Queue).to receive(:new).and_return(queue)
+
+ expect(queue).to receive(:clear)
+
+ delete settings_background_job_url('queue_name')
end
-
- end
- end
- describe "DELETE /destroy" do
- it "destroys the requested settings_background_job" do
- background_job = Settings::BackgroundJob.create! valid_attributes
- expect {
- delete settings_background_job_url(background_job)
- }.to change(Settings::BackgroundJob, :count).by(-1)
- end
+ it 'redirects to the settings_background_jobs list' do
+ delete settings_background_job_url('queue_name')
- it "redirects to the settings_background_jobs list" do
- background_job = Settings::BackgroundJob.create! valid_attributes
- delete settings_background_job_url(background_job)
- expect(response).to redirect_to(settings_background_jobs_url)
+ expect(response).to redirect_to(settings_background_jobs_url)
+ end
end
end
end
diff --git a/spec/services/jobs/create_spec.rb b/spec/services/jobs/create_spec.rb
new file mode 100644
index 00000000..cef21660
--- /dev/null
+++ b/spec/services/jobs/create_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Jobs::Create do
+ describe '#call' do
+ context 'when job_name is start_reverse_geocoding' do
+ let(:user) { create(:user) }
+ let(:points) { create_list(:point, 4, user:) }
+ let(:job_name) { 'start_reverse_geocoding' }
+
+ it 'enqueues reverse geocoding for all user points' do
+ allow(ReverseGeocodingJob).to receive(:perform_later).and_return(nil)
+
+ described_class.new(job_name, user.id).call
+
+ points.each do |point|
+ expect(ReverseGeocodingJob).to have_received(:perform_later).with(point.id)
+ end
+ end
+ end
+
+ context 'when job_name is continue_reverse_geocoding' do
+ let(:user) { create(:user) }
+ let(:points_without_address) { create_list(:point, 4, user:, country: nil, city: nil) }
+ let(:points_with_address) { create_list(:point, 5, user:, country: 'Country', city: 'City') }
+
+ let(:job_name) { 'continue_reverse_geocoding' }
+
+ it 'enqueues reverse geocoding for all user points without address' do
+ allow(ReverseGeocodingJob).to receive(:perform_later).and_return(nil)
+
+ described_class.new(job_name, user.id).call
+
+ points_without_address.each do |point|
+ expect(ReverseGeocodingJob).to have_received(:perform_later).with(point.id)
+ end
+ end
+ end
+
+ context 'when job_name is invalid' do
+ let(:user) { create(:user) }
+ let(:job_name) { 'invalid_job_name' }
+
+ it 'raises an error' do
+ expect { described_class.new(job_name, user.id).call }.to raise_error(Jobs::Create::InvalidJobName)
+ end
+ end
+ end
+end
diff --git a/spec/services/reverse_geocoding/fetch_data_spec.rb b/spec/services/reverse_geocoding/fetch_data_spec.rb
new file mode 100644
index 00000000..b7d0e061
--- /dev/null
+++ b/spec/services/reverse_geocoding/fetch_data_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe ReverseGeocoding::FetchData do
+ subject(:fetch_data) { described_class.new(point.id).call }
+
+ let(:point) { create(:point) }
+
+ context 'when Geocoder returns city and country' do
+ before do
+ allow(Geocoder).to receive(:search).and_return([double(city: 'City', country: 'Country',
+ data: { 'address' => 'Address' })])
+ end
+
+ context 'when point does not have city and country' do
+ it 'updates point with city and country' do
+ expect { fetch_data }.to change { point.reload.city }
+ .from(nil).to('City')
+ .and change { point.reload.country }.from(nil).to('Country')
+ end
+
+ it 'updates point with geodata' do
+ expect { fetch_data }.to change { point.reload.geodata }.from({}).to('address' => 'Address')
+ end
+
+ it 'calls Geocoder' do
+ fetch_data
+
+ expect(Geocoder).to have_received(:search).with([point.latitude, point.longitude])
+ end
+ end
+
+ context 'when point has city and country' do
+ let(:point) { create(:point, city: 'City', country: 'Country') }
+
+ before do
+ allow(Geocoder).to receive(:search).and_return(
+ [double(city: 'Another city', country: 'Some country')]
+ )
+ end
+
+ it 'does not update point' do
+ expect { fetch_data }.not_to(change { point.reload.city })
+ end
+
+ it 'does not call Geocoder' do
+ fetch_data
+
+ expect(Geocoder).not_to have_received(:search)
+ end
+ end
+ end
+
+ context 'when Geocoder returns an error' do
+ before do
+ allow(Geocoder).to receive(:search).and_return([double(data: { 'error' => 'Error' })])
+ end
+
+ it 'does not update point' do
+ expect { fetch_data }.not_to(change { point.reload.city })
+ end
+ end
+end