From 9210f0850d08f2b5b44ecb948a67b52e32b635f9 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Sun, 17 Mar 2024 11:18:34 -0700 Subject: [PATCH 01/36] Add starter code for pd frontend --- app/controllers/workshops_controller.rb | 52 +++++++++++++++++++++++++ app/helpers/application_helper.rb | 3 +- app/helpers/workshops_helper.rb | 2 + app/views/workshops/create.html.erb | 2 + app/views/workshops/destroy.html.erb | 2 + app/views/workshops/edit.html.erb | 2 + app/views/workshops/index.html.erb | 34 ++++++++++++++++ app/views/workshops/new.html.erb | 2 + app/views/workshops/show.html.erb | 2 + app/views/workshops/update.html.erb | 2 + config/routes.rb | 8 ++++ 11 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 app/controllers/workshops_controller.rb create mode 100644 app/helpers/workshops_helper.rb create mode 100644 app/views/workshops/create.html.erb create mode 100644 app/views/workshops/destroy.html.erb create mode 100644 app/views/workshops/edit.html.erb create mode 100644 app/views/workshops/index.html.erb create mode 100644 app/views/workshops/new.html.erb create mode 100644 app/views/workshops/show.html.erb create mode 100644 app/views/workshops/update.html.erb diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb new file mode 100644 index 00000000..49278b1b --- /dev/null +++ b/app/controllers/workshops_controller.rb @@ -0,0 +1,52 @@ +class WorkshopsController < ApplicationController + def index + # TODO: here is mocked data for workshops. Fix them after the model is created and the database is seeded. + @workshops = [ + OpenStruct.new( + id: 1, + name: "Web Development Basics", + location: "San Francisco", + start_date: "2024-04-01", + end_date: "2024-04-30", + grade_level: "Beginner", + registration_open: true + ), + OpenStruct.new( + id: 2, + name: "Advanced Pottery", + location: "New York", + start_date: "2024-05-15", + end_date: "2024-06-15", + grade_level: "Advanced", + registration_open: false + ), + OpenStruct.new( + id: 3, + name: "Digital Photography", + location: "London", + start_date: "2024-07-01", + end_date: "2024-07-31", + grade_level: "Intermediate", + registration_open: true + ) + ] + end + + def show + end + + def new + end + + def edit + end + + def create + end + + def update + end + + def destroy + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 1423a740..db597ddc 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -19,7 +19,8 @@ def admin_nav_links "Dashboard": dashboard_path, "Schools": schools_path, "Teachers": teachers_path, - "Email Templates": email_templates_path + "Email Templates": email_templates_path, + "Workshops": workshops_path, } end diff --git a/app/helpers/workshops_helper.rb b/app/helpers/workshops_helper.rb new file mode 100644 index 00000000..215662bd --- /dev/null +++ b/app/helpers/workshops_helper.rb @@ -0,0 +1,2 @@ +module WorkshopsHelper +end diff --git a/app/views/workshops/create.html.erb b/app/views/workshops/create.html.erb new file mode 100644 index 00000000..6010fb78 --- /dev/null +++ b/app/views/workshops/create.html.erb @@ -0,0 +1,2 @@ +

Workshops#create

+

Find me in app/views/workshops/create.html.erb

diff --git a/app/views/workshops/destroy.html.erb b/app/views/workshops/destroy.html.erb new file mode 100644 index 00000000..d5661876 --- /dev/null +++ b/app/views/workshops/destroy.html.erb @@ -0,0 +1,2 @@ +

Workshops#destroy

+

Find me in app/views/workshops/destroy.html.erb

diff --git a/app/views/workshops/edit.html.erb b/app/views/workshops/edit.html.erb new file mode 100644 index 00000000..bed91225 --- /dev/null +++ b/app/views/workshops/edit.html.erb @@ -0,0 +1,2 @@ +

Workshops#edit

+

Find me in app/views/workshops/edit.html.erb

diff --git a/app/views/workshops/index.html.erb b/app/views/workshops/index.html.erb new file mode 100644 index 00000000..3424aeea --- /dev/null +++ b/app/views/workshops/index.html.erb @@ -0,0 +1,34 @@ +<%= provide(:title, "BJC Schools") %> +<%= provide(:header_button, "New Workshop") %> + + + + + + + + + + + + + + + <% @workshops.each_with_index do |workshop, index| %> + + + + + + + + + + <% end %> + +
NameLocationStart DateEnd DateGrade LevelRegistration OpenActions
<%= link_to(workshop.name, workshop_path(workshop)) %><%= workshop.location %><%= workshop.start_date %><%= workshop.end_date %><%= workshop.grade_level %><%= workshop.registration_open ? 'Yes' : 'No' %> + + <%= link_to("Edit", edit_workshop_path(workshop), class: "btn btn-info") %> + <%= link_to("❌", workshop_path(workshop), method: "delete", class: "btn btn-outline-danger", data: {confirm: "Are you sure?"}) %> + +
diff --git a/app/views/workshops/new.html.erb b/app/views/workshops/new.html.erb new file mode 100644 index 00000000..571fde1c --- /dev/null +++ b/app/views/workshops/new.html.erb @@ -0,0 +1,2 @@ +

Workshops#new

+

Find me in app/views/workshops/new.html.erb

diff --git a/app/views/workshops/show.html.erb b/app/views/workshops/show.html.erb new file mode 100644 index 00000000..4986c74e --- /dev/null +++ b/app/views/workshops/show.html.erb @@ -0,0 +1,2 @@ +

Workshops#show

+

Find me in app/views/workshops/show.html.erb

diff --git a/app/views/workshops/update.html.erb b/app/views/workshops/update.html.erb new file mode 100644 index 00000000..1716c5a6 --- /dev/null +++ b/app/views/workshops/update.html.erb @@ -0,0 +1,2 @@ +

Workshops#update

+

Find me in app/views/workshops/update.html.erb

diff --git a/config/routes.rb b/config/routes.rb index beab79d8..ce067803 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,13 @@ # frozen_string_literal: true Rails.application.routes.draw do + get 'workshops/index' + get 'workshops/show' + get 'workshops/new' + get 'workshops/edit' + get 'workshops/create' + get 'workshops/update' + get 'workshops/destroy' mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? # The priority is based upon order of creation: first created -> highest priority. @@ -19,6 +26,7 @@ resources :schools resources :pages, param: :url_slug resources :email_templates, except: [:show] + resources :workshops get "/login", to: "sessions#new", as: "login" delete "/logout", to: "sessions#destroy", as: "logout" From a194ed69ba18fd553a6ee55aedd3599671055305 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Sun, 17 Mar 2024 20:27:46 -0700 Subject: [PATCH 02/36] Backup work for frontend --- app/controllers/workshops_controller.rb | 65 +++++++++++-------- app/models/workshop.rb | 53 +++++++++++++++ app/views/workshops/_form.html.erb | 85 +++++++++++++++++++++++++ app/views/workshops/edit.html.erb | 15 ++++- 4 files changed, 191 insertions(+), 27 deletions(-) create mode 100644 app/models/workshop.rb create mode 100644 app/views/workshops/_form.html.erb diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb index 49278b1b..6bf4a32f 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/workshops_controller.rb @@ -1,29 +1,62 @@ class WorkshopsController < ApplicationController + + # TODO: revise any method using `set_workshops` to use `MockWorkshop.all` instead. It's currently used for mocking data. + before_action :set_workshops, only: [:show, :edit] + def index - # TODO: here is mocked data for workshops. Fix them after the model is created and the database is seeded. + set_workshops + end + + def show + @workshop = @workshops.find { |workshop| workshop.id == params[:id].to_i } + end + + def new + end + + def edit + @workshop = @workshops.find { |workshop| workshop.id == params[:id].to_i } + end + + def create + end + + def update + end + + def destroy + end + + def set_workshops @workshops = [ - OpenStruct.new( + Workshop.new( id: 1, name: "Web Development Basics", - location: "San Francisco", + city: "San Francisco", + state: "CA", + country: "USA", start_date: "2024-04-01", end_date: "2024-04-30", grade_level: "Beginner", registration_open: true ), - OpenStruct.new( + Workshop.new( id: 2, name: "Advanced Pottery", - location: "New York", + city: "New York", + state: "NY", + country: "USA", start_date: "2024-05-15", end_date: "2024-06-15", grade_level: "Advanced", registration_open: false ), - OpenStruct.new( + Workshop.new( id: 3, name: "Digital Photography", - location: "London", + city: "London", + state: "", + country: "UK", start_date: "2024-07-01", end_date: "2024-07-31", grade_level: "Intermediate", @@ -31,22 +64,4 @@ def index ) ] end - - def show - end - - def new - end - - def edit - end - - def create - end - - def update - end - - def destroy - end end diff --git a/app/models/workshop.rb b/app/models/workshop.rb new file mode 100644 index 00000000..2b23ece6 --- /dev/null +++ b/app/models/workshop.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: workshops +# +# id :integer not null, primary key +# city :string +# country :string +# grade_level :integer +# lat :float +# lng :float +# name :string +# state :string +# tags :text default([]), is an Array +# teachers_count :integer default(0) +# website :string TODO: Confirm is it necessary field +# created_at :datetime +# updated_at :datetime +# +# Indexes +# +# TODO: Define indexes + + +class Workshop + include ActiveModel::Model + include ActiveModel::Attributes # Make sure this is included + + # Define attributes + attribute :id, :integer + attribute :name, :string + attribute :city, :string + attribute :state, :string + attribute :country, :string + attribute :start_date, :date + attribute :end_date, :date + attribute :grade_level, :string + attribute :registration_open, :boolean + + def initialize(attributes = {}) + super(attributes) + # Now ActiveModel handles attributes, no need to manually set defaults for attributes defined above + end + + def persisted? + id.present? + end + + def location + "#{city}, #{state}, #{country}" + end +end diff --git a/app/views/workshops/_form.html.erb b/app/views/workshops/_form.html.erb new file mode 100644 index 00000000..e024a559 --- /dev/null +++ b/app/views/workshops/_form.html.erb @@ -0,0 +1,85 @@ +
Create a new Workshop
+
+
+ <%= f.label :name, "Workshop Name", class: "label-required" %> + <%= f.text_field :name, placeholder: 'BJC Teacher Training', class: 'form-control', + required: false, id: 'workshop_name' %> +
+
+
+ +
+ <%= f.label :start_date, "Start Date", class: "label-required", for: "workshop_start_date" %> + <%= f.date_field :start_date, class: 'form-control', required: true, id: 'workshop_start_date' %> +
+ +
+ <%= f.label :end_date, "End Date", class: "label-required", for: "workshop_end_date" %> + <%= f.date_field :end_date, class: 'form-control', required: true, id: 'workshop_end_date' %> +
+
+
+
+ <%= f.label :city, class: "label-required", for: "workshop_city" %> + <%= f.text_field :city, placeholder: 'Berkeley', class: 'form-control', + required: true, id: 'workshop_city' %> +
+ +
+ <%= f.label :state, class: "label-required", for: "workshop_state" %> + <%= f.select :state, School::VALID_STATES, { include_blank: "State" }, { id: "state_select", class: 'form-control' } %> +
+ +
+ <%= f.label :state, for: "workshop_state" %> + <%= f.text_field :state, placeholder: "State", class: 'form-control', id: "state_textfield" %> +
+ +
+ <%= f.label :country, "Country", class: "label-required", for: "workshop_country" %> + <%= f.country_select( + :country, + { priority_countries: ['United States'] }, + { class: 'form-control', required: true, id: 'workshop_country', format: :with_full_country_name, selected: 'United States'} + ) %> +
+ +
+ +
+ <%= f.label :grade_level, "Grade Level", for: "workshop_grade_level" %> + <%= f.select( + :grade_level, + options_for_select(School.grade_level_options, workshop.grade_level), + { include_blank: "Select a grade level" }, + { class: 'form-control', required: true, id: 'workshop_grade_level' } + ) %> +
+ + diff --git a/app/views/workshops/edit.html.erb b/app/views/workshops/edit.html.erb index bed91225..82ee5a36 100644 --- a/app/views/workshops/edit.html.erb +++ b/app/views/workshops/edit.html.erb @@ -1,2 +1,13 @@ -

Workshops#edit

-

Find me in app/views/workshops/edit.html.erb

+<%= provide(:h1, "Update #{@workshop.name}") %> + +<% if @workshop.nil? %> +

Workshop not found.

