Skip to content

Commit dfaf90e

Browse files
committed
Enforce uniqueness constraint on external_id of study_groups
Similar to e9ab9f8, an external_id of study_groups should be unique. In addition to the previously-configured PostgreSQL constraint, we also add a Rails model validation. Furthermore, we adjust the PostgreSQL index by specifying `nulls_not_distinct: true`. This is beneficial, since the external_id of study_groups CAN be NULL (in exactly one case, namely for the default study group per consumer). In all other cases, the external_id MUST be set (which this index enforces). Finally, we use this opportunity to update the migrate_study_group.sql script in case it is needed.
1 parent e9ab9f8 commit dfaf90e

File tree

6 files changed

+54
-10
lines changed

6 files changed

+54
-10
lines changed

app/controllers/concerns/lti.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,17 +196,19 @@ def set_user
196196
def set_study_group_membership
197197
group = if context_id?
198198
# Ensure to find the group independent of the name and set it only once.
199-
StudyGroup.find_or_create_by(external_id: @provider.context_id, consumer: @consumer) do |new_group|
199+
StudyGroup.find_or_create_by!(external_id: @provider.context_id, consumer: @consumer) do |new_group|
200200
new_group.name = @provider.context_title
201201
end
202202
else
203-
StudyGroup.find_or_create_by(external_id: @provider.resource_link_id, consumer: @consumer)
203+
StudyGroup.find_or_create_by!(external_id: @provider.resource_link_id, consumer: @consumer)
204204
end
205205

206-
study_group_membership = StudyGroupMembership.find_or_create_by(study_group: group, user: @user)
206+
study_group_membership = StudyGroupMembership.find_or_create_by!(study_group: group, user: @user)
207207
study_group_membership.update(role: external_user_role(@provider))
208208
session[:study_group_id] = group.id
209209
@user.store_current_study_group_id(group.id)
210+
rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
211+
retry
210212
end
211213

212214
def set_embedding_options

app/models/study_group.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ class StudyGroup < ApplicationRecord
1414
has_many :pair_programming_exercise_feedbacks
1515
belongs_to :consumer
1616

17+
validates :external_id, uniqueness: {scope: :consumer_id}
18+
1719
def users
1820
external_users + internal_users
1921
end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
3+
class ChangeUniquenessConstraintForStudyGroupExternalId < ActiveRecord::Migration[8.0]
4+
def change
5+
remove_index :study_groups, column: %i[external_id consumer_id], unique: true
6+
add_index :study_groups, %i[external_id consumer_id], unique: true, nulls_not_distinct: true
7+
end
8+
end

db/schema.rb

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

db/scripts/migrate_study_group.sql

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,45 @@ BEGIN
1010
RETURN;
1111
END IF;
1212

13+
UPDATE authentication_tokens SET study_group_id = target_study_group WHERE study_group_id = duplicated_study_group;
1314
UPDATE community_solution_contributions SET study_group_id = target_study_group WHERE study_group_id = duplicated_study_group;
15+
UPDATE events SET study_group_id = target_study_group WHERE study_group_id = duplicated_study_group;
16+
UPDATE events_synchronized_editor SET study_group_id = target_study_group WHERE study_group_id = duplicated_study_group;
17+
18+
WITH existing_lti_parameters AS (SELECT CONCAT(exercise_id, '_', external_user_id) as existing
19+
FROM lti_parameters
20+
WHERE study_group_id = target_study_group)
21+
DELETE FROM lti_parameters
22+
WHERE study_group_id = duplicated_study_group
23+
AND CONCAT(exercise_id, '_', external_user_id) IN (SELECT existing FROM existing_lti_parameters);
24+
25+
UPDATE lti_parameters SET study_group_id = target_study_group WHERE study_group_id = duplicated_study_group;
26+
27+
WITH existing_pair_programming_exercise_feedbacks
28+
AS (SELECT CONCAT(exercise_id, '_', user_type, '_', user_id, '_', programming_group_id) as existing
29+
FROM pair_programming_exercise_feedbacks
30+
WHERE study_group_id = target_study_group)
31+
DELETE FROM pair_programming_exercise_feedbacks
32+
WHERE study_group_id = duplicated_study_group
33+
AND CONCAT(exercise_id, '_', user_type, '_', user_id, '_', programming_group_id) IN
34+
(SELECT existing FROM existing_pair_programming_exercise_feedbacks);
35+
36+
UPDATE pair_programming_exercise_feedbacks SET study_group_id = target_study_group WHERE study_group_id = duplicated_study_group;
1437
UPDATE remote_evaluation_mappings SET study_group_id = target_study_group WHERE study_group_id = duplicated_study_group;
15-
UPDATE authentication_tokens SET study_group_id = target_study_group WHERE study_group_id = duplicated_study_group;
16-
UPDATE subscriptions SET study_group_id = target_study_group WHERE study_group_id = duplicated_study_group;
1738
UPDATE submissions SET study_group_id = target_study_group WHERE study_group_id = duplicated_study_group;
1839

19-
-- Preventing duplicated entries in exercises_proxy_exercises
20-
-- The same proxy exercise should not have two entries for the same exercise it proxies.
40+
WITH existing_subscriptions AS (SELECT CONCAT(request_for_comment_id, '_', user_type, '_', user_id) as existing
41+
FROM subscriptions
42+
WHERE study_group_id = target_study_group)
43+
DELETE FROM subscriptions
44+
WHERE study_group_id = duplicated_study_group
45+
AND CONCAT(request_for_comment_id, '_', user_type, '_', user_id) IN (SELECT existing FROM existing_subscriptions);
46+
47+
-- We don't care about the subscription_type nor deleted flag here.
48+
UPDATE subscriptions SET study_group_id = target_study_group WHERE study_group_id = duplicated_study_group;
49+
50+
-- Preventing duplicated entries in study_group_memberships
51+
-- The same study group should not have two entries for the same user.
2152
DELETE
2253
FROM study_group_memberships
2354
WHERE study_group_id = duplicated_study_group
@@ -37,7 +68,7 @@ $$;
3768
/* Execute migration
3869
do $$
3970
begin
40-
perform migrate_study_group(target_study_group := 237, duplicated_study_group := 695);
71+
perform migrate_study_group(target_study_group := 237, duplicated_study_group := 695);
4172
end
4273
$$;
4374
*/

spec/factories/study_group.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
FactoryBot.define do
44
factory :study_group, class: 'StudyGroup' do
55
consumer
6+
external_id { SecureRandom.uuid }
67
sequence :name do |n|
78
"TestGroup#{n}"
89
end

0 commit comments

Comments
 (0)