Skip to content

Commit cc0c2fa

Browse files
committed
fix: add handler for ActiveRecord::RecordInvalid exceptions
1 parent b569a6b commit cc0c2fa

File tree

9 files changed

+84
-71
lines changed

9 files changed

+84
-71
lines changed

app/controllers/concerns/admin/sponsor_concerns.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ def destroy_sponsor
2727
def host
2828
set_sponsor
2929
@workshop_sponsor = WorkshopSponsor.find_or_create_by(workshop: @workshop, sponsor: @sponsor)
30-
@workshop_sponsor.update_attribute(:host, true)
30+
@workshop_sponsor.set_as_host!
3131
flash[:notice] = 'Host set successfully'
3232

3333
redirect_back fallback_location: root_path
3434
end
3535

3636
def destroy_host
37-
@workshop.workshop_sponsors.find_by(host: true).update_attribute(:host, false)
37+
@workshop.workshop_sponsors.find_by(host: true)&.remove_as_host!
3838

3939
redirect_back fallback_location: root_path
4040
end

app/controllers/concerns/invitation_controller_concerns.rb

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,26 +35,25 @@ def accept
3535
def reject
3636
@workshop = WorkshopPresenter.decorate(@invitation.workshop)
3737
if @invitation.workshop.date_and_time - 3.5.hours >= Time.zone.now
38-
3938
if @invitation.attending.eql? false
4039
redirect_back(fallback_location: invitation_path(@invitation),
4140
notice: t('messages.not_attending_already'))
4241
else
43-
@invitation.update_attribute(:attending, false)
44-
45-
next_spot = WaitingList.next_spot(@invitation.workshop, @invitation.role)
46-
47-
if next_spot.present?
48-
invitation = next_spot.invitation
49-
next_spot.destroy
50-
invitation.update(attending: true, rsvp_time: Time.zone.now, automated_rsvp: true)
51-
@workshop.send_attending_email(invitation, true)
42+
begin
43+
WorkshopInvitation.transaction do
44+
@invitation.decline!
45+
promote_from_waitlist(@invitation.workshop, @invitation.role)
46+
end
47+
redirect_back(
48+
fallback_location: invitation_path(@invitation),
49+
notice: t('messages.rejected_invitation', name: @invitation.member.name)
50+
)
51+
rescue ActiveRecord::RecordInvalid
52+
redirect_back(
53+
fallback_location: invitation_path(@invitation),
54+
alert: 'Unable to process cancellation. Please try again.'
55+
)
5256
end
53-
54-
redirect_back(
55-
fallback_location: invitation_path(@invitation),
56-
notice: t('messages.rejected_invitation', name: @invitation.member.name)
57-
)
5857
end
5958
else
6059
redirect_back(
@@ -66,6 +65,16 @@ def reject
6665

6766
private
6867

68+
def promote_from_waitlist(workshop, role)
69+
next_spot = WaitingList.next_spot(workshop, role)
70+
return unless next_spot
71+
72+
invitation = next_spot.invitation
73+
next_spot.destroy!
74+
invitation.accept!(rsvp_time: Time.zone.now, automated_rsvp: true)
75+
WorkshopPresenter.decorate(workshop).send_attending_email(invitation, true)
76+
end
77+
6978
def attending_or_waitlisted?(workshop, user)
7079
workshop.attendee?(user) || workshop.waitlisted?(user)
7180
end

app/controllers/invitations_controller.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ def attend
2727
return redirect_back fallback_location: root_path, notice: t('messages.already_rsvped') if @invitation.attending?
2828

2929
if @invitation.student_spaces? || @invitation.coach_spaces?
30-
@invitation.update_attribute(:attending, true)
30+
@invitation.accept!
3131

3232
notice = t('messages.invitations.spot_confirmed', event: @invitation.event.name)
3333

3434
unless event.confirmation_required || event.surveys_required
35-
@invitation.update_attribute(:verified, true)
35+
@invitation.verify!
3636
EventInvitationMailer.attending(@invitation.event, @invitation.member, @invitation).deliver_now
3737
end
3838
notice = t('messages.invitations.spot_not_confirmed') if event.surveys_required
@@ -54,7 +54,7 @@ def reject
5454
)
5555
end
5656