+<% else %> + <%= form_for @workshop do |f| %> + <%= render 'workshops/form', f: f, workshop: @workshop %> + +
+ <%= f.submit 'Submit', {class: 'btn btn-primary', id: 'submit_button'} %> +
+ <% end %> +<% end %> From 292e245a1d70313ef018c2aec6a8107430d1b544 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Mon, 18 Mar 2024 12:29:15 -0700 Subject: [PATCH 03/36] Backup frontend work --- app/controllers/workshops_controller.rb | 6 +++--- app/models/workshop.rb | 18 +++++++++++++++++- app/views/workshops/_form.html.erb | 7 +++---- app/views/workshops/index.html.erb | 2 +- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb index 6bf4a32f..0693d38e 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/workshops_controller.rb @@ -37,7 +37,7 @@ def set_workshops country: "USA", start_date: "2024-04-01", end_date: "2024-04-30", - grade_level: "Beginner", + # grade_level: "University", registration_open: true ), Workshop.new( @@ -48,7 +48,7 @@ def set_workshops country: "USA", start_date: "2024-05-15", end_date: "2024-06-15", - grade_level: "Advanced", + grade_level: 1, registration_open: false ), Workshop.new( @@ -59,7 +59,7 @@ def set_workshops country: "UK", start_date: "2024-07-01", end_date: "2024-07-31", - grade_level: "Intermediate", + grade_level: 0, registration_open: true ) ] diff --git a/app/models/workshop.rb b/app/models/workshop.rb index 2b23ece6..00ab48f5 100644 --- a/app/models/workshop.rb +++ b/app/models/workshop.rb @@ -35,9 +35,17 @@ class Workshop attribute :country, :string attribute :start_date, :date attribute :end_date, :date - attribute :grade_level, :string + attribute :grade_level, :integer, default: -1 attribute :registration_open, :boolean + GRADE_LEVELS = { + elementary: 0, + middle_school: 1, + high_school: 2, + community_college: 3, + university: 4 + }.freeze + def initialize(attributes = {}) super(attributes) # Now ActiveModel handles attributes, no need to manually set defaults for attributes defined above @@ -50,4 +58,12 @@ def persisted? def location "#{city}, #{state}, #{country}" end + + def display_grade_level + # Directly access the grade_level attribute + grade_level_value = self.grade_level + return "Unknown" if grade_level_value == -1 + + GRADE_LEVELS.key(grade_level_value).to_s.titlecase + end end diff --git a/app/views/workshops/_form.html.erb b/app/views/workshops/_form.html.erb index e024a559..9907509a 100644 --- a/app/views/workshops/_form.html.erb +++ b/app/views/workshops/_form.html.erb @@ -7,12 +7,10 @@
-
<%= f.label :start_date, "Start Date", class: "label-required", for: "workshop_start_date" %> <%= f.date_field :start_date, class: 'form-control', required: true, id: 'workshop_start_date' %>
-
<%= f.label :end_date, "End Date", class: "label-required", for: "workshop_end_date" %> <%= f.date_field :end_date, class: 'form-control', required: true, id: 'workshop_end_date' %> @@ -40,10 +38,11 @@ <%= f.country_select( :country, { priority_countries: ['United States'] }, - { class: 'form-control', required: true, id: 'workshop_country', format: :with_full_country_name, selected: 'United States'} + { class: 'form-control', required: false, id: 'workshop_country', format: :with_full_country_name, selected: 'United States'} ) %>
+
@@ -52,7 +51,7 @@ :grade_level, options_for_select(School.grade_level_options, workshop.grade_level), { include_blank: "Select a grade level" }, - { class: 'form-control', required: true, id: 'workshop_grade_level' } + { class: 'form-control', required: false, id: 'workshop_grade_level' } ) %>
diff --git a/app/views/workshops/index.html.erb b/app/views/workshops/index.html.erb index 3424aeea..d092f9dc 100644 --- a/app/views/workshops/index.html.erb +++ b/app/views/workshops/index.html.erb @@ -20,7 +20,7 @@ <%= workshop.location %> <%= workshop.start_date %> <%= workshop.end_date %> - <%= workshop.grade_level %> + <%= workshop.display_grade_level %> <%= workshop.registration_open ? 'Yes' : 'No' %> From 4ee54b6e256040ccde874fe6b4bab132e9464563 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Mon, 18 Mar 2024 14:37:14 -0700 Subject: [PATCH 04/36] Back temporary work for frontend --- app/controllers/workshops_controller.rb | 43 +++++++++++++---- app/helpers/workshops_helper.rb | 2 - app/models/pd_registration.rb | 16 +++++++ app/models/workshop.rb | 7 ++- app/views/workshops/create.html.erb | 2 - app/views/workshops/destroy.html.erb | 2 - app/views/workshops/new.html.erb | 11 ++++- app/views/workshops/show.html.erb | 64 ++++++++++++++++++++++++- app/views/workshops/update.html.erb | 2 - config/routes.rb | 7 --- 10 files changed, 127 insertions(+), 29 deletions(-) delete mode 100644 app/helpers/workshops_helper.rb create mode 100644 app/models/pd_registration.rb delete mode 100644 app/views/workshops/create.html.erb delete mode 100644 app/views/workshops/destroy.html.erb delete mode 100644 app/views/workshops/update.html.erb diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb index 0693d38e..689ada70 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/workshops_controller.rb @@ -1,7 +1,10 @@ -class WorkshopsController < ApplicationController +# frozen_string_literal: true + +require "ostruct" +class WorkshopsController < ApplicationController # TODO: revise any method using `set_workshops` to use `MockWorkshop.all` instead. It's currently used for mocking data. - before_action :set_workshops, only: [:show, :edit] + before_action :set_workshops, only: [:show, :edit, :update, :destroy] def index set_workshops @@ -12,6 +15,8 @@ def show end def new + @workshop = Workshop.new + load_ordered_workshops end def edit @@ -19,12 +24,18 @@ def edit end def create + flash[:danger] = "This feature is not yet implemented." + redirect_to schools_path end def update + flash[:danger] = "This feature is not yet implemented." + redirect_to schools_path end def destroy + flash[:danger] = "This feature is not yet implemented." + redirect_to schools_path end def set_workshops @@ -37,8 +48,11 @@ def set_workshops country: "USA", start_date: "2024-04-01", end_date: "2024-04-30", - # grade_level: "University", - registration_open: true + registration_open: true, + pd_registrations: [ + PdRegistration.new(teacher_id: 1, pd_id: 1, attended: true, role: "leader", teacher_name: "Alex Johnson"), + PdRegistration.new(teacher_id: 2, pd_id: 1, attended: false, role: "attendee", teacher_name: "Jamie Smith") + ] ), Workshop.new( id: 2, @@ -48,8 +62,12 @@ def set_workshops country: "USA", start_date: "2024-05-15", end_date: "2024-06-15", - grade_level: 1, - registration_open: false + grade_level: "High School", + registration_open: false, + pd_registrations: [ + PdRegistration.new(teacher_id: 3, pd_id: 2, attended: true, role: "attendee", teacher_name: "Sam Lee"), + PdRegistration.new(teacher_id: 4, pd_id: 2, attended: true, role: "leader", teacher_name: "Chris Doe") + ] ), Workshop.new( id: 3, @@ -59,9 +77,18 @@ def set_workshops country: "UK", start_date: "2024-07-01", end_date: "2024-07-31", - grade_level: 0, - registration_open: true + grade_level: "College", + registration_open: true, + pd_registrations: [ + PdRegistration.new(teacher_id: 5, pd_id: 3, attended: false, role: "attendee", teacher_name: "Morgan Bailey"), + PdRegistration.new(teacher_id: 6, pd_id: 3, attended: true, role: "leader", teacher_name: "Casey Jordan"), + PdRegistration.new(teacher_id: 7, pd_id: 3, attended: true, role: "attendee", teacher_name: "Jordan Casey") # Added an extra registration for variety + ] ) ] end + + def load_ordered_workshops + @ordered_schools = School.all.order(:name) + end end diff --git a/app/helpers/workshops_helper.rb b/app/helpers/workshops_helper.rb deleted file mode 100644 index 215662bd..00000000 --- a/app/helpers/workshops_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module WorkshopsHelper -end diff --git a/app/models/pd_registration.rb b/app/models/pd_registration.rb new file mode 100644 index 00000000..054c393e --- /dev/null +++ b/app/models/pd_registration.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class PdRegistration + include ActiveModel::Model + include ActiveModel::Attributes + + attribute :teacher_id, :integer + attribute :pd_id, :integer + attribute :attended, :boolean + attribute :role, :string + attribute :teacher_name, :string # Adding this for convenience in mocking + + def initialize(attributes = {}) + super(attributes) + end +end diff --git a/app/models/workshop.rb b/app/models/workshop.rb index 00ab48f5..6ed6b53a 100644 --- a/app/models/workshop.rb +++ b/app/models/workshop.rb @@ -12,7 +12,6 @@ # lng :float # name :string # state :string -# tags :text default([]), is an Array # teachers_count :integer default(0) # website :string TODO: Confirm is it necessary field # created_at :datetime @@ -22,7 +21,9 @@ # # TODO: Define indexes - +# This class is a mock representation of the Workshop model. +# In the final application, Workshops and Teachers are associated through PdRegistrations. +# This mock setup uses arrays of mock PdRegistration objects to simulate many-to-many relationships. class Workshop include ActiveModel::Model include ActiveModel::Attributes # Make sure this is included @@ -36,7 +37,9 @@ class Workshop attribute :start_date, :date attribute :end_date, :date attribute :grade_level, :integer, default: -1 + attribute :teachers_count, :integer, default: 0 attribute :registration_open, :boolean + attribute :pd_registrations, default: [] GRADE_LEVELS = { elementary: 0, diff --git a/app/views/workshops/create.html.erb b/app/views/workshops/create.html.erb deleted file mode 100644 index 6010fb78..00000000 --- a/app/views/workshops/create.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -

Workshops#create

-

Find me in app/views/workshops/create.html.erb

diff --git a/app/views/workshops/destroy.html.erb b/app/views/workshops/destroy.html.erb deleted file mode 100644 index d5661876..00000000 --- a/app/views/workshops/destroy.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -

Workshops#destroy

-

Find me in app/views/workshops/destroy.html.erb

diff --git a/app/views/workshops/new.html.erb b/app/views/workshops/new.html.erb index 571fde1c..23582b80 100644 --- a/app/views/workshops/new.html.erb +++ b/app/views/workshops/new.html.erb @@ -1,2 +1,9 @@ -

Workshops#new

-

Find me in app/views/workshops/new.html.erb

+<%= provide(:h1, "Add a School") %> + +<%= form_for @workshop do |f| %> + <%= render 'workshops/form', f: f, workshop: @workshop %> + +
+ <%= f.submit 'Submit', {class: 'btn btn-primary', id: 'submit_button'} %> +
+<% end %> diff --git a/app/views/workshops/show.html.erb b/app/views/workshops/show.html.erb index 4986c74e..83a21340 100644 --- a/app/views/workshops/show.html.erb +++ b/app/views/workshops/show.html.erb @@ -1,2 +1,62 @@ -

Workshops#show

-

Find me in app/views/workshops/show.html.erb

+<%= provide(:h1, @workshop.name) %> + +
+
+
+
+

+ <%= @workshop.name %> +

+
+
+ <%= link_to("Edit", edit_workshop_path(@workshop), class: "btn btn-primary") %> + <%= link_to("Delete", workshop_path(@workshop), method: "delete", class: "btn btn-danger", data: {confirm: "Are you sure?"}) %> +
+
+
+
+
+
+
Location
+ <%= "#{@workshop.location}, #{@workshop.country}" %> +
+ +
+
Dates
+ <%= @workshop.start_date %> to <%= @workshop.end_date %> +
+ +
+
GradeLevel
+ <%= @workshop.display_grade_level %> +
+
+ +
+
+

PD Registrations

+
+ + + + + + + + + + + <% @workshop.pd_registrations.each do |registration| %> + + + + + + + <% end %> + +
Teacher NamePD SessionAttendedRole
<%= link_to(registration.teacher_name, teacher_path(registration.teacher_id)) %><%= @workshop.name %><%= registration.attended ? 'Yes' : 'No' %><%= registration.role %>
+
+
+
+
diff --git a/app/views/workshops/update.html.erb b/app/views/workshops/update.html.erb deleted file mode 100644 index 1716c5a6..00000000 --- a/app/views/workshops/update.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -

Workshops#update

-

Find me in app/views/workshops/update.html.erb

