Skip to content

Commit 5f1a503

Browse files
committed
fix: make join enrollment idempotent under concurrent requests
Wraps role and class-membership creation in a transaction, switches the check-then-create pattern to find_or_create_by!, and rescues ActiveRecord::RecordNotUnique so that two simultaneous POSTs to /api/join/:join_code for the same user no longer surface a 500 when the DB unique index catches a race that the in-memory existence checks missed.
1 parent 348efe2 commit 5f1a503

1 file changed

Lines changed: 9 additions & 2 deletions

File tree

app/controllers/api/join_controller.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,21 @@ def user_in_different_school?
9696
end
9797

9898
def add_student_to_school_and_class
99-
Role.create!(school: @school, user_id: current_user.id, role: :student) unless Role.exists?(school: @school, user_id: current_user.id)
100-
ClassStudent.create!(school_class: @school_class, student_id: current_user.id)
99+
ActiveRecord::Base.transaction do
100+
Role.find_or_create_by!(school: @school, user_id: current_user.id, role: :student)
101+
ClassStudent.find_or_create_by!(school_class: @school_class, student_id: current_user.id)
102+
end
103+
rescue ActiveRecord::RecordNotUnique
104+
# Concurrent join request for the same user/class — DB unique index
105+
# caught a race we couldn't catch at validation time. Already enrolled.
101106
end
102107

103108
def add_user_to_class_as_teacher
104109
class_teacher = @school_class.teachers.build(teacher_id: current_user.id)
105110
class_teacher.teacher = current_user
106111
class_teacher.save!
112+
rescue ActiveRecord::RecordNotUnique
113+
# Concurrent join request for the same teacher/class — already enrolled.
107114
end
108115
end
109116
end

0 commit comments

Comments
 (0)