57-
@invitation.update_attribute(:attending, false)
57+
@invitation.decline!
5858
redirect_back(
5959
fallback_location: root_path,
6060
notice: t('messages.rejected_invitation', name: @invitation.member.name)
@@ -79,7 +79,7 @@ def rsvp_meeting
7979
def cancel_meeting
8080
@invitation = MeetingInvitation.find_by(token: params[:token])
8181

82-
@invitation.update_attribute(:attending, false)
82+
@invitation.decline!
8383

8484
redirect_back fallback_location: root_path, notice: t('messages.invitations.meeting.cancel')
8585
end

spec/controllers/admin/invitations_controller_spec.rb

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,43 @@
33
let(:workshop) { invitation.workshop }
44
let(:admin) { Fabricate(:chapter_organiser) }
55

6-
describe "PUT #update" do
6+
describe 'PUT #update' do
77
before do
88
admin.add_role(:organiser, workshop.chapter)
99

1010
login admin
11-
request.env["HTTP_REFERER"] = "/admin/member/3"
11+
request.env['HTTP_REFERER'] = '/admin/member/3'
1212

1313
expect(invitation.attending).to be_nil
1414
end
1515

16-
it "Successfuly updates an invitation" do
17-
put :update, params: { id: invitation.token, workshop_id: workshop.id, attending: "true" }
18-
16+
it 'Successfuly updates an invitation' do
17+
put :update, params: { id: invitation.token, workshop_id: workshop.id, attending: 'true' }
18+
1919
expect(invitation.reload.attending).to be true
20-
expect(flash[:notice]).to match("You have added")
20+
expect(flash[:notice]).to match('You have added')
2121
end
2222

2323
# While similar to the previous test, this specifically tests that organisers
2424
# have the ability to manually add a student to the workshop that has not
2525
# selected a tutorial. This is helpful for when a student shows up for a
2626
# workshop they have not have a spot — this happens from time to time.
27-
it "Successfuly adds a user as attenting, even without a tutorial" do
28-
invitation.update_attribute(:tutorial, nil)
27+
it 'Successfuly adds a user as attenting, even without a tutorial' do
28+
invitation.update_column(:tutorial, nil)
2929
expect(invitation.automated_rsvp).to be_nil
3030

31-
put :update, params: { id: invitation.token, workshop_id: workshop.id, attending: "true" }
31+
put :update, params: { id: invitation.token, workshop_id: workshop.id, attending: 'true' }
3232
invitation.reload
3333

3434
expect(invitation.attending).to be true
3535
expect(invitation.automated_rsvp).to be true
36-
expect(flash[:notice]).to match("You have added")
36+
expect(flash[:notice]).to match('You have added')
3737
end
3838

39-
it "Records the organiser ID that overrides an invitations" do
40-
put :update, params: { id: invitation.token, workshop_id: workshop.id, attending: "true" }
39+
it 'Records the organiser ID that overrides an invitations' do
40+
put :update, params: { id: invitation.token, workshop_id: workshop.id, attending: 'true' }
4141
invitation.reload
42-
42+
4343
expect(invitation.last_overridden_by_id).to be admin.id
4444
end
4545
end

spec/features/accepting_invitation_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
RSpec.feature 'Accepting a workshop invitation', type: :feature do
2-
context '#workshop' do
2+
describe '#workshop' do
33
let(:member) { Fabricate(:member) }
44
let(:invitation) { Fabricate(:workshop_invitation, member: member, tutorial: tutorial.title) }
55
let(:invitation_route) { invitation_path(invitation) }
66
let(:accept_invitation_route) { accept_invitation_path(invitation) }
77
let(:reject_invitation_route) { reject_invitation_path(invitation) }
8-
let(:set_no_available_slots) { invitation.workshop.host.update_attribute(:seats, 0) }
8+
let(:set_no_available_slots) { invitation.workshop.host.update_column(:seats, 0) }
99
let!(:tutorial) { Fabricate(:tutorial) }
1010

1111
it_behaves_like 'invitation route'
1212

1313
context 'amend invitation details' do
1414
context 'a student' do
15-
scenario 'cannot accept an invitation without a tutorial' do
15+
scenario 'cannot accept an invitation without a tutorial' do
1616
invitation.update(attending: nil, tutorial: nil)
1717
visit invitation_route
1818

@@ -22,7 +22,7 @@
2222
end
2323

2424
scenario 'with an accepted invitation can edit the tutorial' do
25-
invitation.update_attribute(:attending, true)
25+
invitation.accept!
2626
visit invitation_route
2727

2828
select tutorial.title, from: :workshop_invitation_tutorial
@@ -66,7 +66,7 @@
6666
click_on 'Update note'
6767

6868
expect(page).to have_field('workshop_invitation_note', with: note)
69-
expect(page).to have_content("Invitation details successfully updated.")
69+
expect(page).to have_content('Invitation details successfully updated.')
7070
end
7171
end
7272
end

spec/features/coach_accepting_invitation_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
RSpec.feature 'a Coach can', type: :feature do
2-
context '#workshop' do
2+
describe '#workshop' do
33
let(:member) { Fabricate(:member) }
44
let(:invitation) { Fabricate(:coach_workshop_invitation, member: member) }
55
let(:invitation_route) { invitation_path(invitation) }
66
let(:reject_invitation_route) { reject_invitation_path(invitation) }
77
let(:accept_invitation_route) { accept_invitation_path(invitation) }
88

9-
let(:set_no_available_slots) { invitation.workshop.host.update_attribute(:seats, 0) }
9+
let(:set_no_available_slots) { invitation.workshop.host.update_column(:seats, 0) }
1010

11-
before(:each) do
11+
before do
1212
login(member)
1313
end
1414

spec/models/workshop_spec.rb

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
RSpec.describe Workshop do
22
subject(:workshop) { Fabricate(:workshop) }
3-
include_examples "Invitable", :workshop_invitation, :workshop
3+
4+
include_examples 'Invitable', :workshop_invitation, :workshop
45
include_examples DateTimeConcerns, :workshop
56

67
context 'validates' do
78
it { is_expected.to validate_presence_of(:chapter_id) }
89

9-
context "#date_and_time" do
10+
describe '#date_and_time' do
1011
it 'does not validate if chapter_id blank' do
1112
workshop.chapter_id = nil
1213
workshop.date_and_time = nil
@@ -22,7 +23,7 @@
2223
end
2324
end
2425

25-
context '#end_at' do
26+
describe '#end_at' do
2627
it 'does not validate if chapter_id blank' do
2728
workshop.chapter_id = nil
2829
workshop.ends_at = nil
@@ -40,6 +41,7 @@
4041

4142
context 'if virtual' do
4243
before { allow(subject).to receive(:virtual?).and_return(true) }
44+
4345
it { is_expected.to validate_presence_of(:slack_channel) }
4446
it { is_expected.to validate_presence_of(:slack_channel_link) }
4547
it { is_expected.to validate_numericality_of(:student_spaces).is_greater_than(0) }
@@ -63,7 +65,8 @@
6365
end
6466

6567
it 'retrieves the local time from the saved UTC value' do
66-
workshop.update_attribute(:date_and_time, utc_time)
68+
workshop.assign_attributes(date_and_time: utc_time)
69+
workshop.save!(validate: false)
6770

6871
expect(workshop.date_and_time).to eq(pacific_time)
6972
expect(workshop.date_and_time.zone).to eq('PDT')
@@ -81,15 +84,16 @@
8184
end
8285

8386
it 'retrieves the local time from the saved UTC value' do
84-
workshop.update_attribute(:rsvp_opens_at, utc_time)
87+
workshop.assign_attributes(rsvp_opens_at: utc_time)
88+
workshop.save!(validate: false)
8589

8690
expect(workshop.rsvp_opens_at).to eq(pacific_time)
8791
expect(workshop.rsvp_opens_at.zone).to eq('PDT')
8892
end
8993
end
9094
end
9195

92-
context '#rsvp_available?' do
96+
describe '#rsvp_available?' do
9397
context 'rsvp is available' do
9498
it 'when the event is in the future' do
9599
workshop.date_and_time = 1.day.from_now
@@ -119,7 +123,7 @@
119123
end
120124
end
121125

122-
context '#to_s' do
126+
describe '#to_s' do
123127
it 'when physical workshop' do
124128
expect(workshop.to_s).to eq('Workshop')
125129
end
@@ -130,7 +134,7 @@
130134
end
131135
end
132136

133-
context '#scopes' do
137+
describe '#scopes' do
134138
describe '#host' do
135139
it 'includes workshops with sponsored hosts' do
136140
workshop_sponsor = Fabricate(:workshop_sponsor, host: true)
@@ -189,7 +193,7 @@
189193
end
190194
end
191195

192-
context '#invitable_yet?' do
196+
describe '#invitable_yet?' do
193197
it 'is invitable if invitable set to true, no RSVP open time/date set' do
194198
workshop = Fabricate.build(:workshop, invitable: true)
195199
expect(workshop.invitable_yet?).to be true

spec/presenters/invitation_presenter_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
expect(invitation_presenter.member).to eq(invitation.member)
77
end
88

9-
context '#attendance_status' do
9+
describe '#attendance_status' do
1010
it 'returns Attending when attending' do
11-
invitation.update_attribute(:attending, true)
11+
invitation.accept!
1212

1313
expect(invitation_presenter.attendance_status).to eq('Attending')
1414
end

0 commit comments

Comments
 (0)