diff --git a/config/routes.rb b/config/routes.rb index ce067803..ba682f22 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,13 +1,6 @@ # frozen_string_literal: true Rails.application.routes.draw do - get 'workshops/index' - get 'workshops/show' - get 'workshops/new' - get 'workshops/edit' - get 'workshops/create' - get 'workshops/update' - get 'workshops/destroy' mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? # The priority is based upon order of creation: first created -> highest priority. From c0e563482b6d1100fedd5f253c97327c543b4a2e Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Wed, 20 Mar 2024 09:53:25 -0700 Subject: [PATCH 05/36] Rename workshop to professional developments --- ...> professional_developments_controller.rb} | 36 +++++++++--------- app/helpers/application_helper.rb | 2 +- ...orkshop.rb => professional_development.rb} | 8 ++-- .../_form.html.erb | 38 +++++++++---------- .../professional_developments/edit.html.erb | 13 +++++++ .../professional_developments/index.html.erb | 34 +++++++++++++++++ .../professional_developments/new.html.erb | 9 +++++ .../show.html.erb | 18 ++++----- app/views/workshops/edit.html.erb | 13 ------- app/views/workshops/index.html.erb | 34 ----------------- app/views/workshops/new.html.erb | 9 ----- config/routes.rb | 2 +- 12 files changed, 108 insertions(+), 108 deletions(-) rename app/controllers/{workshops_controller.rb => professional_developments_controller.rb} (70%) rename app/models/{workshop.rb => professional_development.rb} (87%) rename app/views/{workshops => professional_developments}/_form.html.erb (60%) create mode 100644 app/views/professional_developments/edit.html.erb create mode 100644 app/views/professional_developments/index.html.erb create mode 100644 app/views/professional_developments/new.html.erb rename app/views/{workshops => professional_developments}/show.html.erb (61%) delete mode 100644 app/views/workshops/edit.html.erb delete mode 100644 app/views/workshops/index.html.erb delete mode 100644 app/views/workshops/new.html.erb diff --git a/app/controllers/workshops_controller.rb b/app/controllers/professional_developments_controller.rb similarity index 70% rename from app/controllers/workshops_controller.rb rename to app/controllers/professional_developments_controller.rb index 689ada70..fb11619b 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/professional_developments_controller.rb @@ -2,45 +2,45 @@ require "ostruct" -class WorkshopsController < ApplicationController - # TODO: revise any method using `set_workshops` to use `MockWorkshop.all` instead. It's currently used for mocking data. - before_action :set_workshops, only: [:show, :edit, :update, :destroy] +class ProfessionalDevelopmentsController < ApplicationController + # TODO: revise any method using `set_pds` to use `MockProfessionalDevelopments.all` instead. It's currently used for mocking data. + before_action :set_pds, only: [:show, :edit, :update, :destroy] def index - set_workshops + set_pds end def show - @workshop = @workshops.find { |workshop| workshop.id == params[:id].to_i } + @professional_development = @professional_developments.find { |pd| pd.id == params[:id].to_i } end def new - @workshop = Workshop.new - load_ordered_workshops + @professional_development = ProfessionalDevelopment.new + load_ordered_pds end def edit - @workshop = @workshops.find { |workshop| workshop.id == params[:id].to_i } + @professional_development = @professional_developments.find { |pd| pd.id == params[:id].to_i } end def create flash[:danger] = "This feature is not yet implemented." - redirect_to schools_path + redirect_to new_professional_development_path end def update flash[:danger] = "This feature is not yet implemented." - redirect_to schools_path + redirect_to edit_professional_development_path end def destroy flash[:danger] = "This feature is not yet implemented." - redirect_to schools_path + redirect_to professional_developments_path end - def set_workshops - @workshops = [ - Workshop.new( + def set_pds + @professional_developments = [ + ProfessionalDevelopment.new( id: 1, name: "Web Development Basics", city: "San Francisco", @@ -54,7 +54,7 @@ def set_workshops PdRegistration.new(teacher_id: 2, pd_id: 1, attended: false, role: "attendee", teacher_name: "Jamie Smith") ] ), - Workshop.new( + ProfessionalDevelopment.new( id: 2, name: "Advanced Pottery", city: "New York", @@ -69,7 +69,7 @@ def set_workshops PdRegistration.new(teacher_id: 4, pd_id: 2, attended: true, role: "leader", teacher_name: "Chris Doe") ] ), - Workshop.new( + ProfessionalDevelopment.new( id: 3, name: "Digital Photography", city: "London", @@ -88,7 +88,7 @@ def set_workshops ] end - def load_ordered_workshops - @ordered_schools = School.all.order(:name) + def load_ordered_pds + # not yet implemented end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index db597ddc..060ab96d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -20,7 +20,7 @@ def admin_nav_links "Schools": schools_path, "Teachers": teachers_path, "Email Templates": email_templates_path, - "Workshops": workshops_path, + "Professional Developments": professional_developments_path, } end diff --git a/app/models/workshop.rb b/app/models/professional_development.rb similarity index 87% rename from app/models/workshop.rb rename to app/models/professional_development.rb index 6ed6b53a..2954844c 100644 --- a/app/models/workshop.rb +++ b/app/models/professional_development.rb @@ -2,7 +2,7 @@ # == Schema Information # -# Table name: workshops +# Table name: professional_developments # # id :integer not null, primary key # city :string @@ -21,10 +21,10 @@ # # TODO: Define indexes -# This class is a mock representation of the Workshop model. -# In the final application, Workshops and Teachers are associated through PdRegistrations. +# This class is a mock representation of the ProfessionalDevelopment model. +# In the final application, Professional Developments and Teachers are associated through PdRegistrations. # This mock setup uses arrays of mock PdRegistration objects to simulate many-to-many relationships. -class Workshop +class ProfessionalDevelopment include ActiveModel::Model include ActiveModel::Attributes # Make sure this is included diff --git a/app/views/workshops/_form.html.erb b/app/views/professional_developments/_form.html.erb similarity index 60% rename from app/views/workshops/_form.html.erb rename to app/views/professional_developments/_form.html.erb index 9907509a..1d4244f6 100644 --- a/app/views/workshops/_form.html.erb +++ b/app/views/professional_developments/_form.html.erb @@ -1,44 +1,44 @@ -
Create a new Workshop
+
Create a new Professional Development
- <%= f.label :name, "Workshop Name", class: "label-required" %> + <%= f.label :name, "Professional Development Name", class: "label-required" %> <%= f.text_field :name, placeholder: 'BJC Teacher Training', class: 'form-control', - required: false, id: 'workshop_name' %> + required: false, id: 'professional_development_name' %>
- <%= f.label :start_date, "Start Date", class: "label-required", for: "workshop_start_date" %> - <%= f.date_field :start_date, class: 'form-control', required: true, id: 'workshop_start_date' %> + <%= f.label :start_date, "Start Date", class: "label-required", for: "professional_development_start_date" %> + <%= f.date_field :start_date, class: 'form-control', required: true, id: 'professional_development_start_date' %>
- <%= f.label :end_date, "End Date", class: "label-required", for: "workshop_end_date" %> - <%= f.date_field :end_date, class: 'form-control', required: true, id: 'workshop_end_date' %> + <%= f.label :end_date, "End Date", class: "label-required", for: "professional_development_end_date" %> + <%= f.date_field :end_date, class: 'form-control', required: true, id: 'professional_development_end_date' %>
- <%= f.label :city, class: "label-required", for: "workshop_city" %> + <%= f.label :city, class: "label-required", for: "professional_development_city" %> <%= f.text_field :city, placeholder: 'Berkeley', class: 'form-control', - required: true, id: 'workshop_city' %> + required: true, id: 'professional_development_city' %>
- <%= f.label :state, class: "label-required", for: "workshop_state" %> + <%= f.label :state, class: "label-required", for: "professional_development_state" %> <%= f.select :state, School::VALID_STATES, { include_blank: "State" }, { id: "state_select", class: 'form-control' } %>
- <%= f.label :state, for: "workshop_state" %> + <%= f.label :state, for: "professional_development_state" %> <%= f.text_field :state, placeholder: "State", class: 'form-control', id: "state_textfield" %>
- <%= f.label :country, "Country", class: "label-required", for: "workshop_country" %> + <%= f.label :country, "Country", class: "label-required", for: "professional_development_country" %> <%= f.country_select( :country, { priority_countries: ['United States'] }, - { class: 'form-control', required: false, id: 'workshop_country', format: :with_full_country_name, selected: 'United States'} + { class: 'form-control', required: false, id: 'professional_development_country', format: :with_full_country_name, selected: 'United States'} ) %>
@@ -46,18 +46,18 @@
- <%= f.label :grade_level, "Grade Level", for: "workshop_grade_level" %> + <%= f.label :grade_level, "Grade Level", for: "professional_development_grade_level" %> <%= f.select( :grade_level, - options_for_select(School.grade_level_options, workshop.grade_level), + options_for_select(School.grade_level_options, professional_development.grade_level), { include_blank: "Select a grade level" }, - { class: 'form-control', required: false, id: 'workshop_grade_level' } + { class: 'form-control', required: false, id: 'professional_development_grade_level' } ) %>
diff --git a/config/routes.rb b/config/routes.rb index bef95fa9..56dbea88 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,7 +19,9 @@ resources :schools resources :pages, param: :url_slug resources :email_templates, except: [:show] - resources :professional_developments + resources :professional_developments do + resources :pd_registrations, except: [:show] + end get "/login", to: "sessions#new", as: "login" delete "/logout", to: "sessions#destroy", as: "logout" From 669e1a2e29a01e3b499002b46e7dbea0fea7b674 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Wed, 20 Mar 2024 12:12:23 -0700 Subject: [PATCH 08/36] Refract and clean up current frontend code --- .../pd_registrations_controller.rb | 2 + .../professional_developments_controller.rb | 8 +- app/models/pd_registration.rb | 2 + .../professional_developments/edit.html.erb | 1 - .../professional_developments/show.html.erb | 117 +++++++++--------- 5 files changed, 64 insertions(+), 66 deletions(-) diff --git a/app/controllers/pd_registrations_controller.rb b/app/controllers/pd_registrations_controller.rb index 8b2bc110..e92ea50f 100644 --- a/app/controllers/pd_registrations_controller.rb +++ b/app/controllers/pd_registrations_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class PdRegistrationsController < ApplicationController def create flash[:danger] = "Create feature is not yet implemented." diff --git a/app/controllers/professional_developments_controller.rb b/app/controllers/professional_developments_controller.rb index bd7d6aa4..bc91219a 100644 --- a/app/controllers/professional_developments_controller.rb +++ b/app/controllers/professional_developments_controller.rb @@ -22,17 +22,17 @@ def edit end def create - flash[:danger] = "This feature is not yet implemented." + flash[:danger] = "Create is not yet implemented." redirect_to new_professional_development_path end def update - flash[:danger] = "This feature is not yet implemented." + flash[:danger] = "Update feature is not yet implemented." redirect_to edit_professional_development_path end def destroy - flash[:danger] = "This feature is not yet implemented." + flash[:danger] = "Destroy feature is not yet implemented." redirect_to professional_developments_path end @@ -87,6 +87,6 @@ def set_pds end def load_ordered_pds - # not yet implemented + # not yet implemented end end diff --git a/app/models/pd_registration.rb b/app/models/pd_registration.rb index 9981b93b..c86744c8 100644 --- a/app/models/pd_registration.rb +++ b/app/models/pd_registration.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# This class is a mock representation of the PdRegistration model. +# In the final application, Professional Developments and Teachers are associated through PdRegistrations. class PdRegistration include ActiveModel::Model include ActiveModel::Attributes diff --git a/app/views/professional_developments/edit.html.erb b/app/views/professional_developments/edit.html.erb index 3bc42087..64f4f467 100644 --- a/app/views/professional_developments/edit.html.erb +++ b/app/views/professional_developments/edit.html.erb @@ -5,7 +5,6 @@ <% else %> <%= form_for @professional_development do |f| %> <%= render 'professional_developments/form', f: f, professional_development: @professional_development %> -
<%= f.submit 'Submit', {class: 'btn btn-primary', id: 'submit_button'} %>
diff --git a/app/views/professional_developments/show.html.erb b/app/views/professional_developments/show.html.erb index d4fa1899..63e8c064 100644 --- a/app/views/professional_developments/show.html.erb +++ b/app/views/professional_developments/show.html.erb @@ -30,7 +30,6 @@

PD Registrations

