Extract update location sharing logic to a service object

This commit is contained in:
Eugene Burmakin 2025-10-05 19:40:42 +02:00
parent 018760812a
commit 6fb5d98b19
4 changed files with 165 additions and 48 deletions

View file

@ -77,57 +77,17 @@ class FamiliesController < ApplicationController
end
def update_location_sharing
enabled = ActiveModel::Type::Boolean.new.cast(params[:enabled])
duration = params[:duration]
result = Families::UpdateLocationSharing.new(
user: current_user,
enabled: params[:enabled],
duration: params[:duration]
).call
if current_user.update_family_location_sharing!(enabled, duration: duration)
response_data = {
success: true,
enabled: enabled,
duration: current_user.family_sharing_duration,
message: build_sharing_message(enabled, duration)
}
if enabled && current_user.family_sharing_expires_at.present?
response_data[:expires_at] = current_user.family_sharing_expires_at.iso8601
response_data[:expires_at_formatted] = current_user.family_sharing_expires_at.strftime('%b %d at %I:%M %p')
end
render json: response_data
else
render json: {
success: false,
message: 'Failed to update location sharing setting'
}, status: :unprocessable_content
end
rescue => e
render json: {
success: false,
message: 'An error occurred while updating location sharing'
}, status: :internal_server_error
render json: result.payload, status: result.status
end
private
def build_sharing_message(enabled, duration)
return 'Location sharing disabled' unless enabled
case duration
when '1h'
'Location sharing enabled for 1 hour'
when '6h'
'Location sharing enabled for 6 hours'
when '12h'
'Location sharing enabled for 12 hours'
when '24h'
'Location sharing enabled for 24 hours'
when 'permanent', nil
'Location sharing enabled'
else
duration.to_i > 0 ? "Location sharing enabled for #{duration.to_i} hours" : 'Location sharing enabled'
end
end
def set_family
@family = current_user.family
redirect_to new_family_path, alert: 'You are not in a family' unless @family

View file

@ -1,8 +1,8 @@
# frozen_string_literal: true
class Family::InvitationsController < ApplicationController
before_action :authenticate_user!, except: %i[show accept]
before_action :ensure_family_feature_enabled!, except: %i[show accept]
before_action :authenticate_user!, except: %i[show]
before_action :ensure_family_feature_enabled!, except: %i[show]
before_action :set_family, except: %i[show accept]
before_action :set_invitation_by_id_and_family, only: %i[destroy]
before_action :set_invitation_by_id, only: %i[accept]

View file

@ -0,0 +1,69 @@
# frozen_string_literal: true
class Families::UpdateLocationSharing
Result = Struct.new(:success?, :payload, :status, keyword_init: true)
def initialize(user:, enabled:, duration:)
@user = user
@enabled_param = enabled
@duration_param = duration
@boolean_caster = ActiveModel::Type::Boolean.new
end
def call
if update_location_sharing
success_result
else
failure_result('Failed to update location sharing setting', :unprocessable_content)
end
rescue => error
Rails.logger.error("Failed to update family location sharing: #{error.message}") if defined?(Rails)
failure_result('An error occurred while updating location sharing', :internal_server_error)
end
private
attr_reader :user, :enabled_param, :duration_param, :boolean_caster
def update_location_sharing
user.update_family_location_sharing!(enabled?, duration: duration_param)
end
def enabled?
@enabled ||= boolean_caster.cast(enabled_param)
end
def success_result
payload = {
success: true,
enabled: enabled?,
duration: user.family_sharing_duration,
message: build_sharing_message
}
if enabled? && user.family_sharing_expires_at.present?
payload[:expires_at] = user.family_sharing_expires_at.iso8601
payload[:expires_at_formatted] = user.family_sharing_expires_at.strftime('%b %d at %I:%M %p')
end
Result.new(success?: true, payload: payload, status: :ok)
end
def failure_result(message, status)
Result.new(success?: false, payload: { success: false, message: message }, status: status)
end
def build_sharing_message
return 'Location sharing disabled' unless enabled?
case duration_param
when '1h' then 'Location sharing enabled for 1 hour'
when '6h' then 'Location sharing enabled for 6 hours'
when '12h' then 'Location sharing enabled for 12 hours'
when '24h' then 'Location sharing enabled for 24 hours'
when 'permanent', nil then 'Location sharing enabled'
else
duration_param.to_i.positive? ? "Location sharing enabled for #{duration_param.to_i} hours" : 'Location sharing enabled'
end
end
end

View file

@ -0,0 +1,88 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Families::UpdateLocationSharing do
include ActiveSupport::Testing::TimeHelpers
describe '.call' do
subject(:call_service) do
described_class.new(user: user, enabled: enabled, duration: duration).call
end
let(:duration) { '1h' }
context 'when the user is in a family' do
let(:user) { create(:user) }
let!(:family_membership) { create(:family_membership, user: user) }
context 'when enabling location sharing with a duration' do
let(:enabled) { true }
around do |example|
travel_to(Time.zone.local(2024, 1, 1, 12, 0, 0)) { example.run }
end
it 'returns a successful result with the expected payload' do
result = call_service
expect(result).to be_success
expect(result.status).to eq(:ok)
expect(result.payload[:success]).to be true
expect(result.payload[:enabled]).to be true
expect(result.payload[:duration]).to eq('1h')
expect(result.payload[:message]).to eq('Location sharing enabled for 1 hour')
expect(result.payload[:expires_at]).to eq(1.hour.from_now.iso8601)
expect(result.payload[:expires_at_formatted]).to eq(1.hour.from_now.strftime('%b %d at %I:%M %p'))
end
end
context 'when disabling location sharing' do
let(:enabled) { false }
let(:duration) { nil }
it 'returns a successful result without expiration details' do
result = call_service
expect(result).to be_success
expect(result.payload[:success]).to be true
expect(result.payload[:enabled]).to be false
expect(result.payload[:message]).to eq('Location sharing disabled')
expect(result.payload).not_to have_key(:expires_at)
expect(result.payload).not_to have_key(:expires_at_formatted)
end
end
context 'when update raises an unexpected error' do
let(:enabled) { true }
before do
allow(user).to receive(:update_family_location_sharing!).and_raise(StandardError, 'boom')
end
it 'returns a failure result with internal server error status' do
result = call_service
expect(result).not_to be_success
expect(result.status).to eq(:internal_server_error)
expect(result.payload[:success]).to be false
expect(result.payload[:message]).to eq('An error occurred while updating location sharing')
end
end
end
context 'when the user is not in a family' do
let(:user) { create(:user) }
let(:enabled) { true }
it 'returns a failure result with unprocessable content status' do
result = call_service
expect(result).not_to be_success
expect(result.status).to eq(:unprocessable_content)
expect(result.payload[:success]).to be false
expect(result.payload[:message]).to eq('Failed to update location sharing setting')
end
end
end
end