Skip to content

Commit cbf3d60

Browse files
authored
Merge pull request #2556 from mroderick/fix/invitation-log-retry-handling
fix: handle duplicate InvitationLogEntry creation on retry
2 parents 80b235c + de2eae1 commit cbf3d60

2 files changed

Lines changed: 58 additions & 16 deletions

File tree

app/services/invitation_logger.rb

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,36 +22,37 @@ def start_batch
2222
def log_success(member, invitation = nil)
2323
return unless @log
2424

25-
@log.entries.create!(
26-
member: member,
27-
invitation: invitation,
28-
status: :success,
29-
processed_at: Time.current
30-
).tap { @log.increment!(:success_count) }
25+
entry = find_or_build_entry(member, invitation, :success)
26+
return entry if entry.persisted?
27+
28+
entry.assign_attributes(processed_at: Time.current)
29+
save_entry(entry, :success_count)
3130
end
3231

3332
def log_failure(member, invitation, error)
3433
return unless @log
3534

36-
@log.entries.create!(
37-
member: member,
38-
invitation: invitation,
39-
status: :failed,
35+
entry = find_or_build_entry(member, invitation, :failed)
36+
return entry if entry.persisted?
37+
38+
entry.assign_attributes(
4039
failure_reason: error.message,
4140
processed_at: Time.current
42-
).tap { @log.increment!(:failure_count) }
41+
)
42+
save_entry(entry, :failure_count)
4343
end
4444

4545
def log_skipped(member, invitation, reason)
4646
return unless @log
4747

48-
@log.entries.create!(
49-
member: member,
50-
invitation: invitation,
51-
status: :skipped,
48+
entry = find_or_build_entry(member, invitation, :skipped)
49+
return entry if entry.persisted?
50+
51+
entry.assign_attributes(
5252
failure_reason: reason,
5353
processed_at: Time.current
54-
).tap { @log.increment!(:skipped_count) }
54+
)
55+
save_entry(entry, :skipped_count)
5556
end
5657

5758
def finish_batch(total_invitees)
@@ -74,5 +75,21 @@ def fail_batch(error)
7475
)
7576
end
7677

78+
private
79+
80+
def find_or_build_entry(member, invitation, status)
81+
@log.entries.find_or_initialize_by(
82+
member:,
83+
invitation:,
84+
status:
85+
)
86+
end
87+
88+
def save_entry(entry, counter)
89+
entry.save!
90+
@log.increment!(counter)
91+
entry
92+
end
93+
7794
attr_reader :log
7895
end

spec/services/invitation_logger_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@
4646
expect(entry.invitation).to eq invitation
4747
expect(log.reload.success_count).to eq 1
4848
end
49+
50+
it 'does not create duplicate entry on retry' do
51+
entry1 = logger.log_success(member, invitation)
52+
entry2 = logger.log_success(member, invitation)
53+
54+
expect(entry2).to eq entry1
55+
expect(log.reload.success_count).to eq 1
56+
end
4957
end
5058

5159
describe '#log_failure' do
@@ -60,6 +68,15 @@
6068
expect(entry.failure_reason).to eq 'SMTP error'
6169
expect(log.reload.failure_count).to eq 1
6270
end
71+
72+
it 'does not create duplicate entry on retry' do
73+
error = StandardError.new('SMTP error')
74+
entry1 = logger.log_failure(member, invitation, error)
75+
entry2 = logger.log_failure(member, invitation, error)
76+
77+
expect(entry2).to eq entry1
78+
expect(log.reload.failure_count).to eq 1
79+
end
6380
end
6481

6582
describe '#log_skipped' do
@@ -73,6 +90,14 @@
7390
expect(entry.failure_reason).to eq 'Already invited'
7491
expect(log.reload.skipped_count).to eq 1
7592
end
93+
94+
it 'does not create duplicate entry on retry' do
95+
entry1 = logger.log_skipped(member, invitation, 'Already invited')
96+
entry2 = logger.log_skipped(member, invitation, 'Already invited')
97+
98+
expect(entry2).to eq entry1
99+
expect(log.reload.skipped_count).to eq 1
100+
end
76101
end
77102

78103
describe '#finish_batch' do

0 commit comments

Comments
 (0)