From f0690ef98bfb54959d9178bbdbee72cb7f385bd9 Mon Sep 17 00:00:00 2001 From: Sean Dickinson Date: Sun, 26 Apr 2026 14:08:32 -0400 Subject: [PATCH 1/2] fix: create classrooms --- app/controllers/classrooms_controller.rb | 29 +++++++++++++++++++ app/controllers/students_controller.rb | 5 ++-- app/models/classroom.rb | 4 +++ app/models/school.rb | 1 + app/models/student.rb | 1 + app/views/classrooms/_classroom.html.erb | 14 +++++++++ app/views/classrooms/_form.html.erb | 26 +++++++++++++++++ app/views/classrooms/edit.html.erb | 16 ++++++++++ app/views/classrooms/index.html.erb | 29 +++++++++++++++++++ app/views/students/_form.html.erb | 2 ++ app/views/students/edit.html.erb | 2 +- app/views/students/index.html.erb | 2 ++ app/views/students/new.html.erb | 2 +- config/routes.rb | 1 + .../20260428233313_create_classrooms.rb | 16 ++++++++++ db/schema.rb | 17 ++++++++++- db/seeds.rb | 5 +++- .../controllers/classrooms_controller_test.rb | 18 ++++++++++++ test/controllers/students_controller_test.rb | 11 ++++++- test/fixtures/classrooms.yml | 13 +++++++++ test/fixtures/students.yml | 2 ++ test/models/classroom_test.rb | 7 +++++ 22 files changed, 216 insertions(+), 7 deletions(-) create mode 100644 app/controllers/classrooms_controller.rb create mode 100644 app/models/classroom.rb create mode 100644 app/views/classrooms/_classroom.html.erb create mode 100644 app/views/classrooms/_form.html.erb create mode 100644 app/views/classrooms/edit.html.erb create mode 100644 app/views/classrooms/index.html.erb create mode 100644 db/migrate/20260428233313_create_classrooms.rb create mode 100644 test/controllers/classrooms_controller_test.rb create mode 100644 test/fixtures/classrooms.yml create mode 100644 test/models/classroom_test.rb diff --git a/app/controllers/classrooms_controller.rb b/app/controllers/classrooms_controller.rb new file mode 100644 index 0000000..e50e297 --- /dev/null +++ b/app/controllers/classrooms_controller.rb @@ -0,0 +1,29 @@ +class ClassroomsController < ApplicationController + before_action :set_classroom, only: %i[ edit update ] + # GET /classrooms/1/edit + def edit + end + # PATCH/PUT /classrooms/1 or /classrooms/1.json + def update + respond_to do |format| + if @classroom.update(classroom_params) + format.html { redirect_to school_students_url(@classroom.school), notice: "Classroom was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @classroom } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @classroom.errors, status: :unprocessable_entity } + end + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_classroom + @classroom = Classroom.find(params.expect(:id)) + end + + # Only allow a list of trusted parameters through. + def classroom_params + params.expect(classroom: [ :name, :teacher ]) + end +end diff --git a/app/controllers/students_controller.rb b/app/controllers/students_controller.rb index 9ce0047..0e8e46e 100644 --- a/app/controllers/students_controller.rb +++ b/app/controllers/students_controller.rb @@ -4,7 +4,7 @@ class StudentsController < ApplicationController # GET /students or /students.json def index - @students = @school.students.all + @students = @school.students.includes(:classroom).all end # GET /students/1 or /students/1.json @@ -18,6 +18,7 @@ def new # GET /students/1/edit def edit + @classrooms = @student.school.classrooms.all end # POST /students or /students.json @@ -70,6 +71,6 @@ def set_school # Only allow a list of trusted parameters through. def student_params - params.expect(student: [ :first_name, :last_name, :email, :grade_level, :gender ]) + params.expect(student: [ :first_name, :last_name, :email, :grade_level, :gender, :classroom_id ]) end end diff --git a/app/models/classroom.rb b/app/models/classroom.rb new file mode 100644 index 0000000..7e1ca13 --- /dev/null +++ b/app/models/classroom.rb @@ -0,0 +1,4 @@ +class Classroom < ApplicationRecord + belongs_to :school + has_many :students +end diff --git a/app/models/school.rb b/app/models/school.rb index e05b6b4..03261ec 100644 --- a/app/models/school.rb +++ b/app/models/school.rb @@ -1,4 +1,5 @@ class School < ApplicationRecord has_many :students, dependent: :destroy + has_many :classrooms, dependent: :destroy validates :name, presence: true end diff --git a/app/models/student.rb b/app/models/student.rb index 031257f..18e1d31 100644 --- a/app/models/student.rb +++ b/app/models/student.rb @@ -1,5 +1,6 @@ class Student < ApplicationRecord belongs_to :school + belongs_to :classroom enum :gender, %i[male female other].index_by(&:itself) diff --git a/app/views/classrooms/_classroom.html.erb b/app/views/classrooms/_classroom.html.erb new file mode 100644 index 0000000..37e5113 --- /dev/null +++ b/app/views/classrooms/_classroom.html.erb @@ -0,0 +1,14 @@ +
+
+ Name: + <%= classroom.name %> +
+
+ School: + <%= classroom.school_id %> +
+
+ Teacher: + <%= classroom.teacher %> +
+
diff --git a/app/views/classrooms/_form.html.erb b/app/views/classrooms/_form.html.erb new file mode 100644 index 0000000..506a6a5 --- /dev/null +++ b/app/views/classrooms/_form.html.erb @@ -0,0 +1,26 @@ +<%= form_with(model: classroom) do |form| %> +
+ <% if classroom.errors.any? %> +
+

