diff --git a/.app_version b/.app_version
index 473f1fb3..5c50d3ed 100644
--- a/.app_version
+++ b/.app_version
@@ -1 +1 @@
-0.30.8
+0.30.9
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 62a6aa37..d9d96a18 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,12 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
-# [0.30.9] - 2025-08-11
+
+# [0.30.9] - 2025-08-18
+
+## Changed
+
+- Countries, visited during a trip, are now being calculated from points to improve performance.
## Added
- X-Dawarich-Response and X-Dawarich-Version headers are now returned for all API responses.
+
# [0.30.8] - 2025-08-01
## Fixed
diff --git a/app/models/concerns/calculateable.rb b/app/models/concerns/calculateable.rb
index 31e4ff53..12caeac2 100644
--- a/app/models/concerns/calculateable.rb
+++ b/app/models/concerns/calculateable.rb
@@ -10,6 +10,7 @@ module Calculateable
def calculate_distance
calculated_distance_meters = calculate_distance_from_coordinates
+
self.distance = convert_distance_for_storage(calculated_distance_meters)
end
diff --git a/app/models/trip.rb b/app/models/trip.rb
index 7ba14ad5..e409a47b 100644
--- a/app/models/trip.rb
+++ b/app/models/trip.rb
@@ -21,12 +21,6 @@ class Trip < ApplicationRecord
user.tracked_points.where(timestamp: started_at.to_i..ended_at.to_i).order(:timestamp)
end
- def countries
- return points.pluck(:country).uniq.compact if DawarichSettings.store_geodata?
-
- visited_countries
- end
-
def photo_previews
@photo_previews ||= select_dominant_orientation(photos).sample(12)
end
@@ -35,13 +29,8 @@ class Trip < ApplicationRecord
@photo_sources ||= photos.map { _1[:source] }.uniq
end
-
-
def calculate_countries
- countries =
- Country.where(id: points.pluck(:country_id).compact.uniq).pluck(:name)
-
- self.visited_countries = countries
+ self.visited_countries = points.pluck(:country_name).uniq.compact
end
private
diff --git a/app/views/trips/_countries.html.erb b/app/views/trips/_countries.html.erb
index 0ae8f7e5..ce6f3c7c 100644
--- a/app/views/trips/_countries.html.erb
+++ b/app/views/trips/_countries.html.erb
@@ -15,12 +15,9 @@
Countries
- <% if trip.countries.any? %>
- <%= trip.countries.join(', ') %>
- <% elsif trip.visited_countries.present? %>
+ <% if trip.visited_countries.any? %>
<%= trip.visited_countries.join(', ') %>
<% else %>
- Countries are being calculated...
<% end %>
diff --git a/spec/jobs/trips/calculate_countries_job_spec.rb b/spec/jobs/trips/calculate_countries_job_spec.rb
new file mode 100644
index 00000000..d6d8abaa
--- /dev/null
+++ b/spec/jobs/trips/calculate_countries_job_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Trips::CalculateCountriesJob, type: :job do
+ describe '#perform' do
+ let(:user) { create(:user) }
+ let(:trip) { create(:trip, user: user) }
+ let(:distance_unit) { 'km' }
+ let(:points) do
+ [
+ create(:point, user: user, country_name: 'Germany', timestamp: trip.started_at.to_i + 1.hour),
+ create(:point, user: user, country_name: 'France', timestamp: trip.started_at.to_i + 2.hours),
+ create(:point, user: user, country_name: 'Germany', timestamp: trip.started_at.to_i + 3.hours),
+ create(:point, user: user, country_name: 'Italy', timestamp: trip.started_at.to_i + 4.hours)
+ ]
+ end
+
+ before do
+ points # Create the points
+ end
+
+ it 'finds the trip and calculates countries' do
+ expect(Trip).to receive(:find).with(trip.id).and_return(trip)
+ expect(trip).to receive(:calculate_countries)
+ expect(trip).to receive(:save!)
+
+ described_class.perform_now(trip.id, distance_unit)
+ end
+
+ it 'calculates unique countries from trip points' do
+ described_class.perform_now(trip.id, distance_unit)
+
+ trip.reload
+ expect(trip.visited_countries).to contain_exactly('Germany', 'France', 'Italy')
+ end
+
+ it 'broadcasts the update with correct parameters' do
+ expect(Turbo::StreamsChannel).to receive(:broadcast_update_to).with(
+ "trip_#{trip.id}",
+ target: "trip_countries",
+ partial: "trips/countries",
+ locals: { trip: trip, distance_unit: distance_unit }
+ )
+
+ described_class.perform_now(trip.id, distance_unit)
+ end
+
+ context 'when trip has no points' do
+ let(:trip_without_points) { create(:trip, user: user) }
+
+ it 'sets visited_countries to empty array' do
+ trip_without_points.points.destroy_all
+ described_class.perform_now(trip_without_points.id, distance_unit)
+
+ trip_without_points.reload
+
+ expect(trip_without_points.visited_countries).to eq([])
+ end
+ end
+
+ context 'when points have nil country names' do
+ let(:points_with_nil_countries) do
+ [
+ create(:point, user: user, country_name: 'Germany', timestamp: trip.started_at.to_i + 1.hour),
+ create(:point, user: user, country_name: nil, timestamp: trip.started_at.to_i + 2.hours),
+ create(:point, user: user, country_name: 'France', timestamp: trip.started_at.to_i + 3.hours)
+ ]
+ end
+
+ before do
+ # Remove existing points and create new ones with nil countries
+ Point.where(user: user).destroy_all
+ points_with_nil_countries
+ end
+
+ it 'filters out nil country names' do
+ described_class.perform_now(trip.id, distance_unit)
+
+ trip.reload
+ expect(trip.visited_countries).to contain_exactly('Germany', 'France')
+ end
+ end
+
+ context 'when trip is not found' do
+ it 'raises ActiveRecord::RecordNotFound' do
+ expect {
+ described_class.perform_now(999999, distance_unit)
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'when distance_unit is different' do
+ let(:distance_unit) { 'mi' }
+
+ it 'passes the correct distance_unit to broadcast' do
+ expect(Turbo::StreamsChannel).to receive(:broadcast_update_to).with(
+ "trip_#{trip.id}",
+ target: "trip_countries",
+ partial: "trips/countries",
+ locals: { trip: trip, distance_unit: 'mi' }
+ )
+
+ described_class.perform_now(trip.id, distance_unit)
+ end
+ end
+
+ describe 'queue configuration' do
+ it 'uses the trips queue' do
+ expect(described_class.queue_name).to eq('trips')
+ end
+ end
+ end
+end
diff --git a/spec/models/trip_spec.rb b/spec/models/trip_spec.rb
index 20bb5ba3..8c46a65a 100644
--- a/spec/models/trip_spec.rb
+++ b/spec/models/trip_spec.rb
@@ -26,34 +26,6 @@ RSpec.describe Trip, type: :model do
trip.save
end
end
-
- context 'when DawarichSettings.store_geodata? is enabled' do
- before do
- allow(DawarichSettings).to receive(:store_geodata?).and_return(true)
- end
-
- it 'sets the countries' do
- expect(trip.countries).to eq(trip.points.pluck(:country).uniq.compact)
- end
- end
- end
-
- describe '#countries' do
- let(:user) { create(:user) }
- let(:trip) { create(:trip, user:) }
- let(:points) do
- create_list(
- :point,
- 25,
- :reverse_geocoded,
- user:,
- timestamp: (trip.started_at.to_i..trip.ended_at.to_i).to_a.sample
- )
- end
-
- it 'returns the unique countries of the points' do
- expect(trip.countries).to eq(trip.points.pluck(:country).uniq.compact)
- end
end
describe '#photo_previews' do