- <%= button_tag "Add Teacher", type: 'button', class: "btn btn-success", data: { toggle: "modal", target: "#addTeacherModal" } %>
@@ -79,23 +78,19 @@ <%= form_for :pd_registration, url: professional_development_pd_registrations_path(@professional_development), method: :post do |f| %>
<%= f.label :teacher_name, "Teacher Name" %> - <%= f.text_field :teacher_name, class: "form-control", placeholder: "Enter teacher name", required: true %>
<%= f.label :role, "Role" %> - <%= f.select :role, [['Leader', 'Leader'], ['Attendee', 'Attendee']], { prompt: "Select your role" }, { class: "form-control", required: true } %>
<%= f.label :attended, "Attended" %> - <%= f.select :attended, [['Yes', true], ['No', false]], { prompt: "Did the teacher attend?" }, { class: "form-control", required: true } %>
- <%= f.hidden_field :professional_development_id, value: @professional_development.id %> <%= f.submit "Add", class: "btn btn-primary" %> @@ -106,75 +101,75 @@
From edfedf57ad778adae8b275884ebc9e593b75f317 Mon Sep 17 00:00:00 2001 From: JacksonXu33 Date: Fri, 22 Mar 2024 18:28:56 -0700 Subject: [PATCH 09/36] crud, but no schema yet --- .../pd_registrations_controller.rb | 41 ++++++++++++++++--- .../professional_developments_controller.rb | 41 +++++++++++++------ app/models/pd_registration.rb | 6 +++ app/models/professional_development.rb | 5 +++ app/models/teacher.rb | 2 + 5 files changed, 76 insertions(+), 19 deletions(-) diff --git a/app/controllers/pd_registrations_controller.rb b/app/controllers/pd_registrations_controller.rb index e92ea50f..e51c8ccf 100644 --- a/app/controllers/pd_registrations_controller.rb +++ b/app/controllers/pd_registrations_controller.rb @@ -1,18 +1,47 @@ # frozen_string_literal: true +# Not sure how the ids are going to work with professional development. +# With a belongs to relationship, depending on customer implementation, +# might have 2 ids in params that we need to distinguish between. class PdRegistrationsController < ApplicationController + def index + set_pds + end + + def show + @pd_registration = PdRegistration.find(params[:id]) + end + + def new + @pd_registration = PdRegistration.new + end + + def edit + @pd_registration = PdRegistration.find(params[:id]) + end + def create - flash[:danger] = "Create feature is not yet implemented." - redirect_to professional_development_path(params[:professional_development_id]) + @pd_registration = PdRegistration.new(pd_registration_params) + if !@pd_registration.save + flash.now[:alert] = "Failed to save registration: #{@pd_registration.errors.full_messages.join(", ")}" + render :new + end + redirect_to pd_registrations_path, success: "PD registration created successfully." end def update - flash[:danger] = "Update feature is not yet implemented." - redirect_to professional_development_path(params[:professional_development_id]) + @pd_registration = PdRegistration.find(params[:id]) + if !@pd_registration.update(pd_registration_params) + flash.now[:alert] = "Failed to save registration: #{@pd_registration.errors.full_messages.join(", ")}" + render "edit" + end + redirect_to pd_registrations_path, success: "Saved the PD registration." end def destroy - flash[:danger] = "Destroy feature is not yet implemented." - redirect_to professional_development_path(params[:professional_development_id]) + if !@pd_registration.destroy + redirect_back(fallback_location: pd_registrations_path, alert: "Failed to delete PD registration.") + end + redirect_to pd_registrations_path, success: "Deleted PD registration successfully." end end diff --git a/app/controllers/professional_developments_controller.rb b/app/controllers/professional_developments_controller.rb index bc91219a..3a195a7f 100644 --- a/app/controllers/professional_developments_controller.rb +++ b/app/controllers/professional_developments_controller.rb @@ -3,37 +3,56 @@ class ProfessionalDevelopmentsController < ApplicationController # TODO: revise any method using `set_pds` to use `MockProfessionalDevelopments.all` instead. It's currently used for mocking data. before_action :set_pds, only: [:show, :edit, :update, :destroy] + before_action :require_login + before_action :require_admin def index set_pds end def show - @professional_development = @professional_developments.find { |pd| pd.id == params[:id].to_i } + @professional_development = ProfessionalDevelopment.find(params[:id]) + @pd_registrations = @professional_development.pd_registrations end def new @professional_development = ProfessionalDevelopment.new - load_ordered_pds end def edit - @professional_development = @professional_developments.find { |pd| pd.id == params[:id].to_i } + @professional_development = ProfessionalDevelopment.find(params[:id]) + @pd_registrations = @professional_development.pd_registrations end def create - flash[:danger] = "Create is not yet implemented." - redirect_to new_professional_development_path + @professional_development = ProfessionalDevelopment.find_by(name: professional_development_params[:name]) + if @professional_development + flash.now[:alert] = "A professional development with the name #{@professional_development.name} already exists." + render :new + end + @professional_development = ProfessionalDevelopment.new(professional_development_params) + if !@professional_development.save + flash.now[:alert] = "Failed to save #{@professional_development.name}: #{@professional_development.errors.full_messages.join(", ")}" + render :new + end + redirect_to professional_developments_path, success: "Professional development created successfully." end def update - flash[:danger] = "Update feature is not yet implemented." - redirect_to edit_professional_development_path + @professional_development = ProfessionalDevelopment.find(params[:id]) + @pd_registrations = @professional_development.pd_registrations + if !@professional_development.update(professional_development_params) + flash.now[:alert] = "Failed to save #{@professional_development.name}: #{@professional_development.errors.full_messages.join(", ")}" + render "edit" + end + redirect_to professional_developments_path, success: "Saved #{@professional_development.name}" end def destroy - flash[:danger] = "Destroy feature is not yet implemented." - redirect_to professional_developments_path + if !@professional_development.destroy + redirect_back(fallback_location: professional_developments_path, alert: "Failed to delete #{@professional_development.name}") + end + redirect_to professional_developments_path, success: "Deleted #{@professional_development.name} successfully." end def set_pds @@ -85,8 +104,4 @@ def set_pds ) ] end - - def load_ordered_pds - # not yet implemented - end end diff --git a/app/models/pd_registration.rb b/app/models/pd_registration.rb index c86744c8..4585f327 100644 --- a/app/models/pd_registration.rb +++ b/app/models/pd_registration.rb @@ -13,6 +13,12 @@ class PdRegistration attribute :role, :string attribute :teacher_name, :string # Adding this for convenience in mocking + # probably don't need the 3 id attributes (id, teacherid, pdid) if adding this relationship status into model + belongs_to :professional_development + belongs_to :teacher + + validates :professional_development_id, uniqueness: { scope: :teacher_id, message: "Teacher already has a registration for this PD" } + def initialize(attributes = {}) super(attributes) end diff --git a/app/models/professional_development.rb b/app/models/professional_development.rb index 2954844c..ce2b835a 100644 --- a/app/models/professional_development.rb +++ b/app/models/professional_development.rb @@ -41,6 +41,8 @@ class ProfessionalDevelopment attribute :registration_open, :boolean attribute :pd_registrations, default: [] + validates :name, presence: { message: "can't be blank" }, uniqueness: { message: "must be unique" } + GRADE_LEVELS = { elementary: 0, middle_school: 1, @@ -49,6 +51,9 @@ class ProfessionalDevelopment university: 4 }.freeze + has_many :pd_registrations + has_many :teachers, through: :pd_registrations + def initialize(attributes = {}) super(attributes) # Now ActiveModel handles attributes, no need to manually set defaults for attributes defined above diff --git a/app/models/teacher.rb b/app/models/teacher.rb index 76fa7b34..a6ab386d 100644 --- a/app/models/teacher.rb +++ b/app/models/teacher.rb @@ -51,6 +51,8 @@ class Teacher < ApplicationRecord validates_inclusion_of :application_status, in: application_statuses.keys belongs_to :school, counter_cache: true + has_many :professional_development_registrations + has_many :professional_developments, through: :professional_development_registrations # Non-admin teachers whose application has neither been accepted nor denied # It might or might not have been reviewed. From 1a6c703c406ffa36b0286f1e694b3a3cf2b2f0d6 Mon Sep 17 00:00:00 2001 From: JacksonXu33 Date: Sat, 30 Mar 2024 16:20:01 -0700 Subject: [PATCH 10/36] add comments --- .../pd_registrations_controller.rb | 32 ++++++++++++++++++- app/models/pd_registration.rb | 3 +- app/models/professional_development.rb | 3 +- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/app/controllers/pd_registrations_controller.rb b/app/controllers/pd_registrations_controller.rb index e51c8ccf..7b63d770 100644 --- a/app/controllers/pd_registrations_controller.rb +++ b/app/controllers/pd_registrations_controller.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true # Not sure how the ids are going to work with professional development. -# With a belongs to relationship, depending on customer implementation, +# With a belongs to relationship, depending on the implementation, # might have 2 ids in params that we need to distinguish between. class PdRegistrationsController < ApplicationController + before_action :require_login + def index set_pds end @@ -20,8 +22,16 @@ def edit @pd_registration = PdRegistration.find(params[:id]) end + # Create and update do NOT support admin overriding user functionality yet, only normal user functionality def create + @professional_development = pd_name_to_pd() + if (!@professional_development) + flash.now[:alert] = "Failed to save registration: No PD with that name exists.}" + render :new + end @pd_registration = PdRegistration.new(pd_registration_params) + @pd_registration.professional_development = @professional_development + @pd_registration.teacher = current_user if !@pd_registration.save flash.now[:alert] = "Failed to save registration: #{@pd_registration.errors.full_messages.join(", ")}" render :new @@ -30,7 +40,14 @@ def create end def update + @professional_development = pd_name_to_pd() + if (!@professional_development) + flash.now[:alert] = "Failed to save registration: No PD with that name exists.}" + render :new + end @pd_registration = PdRegistration.find(params[:id]) + @pd_registration.professional_development = @professional_development + @pd_registration.teacher = current_user if !@pd_registration.update(pd_registration_params) flash.now[:alert] = "Failed to save registration: #{@pd_registration.errors.full_messages.join(", ")}" render "edit" @@ -44,4 +61,17 @@ def destroy end redirect_to pd_registrations_path, success: "Deleted PD registration successfully." end + + private + # This method will take in a that will be the professional_development name, + # and return the pd; nil if doesn't exist or pd is not open + # It assumes the pd_registrations form will have a place for the user to input the name of a pd to register for + # But feel free to edit this if this is not the planned implementation + def pd_name_to_pd + professional_development = ProfessionalDevelopment.find(pd_registration_params[:name]) + if professional_development.nil? || !professional_development.registration_open + return nil + end + return professional_development + end end diff --git a/app/models/pd_registration.rb b/app/models/pd_registration.rb index 4585f327..4cb6b641 100644 --- a/app/models/pd_registration.rb +++ b/app/models/pd_registration.rb @@ -2,7 +2,7 @@ # This class is a mock representation of the PdRegistration model. # In the final application, Professional Developments and Teachers are associated through PdRegistrations. -class PdRegistration +class PdRegistration < ApplicationRecord include ActiveModel::Model include ActiveModel::Attributes @@ -17,6 +17,7 @@ class PdRegistration belongs_to :professional_development belongs_to :teacher + # if teacher should only have one registration per pd validates :professional_development_id, uniqueness: { scope: :teacher_id, message: "Teacher already has a registration for this PD" } def initialize(attributes = {}) diff --git a/app/models/professional_development.rb b/app/models/professional_development.rb index ce2b835a..de718379 100644 --- a/app/models/professional_development.rb +++ b/app/models/professional_development.rb @@ -24,7 +24,7 @@ # This class is a mock representation of the ProfessionalDevelopment model. # In the final application, Professional Developments and Teachers are associated through PdRegistrations. # This mock setup uses arrays of mock PdRegistration objects to simulate many-to-many relationships. -class ProfessionalDevelopment +class ProfessionalDevelopment < ApplicationRecord include ActiveModel::Model include ActiveModel::Attributes # Make sure this is included @@ -41,6 +41,7 @@ class ProfessionalDevelopment attribute :registration_open, :boolean attribute :pd_registrations, default: [] + # Assuming that the names will have to be unique, to make it possible for teachers to link registration to pd validates :name, presence: { message: "can't be blank" }, uniqueness: { message: "must be unique" } GRADE_LEVELS = { From 08273f2e97ba6f5636309beb3e3ca070bf7c7119 Mon Sep 17 00:00:00 2001 From: JacksonXu33 Date: Sat, 30 Mar 2024 16:22:19 -0700 Subject: [PATCH 11/36] link registrations and pd and teacher --- app/controllers/pd_registrations_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/pd_registrations_controller.rb b/app/controllers/pd_registrations_controller.rb index 7b63d770..695fa82b 100644 --- a/app/controllers/pd_registrations_controller.rb +++ b/app/controllers/pd_registrations_controller.rb @@ -25,7 +25,7 @@ def edit # Create and update do NOT support admin overriding user functionality yet, only normal user functionality def create @professional_development = pd_name_to_pd() - if (!@professional_development) + if !@professional_development flash.now[:alert] = "Failed to save registration: No PD with that name exists.}" render :new end @@ -41,7 +41,7 @@ def create def update @professional_development = pd_name_to_pd() - if (!@professional_development) + if !@professional_development flash.now[:alert] = "Failed to save registration: No PD with that name exists.}" render :new end @@ -72,6 +72,6 @@ def pd_name_to_pd if professional_development.nil? || !professional_development.registration_open return nil end - return professional_development + professional_development end end From 3b1625fd6cae99c65825eee9fea73ea34092c740 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:54:28 -0700 Subject: [PATCH 12/36] Implement backend model & migration --- app/models/pd_registration.rb | 57 +++++++++---- app/models/professional_development.rb | 84 ++++++++----------- ...190834_create_professional_developments.rb | 17 ++++ .../20240404191433_create_pd_registrations.rb | 17 ++++ db/schema.rb | 27 +++++- 5 files changed, 135 insertions(+), 67 deletions(-) create mode 100644 db/migrate/20240404190834_create_professional_developments.rb create mode 100644 db/migrate/20240404191433_create_pd_registrations.rb diff --git a/app/models/pd_registration.rb b/app/models/pd_registration.rb index 4cb6b641..e786ce25 100644 --- a/app/models/pd_registration.rb +++ b/app/models/pd_registration.rb @@ -1,26 +1,49 @@ # frozen_string_literal: true -# This class is a mock representation of the PdRegistration model. -# In the final application, Professional Developments and Teachers are associated through PdRegistrations. +# == Schema Information +# +# Table name: pd_registrations +# +# id :bigint not null, primary key +# attended :boolean default(FALSE) +# role :string not null +# created_at :datetime not null +# updated_at :datetime not null +# professional_development_id :integer not null +# teacher_id :integer not null +# +# Indexes +# +# index_pd_reg_on_teacher_id_and_pd_id (teacher_id,professional_development_id) UNIQUE +# +# Foreign Keys +# +# fk_rails_... (professional_development_id => professional_developments.id) +# fk_rails_... (teacher_id => teachers.id) +# class PdRegistration < ApplicationRecord - include ActiveModel::Model - include ActiveModel::Attributes + belongs_to :teacher + belongs_to :professional_development - attribute :id, :integer - attribute :teacher_id, :integer - attribute :pd_id, :integer - attribute :attended, :boolean - attribute :role, :string - attribute :teacher_name, :string # Adding this for convenience in mocking + validates :teacher_id, uniqueness: { scope: :professional_development_id, message: "already has a registration for this PD" } + validates :role, inclusion: { in: %w[leader attendee], message: "%{value} is not a valid role" } + validates :attended, inclusion: { in: [true, false] } - # probably don't need the 3 id attributes (id, teacherid, pdid) if adding this relationship status into model - belongs_to :professional_development - belongs_to :teacher + validate :professional_development_dates_passed, on: :create + + def teacher_name + teacher = Teacher.find_by(id: teacher_id) + if teacher.present? + "#{teacher.first_name} #{teacher.last_name}" + else + "Teacher not found" + end + end - # if teacher should only have one registration per pd - validates :professional_development_id, uniqueness: { scope: :teacher_id, message: "Teacher already has a registration for this PD" } + private + def professional_development_dates_passed + return unless professional_development&.end_date&.past? - def initialize(attributes = {}) - super(attributes) + errors.add(:professional_development_id, "can't register for past events") end end diff --git a/app/models/professional_development.rb b/app/models/professional_development.rb index de718379..8962e17f 100644 --- a/app/models/professional_development.rb +++ b/app/models/professional_development.rb @@ -4,75 +4,61 @@ # # Table name: professional_developments # -# id :integer not null, primary key -# city :string -# country :string -# grade_level :integer -# lat :float -# lng :float -# name :string -# state :string -# teachers_count :integer default(0) -# website :string TODO: Confirm is it necessary field -# created_at :datetime -# updated_at :datetime +# id :bigint not null, primary key +# city :string not null +# country :string not null +# end_date :date not null +# grade_level :integer not null +# name :string not null +# start_date :date not null +# state :string +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # -# TODO: Define indexes - -# This class is a mock representation of the ProfessionalDevelopment model. -# In the final application, Professional Developments and Teachers are associated through PdRegistrations. -# This mock setup uses arrays of mock PdRegistration objects to simulate many-to-many relationships. +# index_professional_developments_on_name_and_start_date (name,start_date) UNIQUE +# class ProfessionalDevelopment < ApplicationRecord - include ActiveModel::Model - include ActiveModel::Attributes # Make sure this is included - - # Define attributes - attribute :id, :integer - attribute :name, :string - attribute :city, :string - attribute :state, :string - attribute :country, :string - attribute :start_date, :date - attribute :end_date, :date - attribute :grade_level, :integer, default: -1 - attribute :teachers_count, :integer, default: 0 - attribute :registration_open, :boolean - attribute :pd_registrations, default: [] + VALID_STATES = %w[AL AK AS AZ AR CA CO CT DE DC FM FL GA GU HI ID IL IN IA KS KY LA ME MH MD MA MI MN MS MO MT NE NV + NH NJ NM NY NC ND MP OH OK OR PW PA PR RI SC SD TN TX UT VT VI VA WA WV WI WY].freeze - # Assuming that the names will have to be unique, to make it possible for teachers to link registration to pd - validates :name, presence: { message: "can't be blank" }, uniqueness: { message: "must be unique" } + validates :name, :city, :country, :start_date, :end_date, presence: true + validates :name, uniqueness: { scope: :start_date, message: "should be unique per start date" } + validates :state, presence: true, if: -> { country == "US" } + validates :state, inclusion: { in: VALID_STATES, message: "%{value} is not a valid state" }, + if: -> { country == "US" } + validate :end_date_after_start_date - GRADE_LEVELS = { + enum grade_level: { elementary: 0, middle_school: 1, high_school: 2, community_college: 3, university: 4 - }.freeze + } - has_many :pd_registrations + has_many :pd_registrations, dependent: :destroy has_many :teachers, through: :pd_registrations - def initialize(attributes = {}) - super(attributes) - # Now ActiveModel handles attributes, no need to manually set defaults for attributes defined above + def location + "#{city}, #{state}, #{country}" end - def persisted? - id.present? + def display_grade_level + return "Unknown" if grade_level_before_type_cast.to_i == -1 + + grade_level.to_s.titlecase end - def location - "#{city}, #{state}, #{country}" + def registration_open + @professional_development.registration_open ? "Yes" : "No" end - def display_grade_level - # Directly access the grade_level attribute - grade_level_value = self.grade_level - return "Unknown" if grade_level_value == -1 + private + def end_date_after_start_date + return if end_date.blank? || start_date.blank? - GRADE_LEVELS.key(grade_level_value).to_s.titlecase + errors.add(:end_date, "must be after the start date") if end_date < start_date end end diff --git a/db/migrate/20240404190834_create_professional_developments.rb b/db/migrate/20240404190834_create_professional_developments.rb new file mode 100644 index 00000000..61ba5f50 --- /dev/null +++ b/db/migrate/20240404190834_create_professional_developments.rb @@ -0,0 +1,17 @@ +class CreateProfessionalDevelopments < ActiveRecord::Migration[6.1] + def change + create_table :professional_developments do |t| + t.string :name, null: false + t.string :city, null: false + t.string :state + t.string :country, null: false + t.date :start_date, null: false + t.date :end_date, null: false + t.integer :grade_level, null: false + + t.timestamps + end + + add_index :professional_developments, [:name, :start_date], unique: true + end +end diff --git a/db/migrate/20240404191433_create_pd_registrations.rb b/db/migrate/20240404191433_create_pd_registrations.rb new file mode 100644 index 00000000..e81ddd0e --- /dev/null +++ b/db/migrate/20240404191433_create_pd_registrations.rb @@ -0,0 +1,17 @@ +class CreatePdRegistrations < ActiveRecord::Migration[6.1] + def change + create_table :pd_registrations do |t| + t.integer :teacher_id, null: false + t.integer :professional_development_id, null: false + t.boolean :attended, default: false + t.string :role, null: false + + t.timestamps + end + + add_index :pd_registrations, [:teacher_id, :professional_development_id], unique: true, + name: 'index_pd_reg_on_teacher_id_and_pd_id' + add_foreign_key :pd_registrations, :teachers + add_foreign_key :pd_registrations, :professional_developments + end +end diff --git a/db/schema.rb b/db/schema.rb index a9927666..44f6d7f9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_03_07_225738) do +ActiveRecord::Schema.define(version: 2024_04_04_191433) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -82,6 +82,29 @@ t.index ["url_slug"], name: "index_pages_on_url_slug", unique: true end + create_table "pd_registrations", force: :cascade do |t| + t.integer "teacher_id", null: false + t.integer "professional_development_id", null: false + t.boolean "attended", default: false + t.string "role", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["teacher_id", "professional_development_id"], name: "index_pd_reg_on_teacher_id_and_pd_id", unique: true + end + + create_table "professional_developments", force: :cascade do |t| + t.string "name", null: false + t.string "city", null: false + t.string "state" + t.string "country", null: false + t.date "start_date", null: false + t.date "end_date", null: false + t.integer "grade_level", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["name", "start_date"], name: "index_professional_developments_on_name_and_start_date", unique: true + end + create_table "schools", id: :serial, force: :cascade do |t| t.string "name" t.string "city" @@ -130,5 +153,7 @@ add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "pages", "teachers", column: "creator_id" add_foreign_key "pages", "teachers", column: "last_editor_id" + add_foreign_key "pd_registrations", "professional_developments" + add_foreign_key "pd_registrations", "teachers" add_foreign_key "teachers", "schools" end From 67dfaf5f9ca0e2ea005a0bc2bf92c2739e3a5a03 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:54:56 -0700 Subject: [PATCH 13/36] Adjust frontend view to align with backend implementation --- .../professional_developments/_form.html.erb | 6 ++-- .../professional_developments/index.html.erb | 4 +-- .../professional_developments/show.html.erb | 30 +++++++++++-------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/app/views/professional_developments/_form.html.erb b/app/views/professional_developments/_form.html.erb index 1d4244f6..f9748bea 100644 --- a/app/views/professional_developments/_form.html.erb +++ b/app/views/professional_developments/_form.html.erb @@ -25,7 +25,7 @@
<%= f.label :state, class: "label-required", for: "professional_development_state" %> - <%= f.select :state, School::VALID_STATES, { include_blank: "State" }, { id: "state_select", class: 'form-control' } %> + <%= f.select :state, ProfessionalDevelopment::VALID_STATES, { include_blank: "State" }, { id: "state_select", class: 'form-control' } %>
@@ -69,13 +69,13 @@ stateTextfieldContainer.hide(); stateTextfield.removeAttr('name'); - stateSelect.attr('name', 'mock_professional_development[state]'); + stateSelect.attr('name', 'professional_development[state]'); } else { stateTextfieldContainer.show().attr('required', false); stateSelectContainer.hide(); stateSelect.removeAttr('name'); - stateTextfield.attr('name', 'mock_professional_development[state]'); + stateTextfield.attr('name', 'professional_development[state]'); } } countrySelected.change(handleCountryChange); diff --git a/app/views/professional_developments/index.html.erb b/app/views/professional_developments/index.html.erb index d452b0a8..ed1bb5b4 100644 --- a/app/views/professional_developments/index.html.erb +++ b/app/views/professional_developments/index.html.erb @@ -9,7 +9,8 @@ Start Date End Date Grade Level - Registration Open + + Actions @@ -21,7 +22,6 @@ <%= pd.start_date %> <%= pd.end_date %> <%= pd.display_grade_level %> - <%= pd.registration_open ? 'Yes' : 'No' %> <%= link_to("Edit", edit_professional_development_path(pd), class: "btn btn-info") %> diff --git a/app/views/professional_developments/show.html.erb b/app/views/professional_developments/show.html.erb index 63e8c064..b92dde35 100644 --- a/app/views/professional_developments/show.html.erb +++ b/app/views/professional_developments/show.html.erb @@ -18,7 +18,7 @@
<% [['Location', @professional_development.location], ['Dates', "#{@professional_development.start_date.to_s} to #{@professional_development.end_date.to_s}"], - ['GradeLevel', @professional_development.display_grade_level]].each do |label, value| %> + ['Grade Level', @professional_development.display_grade_level]].each do |label, value| %>
<%= label %>
<%= value %> @@ -28,7 +28,10 @@
-

