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 %>

+
+ 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:

+ + +
+ <% 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