Skip to content

Commit 5e34ba0

Browse files
Feat: Add programs (#30)
* feat: programs * feat: classroom programs * fix: turbo bug * chore: lint * Content modules (#31) * blank * feat: modules * fix: brakeman warning * feat: add nav * Classroom modules (#32) * blank * feat: classroom modules * feat: add scheduling * Student view (#33) * blank * feat: student page
1 parent d971ffe commit 5e34ba0

53 files changed

Lines changed: 1468 additions & 31 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
class ClassroomModulesController < AdminController
2+
before_action :set_classroom_module
3+
4+
def update
5+
if @classroom_module.update(classroom_module_params)
6+
respond_to do |format|
7+
format.turbo_stream
8+
format.html { redirect_to schedule_classroom_url(@classroom_module.classroom_program.classroom) }
9+
end
10+
else
11+
respond_to do |format|
12+
format.turbo_stream do
13+
render turbo_stream: turbo_stream.replace(
14+
dom_id(@classroom_module),
15+
partial: "classroom_modules/row",
16+
locals: { classroom_module: @classroom_module }
17+
), status: :unprocessable_entity
18+
end
19+
format.html { redirect_to schedule_classroom_url(@classroom_module.classroom_program.classroom) }
20+
end
21+
end
22+
end
23+
24+
private
25+
26+
def set_classroom_module
27+
@classroom_module = ClassroomModule.find(params[:id])
28+
end
29+
30+
def classroom_module_params
31+
params.expect(classroom_module: [ :publish_on ])
32+
end
33+
end
Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,45 @@
11
class ClassroomsController < AdminController
2-
before_action :set_classroom, only: %i[ edit update ]
2+
before_action :set_classroom, only: %i[edit update schedule]
33

4-
# GET /classrooms/1/edit
54
def edit
5+
build_missing_program_enrollments
66
end
7-
# PATCH/PUT /classrooms/1 or /classrooms/1.json
7+
8+
def schedule
9+
@classroom_programs = @classroom.classroom_programs
10+
.includes(:program, classroom_modules: :content_module)
11+
.order("programs.name")
12+
@active_enrollment = @classroom_programs.find { |cp| cp.id.to_s == params[:classroom_program_id] } || @classroom_programs.first
13+
end
14+
815
def update
916
respond_to do |format|
1017
if @classroom.update(classroom_params)
18+
@classroom.classroom_programs.reload.each(&:generate_modules!)
1119
format.html { redirect_to school_students_url(@classroom.school), notice: "Classroom was successfully updated.", status: :see_other }
1220
format.json { render :show, status: :ok, location: @classroom }
1321
else
22+
build_missing_program_enrollments
1423
format.html { render :edit, status: :unprocessable_entity }
1524
format.json { render json: @classroom.errors, status: :unprocessable_entity }
1625
end
1726
end
1827
end
1928

2029
private
21-
# Use callbacks to share common setup or constraints between actions.
2230
def set_classroom
2331
@classroom = Classroom.find(params.expect(:id))
2432
end
2533

26-
# Only allow a list of trusted parameters through.
34+
def build_missing_program_enrollments
35+
@classroom.classroom_programs.load unless @classroom.classroom_programs.loaded?
36+
enrolled_ids = @classroom.classroom_programs.target.map(&:program_id).to_set
37+
Program.order(:name).each do |program|
38+
@classroom.classroom_programs.build(program: program) unless enrolled_ids.include?(program.id)
39+
end
40+
end
41+
2742
def classroom_params
28-
params.expect(classroom: [ :name, :teacher ])
43+
params.expect(classroom: [ :name, :teacher, classroom_programs_attributes: [ [ :id, :program_id, :level, :_destroy ] ] ])
2944
end
3045
end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
class ContentModulesController < AdminController
2+
before_action :set_content_module, only: %i[edit update destroy]
3+
4+
def index
5+
@programs = Program.order(:name)
6+
@active_program = @programs.find { |p| p.id.to_s == params[:program_id] } || @programs.first
7+
@modules_by_level = @active_program
8+
.content_modules
9+
.includes(:links)
10+
.group_by(&:level)
11+
end
12+
13+
def new
14+
@content_module = ContentModule.new
15+
end
16+
17+
def create
18+
@content_module = ContentModule.new(content_module_params)
19+
20+
if @content_module.save
21+
redirect_to content_modules_path, notice: "Module was successfully created."
22+
else
23+
render :new, status: :unprocessable_entity
24+
end
25+
end
26+
27+
def edit
28+
end
29+
30+
def update
31+
if @content_module.update(content_module_params)
32+
redirect_to content_modules_path, notice: "Module was successfully updated."
33+
else
34+
render :edit, status: :unprocessable_entity
35+
end
36+
end
37+
38+
def destroy
39+
if @content_module.destroy
40+
redirect_to content_modules_path, notice: "Module was successfully deleted.", status: :see_other
41+
else
42+
redirect_to content_modules_path, alert: "Cannot delete a module that has been assigned to classrooms."
43+
end
44+
end
45+
46+
private
47+
def set_content_module
48+
@content_module = ContentModule.find(params.expect(:id))
49+
end
50+
51+
def content_module_params
52+
params.expect(content_module: [ :name, :program_id, :level, :position ])
53+
end
54+
end
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
class LinksController < AdminController
2+
before_action :set_content_module, only: %i[new create]
3+
before_action :set_link, only: %i[edit update destroy]
4+
5+
def new
6+
@link = @content_module.links.build
7+
end
8+
9+
def create
10+
@link = @content_module.links.build(link_params)
11+
12+
if @link.save
13+
redirect_to edit_content_module_path(@content_module), notice: "Link was successfully created."
14+
else
15+
render :new, status: :unprocessable_entity
16+
end
17+
end
18+
19+
def edit
20+
end
21+
22+
def update
23+
if @link.update(link_params)
24+
redirect_to edit_content_module_path(@link.content_module), notice: "Link was successfully updated."
25+
else
26+
render :edit, status: :unprocessable_entity
27+
end
28+
end
29+
30+
def destroy
31+
@link.destroy!
32+
redirect_to edit_content_module_path(@link.content_module), notice: "Link was successfully deleted.", status: :see_other
33+
end
34+
35+
private
36+
def set_content_module
37+
@content_module = ContentModule.find(params.expect(:content_module_id))
38+
end
39+
40+
def set_link
41+
@link = Link.find(params.expect(:id))
42+
end
43+
44+
def link_params
45+
params.expect(link: [ :title, :url, :link_type, :position ])
46+
end
47+
end

app/controllers/student_homes_controller.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,15 @@ class StudentHomesController < ApplicationController
33

44
def index
55
@student = Current.student
6+
classroom = @student.classroom
7+
@classroom_programs = classroom.classroom_programs
8+
.includes(:program)
9+
.order("programs.name")
10+
@active_program = @classroom_programs.find { |cp| cp.id.to_s == params[:classroom_program_id] } || @classroom_programs.first
11+
@published_modules = if @active_program
12+
@active_program.classroom_modules.published.includes(content_module: :links)
13+
else
14+
[]
15+
end
616
end
717
end

app/helpers/application_helper.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
module ApplicationHelper
2+
def safe_external_url(url)
3+
uri = URI.parse(url.to_s)
4+
uri.scheme.in?(%w[http https]) ? url : "#"
5+
rescue URI::InvalidURIError
6+
"#"
7+
end
28
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
3+
export default class extends Controller {
4+
static targets = ["levelSection", "destroy"]
5+
6+
toggle(event) {
7+
const enrolled = event.target.checked
8+
this.levelSectionTarget.style.display = enrolled ? "" : "none"
9+
this.destroyTarget.value = enrolled ? "0" : "1"
10+
}
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
3+
export default class extends Controller {
4+
static targets = ["date"]
5+
6+
setToday() {
7+
this.dateTarget.value = new Date().toISOString().split("T")[0]
8+
this.element.requestSubmit()
9+
}
10+
}

app/models/classroom.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
11
class Classroom < ApplicationRecord
22
belongs_to :school
33
has_many :students
4+
has_many :classroom_programs, dependent: :destroy
5+
has_many :programs, through: :classroom_programs
6+
7+
accepts_nested_attributes_for :classroom_programs,
8+
allow_destroy: true,
9+
reject_if: proc { |attrs| attrs["id"].blank? && attrs["_destroy"] == "1" }
10+
11+
validate :at_least_one_active_program, on: :update
12+
13+
private
14+
15+
def at_least_one_active_program
16+
active = classroom_programs.reject(&:marked_for_destruction?)
17+
errors.add(:base, "must have at least one program enrolled") if active.empty?
18+
end
419
end

app/models/classroom_module.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class ClassroomModule < ApplicationRecord
2+
belongs_to :classroom_program
3+
belongs_to :content_module
4+
5+
scope :published, -> { where.not(publish_on: nil).where("publish_on <= ?", Date.current) }
6+
end

0 commit comments

Comments
 (0)