<%= pluralize(classroom.errors.count, "error") %> prohibited this classroom from being saved:

+ +
    + <% classroom.errors.each do |error| %> +
  • <%= error.full_message %>
  • + <% end %> +
+
+ <% end %> + + <%= form.label :name, class: "label" %> + <%= form.text_field :name, class: "input w-full" %> + + <%= form.label :teacher, class: "label" %> + <%= form.text_field :teacher, class: "input w-full" %> + + <%= form.label :Link, class: "label" %> + https://www.classroom.google.com/ + + <%= form.submit class: "btn btn-primary" %> +
+<% end %> diff --git a/app/views/classrooms/edit.html.erb b/app/views/classrooms/edit.html.erb new file mode 100644 index 0000000..bc8a7a2 --- /dev/null +++ b/app/views/classrooms/edit.html.erb @@ -0,0 +1,16 @@ +<% content_for :title, "Editing classroom" %> + +
+
+
+

Editing Classroom

+ <%= render "form", classroom: @classroom %> +
+
+
diff --git a/app/views/classrooms/index.html.erb b/app/views/classrooms/index.html.erb new file mode 100644 index 0000000..4b8cb19 --- /dev/null +++ b/app/views/classrooms/index.html.erb @@ -0,0 +1,29 @@ +<% content_for :title, "Classrooms" %> + +
+ <% if notice.present? %> +

<%= notice %>

+ <% end %> + +
+

Classrooms

+ <%= link_to "New classroom", new_classroom_path, class: "rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white block font-medium" %> +
+ +
+ <% if @classrooms.any? %> + <% @classrooms.each do |classroom| %> +
+ <%= render classroom %> +
+ <%= link_to "Show", classroom, class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %> + <%= link_to "Edit", edit_classroom_path(classroom), class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %> + <%= button_to "Destroy", classroom, method: :delete, class: "w-full sm:w-auto rounded-md px-3.5 py-2.5 text-white bg-red-600 hover:bg-red-500 font-medium cursor-pointer", data: { turbo_confirm: "Are you sure?" } %> +
+
+ <% end %> + <% else %> +

No classrooms found.

+ <% end %> +
+
diff --git a/app/views/students/_form.html.erb b/app/views/students/_form.html.erb index 4c9654c..4c795a5 100644 --- a/app/views/students/_form.html.erb +++ b/app/views/students/_form.html.erb @@ -27,6 +27,8 @@ <%= form.label :gender, class: "label" %> <%= form.text_field :gender, class: "input w-full" %> + <%= form.collection_select :classroom_id, classrooms, :id, :name, { include_blank: "Select a classroom" }, class: "select w-full" %> + <%= form.submit class: "btn btn-primary" %> <% end %> diff --git a/app/views/students/edit.html.erb b/app/views/students/edit.html.erb index 76317ac..aa9223f 100644 --- a/app/views/students/edit.html.erb +++ b/app/views/students/edit.html.erb @@ -10,7 +10,7 @@

