-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support multiple teachers per class #456
base: main
Are you sure you want to change the base?
Changes from 30 commits
a876fcb
c81c521
8d9eec7
ea9ea6e
c8f54e1
720ef16
a6a5a2f
d41dd2a
0ca6424
6215d97
f050fbc
287cd4a
82fb69e
8e4fba0
003a012
e4029e4
3024068
3ec5215
36add40
3d19d2e
6714801
51c855e
ee3fca7
dbf031f
9b0e9b4
30fa842
c670d11
b697e17
4a8bed9
0a418c1
c0c49f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# frozen_string_literal: true | ||
|
||
class ClassTeacher < ApplicationRecord | ||
belongs_to :school_class | ||
delegate :school, to: :school_class | ||
attr_accessor :teacher | ||
|
||
validates :teacher_id, presence: true, uniqueness: { | ||
scope: :school_class_id, | ||
case_sensitive: false | ||
} | ||
|
||
validate :teacher_has_the_school_teacher_role_for_the_school | ||
|
||
has_paper_trail( | ||
meta: { | ||
meta_school_id: ->(cm) { cm.school_class&.school_id } | ||
} | ||
) | ||
|
||
private | ||
|
||
def teacher_has_the_school_teacher_role_for_the_school | ||
return unless teacher_id_changed? && errors.blank? && teacher.present? | ||
|
||
return if teacher.school_teacher?(school) | ||
|
||
msg = "'#{teacher.id}' does not have the 'school-teacher' role for organisation '#{school.id}'" | ||
errors.add(:teacher, msg) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,7 @@ class Project < ApplicationRecord | |
validate :identifier_cannot_be_taken_by_another_user | ||
validates :locale, presence: true, unless: :user_id | ||
validate :user_has_a_role_within_the_school | ||
validate :user_is_a_member_or_the_owner_of_the_lesson | ||
validate :user_is_class_teacher_or_student | ||
validate :project_with_instructions_must_belong_to_school | ||
validate :project_with_school_id_has_school_project | ||
validate :school_project_school_matches_project_school | ||
|
@@ -96,11 +96,22 @@ def user_has_a_role_within_the_school | |
errors.add(:user, msg) | ||
end | ||
|
||
def user_is_a_member_or_the_owner_of_the_lesson | ||
def user_is_class_teacher_or_student | ||
# TODO: Revisit the case where the lesson is not associated to a class i.e. when we build a lesson library | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. think we can remove this todo now? |
||
return if !lesson || user_id == lesson.user_id || !lesson.school_class || lesson.school_class&.members&.exists?(student_id: user_id) | ||
no_lesson = !lesson | ||
no_school_class = lesson && !lesson.school_class | ||
|
||
errors.add(:user, "'#{user_id}' is not the owner or a member of the lesson '#{lesson_id}'") | ||
return if no_lesson || no_school_class || user_is_class_student || user_is_class_teacher | ||
|
||
errors.add(:user, "'#{user_id}' is not a class member or the owner of the lesson '#{lesson_id}'") | ||
end | ||
|
||
def user_is_class_student | ||
lesson&.school_class&.students&.exists?(student_id: user_id) | ||
end | ||
|
||
def user_is_class_teacher | ||
lesson&.school_class&.class_teachers&.exists?(teacher_id: user_id) | ||
end | ||
|
||
def project_with_instructions_must_belong_to_school | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,12 +2,15 @@ | |
|
||
class SchoolClass < ApplicationRecord | ||
belongs_to :school | ||
has_many :members, class_name: :ClassMember, inverse_of: :school_class, dependent: :destroy | ||
has_many :students, class_name: :ClassStudent, inverse_of: :school_class, dependent: :destroy | ||
has_many :class_teachers, class_name: :ClassTeacher, inverse_of: :school_class, dependent: :destroy | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why use |
||
has_many :lessons, dependent: :nullify | ||
accepts_nested_attributes_for :class_teachers | ||
|
||
scope :with_class_teachers, ->(user_id) { joins(:class_teachers).where(class_teachers: { id: user_id }) } | ||
|
||
validates :teacher_id, presence: true | ||
validates :name, presence: true | ||
validate :teacher_has_the_school_teacher_role_for_the_school | ||
validate :school_class_has_at_least_one_teacher | ||
|
||
has_paper_trail( | ||
meta: { | ||
|
@@ -16,27 +19,28 @@ class SchoolClass < ApplicationRecord | |
) | ||
|
||
def self.teachers | ||
User.from_userinfo(ids: pluck(:teacher_id)) | ||
teacher_ids = all.map(&:teacher_ids).flatten.uniq | ||
User.from_userinfo(ids: teacher_ids) | ||
end | ||
|
||
def self.with_teachers | ||
by_id = teachers.index_by(&:id) | ||
all.map { |instance| [instance, by_id[instance.teacher_id]] } | ||
all.map { |instance| [instance, instance.teacher_ids.map { |teacher_id| by_id[teacher_id] }] } | ||
end | ||
|
||
def with_teacher | ||
[self, User.from_userinfo(ids: teacher_id).first] | ||
def teacher_ids | ||
class_teachers.pluck(:teacher_id) | ||
end | ||
|
||
private | ||
def with_teachers | ||
[self, User.from_userinfo(ids: teacher_ids)] | ||
end | ||
|
||
def teacher_has_the_school_teacher_role_for_the_school | ||
return unless teacher_id_changed? && errors.blank? | ||
private | ||
|
||
user = User.new(id: teacher_id) | ||
return if user.school_teacher?(school) | ||
def school_class_has_at_least_one_teacher | ||
return if class_teachers.present? | ||
|
||
msg = "'#{teacher_id}' does not have the 'school-teacher' role for organisation '#{school.id}'" | ||
errors.add(:user, msg) | ||
errors.add(:class_teachers, 'must have at least one teacher') | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,23 @@ | ||
# frozen_string_literal: true | ||
|
||
json.array!(@school_classes_with_teachers) do |school_class, teacher| | ||
json.array!(@school_classes_with_teachers) do |school_class, teachers| | ||
json.call( | ||
school_class, | ||
:id, | ||
:description, | ||
:school_id, | ||
:teacher_id, | ||
:name, | ||
:created_at, | ||
:updated_at | ||
) | ||
|
||
json.teacher_name(teacher&.name) | ||
json.teachers(teachers) do |teacher| | ||
if teacher.present? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there is already a _school_teacher.json.builder template you can use here and in the show template, you might just need to make the email optional |
||
json.call( | ||
teacher, | ||
:id, | ||
:name | ||
) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,21 @@ | ||
# frozen_string_literal: true | ||
|
||
school_class, teacher = @school_class_with_teacher | ||
school_class, teachers = @school_class_with_teachers | ||
|
||
json.call( | ||
school_class, | ||
:id, | ||
:description, | ||
:school_id, | ||
:teacher_id, | ||
:name, | ||
:created_at, | ||
:updated_at | ||
) | ||
|
||
json.teacher_name(teacher&.name) | ||
json.teachers(teachers) do |teacher| | ||
json.call( | ||
teacher, | ||
:id, | ||
:name | ||
) | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
class CreateClassTeachers < ActiveRecord::Migration[7.1] | ||
def change | ||
create_table :class_teachers, id: :uuid do |t| | ||
t.references :school_class, type: :uuid, foreign_key: true, index: true, null: false | ||
t.uuid :teacher_id, null: false | ||
t.timestamps | ||
end | ||
|
||
add_index :class_teachers, :teacher_id | ||
add_index :class_teachers, %i[school_class_id teacher_id], unique: true | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
class MoveTeacherDataToClassTeachersTable < ActiveRecord::Migration[7.1] | ||
def up | ||
SchoolClass.find_each do |school_class| | ||
ClassTeacher.create!(school_class: school_class, teacher_id: school_class.teacher_id) | ||
end | ||
end | ||
|
||
def down | ||
ClassTeacher.destroy_all | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
class RemoveClassTeacherId < ActiveRecord::Migration[7.1] | ||
def up | ||
remove_column :school_classes, :teacher_id | ||
end | ||
|
||
def down | ||
add_column :school_classes, :teacher_id, :uuid | ||
ClassTeacher.find_each do |class_teacher| | ||
school_class = class_teacher.school_class | ||
school_class.update!(teacher_id: class_teacher.teacher_id) | ||
end | ||
change_column_null :school_classes, :teacher_id, false | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
class RenameClassMembersToClassStudents < ActiveRecord::Migration[7.1] | ||
def up | ||
rename_table :class_members, :class_students | ||
end | ||
|
||
def down | ||
rename_table :class_students, :class_members | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you need
project.lesson_id.present?
? I think that's implicit byproject.lesson
existing, since the relationship wouldn't exist otherwise