From 55a121cdd75cea1058f4b0e0b7fc9715aeaf3246 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Wed, 20 May 2026 13:48:32 -0500 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 27c31ff7e95506bfa06e652801809ba02be2d3d5 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Thu, 21 May 2026 14:54:17 -0500 Subject: [PATCH 7/8] remove CodeOfConductAgreement table --- app/models/code_of_conduct_agreement.rb | 4 ---- ...0521194947_drop_code_of_conduct_agreements.rb | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) delete mode 100644 app/models/code_of_conduct_agreement.rb create mode 100644 db/migrate/20260521194947_drop_code_of_conduct_agreements.rb diff --git a/app/models/code_of_conduct_agreement.rb b/app/models/code_of_conduct_agreement.rb deleted file mode 100644 index c8c8baf8..00000000 --- a/app/models/code_of_conduct_agreement.rb +++ /dev/null @@ -1,4 +0,0 @@ -class CodeOfConductAgreement < ActiveRecord::Base - belongs_to :participant - belongs_to :event -end diff --git a/db/migrate/20260521194947_drop_code_of_conduct_agreements.rb b/db/migrate/20260521194947_drop_code_of_conduct_agreements.rb new file mode 100644 index 00000000..72a04ae3 --- /dev/null +++ b/db/migrate/20260521194947_drop_code_of_conduct_agreements.rb @@ -0,0 +1,16 @@ +class DropCodeOfConductAgreements < ActiveRecord::Migration[7.2] + def up + drop_table :code_of_conduct_agreements + end + + def down + create_table :code_of_conduct_agreements do |t| + t.bigint :participant_id, null: false + t.bigint :event_id, null: false + t.timestamps precision: nil, null: false + end + + add_index :code_of_conduct_agreements, :event_id + add_index :code_of_conduct_agreements, :participant_id + end +end From 7709968a3e49a7e2c51093a77ba91fcc7aa10b06 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Fri, 22 May 2026 12:24:48 -0500 Subject: [PATCH 8/8] remove refs to CodeOfConductAgreement and update schema --- app/models/event.rb | 1 - app/models/participant.rb | 1 - db/schema.rb | 11 +---------- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/app/models/event.rb b/app/models/event.rb index 5ea49b94..563bc60e 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -9,7 +9,6 @@ class NotEnoughRoomsError < StandardError; end has_many :categories, through: :event_categories has_many :presenter_timeslot_restrictions, :through => :timeslots - has_many :code_of_conduct_agreements, dependent: :destroy # Careful! Large joins here; use with caution: has_many :attendances, through: :sessions diff --git a/app/models/participant.rb b/app/models/participant.rb index e0b5279b..aa0a1d74 100644 --- a/app/models/participant.rb +++ b/app/models/participant.rb @@ -5,7 +5,6 @@ class Participant < ActiveRecord::Base has_many :presentations has_many :sessions_presenting, :through => :presentations, :source => :session has_many :presenter_timeslot_restrictions, dependent: :destroy - has_many :code_of_conduct_agreements, dependent: :destroy validates :name, presence: true validates :email, presence: true diff --git a/db/schema.rb b/db/schema.rb index 24a064a9..442fc0e4 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_21_194947) do create_schema "heroku_ext" # These are extensions that must be enabled in order to support this database @@ -55,15 +55,6 @@ t.index ["category_id", "session_id"], name: "index_categorizations_on_category_id_and_session_id", unique: true end - create_table "code_of_conduct_agreements", force: :cascade do |t| - t.bigint "participant_id", null: false - t.bigint "event_id", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.index ["event_id"], name: "index_code_of_conduct_agreements_on_event_id" - t.index ["participant_id"], name: "index_code_of_conduct_agreements_on_participant_id" - end - create_table "event_categories", force: :cascade do |t| t.bigint "event_id", null: false t.bigint "category_id", null: false