From 3ca9ae343f5ebb871b71ce0d8c73b5f68c6612cb Mon Sep 17 00:00:00 2001 From: endoooo Date: Thu, 12 Dec 2024 17:20:10 -0300 Subject: [PATCH 1/7] chore: created base migration and schema for students records and classes relationship - created `students_records_classes` table - created `StudentsRecords.StudentRecordClassRelationship` schema - added classes fields to `StudentRecord` schema --- .../students_records/student_record.ex | 28 +++++++++++++++++++ .../student_record_class_relationship.ex | 22 +++++++++++++++ .../student_record_relationship.ex | 2 +- ...195844_create_students_records_classes.exs | 28 +++++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 lib/lanttern/students_records/student_record_class_relationship.ex create mode 100644 priv/repo/migrations/20241212195844_create_students_records_classes.exs diff --git a/lib/lanttern/students_records/student_record.ex b/lib/lanttern/students_records/student_record.ex index 4d6ab6cf..b6a08db4 100644 --- a/lib/lanttern/students_records/student_record.ex +++ b/lib/lanttern/students_records/student_record.ex @@ -8,9 +8,11 @@ defmodule Lanttern.StudentsRecords.StudentRecord do import LantternWeb.Gettext + alias Lanttern.StudentsRecords.StudentRecordClassRelationship alias Lanttern.StudentsRecords.StudentRecordRelationship alias Lanttern.StudentsRecords.StudentRecordStatus alias Lanttern.StudentsRecords.StudentRecordType + alias Lanttern.Schools.Class alias Lanttern.Schools.School alias Lanttern.Schools.Student @@ -21,6 +23,9 @@ defmodule Lanttern.StudentsRecords.StudentRecord do date: Date.t(), time: Time.t(), students: [Student.t()], + students_ids: [pos_integer()], + classes: [Class.t()], + classes_ids: [pos_integer()], school_id: pos_integer(), school: School.t(), status_id: pos_integer(), @@ -37,17 +42,21 @@ defmodule Lanttern.StudentsRecords.StudentRecord do field :date, :date field :time, :time field :students_ids, {:array, :id}, virtual: true + field :classes_ids, {:array, :id}, virtual: true belongs_to :school, School belongs_to :status, StudentRecordStatus belongs_to :type, StudentRecordType has_many :students_relationships, StudentRecordRelationship, on_replace: :delete + has_many :classes_relationships, StudentRecordClassRelationship, on_replace: :delete many_to_many :students, Student, join_through: "students_students_records", preload_order: [asc: :name] + many_to_many :classes, Class, join_through: "students_records_classes" + timestamps() end @@ -60,12 +69,14 @@ defmodule Lanttern.StudentsRecords.StudentRecord do :date, :time, :students_ids, + :classes_ids, :school_id, :type_id, :status_id ]) |> validate_required([:description, :date, :school_id, :type_id, :status_id]) |> cast_and_validate_students() + |> cast_classes() end def cast_and_validate_students(changeset) do @@ -96,4 +107,21 @@ defmodule Lanttern.StudentsRecords.StudentRecord do end defp cast_students(changeset, _), do: changeset + + defp cast_classes(changeset) do + case get_change(changeset, :classes_ids) do + classes_ids when is_list(classes_ids) -> + school_id = get_field(changeset, :school_id) + + classes_relationships_params = + Enum.map(classes_ids, &%{class_id: &1, school_id: school_id}) + + changeset + |> put_change(:classes_relationships, classes_relationships_params) + |> cast_assoc(:classes_relationships) + + _ -> + changeset + end + end end diff --git a/lib/lanttern/students_records/student_record_class_relationship.ex b/lib/lanttern/students_records/student_record_class_relationship.ex new file mode 100644 index 00000000..8ea4e5c3 --- /dev/null +++ b/lib/lanttern/students_records/student_record_class_relationship.ex @@ -0,0 +1,22 @@ +defmodule Lanttern.StudentsRecords.StudentRecordClassRelationship do + @moduledoc """ + The `StudentRecordClassRelationship` schema (join table) + """ + + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + schema "students_records_classes" do + field :student_record_id, :id, primary_key: true + field :class_id, :id, primary_key: true + field :school_id, :id + end + + @doc false + def changeset(student_record_class_relationship, attrs) do + student_record_class_relationship + |> cast(attrs, [:class_id, :student_record_id, :school_id]) + |> validate_required([:class_id, :student_record_id, :school_id]) + end +end diff --git a/lib/lanttern/students_records/student_record_relationship.ex b/lib/lanttern/students_records/student_record_relationship.ex index c0623074..9b3a85c9 100644 --- a/lib/lanttern/students_records/student_record_relationship.ex +++ b/lib/lanttern/students_records/student_record_relationship.ex @@ -16,7 +16,7 @@ defmodule Lanttern.StudentsRecords.StudentRecordRelationship do @doc false def changeset(student_record_relationship, attrs) do student_record_relationship - |> cast(attrs, [:student_id, :student_record_i, :school_id]) + |> cast(attrs, [:student_id, :student_record_id, :school_id]) |> validate_required([:student_id, :student_record_id, :school_id]) end end diff --git a/priv/repo/migrations/20241212195844_create_students_records_classes.exs b/priv/repo/migrations/20241212195844_create_students_records_classes.exs new file mode 100644 index 00000000..1ee3dbfb --- /dev/null +++ b/priv/repo/migrations/20241212195844_create_students_records_classes.exs @@ -0,0 +1,28 @@ +defmodule Lanttern.Repo.Migrations.CreateStudentsRecordsClasses do + use Ecto.Migration + + def change do + # creating unique constraints to allow composite foreign keys. + # this guarantees, in the database level, that record and class + # belong to the same school + + # removing existing "classes_school_id_index" to prevent unnecessary index + drop index(:classes, [:school_id]) + create unique_index(:classes, [:school_id, :id]) + + create table(:students_records_classes, primary_key: false) do + # in the future we will handle how to cascade class and school deletion to ss records + add :class_id, references(:classes, with: [school_id: :school_id], on_delete: :nothing), + null: false + + add :student_record_id, + references(:students_records, with: [school_id: :school_id], on_delete: :delete_all), + null: false + + add :school_id, references(:schools, on_delete: :nothing), null: false + end + + create index(:students_records_classes, [:class_id]) + create unique_index(:students_records_classes, [:student_record_id, :class_id]) + end +end From 4cd74b8d6d92851c41c837ab0c574fd33d77bf5f Mon Sep 17 00:00:00 2001 From: endoooo Date: Fri, 13 Dec 2024 09:07:03 -0300 Subject: [PATCH 2/7] chore: added basic tests of student record classes and added `classes_ids` to log --- .../student_record_log.ex | 2 ++ ...dd_classes_ids_to_students_records_log.exs | 11 ++++++ test/lanttern/students_records_test.exs | 34 ++++++++++++++++--- 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 priv/repo/migrations/20241213115237_add_classes_ids_to_students_records_log.exs diff --git a/lib/lanttern/students_records_log/student_record_log.ex b/lib/lanttern/students_records_log/student_record_log.ex index 8b4459b7..ab586138 100644 --- a/lib/lanttern/students_records_log/student_record_log.ex +++ b/lib/lanttern/students_records_log/student_record_log.ex @@ -17,6 +17,7 @@ defmodule Lanttern.StudentsRecordsLog.StudentRecordLog do field :date, :date field :time, :time field :students_ids, {:array, :id} + field :classes_ids, {:array, :id} field :school_id, :id field :type_id, :id field :status_id, :id @@ -36,6 +37,7 @@ defmodule Lanttern.StudentsRecordsLog.StudentRecordLog do :date, :time, :students_ids, + :classes_ids, :school_id, :type_id, :status_id diff --git a/priv/repo/migrations/20241213115237_add_classes_ids_to_students_records_log.exs b/priv/repo/migrations/20241213115237_add_classes_ids_to_students_records_log.exs new file mode 100644 index 00000000..04abe6c9 --- /dev/null +++ b/priv/repo/migrations/20241213115237_add_classes_ids_to_students_records_log.exs @@ -0,0 +1,11 @@ +defmodule Lanttern.Repo.Migrations.AddClassesIdsToStudentsRecordsLog do + use Ecto.Migration + + @prefix "log" + + def change do + alter table(:students_records, prefix: @prefix) do + add :classes_ids, {:array, :bigint} + end + end +end diff --git a/test/lanttern/students_records_test.exs b/test/lanttern/students_records_test.exs index a8560ffc..e9ce97ae 100644 --- a/test/lanttern/students_records_test.exs +++ b/test/lanttern/students_records_test.exs @@ -145,6 +145,7 @@ defmodule Lanttern.StudentsRecordsTest do type = student_record_type_fixture(%{school_id: school.id}) status = student_record_status_fixture(%{school_id: school.id}) student = SchoolsFixtures.student_fixture(%{school_id: school.id}) + class = SchoolsFixtures.class_fixture(%{school_id: school.id}) # profile to test log profile = Lanttern.IdentityFixtures.teacher_profile_fixture() @@ -157,7 +158,8 @@ defmodule Lanttern.StudentsRecordsTest do date: ~D[2024-09-15], time: ~T[14:00:00], description: "some description", - students_ids: [student.id] + students_ids: [student.id], + classes_ids: [class.id] } assert {:ok, %StudentRecord{} = student_record} = @@ -171,6 +173,10 @@ defmodule Lanttern.StudentsRecordsTest do assert student_record.time == ~T[14:00:00] assert student_record.description == "some description" + student_record = student_record |> Repo.preload([:students, :classes]) + assert student_record.students == [student] + assert student_record.classes == [class] + on_exit(fn -> assert_supervised_tasks_are_down() @@ -184,6 +190,7 @@ defmodule Lanttern.StudentsRecordsTest do assert student_record_log.operation == "CREATE" assert student_record_log.students_ids == [student.id] + assert student_record_log.classes_ids == [class.id] assert student_record_log.school_id == student_record.school_id assert student_record_log.status_id == student_record.status_id assert student_record_log.type_id == student_record.type_id @@ -199,13 +206,27 @@ defmodule Lanttern.StudentsRecordsTest do end test "update_student_record/2 with valid data updates the student_record" do - student_record = student_record_fixture() + school = SchoolsFixtures.school_fixture() + student = SchoolsFixtures.student_fixture(%{school_id: school.id}) + class = SchoolsFixtures.class_fixture(%{school_id: school.id}) + + student_record = + student_record_fixture(%{ + school_id: school.id, + students_ids: [student.id], + classes_ids: [class.id] + }) + + updated_student = SchoolsFixtures.student_fixture(%{school_id: school.id}) + updated_class = SchoolsFixtures.class_fixture(%{school_id: school.id}) update_attrs = %{ name: "some updated name", date: ~D[2024-09-16], time: ~T[15:01:01], - description: "some updated description" + description: "some updated description", + students_ids: [updated_student.id], + classes_ids: [updated_class.id] } # profile to test log @@ -221,6 +242,10 @@ defmodule Lanttern.StudentsRecordsTest do assert student_record.time == ~T[15:01:01] assert student_record.description == "some updated description" + student_record = student_record |> Repo.preload([:students, :classes]) + assert student_record.students == [updated_student] + assert student_record.classes == [updated_class] + on_exit(fn -> assert_supervised_tasks_are_down() @@ -233,7 +258,8 @@ defmodule Lanttern.StudentsRecordsTest do assert student_record_log.profile_id == profile.id assert student_record_log.operation == "UPDATE" - assert student_record_log.students_ids == student_record.students_ids + assert student_record_log.students_ids == [updated_student.id] + assert student_record_log.classes_ids == [updated_class.id] assert student_record_log.school_id == student_record.school_id assert student_record_log.status_id == student_record.status_id assert student_record_log.type_id == student_record.type_id From d23d9dbaf4db5aee4159b82811d534f85123bc81 Mon Sep 17 00:00:00 2001 From: endoooo Date: Fri, 13 Dec 2024 18:51:34 -0300 Subject: [PATCH 3/7] chore: added support to display classes in students records routes - removed support to classes in `assign_user_filters/2` in favor of new function `assign_classes_filter/2` - created `class_with_cycle/2` school helper - removed `Filters.StrandClassesFilterOverlayComponent` in favor of refactored `Filters.ClassesFilterOverlayComponent` - adjusted classes filter implementation in school's students tab, emphasizing classes related to current user cycle --- lib/lanttern/schools.ex | 2 + .../components/core_components.ex | 10 +- lib/lanttern_web/helpers/filters_helpers.ex | 92 ++++++++--- lib/lanttern_web/helpers/schools_helpers.ex | 32 ++++ .../live/pages/school/students_component.ex | 33 +++- .../pages/strands/id/assessment_component.ex | 4 +- .../live/pages/strands/id/notes_component.ex | 4 +- .../strands/id/strand_rubrics_component.ex | 4 +- .../strands/moment/id/assessment_component.ex | 4 +- .../id/student_record_live.ex | 3 +- .../id/student_record_live.html.heex | 34 +++- .../students_records/students_records_live.ex | 3 +- .../students_records_live.html.heex | 13 ++ .../classes_filter_overlay_component.ex | 51 +++++- ...strand_classes_filter_overlay_component.ex | 154 ------------------ .../shared/schools/class_search_component.ex | 2 +- ...xecute_create_students_records_classes.exs | 20 +++ 17 files changed, 253 insertions(+), 212 deletions(-) delete mode 100644 lib/lanttern_web/live/shared/filters/strand_classes_filter_overlay_component.ex create mode 100644 priv/repo/migrations/20241213131428_execute_create_students_records_classes.exs diff --git a/lib/lanttern/schools.ex b/lib/lanttern/schools.ex index baaf85a5..a2dcaa67 100644 --- a/lib/lanttern/schools.ex +++ b/lib/lanttern/schools.ex @@ -419,6 +419,8 @@ defmodule Lanttern.Schools do @doc """ Returns the list of classes. + Cycle and years are always preloaded. + ## Options: - `:classes_ids` – filter results by given ids diff --git a/lib/lanttern_web/components/core_components.ex b/lib/lanttern_web/components/core_components.ex index a16d179b..e7cb2ca0 100644 --- a/lib/lanttern_web/components/core_components.ex +++ b/lib/lanttern_web/components/core_components.ex @@ -20,6 +20,8 @@ defmodule LantternWeb.CoreComponents do import Phoenix.HTML, only: [raw: 1] import LantternWeb.Gettext + import LantternWeb.SchoolsHelpers, only: [class_with_cycle: 2] + @doc """ Renders a `