Editing Student

- <%= render "form", student: @student %> + <%= render "form", student: @student, classrooms: @classrooms %>
diff --git a/app/views/students/index.html.erb b/app/views/students/index.html.erb index 3fa0122..61b938e 100644 --- a/app/views/students/index.html.erb +++ b/app/views/students/index.html.erb @@ -23,6 +23,7 @@ Email Grade level Gender + Classroom Actions @@ -34,6 +35,7 @@ <%= student.email %> <%= student.grade_level %> <%= student.gender %> + <%= link_to student.classroom.name, edit_classroom_path(student.classroom), class: "btn btn-link" %>
<%= link_to "Edit", edit_student_path(student), class: "btn btn-primary" %> diff --git a/app/views/students/new.html.erb b/app/views/students/new.html.erb index a250adf..d607f9a 100644 --- a/app/views/students/new.html.erb +++ b/app/views/students/new.html.erb @@ -11,7 +11,7 @@

New Student

- <%= render "form", student: @student %> + <%= render "form", student: @student, classrooms: @school.classrooms %>
\ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 608cfc5..90da7a5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,6 +4,7 @@ scope :admin do resources :schools do resources :students, shallow: true + resources :classrooms, shallow: true, only: %i[edit update] end end root to: "schools#index" diff --git a/db/migrate/20260428233313_create_classrooms.rb b/db/migrate/20260428233313_create_classrooms.rb new file mode 100644 index 0000000..121eb1b --- /dev/null +++ b/db/migrate/20260428233313_create_classrooms.rb @@ -0,0 +1,16 @@ +class CreateClassrooms < ActiveRecord::Migration[8.1] + def change + create_table :classrooms do |t| + t.string :name + t.belongs_to :school, null: false, foreign_key: true + t.string :teacher + t.string :uuid, null: false, index: { unique: true } + + t.timestamps + end + + change_table :students do |t| + t.belongs_to :classroom, null: false, foreign_key: true + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e8cf50e..68b3779 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,18 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_04_21_234927) do +ActiveRecord::Schema[8.1].define(version: 2026_04_28_233313) do + create_table "classrooms", force: :cascade do |t| + t.datetime "created_at", null: false + t.string "name" + t.integer "school_id", null: false + t.string "teacher" + t.datetime "updated_at", null: false + t.string "uuid", null: false + t.index ["school_id"], name: "index_classrooms_on_school_id" + t.index ["uuid"], name: "index_classrooms_on_uuid", unique: true + end + create_table "schools", force: :cascade do |t| t.datetime "created_at", null: false t.string "name" @@ -27,6 +38,7 @@ end create_table "students", force: :cascade do |t| + t.integer "classroom_id", null: false t.datetime "created_at", null: false t.string "email" t.string "first_name", null: false @@ -35,6 +47,7 @@ t.string "last_name", null: false t.integer "school_id", null: false t.datetime "updated_at", null: false + t.index ["classroom_id"], name: "index_students_on_classroom_id" t.index ["school_id"], name: "index_students_on_school_id" end @@ -47,6 +60,8 @@ t.index ["email_address"], name: "index_users_on_email_address", unique: true end + add_foreign_key "classrooms", "schools" add_foreign_key "sessions", "users" + add_foreign_key "students", "classrooms" add_foreign_key "students", "schools" end diff --git a/db/seeds.rb b/db/seeds.rb index 9c601be..2e02363 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -25,6 +25,9 @@ def build_student_attrs(overrides = {}) # randomly sample 3 grade levels and create 10 students for each grade level grades = (1..12).to_a.sample(3) grades.each do |grade| - students = 10.times.map { build_student_attrs(grade_level: grade) } + classrooms = 2.times.map do |i| + school.classrooms.create!(name: "Classroom #{ i + 1 }", teacher: maybe { Faker::Name.name }, uuid: SecureRandom.urlsafe_base64(32)) + end + students = 10.times.map { |i| build_student_attrs(grade_level: grade, classroom_id: classrooms[i % 2].id) } school.students.create!(students) end diff --git a/test/controllers/classrooms_controller_test.rb b/test/controllers/classrooms_controller_test.rb new file mode 100644 index 0000000..94ff6db --- /dev/null +++ b/test/controllers/classrooms_controller_test.rb @@ -0,0 +1,18 @@ +require "test_helper" + +class ClassroomsControllerTest < ActionDispatch::IntegrationTest + setup do + @classroom = classrooms(:one) + sign_in_as users(:admin) + end + + test "should get edit" do + get edit_classroom_url(@classroom) + assert_response :success + end + + test "should update classroom" do + patch classroom_url(@classroom), params: { classroom: { name: @classroom.name, teacher: @classroom.teacher } } + assert_redirected_to school_students_url(@classroom.school) + end +end diff --git a/test/controllers/students_controller_test.rb b/test/controllers/students_controller_test.rb index 49c3fa0..f2ae2b3 100644 --- a/test/controllers/students_controller_test.rb +++ b/test/controllers/students_controller_test.rb @@ -19,7 +19,7 @@ class StudentsControllerTest < ActionDispatch::IntegrationTest test "should create student" do assert_difference("Student.count") do - post school_students_url(@school), params: { student: { email: @student.email, first_name: @student.first_name, gender: @student.gender, grade_level: @student.grade_level, last_name: @student.last_name, school_id: @student.school_id } } + post school_students_url(@school), params: { student: { email: @student.email, first_name: @student.first_name, gender: @student.gender, grade_level: @student.grade_level, last_name: @student.last_name, classroom_id: @student.classroom_id } } end assert_redirected_to student_url(Student.last) @@ -40,6 +40,15 @@ class StudentsControllerTest < ActionDispatch::IntegrationTest assert_redirected_to student_url(@student) end + test "can update a student's classroom" do + classroom = @student.classroom.dup + classroom.update! uuid: SecureRandom.urlsafe_base64(32), name: "New Classroom" + assert_changes -> { @student.reload.classroom_id } do + patch student_url(@student), params: { student: { classroom_id: classroom.id } } + assert_redirected_to student_url(@student) + end + end + test "should destroy student" do assert_difference("Student.count", -1) do delete student_url(@student) diff --git a/test/fixtures/classrooms.yml b/test/fixtures/classrooms.yml new file mode 100644 index 0000000..487abe2 --- /dev/null +++ b/test/fixtures/classrooms.yml @@ -0,0 +1,13 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: Classroom 1 + school: one + teacher: Teacher 1 + uuid: abcd-1234-efgh-5678 + +two: + name: Classroom 2 + school: two + teacher: + uuid: wxyz-9876-ijkl-5432 diff --git a/test/fixtures/students.yml b/test/fixtures/students.yml index b6b8a60..ee5c1f5 100644 --- a/test/fixtures/students.yml +++ b/test/fixtures/students.yml @@ -5,6 +5,7 @@ ada: grade_level: 5 gender: school: one + classroom: one grace: first_name: Grace @@ -13,3 +14,4 @@ grace: grade_level: 6 gender: female school: two + classroom: two diff --git a/test/models/classroom_test.rb b/test/models/classroom_test.rb new file mode 100644 index 0000000..5f872f0 --- /dev/null +++ b/test/models/classroom_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class ClassroomTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From d51e5b1387fcaf5ee58b1154970959e7fbae04ff Mon Sep 17 00:00:00 2001 From: Sean Dickinson Date: Tue, 28 Apr 2026 20:23:34 -0400 Subject: [PATCH 2/2] feat: rosters --- app/controllers/classroom_rosters_controller.rb | 7 +++++++ app/controllers/classrooms_controller.rb | 1 + app/helpers/schools_helper.rb | 2 -- app/models/student.rb | 2 ++ app/views/classroom_rosters/show.html.erb | 14 ++++++++++++++ app/views/classrooms/_form.html.erb | 2 +- config/routes.rb | 2 ++ .../classroom_rosters_controller_test.rb | 9 +++++++++ test/controllers/classrooms_controller_test.rb | 1 + 9 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 app/controllers/classroom_rosters_controller.rb delete mode 100644 app/helpers/schools_helper.rb create mode 100644 app/views/classroom_rosters/show.html.erb create mode 100644 test/controllers/classroom_rosters_controller_test.rb diff --git a/app/controllers/classroom_rosters_controller.rb b/app/controllers/classroom_rosters_controller.rb new file mode 100644 index 0000000..9c0f608 --- /dev/null +++ b/app/controllers/classroom_rosters_controller.rb @@ -0,0 +1,7 @@ +class ClassroomRostersController < ApplicationController + allow_unauthenticated_access + + def show + @classroom = Classroom.includes(:students).find_by!(uuid: params.expect(:uuid)) + end +end diff --git a/app/controllers/classrooms_controller.rb b/app/controllers/classrooms_controller.rb index e50e297..bf715f6 100644 --- a/app/controllers/classrooms_controller.rb +++ b/app/controllers/classrooms_controller.rb @@ -1,5 +1,6 @@ class ClassroomsController < ApplicationController before_action :set_classroom, only: %i[ edit update ] + # GET /classrooms/1/edit def edit end diff --git a/app/helpers/schools_helper.rb b/app/helpers/schools_helper.rb deleted file mode 100644 index e1893f4..0000000 --- a/app/helpers/schools_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module SchoolsHelper -end diff --git a/app/models/student.rb b/app/models/student.rb index 18e1d31..6fd9a19 100644 --- a/app/models/student.rb +++ b/app/models/student.rb @@ -5,4 +5,6 @@ class Student < ApplicationRecord enum :gender, %i[male female other].index_by(&:itself) validates :first_name, :last_name, :grade_level, presence: true + + def full_name = [ first_name, last_name ].join(" ") end diff --git a/app/views/classroom_rosters/show.html.erb b/app/views/classroom_rosters/show.html.erb new file mode 100644 index 0000000..f500b42 --- /dev/null +++ b/app/views/classroom_rosters/show.html.erb @@ -0,0 +1,14 @@ +
+

