From 55a121cdd75cea1058f4b0e0b7fc9715aeaf3246 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Wed, 20 May 2026 13:48:32 -0500 Subject: [PATCH 01/10] use coc_agreed_at timestamp instead of CodeOfConductAgreement record --- app/controllers/participants_controller.rb | 21 +++++++-------- ...12517_add_coc_agreed_at_to_participants.rb | 5 ++++ db/schema.rb | 3 ++- .../participants_controller_spec.rb | 27 +++++++++++++++++++ 4 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 db/migrate/20260520012517_add_coc_agreed_at_to_participants.rb diff --git a/app/controllers/participants_controller.rb b/app/controllers/participants_controller.rb index d8b47272..c42e3f0c 100644 --- a/app/controllers/participants_controller.rb +++ b/app/controllers/participants_controller.rb @@ -4,6 +4,7 @@ class ParticipantsController < ApplicationController respond_to :html load_resource except: :confirm_email before_action :verify_owner, :only => [:edit, :update] + before_action :coc_agreement_param_as_timestamp, only: [:create, :update] def index respond_to do |format| @@ -40,7 +41,6 @@ def update end if @participant.update(new_params) - create_code_of_conduct_agreement_if_not_exists! flash[:notice] = "Profile updated successfully." redirect_to participant_path(@participant) else @@ -52,7 +52,6 @@ def update def create @participant.attributes = participant_params.except(:code_of_conduct_agreement) if @participant.save - create_code_of_conduct_agreement_if_not_exists! @participant.deliver_email_confirmation_instructions! flash[:notice] = "Thanks for registering an account. Please check your email to confirm your account." redirect_to root_path @@ -62,15 +61,6 @@ def create end end - def create_code_of_conduct_agreement_if_not_exists! - if participant_params[:code_of_conduct_agreement] == '1' && @participant.signed_code_of_conduct_for_current_event? == false - CodeOfConductAgreement.create!({ - participant_id: @participant.id, - event_id: Event.current_event.id, - }) - end - end - def send_confirmation_email @participant.deliver_email_confirmation_instructions! flash[:notice] = "Confirmation instructions sent! Please check your email." @@ -95,11 +85,18 @@ def participant_params :name, :email, :password, :bio, :code_of_conduct_agreement, - :contact_details + :contact_details, :coc_agreed_at ) end def verify_owner redirect_to participant_path(@participant) if @participant != current_participant end + + def coc_agreement_param_as_timestamp + if params[:participant][:code_of_conduct_agreement] == '1' + params[:participant] ||= {} + params[:participant][:coc_agreed_at] = Time.current + end + end end diff --git a/db/migrate/20260520012517_add_coc_agreed_at_to_participants.rb b/db/migrate/20260520012517_add_coc_agreed_at_to_participants.rb new file mode 100644 index 00000000..6130eae1 --- /dev/null +++ b/db/migrate/20260520012517_add_coc_agreed_at_to_participants.rb @@ -0,0 +1,5 @@ +class AddCocAgreedAtToParticipants < ActiveRecord::Migration[7.2] + def change + add_column :participants, :coc_agreed_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 0e322fbb..24a064a9 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[7.2].define(version: 2026_03_22_192507) do +ActiveRecord::Schema[7.2].define(version: 2026_05_20_012517) do create_schema "heroku_ext" # These are extensions that must be enabled in order to support this database @@ -114,6 +114,7 @@ t.datetime "email_confirmed_at", precision: nil t.integer "presentations_count", default: 0 t.integer "attendances_count", default: 0 + t.datetime "coc_agreed_at" t.index ["email"], name: "index_participants_on_email", unique: true t.index ["perishable_token"], name: "index_participants_on_perishable_token" end diff --git a/spec/controllers/participants_controller_spec.rb b/spec/controllers/participants_controller_spec.rb index 3633eea5..0425f31d 100644 --- a/spec/controllers/participants_controller_spec.rb +++ b/spec/controllers/participants_controller_spec.rb @@ -45,6 +45,25 @@ expect(response).to render_template(:new) expect(flash[:error]).to eq "There was a problem creating your account." end + + it "does not set coc_agreed_at on the participant when the user has not signed the code of conduct" do + post :create, params: { participant: { name: 'Alan Turing', + email: 'tapewriter@example.org', + password: 'infinite-memory', + code_of_conduct_agreement: '0' + } + } + expect(Participant.find_by(email: 'tapewriter@example.org').coc_agreed_at).to be_nil + end + it "sets coc_agreed_at on the participant when the user has signed the code of conduct" do + post :create, params: { participant: { name: 'Alan Turing', + email: 'tapewriter@example.org', + password: 'infinite-memory', + code_of_conduct_agreement: '1' + } + } + expect(Participant.find_by(email: 'tapewriter@example.org').coc_agreed_at).not_to be_nil + end end describe "#show" do @@ -137,6 +156,14 @@ expect(response).to redirect_to participant_path(joe) expect(joe.reload.name).to eq 'schmoe, joe' end + it "does not set coc_agreed_at on the participant when the user has not signed the code of conduct" do + put :update, params: { id: joe, participant: { name: 'Alan Turing', code_of_conduct_agreement: '0' } } + expect(joe.reload.coc_agreed_at).to be_nil + end + it "sets coc_agreed_at on the participant when the user has signed the code of conduct" do + put :update, params: { id: joe, participant: { code_of_conduct_agreement: '1' } } + expect(joe.reload.coc_agreed_at).not_to be_nil + end describe "more attributes are not required" do it "should be successful" do From 613d1162164a56a9983ed7c6b655aebde94afe44 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Wed, 20 May 2026 14:36:38 -0500 Subject: [PATCH 02/10] update session controller to use coc_agreed_at timestamp instead of CodeOfConductAgreement record --- app/controllers/sessions_controller.rb | 8 ++------ app/models/participant.rb | 7 +------ spec/controllers/presentations_controller_spec.rb | 10 ++-------- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 6068295c..2880a638 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -78,11 +78,8 @@ def create end def create_code_of_conduct_agreement_if_not_exists! - if session_params[:code_of_conduct_agreement] == '1' && @session.participant.signed_code_of_conduct_for_current_event? == false - CodeOfConductAgreement.create!({ - participant_id: @session.participant.id, - event_id: Event.current_event.id, - }) + if session_params[:code_of_conduct_agreement] == '1' && !@session.participant.signed_code_of_conduct_for_current_event? + current_participant.update!(coc_agreed_at: Time.current) end end @@ -139,5 +136,4 @@ def sessions_for_event(event) .order('created_at desc') .distinct end - end diff --git a/app/models/participant.rb b/app/models/participant.rb index 724e52dd..75fe4f27 100644 --- a/app/models/participant.rb +++ b/app/models/participant.rb @@ -82,12 +82,7 @@ def deliver_password_reset_instructions! end def signed_code_of_conduct_for_current_event? - return false unless Event.current_event - - CodeOfConductAgreement.where({ - participant_id: id, - event_id: Event.current_event.id, - }).exists? + coc_agreed_at != nil end def attending_session?(session) diff --git a/spec/controllers/presentations_controller_spec.rb b/spec/controllers/presentations_controller_spec.rb index d2493c12..3fe11228 100644 --- a/spec/controllers/presentations_controller_spec.rb +++ b/spec/controllers/presentations_controller_spec.rb @@ -20,10 +20,7 @@ context "when the user is found by id" do it "should be successful when the user has signed the code of conduct" do - CodeOfConductAgreement.create!({ - participant_id: participant.id, - event_id: Event.current_event.id, - }) + participant.update!(coc_agreed_at: Time.current) expect { post :create, params: { session_id: session, id: participant.id } @@ -41,10 +38,7 @@ context "when the user is found by name" do it "should be successful when the user has signed the code of conduct" do - CodeOfConductAgreement.create!({ - participant_id: participant.id, - event_id: Event.current_event.id, - }) + participant.update!(coc_agreed_at: Time.current) expect { post :create, params: { session_id: session, name: participant.name } From eae16a1c2372530429304767fce270e288ab21d1 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Thu, 21 May 2026 13:21:54 -0500 Subject: [PATCH 03/10] add tests for coc agreement on session controller --- spec/controllers/sessions_controller_spec.rb | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 13b19981..801353e0 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -138,6 +138,29 @@ expect(assigns[:session].category_ids).to include category.id expect(flash[:notice]).to eq "Thanks for adding your session." end + it "should sign code of conduct if param is present" do + expect { + post :create, params: { session: { title: "new title", + description: "new description", + category_ids: [category.id], + level_id: "2", + code_of_conduct_agreement: "1", + } + } + }.to change { Session.count }.by(1) + expect(user.reload.coc_agreed_at).not_to be_nil + end + it "should not sign code of conduct if param is not present" do + expect { + post :create, params: { session: { title: "new title", + description: "new description", + category_ids: [category.id], + level_id: "2", + } + } + }.to change { Session.count }.by(1) + expect(user.reload.coc_agreed_at).to be_nil + end end context "with invalid values" do From 588ecd59a76711e30db9ba1be7800fadfa2fe7b8 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Thu, 21 May 2026 14:26:41 -0500 Subject: [PATCH 04/10] add backfill rake task for coc_agreed_at --- lib/tasks/backfill.rake | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 lib/tasks/backfill.rake diff --git a/lib/tasks/backfill.rake b/lib/tasks/backfill.rake new file mode 100644 index 00000000..a7c6af99 --- /dev/null +++ b/lib/tasks/backfill.rake @@ -0,0 +1,16 @@ +namespace :backfill do + + desc 'backfill coc_agreed_at for participants who have already accepted' + task coc_agreed_at: :environment do + ActiveRecord::Base.connection.execute(<<~SQL) + UPDATE participants + SET coc_agreed_at = coc.created_at + FROM ( + SELECT DISTINCT ON (participant_id) participant_id, created_at + FROM code_of_conduct_agreements + ) AS coc + WHERE participants.id = coc.participant_id + AND participants.coc_agreed_at IS NULL + SQL + end +end \ No newline at end of file From 9cf28d797a3a8716486e13ddd9cede4c315ae191 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Thu, 21 May 2026 14:27:00 -0500 Subject: [PATCH 05/10] update faker app to set coc_agreed_at timestamp --- lib/tasks/app.rake | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tasks/app.rake b/lib/tasks/app.rake index aae3860d..5f8533ac 100644 --- a/lib/tasks/app.rake +++ b/lib/tasks/app.rake @@ -517,6 +517,7 @@ namespace :app do participant.email = FFaker::Internet.safe_email participant.password = 'password' participant.bio = FFaker::Lorem.paragraph if [true, false].sample + participant.coc_agreed_at = Time.current participant.save! progress.increment end From 2da0a8efb0859cffae7f2f1c7b3ed6288a9c41bd Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Fri, 22 May 2026 11:00:55 -0500 Subject: [PATCH 06/10] cleanup untidy code --- app/controllers/participants_controller.rb | 1 - app/models/participant.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/participants_controller.rb b/app/controllers/participants_controller.rb index c42e3f0c..8f0de7a3 100644 --- a/app/controllers/participants_controller.rb +++ b/app/controllers/participants_controller.rb @@ -95,7 +95,6 @@ def verify_owner def coc_agreement_param_as_timestamp if params[:participant][:code_of_conduct_agreement] == '1' - params[:participant] ||= {} params[:participant][:coc_agreed_at] = Time.current end end diff --git a/app/models/participant.rb b/app/models/participant.rb index 75fe4f27..e0b5279b 100644 --- a/app/models/participant.rb +++ b/app/models/participant.rb @@ -82,7 +82,7 @@ def deliver_password_reset_instructions! end def signed_code_of_conduct_for_current_event? - coc_agreed_at != nil + coc_agreed_at.present? end def attending_session?(session) From 3666f73bcda2f475f2f2deb0c48310e9a5024293 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Tue, 26 May 2026 12:44:35 -0500 Subject: [PATCH 07/10] change from rake task to migration --- .../migrate/20260526173525_backfill_coc_agreed_at.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) rename lib/tasks/backfill.rake => db/migrate/20260526173525_backfill_coc_agreed_at.rb (72%) diff --git a/lib/tasks/backfill.rake b/db/migrate/20260526173525_backfill_coc_agreed_at.rb similarity index 72% rename from lib/tasks/backfill.rake rename to db/migrate/20260526173525_backfill_coc_agreed_at.rb index a7c6af99..003a36a3 100644 --- a/lib/tasks/backfill.rake +++ b/db/migrate/20260526173525_backfill_coc_agreed_at.rb @@ -1,7 +1,5 @@ -namespace :backfill do - - desc 'backfill coc_agreed_at for participants who have already accepted' - task coc_agreed_at: :environment do +class BackfillCocAgreedAt < ActiveRecord::Migration[7.2] + def up ActiveRecord::Base.connection.execute(<<~SQL) UPDATE participants SET coc_agreed_at = coc.created_at @@ -13,4 +11,8 @@ AND participants.coc_agreed_at IS NULL SQL end -end \ No newline at end of file + + def down + Participant.update_all(coc_agreed_at: nil) + end +end From 10ef399020ee364d747ea49db4078976fc07ffed Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Tue, 26 May 2026 12:57:27 -0500 Subject: [PATCH 08/10] use ActiveRecord instead of raw SQL --- .../20260526173525_backfill_coc_agreed_at.rb | 15 +++++---------- db/schema.rb | 2 +- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/db/migrate/20260526173525_backfill_coc_agreed_at.rb b/db/migrate/20260526173525_backfill_coc_agreed_at.rb index 003a36a3..f4cbf41c 100644 --- a/db/migrate/20260526173525_backfill_coc_agreed_at.rb +++ b/db/migrate/20260526173525_backfill_coc_agreed_at.rb @@ -1,15 +1,10 @@ class BackfillCocAgreedAt < ActiveRecord::Migration[7.2] def up - ActiveRecord::Base.connection.execute(<<~SQL) - UPDATE participants - SET coc_agreed_at = coc.created_at - FROM ( - SELECT DISTINCT ON (participant_id) participant_id, created_at - FROM code_of_conduct_agreements - ) AS coc - WHERE participants.id = coc.participant_id - AND participants.coc_agreed_at IS NULL - SQL + Participant.where(coc_agreed_at: nil).find_each do |participant| + aggreement = CodeOfConductAgreement.where(participant_id: participant.id) + .order(created_at: :desc).first + participant.update_column(:coc_agreed_at, aggreement.created_at) if aggreement + end end def down diff --git a/db/schema.rb b/db/schema.rb index 24a064a9..2c4e8a56 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[7.2].define(version: 2026_05_20_012517) do +ActiveRecord::Schema[7.2].define(version: 2026_05_26_173525) do create_schema "heroku_ext" # These are extensions that must be enabled in order to support this database From bde502e3e5908558cd7ae218beb00013dc0f69cc Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Tue, 26 May 2026 13:27:23 -0500 Subject: [PATCH 09/10] rename signed_code_of_conduct_for_current_event? -> signed_code_of_conduct? --- app/controllers/presentations_controller.rb | 2 +- app/controllers/sessions_controller.rb | 2 +- app/models/participant.rb | 2 +- app/views/participants/edit.html.erb | 4 ++-- app/views/participants/new.html.erb | 4 ++-- app/views/sessions/_form.html.erb | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/controllers/presentations_controller.rb b/app/controllers/presentations_controller.rb index 077a69ae..5fef621a 100644 --- a/app/controllers/presentations_controller.rb +++ b/app/controllers/presentations_controller.rb @@ -20,7 +20,7 @@ def create flash[:error] = "Sorry, no presenter #{participant_params[:name] ? "matching '#{participant_params[:name]}' " : "" }was found. Please try again." redirect_to session_presentations_path(@session) return - elsif participant.signed_code_of_conduct_for_current_event? == false + elsif participant.signed_code_of_conduct? == false flash[:error] = "Sorry, #{participant.name} hasn't signed the current Code of Conduct." redirect_to session_presentations_path(@session) return diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 2880a638..afdfc6b2 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -78,7 +78,7 @@ def create end def create_code_of_conduct_agreement_if_not_exists! - if session_params[:code_of_conduct_agreement] == '1' && !@session.participant.signed_code_of_conduct_for_current_event? + if session_params[:code_of_conduct_agreement] == '1' && !@session.participant.signed_code_of_conduct? current_participant.update!(coc_agreed_at: Time.current) end end diff --git a/app/models/participant.rb b/app/models/participant.rb index e0b5279b..020962f4 100644 --- a/app/models/participant.rb +++ b/app/models/participant.rb @@ -81,7 +81,7 @@ def deliver_password_reset_instructions! Notifier.password_reset_instructions(self).deliver_now! end - def signed_code_of_conduct_for_current_event? + def signed_code_of_conduct? coc_agreed_at.present? end diff --git a/app/views/participants/edit.html.erb b/app/views/participants/edit.html.erb index 331cd663..e7447cde 100644 --- a/app/views/participants/edit.html.erb +++ b/app/views/participants/edit.html.erb @@ -28,8 +28,8 @@ as: :boolean, label: ("I agree to the #{link_to 'Code of Conduct', 'https://minnestar.org/code-of-conduct'} governing this event.").html_safe, input_html: { - checked: @participant.signed_code_of_conduct_for_current_event?, - disabled: @participant.signed_code_of_conduct_for_current_event? + checked: @participant.signed_code_of_conduct?, + disabled: @participant.signed_code_of_conduct? } %> <% end %> diff --git a/app/views/participants/new.html.erb b/app/views/participants/new.html.erb index 8e00e969..32a50e94 100644 --- a/app/views/participants/new.html.erb +++ b/app/views/participants/new.html.erb @@ -17,8 +17,8 @@ required: true, # this only adds an asterisk to the label, it doesn't make the field required label: ("I agree to the #{link_to 'Code of Conduct', 'https://minnestar.org/code-of-conduct'} governing this event.").html_safe, input_html: { - checked: @participant.signed_code_of_conduct_for_current_event?, - disabled: @participant.signed_code_of_conduct_for_current_event? + checked: @participant.signed_code_of_conduct?, + disabled: @participant.signed_code_of_conduct? } %> <% end %> diff --git a/app/views/sessions/_form.html.erb b/app/views/sessions/_form.html.erb index 358bf97c..bf6ad7ef 100644 --- a/app/views/sessions/_form.html.erb +++ b/app/views/sessions/_form.html.erb @@ -17,8 +17,8 @@ required: true, label: ("I agree to the #{link_to 'Code of Conduct', 'https://minnestar.org/code-of-conduct'} governing this event.").html_safe, input_html: { - checked: current_participant.signed_code_of_conduct_for_current_event?, - disabled: current_participant.signed_code_of_conduct_for_current_event? + checked: current_participant.signed_code_of_conduct?, + disabled: current_participant.signed_code_of_conduct? } %> <% end %> From 79d3fab6de855f731ae8925a4247d62c38b29e1b Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Tue, 26 May 2026 13:30:09 -0500 Subject: [PATCH 10/10] minor cleanup --- app/controllers/sessions_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index afdfc6b2..09a27794 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -78,7 +78,7 @@ def create end def create_code_of_conduct_agreement_if_not_exists! - if session_params[:code_of_conduct_agreement] == '1' && !@session.participant.signed_code_of_conduct? + if session_params[:code_of_conduct_agreement] == '1' && !current_participant.signed_code_of_conduct? current_participant.update!(coc_agreed_at: Time.current) end end