Skip to content

Commit 88f9311

Browse files
committed
1036: Refactor sso batch to only create new roles where necessary, avoiding an n+1, also refactors to smaller methods to avoid a cyclomatic depdency issue
1 parent a44daab commit 88f9311

2 files changed

Lines changed: 58 additions & 6 deletions

File tree

lib/concepts/school_student/create_batch_sso.rb

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,36 @@ def call(school:, school_students_params:, current_user:)
3232
private
3333

3434
def create_batch_sso(school, students, token)
35+
students = normalize_student_params(students)
36+
responses = ProfileApiClient.create_school_students_sso(token:, students:, school_id: school.id)
37+
38+
create_student_roles(school, responses)
39+
format_student_responses(responses)
40+
rescue ProfileApiClient::Student422Error => e
41+
handle_validations(e.errors)
42+
end
43+
44+
def normalize_student_params(students)
3545
# Ensure that nil values are empty strings, else Profile will swallow validations
36-
students = students.map do |student|
46+
students.map do |student|
3747
student.transform_values { |value| value.nil? ? '' : value }
3848
end
49+
end
3950

40-
responses = ProfileApiClient.create_school_students_sso(token:, students:, school_id: school.id)
51+
def create_student_roles(school, responses)
52+
# Bulk check which roles already exist to avoid N+1 queries
53+
user_ids = responses.pluck(:id)
54+
existing_roles = Role.student.where(school_id: school.id, user_id: user_ids).index_by(&:user_id)
55+
56+
# Create only the missing roles
57+
user_ids.each do |user_id|
58+
next if existing_roles[user_id]
4159

42-
responses.each do |student|
43-
Role.student.find_or_create_by(school_id: school.id, user_id: student[:id])
60+
Role.create!(role: :student, school_id: school.id, user_id: user_id)
4461
end
62+
end
4563

64+
def format_student_responses(responses)
4665
# Convert hash responses to User objects with separate metadata
4766
# This separates student data from metadata (success, error, created flags)
4867
responses.map do |student_data|
@@ -53,8 +72,6 @@ def create_batch_sso(school, students, token)
5372
created: student_data[:created]
5473
}
5574
end
56-
rescue ProfileApiClient::Student422Error => e
57-
handle_validations(e.errors)
5875
end
5976

6077
def handle_validations(errors)

spec/concepts/school_student/create_batch_sso_spec.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
let(:user_ids) { [SecureRandom.uuid, SecureRandom.uuid] }
2424

2525
before do
26+
# Force memoization of user_ids before stub so they're consistent
27+
user_ids
2628
stub_profile_api_create_school_students_sso(user_ids:)
2729
end
2830

@@ -83,6 +85,39 @@
8385
expect(second_student_item[:student].name).to eq('SSO Test Student 2')
8486
expect(second_student_item[:success]).to be(true)
8587
end
88+
89+
context 'when roles already exist for some students' do
90+
let(:user_ids) { [SecureRandom.uuid, SecureRandom.uuid] }
91+
92+
before do
93+
# Pre-create a role for the first student
94+
Role.create!(role: :student, school_id: school.id, user_id: user_ids[0])
95+
end
96+
97+
it 'does not duplicate existing roles' do
98+
roles_before_call = Role.student.where(school_id: school.id).to_a
99+
100+
described_class.call(school:, school_students_params:, current_user:)
101+
102+
roles_after_call = Role.student.where(school_id: school.id).to_a
103+
new_student_roles = roles_after_call - roles_before_call
104+
105+
# Should only create 1 new student role (for second student) since first already exists
106+
expect(new_student_roles.length).to eq(1)
107+
expect(new_student_roles.first.user_id).to eq(user_ids[1])
108+
end
109+
110+
it 'does not raise an error' do
111+
expect do
112+
described_class.call(school:, school_students_params:, current_user:)
113+
end.not_to raise_error
114+
end
115+
116+
it 'still returns all students in the response' do
117+
response = described_class.call(school:, school_students_params:, current_user:)
118+
expect(response[:school_students].length).to eq(2)
119+
end
120+
end
86121
end
87122

88123
context 'when validation errors occur' do

0 commit comments

Comments
 (0)