<%= @classroom.name %>

+
+
    + <% @classroom.students.sort_by(&:full_name).each do |student| %> + <%= link_to "#" do %> + + <% end %> + <% end %> +
diff --git a/app/views/classrooms/_form.html.erb b/app/views/classrooms/_form.html.erb index 506a6a5..81cccc1 100644 --- a/app/views/classrooms/_form.html.erb +++ b/app/views/classrooms/_form.html.erb @@ -19,7 +19,7 @@ <%= form.text_field :teacher, class: "input w-full" %> <%= form.label :Link, class: "label" %> - https://www.classroom.google.com/ + <%= link_to classroom_roster_url(classroom.uuid), classroom_roster_path(classroom.uuid), class: 'link' %> <%= form.submit class: "btn btn-primary" %>
diff --git a/config/routes.rb b/config/routes.rb index 90da7a5..f43a6d5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,8 @@ Rails.application.routes.draw do + get "classroom_rosters/show" resource :session resources :passwords, param: :token + resources :classroom_rosters, only: %i[show], param: :uuid scope :admin do resources :schools do resources :students, shallow: true diff --git a/test/controllers/classroom_rosters_controller_test.rb b/test/controllers/classroom_rosters_controller_test.rb new file mode 100644 index 0000000..f3c6714 --- /dev/null +++ b/test/controllers/classroom_rosters_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class ClassroomRostersControllerTest < ActionDispatch::IntegrationTest + test "should get show without signing in" do + classroom = classrooms(:one) + get classroom_roster_url(classroom.uuid) + assert_response :success + end +end diff --git a/test/controllers/classrooms_controller_test.rb b/test/controllers/classrooms_controller_test.rb index 94ff6db..a4fa5f6 100644 --- a/test/controllers/classrooms_controller_test.rb +++ b/test/controllers/classrooms_controller_test.rb @@ -12,6 +12,7 @@ class ClassroomsControllerTest < ActionDispatch::IntegrationTest end test "should update classroom" do + sign_in_as users(:admin) patch classroom_url(@classroom), params: { classroom: { name: @classroom.name, teacher: @classroom.teacher } } assert_redirected_to school_students_url(@classroom.school) end