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
new file mode 100644
index 0000000..bf715f6
--- /dev/null
+++ b/app/controllers/classrooms_controller.rb
@@ -0,0 +1,30 @@
+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/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/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..6fd9a19 100644
--- a/app/models/student.rb
+++ b/app/models/student.rb
@@ -1,7 +1,10 @@
class Student < ApplicationRecord
belongs_to :school
+ belongs_to :classroom
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 %>
+ -
+
+ <%= student.full_name %>
+
+
+ <% end %>
+ <% end %>
+
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..81cccc1
--- /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" %>
+ <%= link_to classroom_roster_url(classroom.uuid), classroom_roster_path(classroom.uuid), class: 'link' %>
+
+ <%= 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..f43a6d5 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,9 +1,12 @@
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
+ 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/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
new file mode 100644
index 0000000..a4fa5f6
--- /dev/null
+++ b/test/controllers/classrooms_controller_test.rb
@@ -0,0 +1,19 @@
+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
+ 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
+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
|