Skip to content

Commit 3bb9682

Browse files
YaleChen299angelsl
authored andcommitted
Avoid use of Ecto schemas in migrations (#794)
1 parent 8ebea1b commit 3bb9682

File tree

2 files changed

+128
-198
lines changed

2 files changed

+128
-198
lines changed

priv/repo/migrations/20210531155751_multitenant_upgrade.exs

Lines changed: 126 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@ defmodule Cadet.Repo.Migrations.MultitenantUpgrade do
22
use Ecto.Migration
33
import Ecto.Query
44

5-
alias Cadet.Accounts.{CourseRegistration, Notification}
6-
alias Cadet.Assessments.{Answer, Assessment, Question, Submission, SubmissionVotes}
7-
alias Cadet.Courses.{AssessmentConfig, Course, Group, Sourcecast}
8-
alias Cadet.Repo
9-
alias Cadet.Stories.Story
10-
115
def change do
126
# Tracks course configurations
137
create table(:courses) do
@@ -139,236 +133,172 @@ defmodule Cadet.Repo.Migrations.MultitenantUpgrade do
139133
execute(
140134
fn ->
141135
# Create the new course for migration
142-
{:ok, course} =
143-
%Course{}
144-
|> Course.changeset(%{
145-
course_name: "CS1101S Programming Methodology (AY21/22 Sem 1)",
146-
course_short_name: "CS1101S",
147-
viewable: true,
148-
enable_game: true,
149-
enable_achievments: true,
150-
enable_sourcecast: true,
151-
source_chapter: 1,
152-
source_variant: "default"
153-
})
154-
|> Repo.insert()
136+
{1, [course | _]} =
137+
repo().insert_all(
138+
"courses",
139+
[
140+
%{
141+
course_name: "CS1101S Programming Methodology (AY21/22 Sem 1)",
142+
course_short_name: "CS1101S",
143+
viewable: true,
144+
enable_game: true,
145+
enable_achievements: true,
146+
enable_sourcecast: true,
147+
source_chapter: 1,
148+
source_variant: "default",
149+
inserted_at: Timex.now(),
150+
updated_at: Timex.now()
151+
}
152+
],
153+
returning: [:id]
154+
)
155155

156156
# Namespace existing usernames
157157
from(u in "users", update: [set: [username: fragment("? || ? ", "luminus/", u.username)]])
158158
|> repo().update_all([])
159159

160160
# Create course registrations for existing users
161-
from(u in "users", select: {u.id, u.role, u.group_id, u.game_states})
162-
|> Repo.all()
163-
|> Enum.each(fn user ->
164-
%CourseRegistration{}
165-
|> CourseRegistration.changeset(%{
166-
user_id: elem(user, 0),
167-
role: elem(user, 1),
168-
group_id: elem(user, 2),
169-
game_states: elem(user, 3),
170-
course_id: course.id
161+
from(u in "users",
162+
select: %{
163+
user_id: u.id,
164+
role: u.role,
165+
group_id: u.group_id,
166+
game_states: u.game_states
167+
}
168+
)
169+
|> repo().all()
170+
|> Enum.map(fn user ->
171+
Map.merge(user, %{
172+
course_id: course.id,
173+
inserted_at: Timex.now(),
174+
updated_at: Timex.now()
171175
})
172-
|> Repo.insert()
173176
end)
177+
|> (&repo().insert_all("course_registrations", &1)).()
174178

175179
# Add latest_viewed_id to existing users
176180
repo().update_all("users", set: [latest_viewed_id: course.id])
177181

178-
# Handle groups (adding course_id, and updating leader_id to course registrations)
179-
from(g in "groups", select: {g.id, g.temp_leader_id})
180-
|> Repo.all()
181-
|> Enum.each(fn group ->
182-
leader_id =
183-
case elem(group, 1) do
184-
# leader_id is now going to be non-nullable. if it was previously nil, we will just
185-
# assign a staff to be the leader_id during migration
186-
nil ->
187-
CourseRegistration
188-
|> where([cr], cr.role in [:admin, :staff])
189-
|> Repo.one()
190-
|> Map.fetch!(:id)
191-
192-
id ->
193-
CourseRegistration
194-
|> where(user_id: ^id)
195-
|> Repo.one()
196-
|> Map.fetch!(:id)
197-
end
198-
199-
Group
200-
|> where(id: ^elem(group, 0))
201-
|> Repo.one()
202-
|> Group.changeset(%{leader_id: leader_id, course_id: course.id})
203-
|> Repo.update()
204-
end)
182+
# Handle groups, adding course_id
183+
repo().update_all("groups", set: [course_id: course.id])
205184

206185
# Update existing Path questions with new question config
207186
# The questions from other assessment types are not updated as these fields default to false
208187
from(q in "questions",
209188
join: a in "assessments",
210189
on: a.id == q.assessment_id,
211-
where: a.type == "path",
212-
select: q.id
190+
where: a.type == "path"
213191
)
214-
|> Repo.all()
215-
|> Enum.each(fn question_id ->
216-
Question
217-
|> Repo.get(question_id)
218-
|> Question.changeset(%{
192+
|> repo().update_all(
193+
set: [
219194
show_solution: true,
220195
build_hidden_testcases: true,
221196
blocking: true
222-
})
223-
|> Repo.update()
224-
end)
197+
]
198+
)
225199

226200
# Create Assessment Configurations based on Source Academy Knight
227-
["Missions", "Quests", "Paths", "Contests", "Others"]
228-
|> Enum.with_index(1)
229-
|> Enum.each(fn {assessment_type, idx} ->
230-
%AssessmentConfig{}
231-
|> AssessmentConfig.changeset(%{
232-
order: idx,
233-
type: assessment_type,
234-
course_id: course.id,
235-
show_grading_summary: assessment_type in ["Missions", "Quests"],
236-
is_manually_graded: assessment_type != "Paths",
237-
early_submission_xp: 200,
238-
hours_before_early_xp_decay: 48
239-
})
240-
|> Repo.insert()
241-
end)
201+
{5, configs} =
202+
["Missions", "Quests", "Paths", "Contests", "Others"]
203+
|> Enum.with_index(1)
204+
|> Enum.map(fn {assessment_type, idx} ->
205+
%{
206+
order: idx,
207+
type: assessment_type,
208+
course_id: course.id,
209+
show_grading_summary: assessment_type in ["Missions", "Quests"],
210+
is_manually_graded: assessment_type != "Paths",
211+
early_submission_xp: 100,
212+
hours_before_early_xp_decay: 24,
213+
inserted_at: Timex.now(),
214+
updated_at: Timex.now()
215+
}
216+
end)
217+
|> (&repo().insert_all("assessment_configs", &1, returning: [:id])).()
218+
219+
# assessment_configs = repo().insert_all("assessment_configs", configs, returning: [:id])
242220

243221
# Link existing assessments to an assessment config and course
244-
from(a in "assessments", select: {a.id, a.type})
245-
|> Repo.all()
246-
|> Enum.each(fn assessment ->
247-
assessment_type =
248-
case elem(assessment, 1) do
249-
"mission" -> "Missions"
250-
"sidequest" -> "Quests"
251-
"path" -> "Paths"
252-
"contest" -> "Contests"
253-
"practical" -> "Others"
254-
end
255-
256-
assessment_config =
257-
AssessmentConfig
258-
|> where(type: ^assessment_type)
259-
|> Repo.one()
260-
261-
Assessment
262-
|> where(id: ^elem(assessment, 0))
263-
|> Repo.one()
264-
|> Assessment.changeset(%{config_id: assessment_config.id, course_id: course.id})
265-
|> Repo.update()
222+
[
223+
{"mission", "Missions"},
224+
{"sidequest", "Quests"},
225+
{"path", "Paths"},
226+
{"contest", "Contests"},
227+
{"practical", "Others"}
228+
]
229+
|> Enum.each(fn {old_type, new_type} ->
230+
config_id =
231+
from(ac in "assessment_configs", where: ac.type == ^new_type, select: ac.id)
232+
|> repo().all()
233+
|> Enum.at(0)
234+
235+
from(a in "assessments", where: a.type == ^old_type)
236+
|> repo().update_all(
237+
set: [
238+
config_id: config_id,
239+
course_id: course.id
240+
]
241+
)
266242
end)
267243

268244
# Updating student_id and unsubmitted_by_id from User to CourseRegistration
269-
from(s in "submissions", select: {s.id, s.temp_student_id, s.temp_unsubmitted_by_id})
270-
|> Repo.all()
271-
|> Enum.each(fn submission ->
272-
student_id =
273-
CourseRegistration
274-
|> where(user_id: ^elem(submission, 1))
275-
|> Repo.one()
276-
|> Map.fetch!(:id)
277-
278-
unsubmitted_by_id =
279-
case elem(submission, 2) do
280-
nil ->
281-
nil
282-
283-
id ->
284-
CourseRegistration
285-
|> where(user_id: ^id)
286-
|> Repo.one()
287-
|> Map.fetch!(:id)
288-
end
289-
290-
Submission
291-
|> where(id: ^elem(submission, 0))
292-
|> Repo.one()
293-
|> Submission.changeset(%{student_id: student_id, unsubmitted_by_id: unsubmitted_by_id})
294-
|> Repo.update()
295-
end)
245+
from(
246+
s in "submissions",
247+
join: st in "course_registrations",
248+
on: st.user_id == s.temp_student_id,
249+
update: [set: [student_id: st.id]]
250+
)
251+
|> repo().update_all([])
296252

297-
from(a in "answers", select: {a.id, a.temp_grader_id})
298-
|> Repo.all()
299-
|> Enum.each(fn answer ->
300-
case elem(answer, 1) do
301-
nil ->
302-
nil
303-
304-
user_id ->
305-
grader_id =
306-
CourseRegistration
307-
|> where(user_id: ^user_id)
308-
|> Repo.one()
309-
|> Map.fetch!(:id)
310-
311-
Answer
312-
|> where(id: ^elem(answer, 0))
313-
|> Repo.one()
314-
|> Answer.grading_changeset(%{grader_id: grader_id})
315-
|> Repo.update()
316-
end
317-
end)
253+
from(
254+
s in "submissions",
255+
join: cr in "course_registrations",
256+
on: cr.user_id == s.temp_unsubmitted_by_id,
257+
update: [set: [unsubmitted_by_id: cr.id]]
258+
)
259+
|> repo().update_all([])
318260

319-
from(s in "submission_votes", select: {s.id, s.user_id})
320-
|> Repo.all()
321-
|> Enum.each(fn vote ->
322-
voter_id =
323-
CourseRegistration
324-
|> where(user_id: ^elem(vote, 1))
325-
|> Repo.one()
326-
|> Map.fetch!(:id)
327-
328-
SubmissionVotes
329-
|> where(id: ^elem(vote, 0))
330-
|> Repo.one()
331-
|> SubmissionVotes.changeset(%{voter_id: voter_id})
332-
|> Repo.update()
333-
end)
261+
# Updating grader_id in answer from User to CourseRegistration
262+
from(
263+
a in "answers",
264+
join: cr in "course_registrations",
265+
on: cr.user_id == a.temp_grader_id,
266+
update: [set: [grader_id: cr.id]]
267+
)
268+
|> repo().update_all([])
334269

335-
from(n in "notifications", select: {n.id, n.user_id})
336-
|> Repo.all()
337-
|> Enum.each(fn notification ->
338-
course_reg_id =
339-
CourseRegistration
340-
|> where(user_id: ^elem(notification, 1))
341-
|> Repo.one()
342-
|> Map.fetch!(:id)
343-
344-
Notification
345-
|> where(id: ^elem(notification, 0))
346-
|> Repo.one()
347-
|> Notification.changeset(%{read: true, course_reg_id: course_reg_id})
348-
|> Repo.update()
349-
end)
270+
# Updating user_id to voter_id of CourseRegistration
271+
from(
272+
s in "submission_votes",
273+
join: cr in "course_registrations",
274+
on: cr.user_id == s.user_id,
275+
update: [set: [voter_id: cr.id]]
276+
)
277+
|> repo().update_all([])
278+
279+
# Updating user_id to course_reg_id in Notification
280+
from(
281+
n in "notifications",
282+
join: cr in "course_registrations",
283+
on: cr.user_id == n.user_id,
284+
update: [set: [course_reg_id: cr.id]]
285+
)
286+
|> repo().update_all([])
350287

351288
# Add course id to all Sourcecasts
352-
Sourcecast
353-
|> Repo.all()
354-
|> Enum.each(fn x ->
355-
x
356-
|> Sourcecast.changeset(%{course_id: course.id})
357-
|> Repo.update()
358-
end)
289+
repo().update_all("sourcecasts", set: [course_id: course.id])
359290

360291
# Add course id to all Stories
361-
Story
362-
|> Repo.all()
363-
|> Enum.each(fn x ->
364-
x
365-
|> Story.changeset(%{course_id: course.id})
366-
|> Repo.update()
367-
end)
292+
repo().update_all("stories", set: [course_id: course.id])
368293
end,
369294
fn -> nil end
370295
)
371296

297+
# Update leader_id to course registrations)
298+
execute(
299+
"update groups g set leader_id = coalesce((select cr.id from course_registrations cr where cr.user_id = g.temp_leader_id), (select cr.id from course_registrations cr where cr.role = 'staff' or cr.role = 'admin'))"
300+
)
301+
372302
# Cleanup users table after data migration
373303
alter table(:users) do
374304
remove(:role)

priv/repo/migrations/20210716073359_update_achievement.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ defmodule Cadet.Repo.Migrations.UpdateAchievement do
1616
end
1717

1818
execute(fn ->
19-
courses = from(c in "courses", select: {c.id}) |> repo().all()
20-
course_id = courses |> Enum.at(0) |> elem(0)
19+
courses = from(c in "courses", select: c.id) |> repo().all()
20+
course_id = courses |> Enum.at(0)
2121
repo().update_all("achievements", set: [course_id: course_id])
2222
repo().update_all("goals", set: [course_id: course_id])
2323
end)

0 commit comments

Comments
 (0)