PD Registrations

+
+

PD Registrations

+
Total Registered Teachers: <%= @professional_development.pd_registrations.count %>
+
<%= button_tag "Add Teacher", type: 'button', class: "btn btn-success", data: { toggle: "modal", target: "#addTeacherModal" } %>
@@ -36,6 +39,7 @@ + @@ -46,6 +50,7 @@ <% @professional_development.pd_registrations.each do |registration| %> + @@ -77,13 +82,13 @@
Teacher ID Teacher Name PD Session Attended
<%= registration.teacher_id %> <%= link_to(registration.teacher_name, teacher_path(registration.teacher_id)) %> <%= @professional_development.name %> <%= registration.attended ? 'Yes' : 'No' %>
From 27351f135162d2587d5effce692f3fc5c96156e2 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:55:10 -0700 Subject: [PATCH 20/36] Add cucumber tests --- features/professional_development.feature | 190 ++++++++++++++++++ features/step_definitions/page_steps.rb | 13 ++ .../professional_development_steps.rb | 28 +++ 3 files changed, 231 insertions(+) create mode 100644 features/professional_development.feature create mode 100644 features/step_definitions/professional_development_steps.rb diff --git a/features/professional_development.feature b/features/professional_development.feature new file mode 100644 index 00000000..3dbc6558 --- /dev/null +++ b/features/professional_development.feature @@ -0,0 +1,190 @@ +Feature: Professional Development and Registration Management + + As an admin or a teacher + I want to manage professional development events and registrations + So that I can organize and track participation in PD activities + + Background: Admin and Teacher exist + Given the following teachers exist: + | first_name | last_name | admin | email | id | + | Perry | Zhong | true | testadminuser@berkeley.edu | 100 | + | Joseph | Mamoa | false | testteacher@berkeley.edu | 101 | + And the following professional developments exist: + | id | name | city | state | country | start_date | end_date | grade_level | created_at | updated_at | + | 10 | Classroom Management | San Francisco | CA | United States| 2023-02-01 | 2023-02-05 | university | 2023-02-01 00:00:00 | 2023-02-01 00:00:00 | + | 11 | Teaching Strategies | San Francisco | CA | United States| 2023-01-01 | 2023-01-05 | university | 2023-01-01 00:00:00 | 2023-01-01 00:00:00 | + +# Basic CRUD operations for Professional Developments + Scenario: Admin creates a new Professional Development + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + Then I should see "New Requests" + Then I go to the new professional development page + Then I should see "Create a new Professional Development" + And I fill in "Professional Development Name" with "Innovative Teaching Techniques" + And I fill in "City" with "San Francisco" + And I select "CA" from "State" dropdown + And I select "United States" from "Country" + And I fill in "Start Date" with "2023-12-01" + And I fill in "End Date" with "2023-12-05" + And I select "University" from "Grade Level" + And I press "Submit" + Then I should see a "info" flash message "Professional development created successfully." + And I should arrive at the professional development show page titled "Innovative Teaching Techniques" + + Scenario: Admin updates a Professional Development + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + When I go to the professional developments page +# first "Edit" link is for the Classroom Management, due to alphabetical order in the table + And I follow the first "Edit" link + And I fill in "Professional Development Name" with "Classroom Management 2.0" + And I press "Submit" + Then I should see a "info" flash message "Professional development updated successfully." + And I should arrive at the professional development show page titled "Classroom Management 2.0" + + Scenario: Admin deletes a Professional Development with confirmation + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + When I go to the professional developments page + And I should see "Classroom Management" + Then I follow the first "❌" link + And I confirm the action + Then I should see a "info" flash message "Professional development deleted successfully." + And I should be on the professional developments page + And I should not see "Classroom Management" + + Scenario: Admin deletes a Professional Development without confirmation + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + When I go to the professional developments page + And I should see "Classroom Management" + Then I follow the first "❌" link + And I dismiss the action + And I should be on the professional developments page + And I should see "Classroom Management" + + Scenario: Admin registers a teacher for a Professional Development + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + Then I go to the professional developments page + And I should see "Classroom Management" + And I follow the first "Classroom Management" link + Then I should see "Classroom Management" + Then I press "Add Registration" + Then I should see "Add Teacher to PD Session" + And I fill in "Teacher ID" with "100" + And I select "Attendee" from "Role" + And I select "Yes" from "Attended" + And I press "Add" + Then I should see a "info" flash message "Registration for professional development was successfully created." + And I should see "Total Registered Teachers: 1" + And I should see "Perry Zhong" + +# Basic CRUD operations for Professional Development Registrations + Scenario: Admin updates a teacher's registration for a Professional Development + Given the following professional developments registrations exist + | id | professional_development_id | teacher_id | role | attended | created_at | updated_at | + | 1 | 10 | 100 | attendee | yes | 2023-02-01 00:00:00 | 2023-02-01 00:00:00 | + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + Then I go to the professional developments page + And I should see "Classroom Management" + And I follow the first "Classroom Management" link + Then I should see "Classroom Management" + And I should see "Total Registered Teachers: 1" + And I should see "Perry Zhong" + And I should see "Yes" + And I follow "Update" + Then I should see "Edit Teacher in PD Session" + Then I select "No" from "Attended" + And I press "Add" + Then I should see a "info" flash message "Registration was successfully updated." + And I should see "Perry Zhong" + And I should see "No" + And I should not see "Yes" + + Scenario: Admin cancels a teacher's registration for a Professional Development + Given the following professional developments registrations exist + | id | professional_development_id | teacher_id | role | attended | created_at | updated_at | + | 1 | 10 | 100 | attendee | yes | 2023-02-01 00:00:00 | 2023-02-01 00:00:00 | + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + Then I go to the professional developments page + And I should see "Classroom Management" + And I follow the first "Classroom Management" link + Then I should see "Classroom Management" + And I should see "Total Registered Teachers: 1" + And I should see "Perry Zhong" + And I should see "Yes" + And I follow "❌" + And I confirm the action + Then I should see a "info" flash message "Registration was successfully cancelled." + And I should see "Total Registered Teachers: 0" + And I should not see "Perry Zhong" + And I should not see "Yes" + +# Advanced scenarios for Professional Development and Registration Management + Scenario: Admin creates a duplicate Professional Development registration should fail + Given the following professional developments registrations exist + | id | professional_development_id | teacher_id | role | attended | created_at | updated_at | + | 1 | 10 | 100 | attendee | yes | 2023-02-01 00:00:00 | 2023-02-01 00:00:00 | + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + When I go to the professional developments page + And I follow the first "Classroom Management" link + And I should see "Total Registered Teachers: 1" + And I press "Add Registration" + And I fill in "Teacher ID" with "100" + And I select "Attendee" from "Role" + And I select "Yes" from "Attended" + And I press "Add" + Then I should see a "danger" flash message "Teacher already has a registration for this PD" + And I should see "Total Registered Teachers: 1" + + Scenario: Admin attempts to create a Professional Development with an end date earlier than the start date + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + Then I should see "New Requests" + Then I go to the new professional development page + Then I should see "Create a new Professional Development" + And I fill in "Professional Development Name" with "Future Educational Strategies" + And I fill in "City" with "Los Angeles" + And I select "CA" from "State" dropdown + And I select "United States" from "Country" + And I fill in "Start Date" with "2023-10-01" + And I fill in "End Date" with "2023-09-30" + And I select "High School" from "Grade Level" + And I press "Submit" + Then I should see a "danger" flash message "End date must be after the start date" + And I should see "Add a Professional Development Workshop" + + Scenario: Admin attempts to create a Professional Development without mandatory fields + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + Then I should see "New Requests" + Then I go to the new professional development page + Then I should see "Add a Professional Development Workshop" + And I press "Submit" + Then I should be on the new professional development page + And I should see "Add a Professional Development Workshop" diff --git a/features/step_definitions/page_steps.rb b/features/step_definitions/page_steps.rb index 90c8796e..ca791de5 100644 --- a/features/step_definitions/page_steps.rb +++ b/features/step_definitions/page_steps.rb @@ -70,3 +70,16 @@ page_slug = Page.find_by(title: link_text).url_slug page.execute_script("document.getElementById('pagelink_#{page_slug}').click();") end + + +When(/^I confirm the action$/) do + # Confirm the alert that appears after triggering a confirmation dialog + page.accept_alert do + end +end + +When(/^I dismiss the action$/) do + # Dismiss the alert that appears after triggering a confirmation dialog + page.dismiss_confirm do + end +end diff --git a/features/step_definitions/professional_development_steps.rb b/features/step_definitions/professional_development_steps.rb new file mode 100644 index 00000000..13ab317a --- /dev/null +++ b/features/step_definitions/professional_development_steps.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "cucumber/rspec/doubles" + +Given(/the following professional developments exist/) do |professional_developments_table| + professional_developments_table.symbolic_hashes.each do |development| + ProfessionalDevelopment.create!(development) + end +end + +Given(/the following professional developments registrations exist/) do |pd_registrations_table| + pd_registrations_table.symbolic_hashes.each do |registration_hash| + development = ProfessionalDevelopment.find_by(id: registration_hash[:professional_development_id]) + raise "ProfessionalDevelopment not found: #{registration_hash[:professional_development_name]}" unless development + registration_details = registration_hash.merge(professional_development: development) + PdRegistration.create!(registration_details) + end +end + +Then(/^I should arrive at the professional development show page titled "([^"]*)"$/) do |title| + development = ProfessionalDevelopment.find_by(name: title) + expect(current_path).to eq(professional_development_path(development)) +end + +Then(/^I visit the professional development show page titled "([^"]*)"$/) do |title| + development = ProfessionalDevelopment.find_by(name: title) + visit(professional_development_path(development)) +end From a6a1c01b433d183c4f422f309ff2c0401d229413 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:02:43 -0700 Subject: [PATCH 21/36] Remove debugging msg --- app/controllers/pd_registrations_controller.rb | 2 +- app/controllers/professional_developments_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/pd_registrations_controller.rb b/app/controllers/pd_registrations_controller.rb index e1bfe1d7..aad1afd5 100644 --- a/app/controllers/pd_registrations_controller.rb +++ b/app/controllers/pd_registrations_controller.rb @@ -62,7 +62,7 @@ def set_pd_registration def set_professional_development @professional_development = ProfessionalDevelopment.find_by(id: params[:professional_development_id]) unless @professional_development - redirect_to professional_developments_path, alert: "Professional Development not found. test" + redirect_to professional_developments_path, alert: "Professional Development not found." end end diff --git a/app/controllers/professional_developments_controller.rb b/app/controllers/professional_developments_controller.rb index 67b6808f..673ec330 100644 --- a/app/controllers/professional_developments_controller.rb +++ b/app/controllers/professional_developments_controller.rb @@ -51,7 +51,7 @@ def destroy def set_professional_development @professional_development = ProfessionalDevelopment.find(params[:id]) rescue ActiveRecord::RecordNotFound - redirect_to professional_developments_url, alert: "Professional development not found. test2" + redirect_to professional_developments_url, alert: "Professional development not found." end def professional_development_params From 9e122edf2c0ab6882f0004e76e64415fbb276ea4 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Fri, 5 Apr 2024 18:38:34 -0700 Subject: [PATCH 22/36] Fix indexing & uniqueness check for pd --- app/models/professional_development.rb | 5 ----- .../20240404190834_create_professional_developments.rb | 2 -- db/schema.rb | 1 - 3 files changed, 8 deletions(-) diff --git a/app/models/professional_development.rb b/app/models/professional_development.rb index 8962e17f..9467b217 100644 --- a/app/models/professional_development.rb +++ b/app/models/professional_development.rb @@ -15,16 +15,11 @@ # created_at :datetime not null # updated_at :datetime not null # -# Indexes -# -# index_professional_developments_on_name_and_start_date (name,start_date) UNIQUE -# class ProfessionalDevelopment < ApplicationRecord VALID_STATES = %w[AL AK AS AZ AR CA CO CT DE DC FM FL GA GU HI ID IL IN IA KS KY LA ME MH MD MA MI MN MS MO MT NE NV NH NJ NM NY NC ND MP OH OK OR PW PA PR RI SC SD TN TX UT VT VI VA WA WV WI WY].freeze validates :name, :city, :country, :start_date, :end_date, presence: true - validates :name, uniqueness: { scope: :start_date, message: "should be unique per start date" } validates :state, presence: true, if: -> { country == "US" } validates :state, inclusion: { in: VALID_STATES, message: "%{value} is not a valid state" }, if: -> { country == "US" } diff --git a/db/migrate/20240404190834_create_professional_developments.rb b/db/migrate/20240404190834_create_professional_developments.rb index 61ba5f50..f551d470 100644 --- a/db/migrate/20240404190834_create_professional_developments.rb +++ b/db/migrate/20240404190834_create_professional_developments.rb @@ -11,7 +11,5 @@ def change t.timestamps end - - add_index :professional_developments, [:name, :start_date], unique: true end end diff --git a/db/schema.rb b/db/schema.rb index 44f6d7f9..0f32ca27 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -102,7 +102,6 @@ t.integer "grade_level", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false - t.index ["name", "start_date"], name: "index_professional_developments_on_name_and_start_date", unique: true end create_table "schools", id: :serial, force: :cascade do |t| From 700c87ea4bcc6a08668b50a3d9d76ebdf318f0db Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Fri, 5 Apr 2024 18:39:31 -0700 Subject: [PATCH 23/36] Add rspec tests --- Gemfile | 3 + Gemfile.lock | 5 + .../pd_registrations_controller_spec.rb | 120 ++++++++++++++++++ ...ofessional_developments_controller_spec.rb | 119 +++++++++++++++++ 4 files changed, 247 insertions(+) create mode 100644 spec/controllers/pd_registrations_controller_spec.rb create mode 100644 spec/controllers/professional_developments_controller_spec.rb diff --git a/Gemfile b/Gemfile index 0b73d21e..e1dac083 100644 --- a/Gemfile +++ b/Gemfile @@ -96,4 +96,7 @@ group :test do # Accessibility Testing gem "axe-core-rspec" gem "axe-core-cucumber" + + # Test suite speedup + gem "rails-controller-testing" end diff --git a/Gemfile.lock b/Gemfile.lock index 24ef2246..9d16489e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -399,6 +399,10 @@ GEM bundler (>= 1.15.0) railties (= 6.1.7.6) sprockets-rails (>= 2.0.0) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -595,6 +599,7 @@ DEPENDENCIES puma (~> 5) rack-mini-profiler (~> 2.0) rails (= 6.1.7.6) + rails-controller-testing rspec-rails rubocop rubocop-faker diff --git a/spec/controllers/pd_registrations_controller_spec.rb b/spec/controllers/pd_registrations_controller_spec.rb new file mode 100644 index 00000000..1a91a95f --- /dev/null +++ b/spec/controllers/pd_registrations_controller_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "PdRegistrations", type: :request do + fixtures :all + + let(:admin_user) { teachers(:admin) } + let(:regular_user) { teachers(:validated_teacher) } + let(:professional_development) { ProfessionalDevelopment.create!(name: "Effective Teaching Strategies", city: "City", state: "State", country: "Country", start_date: Date.today, end_date: Date.tomorrow, grade_level: "university") } + let(:teacher1) { teachers(:bob) } + let(:teacher2) { teachers(:long) } + let(:valid_registration_attributes) { + { + teacher_id: teacher1.id, + professional_development_id: professional_development.id, + attended: true, + role: "attendee" + } + } + let(:valid_registration_attributes2) { + { + teacher_id: teacher2.id, + professional_development_id: professional_development.id, + attended: true, + role: "attendee" + } + } + let(:invalid_registration_attributes) { + { + teacher_id: nil, + professional_development_id: nil, + attended: false, + role: "" + } + } + let!(:pd_registration) { PdRegistration.create!(valid_registration_attributes) } + + shared_examples "admin access" do + it "allows operation" do + action + expect(flash[:notice]).to match(/successfully/) + end + end + + shared_examples "regular user denied" do + it "denies operation" do + action + expect(flash[:danger]).to be_present + end + end + + describe "CRUD operations for PD Registrations" do + describe "CREATE" do + let(:action) { post professional_development_pd_registrations_path( + professional_development_id: professional_development.id), + params: { pd_registration: valid_registration_attributes2 } } + + context "as an admin" do + before { log_in(admin_user) } + include_examples "admin access" + end + + context "as a regular user" do + before { log_in(regular_user) } + include_examples "regular user denied" + end + end + + describe "UPDATE" do + context "with valid attributes" do + let(:action) { patch professional_development_pd_registration_path( + professional_development_id: professional_development.id, id: pd_registration.id), + params: { pd_registration: { role: "leader" } } } + + context "as an admin" do + before { log_in(admin_user) } + include_examples "admin access" + end + + context "as a regular user" do + before { log_in(regular_user) } + include_examples "regular user denied" + end + end + + context "with invalid attributes" do + let(:action) { patch professional_development_pd_registration_path( + professional_development_id: professional_development.id, id: pd_registration.id), + params: { pd_registration: invalid_registration_attributes } } + + it "fails to update and redirects for admin" do + log_in(admin_user) + action + expect(response).to render_template(:show) + expect(flash[:alert]).to be_present + end + end + end + + describe "DELETE" do + let(:action) { delete professional_development_pd_registration_path( + professional_development_id: professional_development.id, id: pd_registration.id) } + + context "as an admin" do + before { log_in(admin_user) } + it "deletes the PD registration" do + expect { action }.to change(PdRegistration, :count).by(-1) + expect(response).to redirect_to(professional_development_path(professional_development)) + expect(flash[:notice]).to match(/successfully cancelled/) + end + end + + context "as a regular user" do + before { log_in(regular_user) } + include_examples "regular user denied" + end + end + end +end diff --git a/spec/controllers/professional_developments_controller_spec.rb b/spec/controllers/professional_developments_controller_spec.rb new file mode 100644 index 00000000..a6952181 --- /dev/null +++ b/spec/controllers/professional_developments_controller_spec.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "ProfessionalDevelopments", type: :request do + fixtures :all + + let(:admin_user) { teachers(:admin) } + let(:regular_user) { teachers(:validated_teacher) } + let(:valid_attributes) { + { + name: "Effective Teaching Strategies", + city: "City", + state: "State", + country: "Country", + start_date: Date.today, + end_date: Date.tomorrow, + grade_level: "university", + } + } + let(:valid_attributes2) { + { + name: "PD2", + city: "City", + state: "State", + country: "Country", + start_date: Date.today, + end_date: Date.tomorrow, + grade_level: "university", + } + } + let(:invalid_attributes) { + { + name: "", + city: "", + state: "", + country: "", + start_date: Date.tomorrow, + end_date: Date.today - 1.day, + grade_level: "" + } + } + let!(:professional_development) { ProfessionalDevelopment.create!(valid_attributes) } + + shared_examples "admin access" do + it "allows operation" do + action + expect(flash[:notice]).to match(/successfully/) + end + end + + shared_examples "regular user denied" do + it "denies operation and redirects" do + action + expect(flash[:danger]).to be_present + end + end + + describe "CRUD operations" do + describe "CREATE" do + let(:action) { post professional_developments_path, params: { professional_development: valid_attributes2 } } + + context "as an admin" do + before { log_in(admin_user) } + include_examples "admin access" + end + + context "as a regular user" do + before { log_in(regular_user) } + include_examples "regular user denied" + end + end + + describe "UPDATE" do + context "with valid attributes" do + let(:action) { patch professional_development_path(professional_development), params: { professional_development: { name: "Updated PD Name" } } } + + context "as an admin" do + before { log_in(admin_user) } + include_examples "admin access" + end + + context "as a regular user" do + before { log_in(regular_user) } + include_examples "regular user denied" + end + end + + context "with invalid attributes" do + let(:action) { patch professional_development_path(professional_development), params: { professional_development: invalid_attributes } } + + it "fails to update and re-renders edit for admin" do + log_in(admin_user) + action + expect(response).to render_template(:edit) + expect(flash[:alert]).to be_present + end + end + end + + describe "DELETE" do + let(:action) { delete professional_development_path(professional_development) } + + context "as an admin" do + before { log_in(admin_user) } + it "deletes the professional development" do + expect { action }.to change(ProfessionalDevelopment, :count).by(-1) + expect(response).to redirect_to(professional_developments_path) + expect(flash[:notice]).to match(/deleted successfully/) + end + end + + context "as a regular user" do + before { log_in(regular_user) } + include_examples "regular user denied" + end + end + end +end From dbeb705b89baf3b759f76b4fc9d39471adb0f5e1 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Fri, 5 Apr 2024 18:40:00 -0700 Subject: [PATCH 24/36] Add admin check for pd registrations --- app/controllers/pd_registrations_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/pd_registrations_controller.rb b/app/controllers/pd_registrations_controller.rb index aad1afd5..55c171ef 100644 --- a/app/controllers/pd_registrations_controller.rb +++ b/app/controllers/pd_registrations_controller.rb @@ -2,6 +2,7 @@ class PdRegistrationsController < ApplicationController before_action :require_login + before_action :require_admin before_action :set_pd_registration, only: [:show, :edit, :update, :destroy] before_action :set_professional_development, only: [:new, :create, :edit, :update, :destroy] From 2c154298816be6ec0bd2b283c3dece298f896c20 Mon Sep 17 00:00:00 2001 From: JacksonXu33 Date: Mon, 8 Apr 2024 21:31:37 -0700 Subject: [PATCH 25/36] fix update path bug for admins, and some refactor --- app/controllers/teachers_controller.rb | 63 ++++++++++++++------------ 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/app/controllers/teachers_controller.rb b/app/controllers/teachers_controller.rb index d64b2430..1b986e08 100644 --- a/app/controllers/teachers_controller.rb +++ b/app/controllers/teachers_controller.rb @@ -107,30 +107,22 @@ def update @teacher.school = @school end send_email_if_application_status_changed_and_email_resend_enabled - if @teacher.denied? && !is_admin? - redirect_to root_path, alert: "Failed to update your information. You have already been denied. If you have questions, please email contact@bjc.berkeley.edu." - return - end - if (@teacher.email_changed? || @teacher.snap_changed?) && !is_admin? - redirect_to edit_teacher_path(current_user.id), alert: "Failed to update your information. If you want to change your email or Snap! username, please email contact@bjc.berkeley.edu." - return - end - if !@teacher.save - redirect_to edit_teacher_path(current_user.id), - alert: "An error occurred: #{@teacher.errors.full_messages.join(', ')}" + + if fail_to_update return end + if !@teacher.validated? && !current_user.admin? TeacherMailer.form_submission(@teacher).deliver_now TeacherMailer.teacher_form_submission(@teacher).deliver_now end + if is_admin? - redirect_to edit_teacher_path(current_user.id), notice: "Saved #{@teacher.full_name}" - return + redirect_to teachers_path, notice: "Saved #{@teacher.full_name}" else @teacher.try_append_ip(request.remote_ip) + redirect_to edit_teacher_path(current_user.id), notice: "Successfully updated your information" end - redirect_to edit_teacher_path(current_user.id), notice: "Successfully updated your information" end def send_email_if_application_status_changed_and_email_resend_enabled @@ -201,6 +193,23 @@ def deny_access redirect_to new_teacher_path, alert: "Email address or Snap username already in use. Please use a different email or Snap username." end + def fail_to_update + failed = false + if @teacher.denied? && !is_admin? + redirect_to root_path, alert: "Failed to update your information. You have already been denied. If you have questions, please email contact@bjc.berkeley.edu." + failed = true + elsif (@teacher.email_changed? || @teacher.snap_changed?) && !is_admin? + flash.now[:alert] = "Failed to update your information. If you want to change your email or Snap! username, please email contact@bjc.berkeley.edu." + render "edit" + failed = true + elsif !@teacher.save + flash.now[:alert] = "Failed to update data. #{@teacher.errors.full_messages.to_sentence}" + render "edit" + failed = true + end + failed + end + def load_school if teacher_params[:school_id].present? @school ||= School.find(teacher_params[:school_id]) @@ -238,22 +247,20 @@ def ordered_schools end def sanitize_params - if params[:teacher] - if params[:teacher][:status] - params[:teacher][:status] = params[:teacher][:status].to_i - end - if params[:teacher][:education_level] - params[:teacher][:education_level] = params[:teacher][:education_level].to_i - end + teacher = params[:teacher] + if teacher && teacher[:status] + teacher[:status] = teacher[:status].to_i + end + if teacher && teacher[:education_level] + teacher[:education_level] = teacher[:education_level].to_i end - if params[:school] - if params[:school][:grade_level] - params[:school][:grade_level] = params[:school][:grade_level].to_i - end - if params[:school][:school_type] - params[:school][:school_type] = params[:school][:school_type].to_i - end + school = params[:school] + if school && school[:grade_level] + school[:grade_level] = school[:grade_level].to_i + end + if school && school[:school_type] + school[:school_type] = school[:school_type].to_i end end From 9099abd2130ebc374ecb86e5dd0effec9a864794 Mon Sep 17 00:00:00 2001 From: JacksonXu33 Date: Thu, 11 Apr 2024 15:49:22 -0700 Subject: [PATCH 26/36] add more helper methods to update and create --- app/controllers/teachers_controller.rb | 74 +++++++++++++++++--------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/app/controllers/teachers_controller.rb b/app/controllers/teachers_controller.rb index 1b986e08..8739064e 100644 --- a/app/controllers/teachers_controller.rb +++ b/app/controllers/teachers_controller.rb @@ -49,25 +49,13 @@ def new # TODO: This needs to be re-written. # If you are logged in and not an admin, this should fail. def create - # Find by email, but allow updating other info. - @teacher = Teacher.find_by(email: teacher_params[:email]) - if @teacher && defined?(current_user.id) && (current_user.id == @teacher.id) - params[:id] = current_user.id - update - return - elsif @teacher - redirect_to login_path, - notice: "You already have signed up with '#{@teacher.email}'. Please log in." + if existing_teacher return end - load_school - if @school.new_record? - @school = School.new(school_params) - unless @school.save - flash[:alert] = "An error occurred! #{@school.errors.full_messages.join(', ')}" - render "new" && return - end + valid_school = find_or_create_school + if !valid_school + return end @teacher = Teacher.new(teacher_params) @@ -96,16 +84,12 @@ def update load_school ordered_schools @teacher.assign_attributes(teacher_params) - if teacher_params[:school_id].present? - @teacher.school = @school - else - @school.update(school_params) if school_params - unless @school.save - flash[:alert] = "An error occurred: #{@school.errors.full_messages.join(', ')}" - render "new" && return - end - @teacher.school = @school + + valid_school = update_school_through_teacher + if !valid_school + return end + send_email_if_application_status_changed_and_email_resend_enabled if fail_to_update @@ -193,6 +177,46 @@ def deny_access redirect_to new_teacher_path, alert: "Email address or Snap username already in use. Please use a different email or Snap username." end + def existing_teacher + # Find by email, but allow updating other info. + @teacher = Teacher.find_by(email: teacher_params[:email]) + if @teacher && defined?(current_user.id) && (current_user.id == @teacher.id) + params[:id] = current_user.id + update + return true + elsif @teacher + redirect_to login_path, notice: "You already have signed up with '#{@teacher.email}'. Please log in." + return true + end + false + end + + def find_or_create_school + load_school + if @school.new_record? + @school = School.new(school_params) + unless @school.save + flash[:alert] = "An error occurred! #{@school.errors.full_messages.join(', ')}" + render "new" + return false + end + end + true + end + + def update_school_through_teacher + if !teacher_params[:school_id].present? + @school.update(school_params) if school_params + unless @school.save + flash[:alert] = "An error occurred: #{@school.errors.full_messages.join(', ')}" + render "new" + return false + end + end + @teacher.school = @school + true + end + def fail_to_update failed = false if @teacher.denied? && !is_admin? From 54eebc6f8fb43c4cd255ecdfeea93cd732e95953 Mon Sep 17 00:00:00 2001 From: JacksonXu33 Date: Thu, 11 Apr 2024 22:17:17 -0700 Subject: [PATCH 27/36] fix school bug in create method --- app/controllers/teachers_controller.rb | 32 +++++++++----------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/app/controllers/teachers_controller.rb b/app/controllers/teachers_controller.rb index 8739064e..725a3610 100644 --- a/app/controllers/teachers_controller.rb +++ b/app/controllers/teachers_controller.rb @@ -53,9 +53,13 @@ def create return end - valid_school = find_or_create_school - if !valid_school - return + load_school + if @school.new_record? + @school = School.new(school_params) + unless @school.save + flash[:alert] = "An error occurred! #{@school.errors.full_messages.join(', ')}" + render "new" && return + end end @teacher = Teacher.new(teacher_params) @@ -191,19 +195,6 @@ def existing_teacher false end - def find_or_create_school - load_school - if @school.new_record? - @school = School.new(school_params) - unless @school.save - flash[:alert] = "An error occurred! #{@school.errors.full_messages.join(', ')}" - render "new" - return false - end - end - true - end - def update_school_through_teacher if !teacher_params[:school_id].present? @school.update(school_params) if school_params @@ -218,20 +209,19 @@ def update_school_through_teacher end def fail_to_update - failed = false if @teacher.denied? && !is_admin? redirect_to root_path, alert: "Failed to update your information. You have already been denied. If you have questions, please email contact@bjc.berkeley.edu." - failed = true + return true elsif (@teacher.email_changed? || @teacher.snap_changed?) && !is_admin? flash.now[:alert] = "Failed to update your information. If you want to change your email or Snap! username, please email contact@bjc.berkeley.edu." render "edit" - failed = true + return true elsif !@teacher.save flash.now[:alert] = "Failed to update data. #{@teacher.errors.full_messages.to_sentence}" render "edit" - failed = true + return true end - failed + false end def load_school From 119fb61b5de23d49e74293d9f3c1a53bcf5eb9af Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:09:45 -0700 Subject: [PATCH 28/36] Backup some frontend change for multi personal emails --- app/models/teacher.rb | 2 +- app/views/teachers/_form.html.erb | 32 ++++++++++++++++------- app/views/teachers/_teacher_info.html.erb | 13 +++++---- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/app/models/teacher.rb b/app/models/teacher.rb index 73661cf7..586d922b 100644 --- a/app/models/teacher.rb +++ b/app/models/teacher.rb @@ -303,6 +303,6 @@ def personal_emails def non_primary_emails # email_addresses.where(primary: false)&.pluck(:email) # below code is temporary for current PR, to make sure the frontend same as before (only one personal email) - email_addresses.where(primary: false)&.pluck(:email)&.first + email_addresses.where(primary: false)&.pluck(:email) end end diff --git a/app/views/teachers/_form.html.erb b/app/views/teachers/_form.html.erb index 0d8e1889..8e228987 100644 --- a/app/views/teachers/_form.html.erb +++ b/app/views/teachers/_form.html.erb @@ -82,16 +82,19 @@ status ONLY IF the person viewing this page is an admin. %> <%# For now... only admins can enter/edit personal emails. %> - <%- if current_user&.admin? || @teacher.personal_emails.present? %> -
-
- <%= label_tag "email[personal_1]" do %> - Personal Email + <% if @current_user&.admin? %> +
+ <%= label_tag "Personal Emails", nil, class: "col-12 label-required" %> + <% @teacher.personal_emails.each_with_index do |email, index| %> +
+ <%= label_tag "email[personal][#{index}]", "Email #{index + 1}", class: "form-label" %> + <%= text_field_tag "email[personal][#{index}]", email, placeholder: 'Enter email', class: 'form-control', type: :email %> +
<% end %> - <%= text_field_tag "email[personal_1]", @teacher.personal_emails, placeholder: 'alonzo@gmail.com', class: 'form-control', - required: false, type: :email, readonly: @readonly %> +
+ <%= link_to 'Add Email', '#', id: 'add_email', class: 'btn btn-secondary' %> +
-
<% end %>
@@ -211,6 +214,15 @@ status ONLY IF the person viewing this page is an admin. %> } - - + // dynamically add new personal email fields + document.getElementById('add_email').addEventListener('click', function(e) { + e.preventDefault(); + var container = this.parentNode.parentNode; + var newEmailIndex = container.querySelectorAll('input[type="email"]').length; + var newField = document.createElement('div'); + newField.classList.add('col-md-6', 'mb-3'); + newField.innerHTML = ` + `; + container.insertBefore(newField, this.parentNode); + }); diff --git a/app/views/teachers/_teacher_info.html.erb b/app/views/teachers/_teacher_info.html.erb index aa101153..b35434a6 100644 --- a/app/views/teachers/_teacher_info.html.erb +++ b/app/views/teachers/_teacher_info.html.erb @@ -19,13 +19,12 @@
Personal Email:
-
- + <% teacher.personal_emails.each do |email| %> +
+ <%= email %> + +
+ <% end %>
From b4b2f75b9621a6c98d6749d71b7a4ef49d61f677 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Thu, 18 Apr 2024 19:17:41 -0700 Subject: [PATCH 29/36] Update backend --- app/controllers/email_addresses_controller.rb | 46 +++++++++++++++++++ app/controllers/teachers_controller.rb | 17 ------- config/routes.rb | 1 + 3 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 app/controllers/email_addresses_controller.rb diff --git a/app/controllers/email_addresses_controller.rb b/app/controllers/email_addresses_controller.rb new file mode 100644 index 00000000..dd0a8079 --- /dev/null +++ b/app/controllers/email_addresses_controller.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class EmailAddressesController < ApplicationController + before_action :require_login + before_action :require_admin + before_action :set_teacher + + def edit + end + + def update + if update_personal_emails + redirect_to teacher_path(@teacher), notice: "Personal email addresses updated successfully." + else + flash.now[:alert] = "An error occurred: " + (@error_messages || "Unknown error") + render :edit + end + end + + private + def set_teacher + @teacher = Teacher.find(params[:teacher_id]) + end + + def update_personal_emails + EmailAddress.transaction do + params[:teacher][:email_addresses_attributes].each do |_, email_attr| + if email_attr[:id].present? + email = EmailAddress.find(email_attr[:id]) + if email_attr[:_destroy] == "1" + email.destroy! + else + email.update!(email: email_attr[:email]) + end + else + @teacher.email_addresses.create!(email: email_attr[:email]) unless email_attr[:email].blank? + end + end + end + true + rescue ActiveRecord::RecordInvalid => e + @error_messages = e.record&.errors&.full_messages&.join(", ") + @error_messages ||= "A validation error occurred, but no specific error details are available." + false + end +end diff --git a/app/controllers/teachers_controller.rb b/app/controllers/teachers_controller.rb index 58d25ddb..a4ef061a 100644 --- a/app/controllers/teachers_controller.rb +++ b/app/controllers/teachers_controller.rb @@ -100,13 +100,10 @@ def update ordered_schools primary_email = params.dig(:email, :primary) - personal_emails = params[:email]&.select { |key, value| key.start_with?("personal_") }&.values - # Now, `params[:teacher]` does not contain primary_email or any personal_emailX fields @teacher.assign_attributes(teacher_params) update_primary_email(primary_email) - update_personal_emails(personal_emails) if teacher_params[:school_id].present? @teacher.school = @school else @@ -283,18 +280,4 @@ def update_primary_email(primary_email) primary_email_record.save if primary_email_record.changed? end - - def update_personal_emails(personal_emails) - return unless personal_emails.present? - personal_emails = personal_emails.reject(&:empty?) - return if personal_emails.empty? - - current_emails = @teacher.email_addresses.pluck(:email) - - new_emails = personal_emails - current_emails - - new_emails.each do |email| - @teacher.email_addresses.build(email:, primary: false) - end - end end diff --git a/config/routes.rb b/config/routes.rb index beab79d8..86352a12 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,6 +8,7 @@ root to: "main#index" resources :teachers do + resource :email_address, only: [:edit, :update, :create] member do post :resend_welcome_email post :validate From 82f4c4c631c607be6c9b81f0f4b6371d4ac15597 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Thu, 18 Apr 2024 19:18:54 -0700 Subject: [PATCH 30/36] Update frontend --- app/views/email_addresses/edit.html.erb | 65 +++++++++++++++++++++++ app/views/schools/show.html.erb | 9 +++- app/views/teachers/_form.html.erb | 27 ---------- app/views/teachers/_teacher.erb | 9 ++-- app/views/teachers/_teacher_info.html.erb | 1 + 5 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 app/views/email_addresses/edit.html.erb diff --git a/app/views/email_addresses/edit.html.erb b/app/views/email_addresses/edit.html.erb new file mode 100644 index 00000000..ebd0a2d0 --- /dev/null +++ b/app/views/email_addresses/edit.html.erb @@ -0,0 +1,65 @@ +<%= form_with(model: @teacher, url: teacher_email_address_path(@teacher), method: :put, local: true) do |form| %> +
+ <%= form.fields_for :email_addresses, @teacher.email_addresses.where(primary: false) do |email_form| %> + + <% end %> +
+
+ <%= link_to 'Add Email', '#', class: 'btn btn-secondary', id: 'add_email' %> +
+
+ <%= form.submit 'Update Emails', class: 'btn btn-primary' %> +
+<% end %> + + diff --git a/app/views/schools/show.html.erb b/app/views/schools/show.html.erb index 092554f0..09ad27fc 100644 --- a/app/views/schools/show.html.erb +++ b/app/views/schools/show.html.erb @@ -66,7 +66,14 @@ <% @school.teachers.each do |teacher| %>
- + diff --git a/app/views/teachers/_form.html.erb b/app/views/teachers/_form.html.erb index 8e228987..7c3cf65e 100644 --- a/app/views/teachers/_form.html.erb +++ b/app/views/teachers/_form.html.erb @@ -82,21 +82,6 @@ status ONLY IF the person viewing this page is an admin. %> <%# For now... only admins can enter/edit personal emails. %> - <% if @current_user&.admin? %> -
- <%= label_tag "Personal Emails", nil, class: "col-12 label-required" %> - <% @teacher.personal_emails.each_with_index do |email, index| %> -
- <%= label_tag "email[personal][#{index}]", "Email #{index + 1}", class: "form-label" %> - <%= text_field_tag "email[personal][#{index}]", email, placeholder: 'Enter email', class: 'form-control', type: :email %> -
- <% end %> -
- <%= link_to 'Add Email', '#', id: 'add_email', class: 'btn btn-secondary' %> -
-
- <% end %> -
<%= f.label :status, "What's your current status?", class: "label-required" %> @@ -213,16 +198,4 @@ status ONLY IF the person viewing this page is an admin. %> } } - - // dynamically add new personal email fields - document.getElementById('add_email').addEventListener('click', function(e) { - e.preventDefault(); - var container = this.parentNode.parentNode; - var newEmailIndex = container.querySelectorAll('input[type="email"]').length; - var newField = document.createElement('div'); - newField.classList.add('col-md-6', 'mb-3'); - newField.innerHTML = ` - `; - container.insertBefore(newField, this.parentNode); - }); diff --git a/app/views/teachers/_teacher.erb b/app/views/teachers/_teacher.erb index aae3c6af..1b926d81 100644 --- a/app/views/teachers/_teacher.erb +++ b/app/views/teachers/_teacher.erb @@ -4,10 +4,11 @@
<%= link_to(teacher.full_name, teacher_path(teacher)) %><%= teacher.primary_email %> + <%= teacher.primary_email %> + <% if teacher.personal_emails.present? %> + <% teacher.personal_emails.each do |email| %> +
<%= email %> + <% end %> + <% end %> +
<%= teacher.display_status %> <%= teacher.display_application_status %> <%= teacher.created_at.strftime("%b %d, %Y") %> <%= teacher.primary_email %> - - <%#- if teacher.personal_emails.present? %> - - <%#- end %> + <% if teacher.personal_emails.present? %> + <% teacher.personal_emails.each do |email| %> +
<%= email %> + <% end %> + <% end %>
<%= teacher.display_education_level %> <%= teacher.full_name %>
<%= link_to "Edit Information", edit_teacher_path(teacher), class: "btn btn-primary mr-2" %> + <%= link_to "Edit Personal Emails", edit_teacher_email_address_path(teacher), class: "btn btn-secondary mr-2" %> <%= link_to("Delete", teacher_path(teacher), method: "delete", class: "btn btn-danger", data: {confirm: "Are you sure?"}) %>
From 5cc4676bd5a44714ca9fc51f8c23b94a64c684c7 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Thu, 18 Apr 2024 19:19:14 -0700 Subject: [PATCH 31/36] Add cucumber tests --- features/email_addresses.feature | 99 +++++++++++++++++++ .../step_definitions/email_addresses_steps.rb | 23 +++++ 2 files changed, 122 insertions(+) create mode 100644 features/email_addresses.feature create mode 100644 features/step_definitions/email_addresses_steps.rb diff --git a/features/email_addresses.feature b/features/email_addresses.feature new file mode 100644 index 00000000..ae066b58 --- /dev/null +++ b/features/email_addresses.feature @@ -0,0 +1,99 @@ +Feature: Managing personal email addresses for teachers + + Background: + Background: Has an Admin in DB + Given the following teachers exist: + | first_name | last_name | admin | primary_email | + | Admin | User | true | testadminuser@berkeley.edu | + Given the following schools exist: + | name | country | city | state | website | grade_level | school_type | + | UC Berkeley | US | Berkeley | CA | https://www.berkeley.edu | university | public | + Given the following teachers exist: + | first_name | last_name | admin | primary_email | school | snap | application_status | + | Joseph | Mamoa | false | jose.primary@email.com | UC Berkeley | false | Validated | + Given the following emails exist: + | first_name | last_name | email | primary | + | Joseph | Mamoa | jose.personal1@email.com | false | + | Joseph | Mamoa | jose.personal2@email.com | false | + + + Scenario: Admin views teacher personal emails + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + When I follow "Teachers" + And I follow "Joseph Mamoa" + Then I should see "Admin View Joseph Mamoa" + Then I should see "jose.personal1@email.com" + Then I should see "jose.personal2@email.com" + Then I should see "Edit Personal Emails" + + Scenario: Admin adds a personal email address to a teacher + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + When I follow "Teachers" + And I follow "Joseph Mamoa" + And I follow "Edit Personal Emails" + And I follow "Add Email" + And I fill in "teacher[email_addresses_attributes][2][email]" with "jose.personal3@email.com" + And I press "Update Emails" + Then I should see a "info" flash message "Personal email addresses updated successfully." + Then I should see "Admin View Joseph Mamoa" + Then I should see "jose.personal3@email.com" + + Scenario: Admin updates an existing personal email address + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + When I follow "Teachers" + And I follow "Joseph Mamoa" + And I follow "Edit Personal Emails" + And I fill in "teacher[email_addresses_attributes][1][email]" with "jose.personalxxxxxx@email.com" + And I press "Update Emails" + Then I should see a "info" flash message "Personal email addresses updated successfully." + Then I should not see "jose.personal2@email.com" + And I should see "jose.personal1@email.com" + And I should see "jose.personalxxxxxx@email.com" + + Scenario: Admin deletes a personal email address + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + When I follow "Teachers" + And I follow "Joseph Mamoa" + And I follow "Edit Personal Emails" + And I press "Remove" next to "jose.personal1@email.com" + And I press "Update Emails" + Then I should see a "info" flash message "Personal email addresses updated successfully." + Then I should not see "jose.personal1@email.com" + And I should see "jose.personal2@email.com" + + Scenario: Admin tries to add an existing personal email address to the same teacher + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + When I follow "Teachers" + And I follow "Joseph Mamoa" + And I follow "Edit Personal Emails" + And I follow "Add Email" + And I fill in "teacher[email_addresses_attributes][2][email]" with "jose.personal1@email.com" + And I press "Update Emails" + Then I should see a "danger" flash message "An error occurred: Email has already been taken" + + Scenario: Admin tries to add an email that exists for another teacher + Given I am on the BJC home page + And I have an admin email + And I follow "Log In" + Then I can log in with Google + When I follow "Teachers" + And I follow "Joseph Mamoa" + And I follow "Edit Personal Emails" + And I fill in "teacher[email_addresses_attributes][0][email]" with "testadminuser@berkeley.edu" + And I press "Update Emails" + Then I should see a "danger" flash message "An error occurred: Email has already been taken" diff --git a/features/step_definitions/email_addresses_steps.rb b/features/step_definitions/email_addresses_steps.rb new file mode 100644 index 00000000..d7c9f537 --- /dev/null +++ b/features/step_definitions/email_addresses_steps.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "cucumber/rspec/doubles" + +Given(/the following emails exist/) do |emails_table| + emails_table.symbolic_hashes.each do |email_hash| + teacher = Teacher.find_by(first_name: email_hash[:first_name], last_name: email_hash[:last_name]) + raise "Teacher not found for email: #{email_hash[:email]}" unless teacher + + EmailAddress.create!( + email: email_hash[:email], + teacher:, + primary: email_hash[:primary] == "true" + ) + end +end + +When(/^I press "([^"]*)" next to "([^"]*)"$/) do |button_text, email_text| + email_field = all('.email-field').find { |field| field.has_css?('input[type="email"][value="' + email_text + '"]') } + within(email_field) do + click_link(button_text) + end +end From 7f31e97892182d06f646a65ddab0d115ff86f4ac Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Thu, 18 Apr 2024 19:33:01 -0700 Subject: [PATCH 32/36] Fix rubocop --- features/step_definitions/email_addresses_steps.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/step_definitions/email_addresses_steps.rb b/features/step_definitions/email_addresses_steps.rb index d7c9f537..4c8f0171 100644 --- a/features/step_definitions/email_addresses_steps.rb +++ b/features/step_definitions/email_addresses_steps.rb @@ -16,7 +16,7 @@ end When(/^I press "([^"]*)" next to "([^"]*)"$/) do |button_text, email_text| - email_field = all('.email-field').find { |field| field.has_css?('input[type="email"][value="' + email_text + '"]') } + email_field = all(".email-field").find { |field| field.has_css?('input[type="email"][value="' + email_text + '"]') } within(email_field) do click_link(button_text) end From 9b318f928a57ea1d95b440d1d0b2e868150418c4 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Thu, 18 Apr 2024 19:40:32 -0700 Subject: [PATCH 33/36] Add rspec for email validation --- .../email_addresses_controller_spec.rb | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 spec/controllers/email_addresses_controller_spec.rb diff --git a/spec/controllers/email_addresses_controller_spec.rb b/spec/controllers/email_addresses_controller_spec.rb new file mode 100644 index 00000000..95880990 --- /dev/null +++ b/spec/controllers/email_addresses_controller_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe EmailAddress, type: :model do + describe "validations" do + it "is invalid with an incorrect email format" do + teacher = Teacher.create(first_name: "John", last_name: "Doe", admin: false) + email_address = EmailAddress.new(email: "invalid-email", teacher:, primary: false) + + expect(email_address.valid?).to be false + expect(email_address.errors[:email]).to include("is invalid") + end + + it "is invalid without an email" do + teacher = Teacher.create(first_name: "John", last_name: "Doe", admin: false) + email_address = EmailAddress.new(teacher:, primary: false) + + expect(email_address.valid?).to be false + expect(email_address.errors[:email]).to include("can't be blank") + end + + it "is valid with a correct email format" do + teacher = Teacher.create(first_name: "John", last_name: "Doe", admin: false) + email_address = EmailAddress.new(email: "valid.email@berkeley.edu", teacher:, primary: false) + + expect(email_address.valid?).to be true + end + end +end From b0fa3d51254a4146d7a8bc6653595d5ff1f2b77f Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:46:22 -0700 Subject: [PATCH 34/36] Fix cucumber tests to adapt new schema from new email address model --- features/professional_development.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/professional_development.feature b/features/professional_development.feature index 3dbc6558..77ec66c6 100644 --- a/features/professional_development.feature +++ b/features/professional_development.feature @@ -6,7 +6,7 @@ Feature: Professional Development and Registration Management Background: Admin and Teacher exist Given the following teachers exist: - | first_name | last_name | admin | email | id | + | first_name | last_name | admin | primary_email | id | | Perry | Zhong | true | testadminuser@berkeley.edu | 100 | | Joseph | Mamoa | false | testteacher@berkeley.edu | 101 | And the following professional developments exist: From 436485fe1c5bf21960d9a1de5b2cb54c02a4bf77 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Fri, 19 Apr 2024 11:04:35 -0700 Subject: [PATCH 35/36] Remove unused variable --- app/controllers/pd_registrations_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/pd_registrations_controller.rb b/app/controllers/pd_registrations_controller.rb index 55c171ef..8dcc2b2d 100644 --- a/app/controllers/pd_registrations_controller.rb +++ b/app/controllers/pd_registrations_controller.rb @@ -7,7 +7,6 @@ class PdRegistrationsController < ApplicationController before_action :set_professional_development, only: [:new, :create, :edit, :update, :destroy] def index - @pd_registrations = PdRegistration.where(professional_development_id: @professional_development.id) end def show From 417adeaa03b481c1945924494a65caf05fe3bcf4 Mon Sep 17 00:00:00 2001 From: Jingchao Zhong <92573736+perryzjc@users.noreply.github.com> Date: Fri, 19 Apr 2024 11:55:17 -0700 Subject: [PATCH 36/36] Add more rspec tests --- .../pd_registrations_controller_spec.rb | 128 ++++++++++-------- 1 file changed, 73 insertions(+), 55 deletions(-) diff --git a/spec/controllers/pd_registrations_controller_spec.rb b/spec/controllers/pd_registrations_controller_spec.rb index 1a91a95f..0ad6dc75 100644 --- a/spec/controllers/pd_registrations_controller_spec.rb +++ b/spec/controllers/pd_registrations_controller_spec.rb @@ -13,7 +13,6 @@ let(:valid_registration_attributes) { { teacher_id: teacher1.id, - professional_development_id: professional_development.id, attended: true, role: "attendee" } @@ -21,7 +20,6 @@ let(:valid_registration_attributes2) { { teacher_id: teacher2.id, - professional_development_id: professional_development.id, attended: true, role: "attendee" } @@ -29,91 +27,111 @@ let(:invalid_registration_attributes) { { teacher_id: nil, - professional_development_id: nil, attended: false, role: "" } } - let!(:pd_registration) { PdRegistration.create!(valid_registration_attributes) } + let!(:pd_registration) { PdRegistration.create!(valid_registration_attributes.merge(professional_development_id: professional_development.id)) } + + describe "Authorization" do + shared_examples "admin access" do + it "allows operation for admin" do + log_in(admin_user) + action + expect(response).to have_http_status(:redirect) + expect(flash[:notice]).to match(/successfully/) + end + end - shared_examples "admin access" do - it "allows operation" do - action - expect(flash[:notice]).to match(/successfully/) + shared_examples "unauthorized access" do + it "denies operation for regular user" do + log_in(regular_user) + action + expect(response).to redirect_to(root_path) + expect(flash[:danger]).to be_present + end end - end - shared_examples "regular user denied" do - it "denies operation" do - action - expect(flash[:danger]).to be_present + describe "CREATE" do + let(:action) { post professional_development_pd_registrations_path(professional_development), params: { pd_registration: valid_registration_attributes2 } } + include_examples "admin access" + include_examples "unauthorized access" + end + + describe "UPDATE" do + let(:action) { patch professional_development_pd_registration_path(professional_development, pd_registration), params: { pd_registration: { role: "leader" } } } + include_examples "admin access" + include_examples "unauthorized access" + end + + describe "DELETE" do + let(:action) { delete professional_development_pd_registration_path(professional_development, pd_registration) } + include_examples "admin access" + include_examples "unauthorized access" end end - describe "CRUD operations for PD Registrations" do - describe "CREATE" do - let(:action) { post professional_development_pd_registrations_path( - professional_development_id: professional_development.id), - params: { pd_registration: valid_registration_attributes2 } } + describe "Admin operations" do + before { log_in(admin_user) } - context "as an admin" do - before { log_in(admin_user) } - include_examples "admin access" + describe "CREATE" do + context "with valid attributes" do + it "creates a new registration successfully" do + expect { + post professional_development_pd_registrations_path(professional_development), params: { pd_registration: valid_registration_attributes2 } + }.to change(PdRegistration, :count).by(1) + expect(response).to redirect_to(professional_development_path(professional_development)) + expect(flash[:notice]).to match(/successfully created/) + end end - context "as a regular user" do - before { log_in(regular_user) } - include_examples "regular user denied" + context "with invalid attributes" do + it "fails to create and renders errors" do + post professional_development_pd_registrations_path(professional_development), params: { pd_registration: invalid_registration_attributes } + expect(response).to render_template("professional_developments/show") + expect(flash.now[:alert]).to be_present + end end end describe "UPDATE" do context "with valid attributes" do - let(:action) { patch professional_development_pd_registration_path( - professional_development_id: professional_development.id, id: pd_registration.id), - params: { pd_registration: { role: "leader" } } } - - context "as an admin" do - before { log_in(admin_user) } - include_examples "admin access" - end - - context "as a regular user" do - before { log_in(regular_user) } - include_examples "regular user denied" + it "updates the registration successfully" do + patch professional_development_pd_registration_path(professional_development, pd_registration), params: { pd_registration: { role: "leader" } } + expect(response).to redirect_to(professional_development_path(professional_development)) + expect(flash[:notice]).to match(/successfully updated/) end end context "with invalid attributes" do - let(:action) { patch professional_development_pd_registration_path( - professional_development_id: professional_development.id, id: pd_registration.id), - params: { pd_registration: invalid_registration_attributes } } - - it "fails to update and redirects for admin" do - log_in(admin_user) - action - expect(response).to render_template(:show) - expect(flash[:alert]).to be_present + it "fails to update and renders errors" do + patch professional_development_pd_registration_path(professional_development, pd_registration), params: { pd_registration: invalid_registration_attributes } + expect(response).to render_template("professional_developments/show") + expect(flash.now[:alert]).to include("Teacher must exist and Role is not a valid role") end end end describe "DELETE" do - let(:action) { delete professional_development_pd_registration_path( - professional_development_id: professional_development.id, id: pd_registration.id) } - - context "as an admin" do - before { log_in(admin_user) } - it "deletes the PD registration" do - expect { action }.to change(PdRegistration, :count).by(-1) + context "when deletion is successful" do + it "deletes the registration successfully and redirects" do + expect { + delete professional_development_pd_registration_path(professional_development, pd_registration) + }.to change(PdRegistration, :count).by(-1) expect(response).to redirect_to(professional_development_path(professional_development)) expect(flash[:notice]).to match(/successfully cancelled/) end end - context "as a regular user" do - before { log_in(regular_user) } - include_examples "regular user denied" + context "when attempting to delete a non-existent registration" do + it "does not change the count of registrations and redirects with an error" do + expect { + # using -1 to simulate a non-existent ID + delete professional_development_pd_registration_path(professional_development, -1) + }.not_to change(PdRegistration, :count) + expect(response).to redirect_to(professional_development_path) + expect(flash[:alert]).to match("Registration not found.") + end end end end