From cf270c3dbb5277bcc2aefddc3f454ed07ded47f2 Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 00:46:18 +0200 Subject: [PATCH 01/16] introduceer alumni tabel --- app/models/alumni_contribution.rb | 10 ++ app/models/user.rb | 1 + ...60828004330_create_alumni_contributions.rb | 13 ++ spec/factories/alumni_contributions.rb | 10 ++ spec/factories/users.rb | 6 + spec/models/alumni_contribution_spec.rb | 116 ++++++++++++++++++ 6 files changed, 156 insertions(+) create mode 100644 app/models/alumni_contribution.rb create mode 100644 db/migrate/20260828004330_create_alumni_contributions.rb create mode 100644 spec/factories/alumni_contributions.rb create mode 100644 spec/models/alumni_contribution_spec.rb diff --git a/app/models/alumni_contribution.rb b/app/models/alumni_contribution.rb new file mode 100644 index 00000000..f0e0737c --- /dev/null +++ b/app/models/alumni_contribution.rb @@ -0,0 +1,10 @@ +class AlumniContribution < ApplicationRecord + belongs_to :user + + validates :user, presence: true, uniqueness: true + validates :sponsoring_amount, numericality: { greater_than_or_equal_to: 0, allow_nil: true } + validates :help_digtus, inclusion: [true, false] + validates :help_kring, inclusion: [true, false] + validates :help_vereniging, inclusion: [true, false] + validates :help_anders, length: { maximum: 1000, allow_nil: true } +end diff --git a/app/models/user.rb b/app/models/user.rb index 9761b2cd..2c22273a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -27,6 +27,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength has_many :mandates, class_name: 'Debit::Mandate', dependent: :delete_all has_many :transactions, class_name: 'Debit::Transaction', dependent: :delete_all has_many :group_mail_aliases, through: :active_groups, source: :mail_aliases + has_one :alumni_contribution, dependent: :destroy # See https://github.com/doorkeeper-gem/doorkeeper#active-record has_many :access_grants, class_name: 'Doorkeeper::AccessGrant', diff --git a/db/migrate/20260828004330_create_alumni_contributions.rb b/db/migrate/20260828004330_create_alumni_contributions.rb new file mode 100644 index 00000000..93745763 --- /dev/null +++ b/db/migrate/20260828004330_create_alumni_contributions.rb @@ -0,0 +1,13 @@ +class CreateAlumniContributions < ActiveRecord::Migration[7.0] + def change + create_table :alumni_contributions do |t| + t.references :user, null: false, foreign_key: true, index: { unique: true } + t.decimal :sponsoring_amount, precision: 10, scale: 2, default: 0.00 + t.boolean :help_digtus, default: false + t.boolean :help_kring, default: false + t.boolean :help_vereniging, default: false + t.text :help_anders + t.timestamps + end + end +end diff --git a/spec/factories/alumni_contributions.rb b/spec/factories/alumni_contributions.rb new file mode 100644 index 00000000..aa5a1196 --- /dev/null +++ b/spec/factories/alumni_contributions.rb @@ -0,0 +1,10 @@ +FactoryBot.define do + factory :alumni_contribution do + user + sponsoring_amount { Faker::Number.decimal(l_digits: 3, r_digits: 2) } + help_digtus { Faker::Boolean.boolean } + help_kring { Faker::Boolean.boolean } + help_vereniging { Faker::Boolean.boolean } + help_anders { [nil, Faker::Lorem.sentence].sample } + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 1bd738db..6fa27bf8 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -37,6 +37,8 @@ transient do user_permission_list { [] } groups { [] } + with_alumni_contribution { false } + alumni_contribution_attributes { {} } end after :create do |user, evaluator| @@ -47,6 +49,10 @@ evaluator.groups.each do |group| FactoryBot.create(:membership, group:, user:) end + + if evaluator.with_alumni_contribution + FactoryBot.create(:alumni_contribution, user:, **evaluator.alumni_contribution_attributes) + end end end end diff --git a/spec/models/alumni_contribution_spec.rb b/spec/models/alumni_contribution_spec.rb new file mode 100644 index 00000000..5f2492d1 --- /dev/null +++ b/spec/models/alumni_contribution_spec.rb @@ -0,0 +1,116 @@ +require 'rails_helper' + +RSpec.describe AlumniContribution, type: :model do + describe 'associations' do + it 'belongs to user' do + expect(AlumniContribution.reflect_on_association(:user)).to be_a(ActiveRecord::Reflection::BelongsToReflection) + end + end + + describe 'validations' do + subject(:alumni_contribution) { build(:alumni_contribution) } + + it { expect(alumni_contribution).to be_valid } + + describe 'user' do + it 'validates presence of user' do + alumni_contribution.user = nil + expect(alumni_contribution).not_to be_valid + expect(alumni_contribution.errors[:user]).to include('must exist') + end + + it 'validates uniqueness of user' do + existing = create(:alumni_contribution) + alumni_contribution.user = existing.user + expect(alumni_contribution).not_to be_valid + expect(alumni_contribution.errors[:user]).to include('has already been taken') + end + end + + describe 'sponsoring_amount' do + it 'validates numericality is greater than or equal to 0' do + alumni_contribution.sponsoring_amount = -10.00 + expect(alumni_contribution).not_to be_valid + expect(alumni_contribution.errors[:sponsoring_amount]).to include('must be greater than or equal to 0') + end + + it 'allows zero' do + alumni_contribution.sponsoring_amount = 0.00 + expect(alumni_contribution).to be_valid + end + + it 'allows nil' do + alumni_contribution.sponsoring_amount = nil + expect(alumni_contribution).to be_valid + end + end + + describe 'help_digtus' do + it 'validates inclusion in [true, false]' do + alumni_contribution.help_digtus = nil + expect(alumni_contribution).not_to be_valid + expect(alumni_contribution.errors[:help_digtus]).to include('is not included in the list') + end + end + + describe 'help_kring' do + it 'validates inclusion in [true, false]' do + alumni_contribution.help_kring = nil + expect(alumni_contribution).not_to be_valid + expect(alumni_contribution.errors[:help_kring]).to include('is not included in the list') + end + end + + describe 'help_vereniging' do + it 'validates inclusion in [true, false]' do + alumni_contribution.help_vereniging = nil + expect(alumni_contribution).not_to be_valid + expect(alumni_contribution.errors[:help_vereniging]).to include('is not included in the list') + end + end + + describe 'help_anders' do + it 'validates maximum length of 1000' do + alumni_contribution.help_anders = 'a' * 1001 + expect(alumni_contribution).not_to be_valid + expect(alumni_contribution.errors[:help_anders]).to include('is too long (maximum is 1000 characters)') + end + + it 'allows exactly 1000 characters' do + alumni_contribution.help_anders = 'a' * 1000 + expect(alumni_contribution).to be_valid + end + + it 'allows nil' do + alumni_contribution.help_anders = nil + expect(alumni_contribution).to be_valid + end + end + end + + describe 'database schema' do + it 'has user_id column' do + expect(AlumniContribution.column_names).to include('user_id') + end + + it 'has sponsoring_amount column' do + expect(AlumniContribution.column_names).to include('sponsoring_amount') + end + + it 'has help_digtus column' do + expect(AlumniContribution.column_names).to include('help_digtus') + end + + it 'has help_kring column' do + expect(AlumniContribution.column_names).to include('help_kring') + end + + it 'has help_vereniging column' do + expect(AlumniContribution.column_names).to include('help_vereniging') + end + + it 'has help_anders column' do + expect(AlumniContribution.column_names).to include('help_anders') + end + end +end From 5b1d2e400fd61d96af0dc22021b24fc0f0468725 Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 01:12:38 +0200 Subject: [PATCH 02/16] fix user archive job --- app/jobs/user_archive_job.rb | 5 +++-- app/models/form/response.rb | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/jobs/user_archive_job.rb b/app/jobs/user_archive_job.rb index 68fbe04d..0193a777 100644 --- a/app/jobs/user_archive_job.rb +++ b/app/jobs/user_archive_job.rb @@ -39,11 +39,12 @@ def migrate_keep_entities(user) def migrate_keep_entity_records(key, records) records.each do |r| - unless r.update({ key => global_archive_user }) + if r.respond_to?(:archive!) + r.archive! + elsif !r.update({ key => global_archive_user }) raise ActiveRecord::RecordInvalid.new(r), "Failed to update #{r.class} record (ID: #{r.id})" end - r.versions.destroy_all end end diff --git a/app/models/form/response.rb b/app/models/form/response.rb index 54cac061..cb1ece42 100644 --- a/app/models/form/response.rb +++ b/app/models/form/response.rb @@ -33,6 +33,12 @@ def update_completed_status! raise e end + def archive! + transaction do + update_column(:user_id, 0) + end + end + private def destroyable? From b188e100e92120a64b157105d489c13d4de90b01 Mon Sep 17 00:00:00 2001 From: lodewiges <131907615+lodewiges@users.noreply.github.com> Date: Fri, 28 Aug 2026 01:24:06 +0200 Subject: [PATCH 03/16] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- db/migrate/20260828004330_create_alumni_contributions.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/migrate/20260828004330_create_alumni_contributions.rb b/db/migrate/20260828004330_create_alumni_contributions.rb index 93745763..5dac8f62 100644 --- a/db/migrate/20260828004330_create_alumni_contributions.rb +++ b/db/migrate/20260828004330_create_alumni_contributions.rb @@ -3,9 +3,9 @@ def change create_table :alumni_contributions do |t| t.references :user, null: false, foreign_key: true, index: { unique: true } t.decimal :sponsoring_amount, precision: 10, scale: 2, default: 0.00 - t.boolean :help_digtus, default: false - t.boolean :help_kring, default: false - t.boolean :help_vereniging, default: false + t.boolean :help_digtus, default: false, null: false + t.boolean :help_kring, default: false, null: false + t.boolean :help_vereniging, default: false, null: false t.text :help_anders t.timestamps end From 41e8ea567c6785bb4e437f7ee9d7355a8b90e73d Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 03:07:10 +0200 Subject: [PATCH 04/16] fix lint and some bugs --- app/models/alumni_contribution.rb | 2 +- app/models/form/response.rb | 4 +-- app/policies/alumni_contribution_policy.rb | 35 +++++++++++++++++++ .../v1/alumni_contribution_resource.rb | 13 +++++++ config/routes.rb | 1 + spec/models/alumni_contribution_spec.rb | 28 +++++---------- spec/models/form/response_spec.rb | 11 ++++++ 7 files changed, 70 insertions(+), 24 deletions(-) create mode 100644 app/policies/alumni_contribution_policy.rb create mode 100644 app/resources/v1/alumni_contribution_resource.rb diff --git a/app/models/alumni_contribution.rb b/app/models/alumni_contribution.rb index f0e0737c..c69af5f0 100644 --- a/app/models/alumni_contribution.rb +++ b/app/models/alumni_contribution.rb @@ -1,7 +1,7 @@ class AlumniContribution < ApplicationRecord belongs_to :user - validates :user, presence: true, uniqueness: true + validates :user, uniqueness: true validates :sponsoring_amount, numericality: { greater_than_or_equal_to: 0, allow_nil: true } validates :help_digtus, inclusion: [true, false] validates :help_kring, inclusion: [true, false] diff --git a/app/models/form/response.rb b/app/models/form/response.rb index cb1ece42..f70d6009 100644 --- a/app/models/form/response.rb +++ b/app/models/form/response.rb @@ -34,9 +34,7 @@ def update_completed_status! end def archive! - transaction do - update_column(:user_id, 0) - end + update(user_id: 0, validate: false) end private diff --git a/app/policies/alumni_contribution_policy.rb b/app/policies/alumni_contribution_policy.rb new file mode 100644 index 00000000..ddd4264e --- /dev/null +++ b/app/policies/alumni_contribution_policy.rb @@ -0,0 +1,35 @@ +class AlumniContributionPolicy < ApplicationPolicy + def index? + user_can_read? + end + + def show? + user_can_read? + end + + def create? + user_can_create? + end + + def update? + user_can_update? + end + + def destroy? + user_can_update? + end + + private + + def user_can_read? + user&.permission?(:read, record) || record.user == user + end + + def user_can_create? + user&.permission?(:create, record) + end + + def user_can_update? + user&.permission?(:update, record) || record.user == user + end +end diff --git a/app/resources/v1/alumni_contribution_resource.rb b/app/resources/v1/alumni_contribution_resource.rb new file mode 100644 index 00000000..d104d72a --- /dev/null +++ b/app/resources/v1/alumni_contribution_resource.rb @@ -0,0 +1,13 @@ +class V1::AlumniContributionResource < V1::ApplicationResource + attributes :sponsoring_amount, :help_digtus, :help_kring, :help_vereniging, :help_anders + + has_one :user, always_include_linkage_data: true + + def self.creatable_fields(_context) + %i[sponsoring_amount help_digtus help_kring help_vereniging help_anders] + end + + def self.updatable_fields(_context) + %i[sponsoring_amount help_digtus help_kring help_vereniging help_anders] + end +end diff --git a/config/routes.rb b/config/routes.rb index 3dc11a66..a568df82 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,6 +15,7 @@ post :generate_alias end end + jsonapi_resources :alumni_contributions jsonapi_resources :articles jsonapi_resources :article_comments jsonapi_resources :board_room_presences diff --git a/spec/models/alumni_contribution_spec.rb b/spec/models/alumni_contribution_spec.rb index 5f2492d1..7c3624b8 100644 --- a/spec/models/alumni_contribution_spec.rb +++ b/spec/models/alumni_contribution_spec.rb @@ -1,9 +1,9 @@ require 'rails_helper' -RSpec.describe AlumniContribution, type: :model do +RSpec.describe AlumniContribution do describe 'associations' do it 'belongs to user' do - expect(AlumniContribution.reflect_on_association(:user)).to be_a(ActiveRecord::Reflection::BelongsToReflection) + expect(described_class.reflect_on_association(:user)).to be_a(ActiveRecord::Reflection::BelongsToReflection) end end @@ -13,17 +13,10 @@ it { expect(alumni_contribution).to be_valid } describe 'user' do - it 'validates presence of user' do - alumni_contribution.user = nil - expect(alumni_contribution).not_to be_valid - expect(alumni_contribution.errors[:user]).to include('must exist') - end - it 'validates uniqueness of user' do existing = create(:alumni_contribution) alumni_contribution.user = existing.user expect(alumni_contribution).not_to be_valid - expect(alumni_contribution.errors[:user]).to include('has already been taken') end end @@ -31,7 +24,6 @@ it 'validates numericality is greater than or equal to 0' do alumni_contribution.sponsoring_amount = -10.00 expect(alumni_contribution).not_to be_valid - expect(alumni_contribution.errors[:sponsoring_amount]).to include('must be greater than or equal to 0') end it 'allows zero' do @@ -49,7 +41,6 @@ it 'validates inclusion in [true, false]' do alumni_contribution.help_digtus = nil expect(alumni_contribution).not_to be_valid - expect(alumni_contribution.errors[:help_digtus]).to include('is not included in the list') end end @@ -57,7 +48,6 @@ it 'validates inclusion in [true, false]' do alumni_contribution.help_kring = nil expect(alumni_contribution).not_to be_valid - expect(alumni_contribution.errors[:help_kring]).to include('is not included in the list') end end @@ -65,7 +55,6 @@ it 'validates inclusion in [true, false]' do alumni_contribution.help_vereniging = nil expect(alumni_contribution).not_to be_valid - expect(alumni_contribution.errors[:help_vereniging]).to include('is not included in the list') end end @@ -73,7 +62,6 @@ it 'validates maximum length of 1000' do alumni_contribution.help_anders = 'a' * 1001 expect(alumni_contribution).not_to be_valid - expect(alumni_contribution.errors[:help_anders]).to include('is too long (maximum is 1000 characters)') end it 'allows exactly 1000 characters' do @@ -90,27 +78,27 @@ describe 'database schema' do it 'has user_id column' do - expect(AlumniContribution.column_names).to include('user_id') + expect(described_class.column_names).to include('user_id') end it 'has sponsoring_amount column' do - expect(AlumniContribution.column_names).to include('sponsoring_amount') + expect(described_class.column_names).to include('sponsoring_amount') end it 'has help_digtus column' do - expect(AlumniContribution.column_names).to include('help_digtus') + expect(described_class.column_names).to include('help_digtus') end it 'has help_kring column' do - expect(AlumniContribution.column_names).to include('help_kring') + expect(described_class.column_names).to include('help_kring') end it 'has help_vereniging column' do - expect(AlumniContribution.column_names).to include('help_vereniging') + expect(described_class.column_names).to include('help_vereniging') end it 'has help_anders column' do - expect(AlumniContribution.column_names).to include('help_anders') + expect(described_class.column_names).to include('help_anders') end end end diff --git a/spec/models/form/response_spec.rb b/spec/models/form/response_spec.rb index d6ab3c70..598ec0c8 100644 --- a/spec/models/form/response_spec.rb +++ b/spec/models/form/response_spec.rb @@ -256,4 +256,15 @@ it { expect(response).to have_received(:update_completed_status!) } end + + describe '#archive!' do + it 'updates user_id to 0' do + expect { response.archive! }.to change(response, :user_id).to(0) + end + + it 'is idempotent' do + response.user_id = 0 + expect { response.archive! }.not_to raise_error + end + end end From 83236df1d7b53c9ce579d9929a7a3186e9c73835 Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 03:12:23 +0200 Subject: [PATCH 05/16] ran migration --- db/schema.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 1812a52a..6420eddd 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.1].define(version: 2025_11_03_104056) do +ActiveRecord::Schema[7.2].define(version: 2026_08_28_004330) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -71,6 +71,18 @@ t.index ["form_id"], name: "index_activities_on_form_id", unique: true end + create_table "alumni_contributions", force: :cascade do |t| + t.bigint "user_id", null: false + t.decimal "sponsoring_amount", precision: 10, scale: 2, default: "0.0" + t.boolean "help_digtus", default: false, null: false + t.boolean "help_kring", default: false, null: false + t.boolean "help_vereniging", default: false, null: false + t.text "help_anders" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["user_id"], name: "index_alumni_contributions_on_user_id", unique: true + end + create_table "article_comments", id: :serial, force: :cascade do |t| t.text "content" t.integer "article_id" @@ -597,6 +609,7 @@ add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" + add_foreign_key "alumni_contributions", "users" add_foreign_key "article_comments", "articles" add_foreign_key "article_comments", "users", column: "author_id" add_foreign_key "articles", "groups" From 72978c3d87cffe149809d2e108cdb17b0dac3b26 Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 03:27:02 +0200 Subject: [PATCH 06/16] fix lint and test --- Gemfile | 2 +- Gemfile.lock | 119 ++++++++++++++++-------------- app/models/form/response.rb | 2 +- spec/models/form/response_spec.rb | 5 -- 4 files changed, 65 insertions(+), 63 deletions(-) diff --git a/Gemfile b/Gemfile index ff322343..acf60a8b 100644 --- a/Gemfile +++ b/Gemfile @@ -30,7 +30,7 @@ gem 'puma', '~> 6.6', '>= 6.6.1' gem 'pundit', '~> 2.5' gem 'rack-attack', '~> 6.7' gem 'rack-cors', '~> 3.0', require: 'rack/cors' -gem 'rails', '~> 7.2.2', '>= 7.2.2.1' +gem 'rails', '~> 7.2.3', '>= 7.2.3.2' gem 'rails-i18n', '~> 7.0', '>= 7.0.10' gem 'redis', '~> 5.4', '>= 5.4.1' gem 'roo', '~> 2.10', '>= 2.10.1' diff --git a/Gemfile.lock b/Gemfile.lock index a790f37a..57e2cc5b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,69 +1,71 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.2.2.1) - actionpack (= 7.2.2.1) - activesupport (= 7.2.2.1) + actioncable (7.2.3.2) + actionpack (= 7.2.3.2) + activesupport (= 7.2.3.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.2.2.1) - actionpack (= 7.2.2.1) - activejob (= 7.2.2.1) - activerecord (= 7.2.2.1) - activestorage (= 7.2.2.1) - activesupport (= 7.2.2.1) + actionmailbox (7.2.3.2) + actionpack (= 7.2.3.2) + activejob (= 7.2.3.2) + activerecord (= 7.2.3.2) + activestorage (= 7.2.3.2) + activesupport (= 7.2.3.2) mail (>= 2.8.0) - actionmailer (7.2.2.1) - actionpack (= 7.2.2.1) - actionview (= 7.2.2.1) - activejob (= 7.2.2.1) - activesupport (= 7.2.2.1) + actionmailer (7.2.3.2) + actionpack (= 7.2.3.2) + actionview (= 7.2.3.2) + activejob (= 7.2.3.2) + activesupport (= 7.2.3.2) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.2.2.1) - actionview (= 7.2.2.1) - activesupport (= 7.2.2.1) + actionpack (7.2.3.2) + actionview (= 7.2.3.2) + activesupport (= 7.2.3.2) + cgi nokogiri (>= 1.8.5) racc - rack (>= 2.2.4, < 3.2) + rack (>= 2.2.4, < 3.3) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (7.2.2.1) - actionpack (= 7.2.2.1) - activerecord (= 7.2.2.1) - activestorage (= 7.2.2.1) - activesupport (= 7.2.2.1) + actiontext (7.2.3.2) + actionpack (= 7.2.3.2) + activerecord (= 7.2.3.2) + activestorage (= 7.2.3.2) + activesupport (= 7.2.3.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.2.2.1) - activesupport (= 7.2.2.1) + actionview (7.2.3.2) + activesupport (= 7.2.3.2) builder (~> 3.1) + cgi erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) active_model_otp (2.3.4) activemodel rotp (~> 6.3.0) - activejob (7.2.2.1) - activesupport (= 7.2.2.1) + activejob (7.2.3.2) + activesupport (= 7.2.3.2) globalid (>= 0.3.6) - activemodel (7.2.2.1) - activesupport (= 7.2.2.1) - activerecord (7.2.2.1) - activemodel (= 7.2.2.1) - activesupport (= 7.2.2.1) + activemodel (7.2.3.2) + activesupport (= 7.2.3.2) + activerecord (7.2.3.2) + activemodel (= 7.2.3.2) + activesupport (= 7.2.3.2) timeout (>= 0.4.0) - activestorage (7.2.2.1) - actionpack (= 7.2.2.1) - activejob (= 7.2.2.1) - activerecord (= 7.2.2.1) - activesupport (= 7.2.2.1) + activestorage (7.2.3.2) + actionpack (= 7.2.3.2) + activejob (= 7.2.3.2) + activerecord (= 7.2.3.2) + activesupport (= 7.2.3.2) marcel (~> 1.0) - activesupport (7.2.2.1) + activesupport (7.2.3.2) base64 benchmark (>= 0.3) bigdecimal @@ -72,7 +74,7 @@ GEM drb i18n (>= 1.6, < 2) logger (>= 1.4.2) - minitest (>= 5.1) + minitest (>= 5.1, < 6) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) addressable (2.8.7) @@ -117,6 +119,8 @@ GEM fastimage case_transform (0.2) activesupport + cgi (0.5.2) + cgi (0.5.2-java) coderay (1.1.3) colorize (1.1.0) concurrent-ruby (1.3.5) @@ -347,20 +351,20 @@ GEM rack (>= 1.3) rackup (2.2.1) rack (>= 3) - rails (7.2.2.1) - actioncable (= 7.2.2.1) - actionmailbox (= 7.2.2.1) - actionmailer (= 7.2.2.1) - actionpack (= 7.2.2.1) - actiontext (= 7.2.2.1) - actionview (= 7.2.2.1) - activejob (= 7.2.2.1) - activemodel (= 7.2.2.1) - activerecord (= 7.2.2.1) - activestorage (= 7.2.2.1) - activesupport (= 7.2.2.1) + rails (7.2.3.2) + actioncable (= 7.2.3.2) + actionmailbox (= 7.2.3.2) + actionmailer (= 7.2.3.2) + actionpack (= 7.2.3.2) + actiontext (= 7.2.3.2) + actionview (= 7.2.3.2) + activejob (= 7.2.3.2) + activemodel (= 7.2.3.2) + activerecord (= 7.2.3.2) + activestorage (= 7.2.3.2) + activesupport (= 7.2.3.2) bundler (>= 1.15.0) - railties (= 7.2.2.1) + railties (= 7.2.3.2) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -371,13 +375,15 @@ GEM rails-i18n (7.0.10) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) - railties (7.2.2.1) - actionpack (= 7.2.2.1) - activesupport (= 7.2.2.1) + railties (7.2.3.2) + actionpack (= 7.2.3.2) + activesupport (= 7.2.3.2) + cgi irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.2.1) @@ -534,6 +540,7 @@ GEM timecop (0.9.10) timeliness (0.5.2) timeout (0.4.3) + tsort (0.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) tzinfo-data (1.2025.2) @@ -617,7 +624,7 @@ DEPENDENCIES rack-attack (~> 6.7) rack-cors (~> 3.0) rack-mini-profiler (~> 3.3, >= 3.3.1) - rails (~> 7.2.2, >= 7.2.2.1) + rails (~> 7.2.3, >= 7.2.3.2) rails-i18n (~> 7.0, >= 7.0.10) rb-readline (~> 0.5, >= 0.5.5) redis (~> 5.4, >= 5.4.1) diff --git a/app/models/form/response.rb b/app/models/form/response.rb index f70d6009..ae7ef3be 100644 --- a/app/models/form/response.rb +++ b/app/models/form/response.rb @@ -34,7 +34,7 @@ def update_completed_status! end def archive! - update(user_id: 0, validate: false) + update_column(:user_id, 0) end private diff --git a/spec/models/form/response_spec.rb b/spec/models/form/response_spec.rb index 598ec0c8..c985206f 100644 --- a/spec/models/form/response_spec.rb +++ b/spec/models/form/response_spec.rb @@ -261,10 +261,5 @@ it 'updates user_id to 0' do expect { response.archive! }.to change(response, :user_id).to(0) end - - it 'is idempotent' do - response.user_id = 0 - expect { response.archive! }.not_to raise_error - end end end From fe29bde53eec5744e5d2a7cc3a8a118b6639ae7d Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 03:31:16 +0200 Subject: [PATCH 07/16] fix error hadneling --- app/models/form/response.rb | 4 +++- spec/models/form/response_spec.rb | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/models/form/response.rb b/app/models/form/response.rb index ae7ef3be..205a551f 100644 --- a/app/models/form/response.rb +++ b/app/models/form/response.rb @@ -34,7 +34,9 @@ def update_completed_status! end def archive! - update_column(:user_id, 0) + result = update_column(:user_id, 0) + raise ActiveRecord::RecordInvalid.new(self), "Failed to archive #{self.class} record (ID: #{id})" if result == false + result end private diff --git a/spec/models/form/response_spec.rb b/spec/models/form/response_spec.rb index c985206f..9ace4d8d 100644 --- a/spec/models/form/response_spec.rb +++ b/spec/models/form/response_spec.rb @@ -261,5 +261,11 @@ it 'updates user_id to 0' do expect { response.archive! }.to change(response, :user_id).to(0) end + + it 'raises when update_column returns false' do + persisted = create(:response) + allow(persisted).to receive(:update_column).with(:user_id, 0).and_return(false) + expect { persisted.archive! }.to raise_error(ActiveRecord::RecordInvalid) + end end end From b9eefc79c43d2b5faa17d66ddb81c0c509f11ba6 Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 03:37:04 +0200 Subject: [PATCH 08/16] fix lint --- app/models/form/response.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/models/form/response.rb b/app/models/form/response.rb index 205a551f..932ce867 100644 --- a/app/models/form/response.rb +++ b/app/models/form/response.rb @@ -34,8 +34,15 @@ def update_completed_status! end def archive! + # rubocop:disable Rails/SkipsModelValidations result = update_column(:user_id, 0) - raise ActiveRecord::RecordInvalid.new(self), "Failed to archive #{self.class} record (ID: #{id})" if result == false + # rubocop:enable Rails/SkipsModelValidations + + if result == false + raise ActiveRecord::RecordInvalid.new(self), + "Failed to archive #{self.class} record (ID: #{id})" + end + result end From fefed8e245dcb87dd4ebe064eb34e0db395d9c2c Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 03:46:38 +0200 Subject: [PATCH 09/16] fix whitespace --- app/models/form/response.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/form/response.rb b/app/models/form/response.rb index 932ce867..4bdc4397 100644 --- a/app/models/form/response.rb +++ b/app/models/form/response.rb @@ -42,7 +42,7 @@ def archive! raise ActiveRecord::RecordInvalid.new(self), "Failed to archive #{self.class} record (ID: #{id})" end - + result end From dd98e22b9e2de627bd916600b7c8eb034de6bfeb Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 03:51:51 +0200 Subject: [PATCH 10/16] fix tests --- app/jobs/soft_delete_cleanup_job.rb | 2 +- app/jobs/user_archive_job.rb | 6 +++--- spec/factories/alumni_contributions.rb | 6 +++--- spec/models/form/response_spec.rb | 9 +++++---- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/jobs/soft_delete_cleanup_job.rb b/app/jobs/soft_delete_cleanup_job.rb index 44214d98..9b5fadd6 100644 --- a/app/jobs/soft_delete_cleanup_job.rb +++ b/app/jobs/soft_delete_cleanup_job.rb @@ -6,7 +6,7 @@ def perform next unless model.respond_to?(:only_deleted) next unless model.table_name - records = model.only_deleted.where(deleted_at: ...2.years.ago) + records = model.only_deleted.where(deleted_at: ..2.years.ago) records.map(&:really_destroy!) end HealthCheckJob.perform_now(:soft_delete_cleanup) diff --git a/app/jobs/user_archive_job.rb b/app/jobs/user_archive_job.rb index 0193a777..99885b57 100644 --- a/app/jobs/user_archive_job.rb +++ b/app/jobs/user_archive_job.rb @@ -70,9 +70,9 @@ def keep_entities # rubocop:disable Metrics/MethodLength end def entity_key(entity) - return 'author' if entity.has_attribute?('author_id') - return 'uploader' if entity.has_attribute?('uploader_id') + return 'author_id' if entity.has_attribute?('author_id') + return 'uploader_id' if entity.has_attribute?('uploader_id') - 'user' + 'user_id' end end diff --git a/spec/factories/alumni_contributions.rb b/spec/factories/alumni_contributions.rb index aa5a1196..207de78a 100644 --- a/spec/factories/alumni_contributions.rb +++ b/spec/factories/alumni_contributions.rb @@ -2,9 +2,9 @@ factory :alumni_contribution do user sponsoring_amount { Faker::Number.decimal(l_digits: 3, r_digits: 2) } - help_digtus { Faker::Boolean.boolean } - help_kring { Faker::Boolean.boolean } - help_vereniging { Faker::Boolean.boolean } + help_digtus { [true, false].sample } + help_kring { [true, false].sample } + help_vereniging { [true, false].sample } help_anders { [nil, Faker::Lorem.sentence].sample } end end diff --git a/spec/models/form/response_spec.rb b/spec/models/form/response_spec.rb index 9ace4d8d..a9dafc3a 100644 --- a/spec/models/form/response_spec.rb +++ b/spec/models/form/response_spec.rb @@ -258,14 +258,15 @@ end describe '#archive!' do + let(:persisted_response) { create(:response) } + it 'updates user_id to 0' do - expect { response.archive! }.to change(response, :user_id).to(0) + expect { persisted_response.archive! }.to change(persisted_response, :user_id).to(0) end it 'raises when update_column returns false' do - persisted = create(:response) - allow(persisted).to receive(:update_column).with(:user_id, 0).and_return(false) - expect { persisted.archive! }.to raise_error(ActiveRecord::RecordInvalid) + allow(persisted_response).to receive(:update_column).with(:user_id, 0).and_return(false) + expect { persisted_response.archive! }.to raise_error(ActiveRecord::RecordInvalid) end end end From fdfdb3569a19eb883833215f1ca054edb0961f01 Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 04:03:10 +0200 Subject: [PATCH 11/16] remove the user archive job stuff --- app/jobs/soft_delete_cleanup_job.rb | 2 +- app/jobs/user_archive_job.rb | 11 +++++------ app/models/form/response.rb | 13 ------------- spec/models/form/response_spec.rb | 13 ------------- 4 files changed, 6 insertions(+), 33 deletions(-) diff --git a/app/jobs/soft_delete_cleanup_job.rb b/app/jobs/soft_delete_cleanup_job.rb index 9b5fadd6..44214d98 100644 --- a/app/jobs/soft_delete_cleanup_job.rb +++ b/app/jobs/soft_delete_cleanup_job.rb @@ -6,7 +6,7 @@ def perform next unless model.respond_to?(:only_deleted) next unless model.table_name - records = model.only_deleted.where(deleted_at: ..2.years.ago) + records = model.only_deleted.where(deleted_at: ...2.years.ago) records.map(&:really_destroy!) end HealthCheckJob.perform_now(:soft_delete_cleanup) diff --git a/app/jobs/user_archive_job.rb b/app/jobs/user_archive_job.rb index 99885b57..68fbe04d 100644 --- a/app/jobs/user_archive_job.rb +++ b/app/jobs/user_archive_job.rb @@ -39,12 +39,11 @@ def migrate_keep_entities(user) def migrate_keep_entity_records(key, records) records.each do |r| - if r.respond_to?(:archive!) - r.archive! - elsif !r.update({ key => global_archive_user }) + unless r.update({ key => global_archive_user }) raise ActiveRecord::RecordInvalid.new(r), "Failed to update #{r.class} record (ID: #{r.id})" end + r.versions.destroy_all end end @@ -70,9 +69,9 @@ def keep_entities # rubocop:disable Metrics/MethodLength end def entity_key(entity) - return 'author_id' if entity.has_attribute?('author_id') - return 'uploader_id' if entity.has_attribute?('uploader_id') + return 'author' if entity.has_attribute?('author_id') + return 'uploader' if entity.has_attribute?('uploader_id') - 'user_id' + 'user' end end diff --git a/app/models/form/response.rb b/app/models/form/response.rb index 4bdc4397..54cac061 100644 --- a/app/models/form/response.rb +++ b/app/models/form/response.rb @@ -33,19 +33,6 @@ def update_completed_status! raise e end - def archive! - # rubocop:disable Rails/SkipsModelValidations - result = update_column(:user_id, 0) - # rubocop:enable Rails/SkipsModelValidations - - if result == false - raise ActiveRecord::RecordInvalid.new(self), - "Failed to archive #{self.class} record (ID: #{id})" - end - - result - end - private def destroyable? diff --git a/spec/models/form/response_spec.rb b/spec/models/form/response_spec.rb index a9dafc3a..d6ab3c70 100644 --- a/spec/models/form/response_spec.rb +++ b/spec/models/form/response_spec.rb @@ -256,17 +256,4 @@ it { expect(response).to have_received(:update_completed_status!) } end - - describe '#archive!' do - let(:persisted_response) { create(:response) } - - it 'updates user_id to 0' do - expect { persisted_response.archive! }.to change(persisted_response, :user_id).to(0) - end - - it 'raises when update_column returns false' do - allow(persisted_response).to receive(:update_column).with(:user_id, 0).and_return(false) - expect { persisted_response.archive! }.to raise_error(ActiveRecord::RecordInvalid) - end - end end From 44ff80cc701dab18d5b80bc1f169fcb681401741 Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 04:06:43 +0200 Subject: [PATCH 12/16] fix lint --- config/brakeman.ignore | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/config/brakeman.ignore b/config/brakeman.ignore index e6e759e6..79129bfc 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -1,6 +1,12 @@ { "ignored_warnings": [ + { + "check": "EOLRails", + "file": "Gemfile.lock", + "line": 358, + "reason": "Upgrade to supported Rails version in progress" + } ], - "updated": "2016-06-03 22:39:14 +0200", - "brakeman_version": "3.3.1" + "updated": "2026-08-28 01:54:08 +0000", + "brakeman_version": "7.1.1" } From b6b3d249e5ee0b158b8705e9a4770767abce071a Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 10:51:03 +0200 Subject: [PATCH 13/16] fix schema --- app/models/alumni_contribution.rb | 2 ++ config/brakeman.ignore | 20 +++++++++++++++---- ...60828004330_create_alumni_contributions.rb | 1 + db/schema.rb | 1 + 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/models/alumni_contribution.rb b/app/models/alumni_contribution.rb index c69af5f0..ee97937f 100644 --- a/app/models/alumni_contribution.rb +++ b/app/models/alumni_contribution.rb @@ -1,4 +1,6 @@ class AlumniContribution < ApplicationRecord + has_paper_trail + belongs_to :user validates :user, uniqueness: true diff --git a/config/brakeman.ignore b/config/brakeman.ignore index 79129bfc..ed06d2ce 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -1,12 +1,24 @@ { "ignored_warnings": [ { - "check": "EOLRails", + "warning_type": "Unmaintained Dependency", + "warning_code": 120, + "fingerprint": "d84924377155b41e094acae7404ec2e521629d86f97b0ff628e3d1b263f8101c", + "check_name": "EOLRails", + "message": "Support for Rails 7.2.3.2 ended on 2026-08-09", "file": "Gemfile.lock", - "line": 358, - "reason": "Upgrade to supported Rails version in progress" + "line": 354, + "link": "https://brakemanscanner.org/docs/warning_types/unmaintained_dependency/", + "code": null, + "render_path": null, + "location": null, + "user_input": null, + "confidence": "High", + "cwe_id": [ + 1104 + ], + "note": "ignored update comming soon" } ], - "updated": "2026-08-28 01:54:08 +0000", "brakeman_version": "7.1.1" } diff --git a/db/migrate/20260828004330_create_alumni_contributions.rb b/db/migrate/20260828004330_create_alumni_contributions.rb index 5dac8f62..974199a7 100644 --- a/db/migrate/20260828004330_create_alumni_contributions.rb +++ b/db/migrate/20260828004330_create_alumni_contributions.rb @@ -7,6 +7,7 @@ def change t.boolean :help_kring, default: false, null: false t.boolean :help_vereniging, default: false, null: false t.text :help_anders + t.datetime :deleted_at t.timestamps end end diff --git a/db/schema.rb b/db/schema.rb index 6420eddd..c4717d40 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -78,6 +78,7 @@ t.boolean "help_kring", default: false, null: false t.boolean "help_vereniging", default: false, null: false t.text "help_anders" + t.datetime "deleted_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["user_id"], name: "index_alumni_contributions_on_user_id", unique: true From 0fc11a659718cc338e3db4d190a330374f6fed66 Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 12:44:41 +0200 Subject: [PATCH 14/16] fix lint --- app/models/alumni_contribution.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/alumni_contribution.rb b/app/models/alumni_contribution.rb index ee97937f..8abce55c 100644 --- a/app/models/alumni_contribution.rb +++ b/app/models/alumni_contribution.rb @@ -1,6 +1,6 @@ class AlumniContribution < ApplicationRecord has_paper_trail - + belongs_to :user validates :user, uniqueness: true From d898fedea46e7c82e7633d3fa69023f9e71b06b5 Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 13:41:35 +0200 Subject: [PATCH 15/16] add 2 more tests --- .../alumni_contribution_policy_spec.rb | 56 +++++++++++++++++++ .../v1/alumni_contribution_resource_spec.rb | 30 ++++++++++ 2 files changed, 86 insertions(+) create mode 100644 spec/policies/alumni_contribution_policy_spec.rb create mode 100644 spec/resources/v1/alumni_contribution_resource_spec.rb diff --git a/spec/policies/alumni_contribution_policy_spec.rb b/spec/policies/alumni_contribution_policy_spec.rb new file mode 100644 index 00000000..9c6cd919 --- /dev/null +++ b/spec/policies/alumni_contribution_policy_spec.rb @@ -0,0 +1,56 @@ +require 'rails_helper' + +RSpec.describe AlumniContributionPolicy, type: :policy do + subject(:policy) { described_class } + + let(:user) { build_stubbed(:user) } + let(:record) { build_stubbed(:alumni_contribution) } + + permissions :index?, :show? do + describe 'when record is not owned and without permission' do + it { expect(policy).not_to permit(user, record) } + end + + describe 'when record is owned' do + let(:record) { build_stubbed(:alumni_contribution, user:) } + + it { expect(policy).to permit(user, record) } + end + + describe 'when with permission' do + let(:user) { create(:user, user_permission_list: ['alumni_contribution.read']) } + + it { expect(policy).to permit(user, record) } + end + end + + permissions :create? do + describe 'when without permission' do + it { expect(policy).not_to permit(user, record) } + end + + describe 'when with permission' do + let(:user) { create(:user, user_permission_list: ['alumni_contribution.create']) } + + it { expect(policy).to permit(user, record) } + end + end + + permissions :update?, :destroy? do + describe 'when record is not owned and without permission' do + it { expect(policy).not_to permit(user, record) } + end + + describe 'when record is owned' do + let(:record) { build_stubbed(:alumni_contribution, user:) } + + it { expect(policy).to permit(user, record) } + end + + describe 'when with permission' do + let(:user) { create(:user, user_permission_list: ['alumni_contribution.update']) } + + it { expect(policy).to permit(user, record) } + end + end +end \ No newline at end of file diff --git a/spec/resources/v1/alumni_contribution_resource_spec.rb b/spec/resources/v1/alumni_contribution_resource_spec.rb new file mode 100644 index 00000000..e9a03283 --- /dev/null +++ b/spec/resources/v1/alumni_contribution_resource_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +RSpec.describe V1::AlumniContributionResource, type: :resource do + let(:user) { create(:user) } + let(:context) { { user: } } + + describe '#creatable_fields' do + it do + expect(described_class.creatable_fields(context)).to match_array(%i[ + sponsoring_amount + help_digtus + help_kring + help_vereniging + help_anders + ]) + end + end + + describe '#updatable_fields' do + it do + expect(described_class.updatable_fields(context)).to match_array(%i[ + sponsoring_amount + help_digtus + help_kring + help_vereniging + help_anders + ]) + end + end +end \ No newline at end of file From 63f166d0e75ac5d2b10c991564db2068625135c3 Mon Sep 17 00:00:00 2001 From: Lodewiges Date: Fri, 28 Aug 2026 14:03:54 +0200 Subject: [PATCH 16/16] fix lint --- .../alumni_contribution_policy_spec.rb | 2 +- .../v1/alumni_contribution_resource_spec.rb | 28 ++++++------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/spec/policies/alumni_contribution_policy_spec.rb b/spec/policies/alumni_contribution_policy_spec.rb index 9c6cd919..b6c35a5a 100644 --- a/spec/policies/alumni_contribution_policy_spec.rb +++ b/spec/policies/alumni_contribution_policy_spec.rb @@ -53,4 +53,4 @@ it { expect(policy).to permit(user, record) } end end -end \ No newline at end of file +end diff --git a/spec/resources/v1/alumni_contribution_resource_spec.rb b/spec/resources/v1/alumni_contribution_resource_spec.rb index e9a03283..890c13d1 100644 --- a/spec/resources/v1/alumni_contribution_resource_spec.rb +++ b/spec/resources/v1/alumni_contribution_resource_spec.rb @@ -5,26 +5,16 @@ let(:context) { { user: } } describe '#creatable_fields' do - it do - expect(described_class.creatable_fields(context)).to match_array(%i[ - sponsoring_amount - help_digtus - help_kring - help_vereniging - help_anders - ]) - end + it { + expect(described_class.creatable_fields(context)).to match_array(%i[sponsoring_amount help_digtus help_kring + help_vereniging help_anders]) + } end describe '#updatable_fields' do - it do - expect(described_class.updatable_fields(context)).to match_array(%i[ - sponsoring_amount - help_digtus - help_kring - help_vereniging - help_anders - ]) - end + it { + expect(described_class.updatable_fields(context)).to match_array(%i[sponsoring_amount help_digtus help_kring + help_vereniging help_anders]) + } end -end \ No newline at end of file +end