From 74112c0d04ae1e14eb573ca2d34f07c19cd5ca0d Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Sat, 2 Aug 2025 00:06:09 +0200 Subject: [PATCH 1/4] Calculate trip's visited countries from points --- app/models/concerns/calculateable.rb | 1 + app/models/trip.rb | 13 +- app/views/trips/_countries.html.erb | 5 +- .../trips/calculate_countries_job_spec.rb | 114 ++++++++++++++++++ spec/models/trip_spec.rb | 28 ----- 5 files changed, 117 insertions(+), 44 deletions(-) create mode 100644 spec/jobs/trips/calculate_countries_job_spec.rb 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 From 542fb943754c17596eb2067b6d126f1f05a7d13d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 18:31:03 +0000 Subject: [PATCH 2/4] Bump puma from 6.6.0 to 6.6.1 Bumps [puma](https://github.com/puma/puma) from 6.6.0 to 6.6.1. - [Release notes](https://github.com/puma/puma/releases) - [Changelog](https://github.com/puma/puma/blob/master/History.md) - [Commits](https://github.com/puma/puma/compare/v6.6.0...v6.6.1) --- updated-dependencies: - dependency-name: puma dependency-version: 6.6.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4b955b5a..a0004c16 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -297,7 +297,7 @@ GEM date stringio public_suffix (6.0.1) - puma (6.6.0) + puma (6.6.1) nio4r (~> 2.0) pundit (2.5.0) activesupport (>= 3.0.0) From 4ef47a1c856276bc40a7bf41358b72e370630004 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 22:43:19 +0000 Subject: [PATCH 3/4] Bump activerecord from 8.0.2 to 8.0.2.1 in the bundler group Bumps the bundler group with 1 update: [activerecord](https://github.com/rails/rails). Updates `activerecord` from 8.0.2 to 8.0.2.1 - [Release notes](https://github.com/rails/rails/releases) - [Changelog](https://github.com/rails/rails/blob/v8.0.2.1/activerecord/CHANGELOG.md) - [Commits](https://github.com/rails/rails/compare/v8.0.2...v8.0.2.1) --- updated-dependencies: - dependency-name: activerecord dependency-version: 8.0.2.1 dependency-type: indirect dependency-group: bundler ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 106 +++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4b955b5a..7ec63f13 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,29 +10,29 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (8.0.2) - actionpack (= 8.0.2) - activesupport (= 8.0.2) + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.0.2) - actionpack (= 8.0.2) - activejob (= 8.0.2) - activerecord (= 8.0.2) - activestorage (= 8.0.2) - activesupport (= 8.0.2) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) mail (>= 2.8.0) - actionmailer (8.0.2) - actionpack (= 8.0.2) - actionview (= 8.0.2) - activejob (= 8.0.2) - activesupport (= 8.0.2) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.0.2) - actionview (= 8.0.2) - activesupport (= 8.0.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -40,38 +40,38 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.0.2) - actionpack (= 8.0.2) - activerecord (= 8.0.2) - activestorage (= 8.0.2) - activesupport (= 8.0.2) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.0.2) - activesupport (= 8.0.2) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (8.0.2) - activesupport (= 8.0.2) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) globalid (>= 0.3.6) - activemodel (8.0.2) - activesupport (= 8.0.2) - activerecord (8.0.2) - activemodel (= 8.0.2) - activesupport (= 8.0.2) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) timeout (>= 0.4.0) activerecord-postgis-adapter (11.0.0) activerecord (~> 8.0.0) rgeo-activerecord (~> 8.0.0) - activestorage (8.0.2) - actionpack (= 8.0.2) - activejob (= 8.0.2) - activerecord (= 8.0.2) - activesupport (= 8.0.2) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) marcel (~> 1.0) - activesupport (8.0.2) + activesupport (8.0.2.1) base64 benchmark (>= 0.3) bigdecimal @@ -311,20 +311,20 @@ GEM rack (>= 1.3) rackup (2.2.1) rack (>= 3) - rails (8.0.2) - actioncable (= 8.0.2) - actionmailbox (= 8.0.2) - actionmailer (= 8.0.2) - actionpack (= 8.0.2) - actiontext (= 8.0.2) - actionview (= 8.0.2) - activejob (= 8.0.2) - activemodel (= 8.0.2) - activerecord (= 8.0.2) - activestorage (= 8.0.2) - activesupport (= 8.0.2) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) bundler (>= 1.15.0) - railties (= 8.0.2) + railties (= 8.0.2.1) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -332,9 +332,9 @@ GEM rails-html-sanitizer (1.6.2) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (8.0.2) - actionpack (= 8.0.2) - activesupport (= 8.0.2) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) From 7b46a663ce01962672135023bb75b5107d828a40 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Mon, 18 Aug 2025 20:53:32 +0200 Subject: [PATCH 4/4] Update changelog and app version --- .app_version | 2 +- CHANGELOG.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) 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 f89a0cf9..21ccee83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ 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-18 + +## Changed + +- Countries, visited during a trip, are now being calculated from points to improve performance. + + # [0.30.8] - 2025-08-01 ## Fixed