From b5abfcdec0f4192693af4b24f2e9448ae1131828 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sat, 21 Feb 2026 10:51:59 +0100 Subject: [PATCH 1/5] feat(seeds): generate more workshops for performance testing Motivation: The existing seeds.rb generated ~60 past workshops, which was insufficient for detecting performance issues during local development. With more data, N+1 queries and missing indexes become immediately obvious. Analysis: - Original seeds: ~60 past workshops over 5 years (1 every 6 months) - New seeds: ~1000 past workshops + ~100 future workshops - Scaled coaches/students from 50 to 500 each - Added ~25,000 workshop invitations for realistic query patterns This makes performance issues visible early in development rather than surfacing in production. Fixes #2302 --- db/seeds.rb | 140 ++++++++++++++++++++++++++++------------------------ 1 file changed, 75 insertions(+), 65 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index d0bd05459..3f89a3c3e 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,114 +1,124 @@ if Rails.env.development? # Check for ImageMagick before seeding imagemagick_available = system('convert --version > /dev/null 2>&1') || - system('magick --version > /dev/null 2>&1') + system('magick --version > /dev/null 2>&1') unless imagemagick_available - Rails.logger.error "=" * 80 - Rails.logger.error "ERROR: ImageMagick is required to run db:seed" - Rails.logger.error "=" * 80 - Rails.logger.error "" - Rails.logger.error "The seed task processes sponsor logo images, which requires ImageMagick." - Rails.logger.error "" - Rails.logger.error "Install ImageMagick:" - Rails.logger.error " macOS: brew install imagemagick" - Rails.logger.error " Ubuntu/Debian: apt-get install imagemagick" - Rails.logger.error " Windows: https://imagemagick.org/script/download.php" - Rails.logger.error "" - Rails.logger.error "See native-installation-instructions.md for details." - Rails.logger.error "=" * 80 + Rails.logger.error '=' * 80 + Rails.logger.error 'ERROR: ImageMagick is required to run db:seed' + Rails.logger.error '=' * 80 + Rails.logger.error '' + Rails.logger.error 'The seed task processes sponsor logo images, which requires ImageMagick.' + Rails.logger.error '' + Rails.logger.error 'Install ImageMagick:' + Rails.logger.error ' macOS: brew install imagemagick' + Rails.logger.error ' Ubuntu/Debian: apt-get install imagemagick' + Rails.logger.error ' Windows: https://imagemagick.org/script/download.php' + Rails.logger.error '' + Rails.logger.error 'See native-installation-instructions.md for details.' + Rails.logger.error '=' * 80 exit 1 end begin Rails.logger.info 'Running migrations...' Rails.application.config.log_level = :info - Rails.logger.info "Creating chapters..." - chapters = ['London', 'Brighton', 'Cambridge', 'Barcelona', 'Paris', 'Merlbourne', 'Berlin', 'New York'].map do |name| + Rails.logger.info 'Creating chapters...' + chapters = ['London', 'Brighton', 'Cambridge', 'Barcelona', 'Paris', 'Merlbourne', 'Berlin', + 'New York'].map do |name| Fabricate(:chapter_with_groups, name: name) end - Rails.logger.info "Creating workshops..." - workshops = 6.times.map do |n| - start = Time.zone.now + 1.month - n.weeks - ends_at = start + 3.hours - Fabricate(:workshop, title: 'Workshop', + Rails.logger.info 'Creating workshops...' + + Rails.logger.info 'Creating 1000 past workshops...' + past_workshops = [] + 1000.times do + months_ago = rand(0..60) + start = Time.zone.now - months_ago.months + rand(0..28).days + rand(0..23).hours + workshop = Fabricate(:workshop, + title: 'Workshop', chapter: chapters.sample, date_and_time: start, - ends_at: ends_at) + ends_at: start + 3.hours) + past_workshops << workshop end - workshops.concat Fabricate.times(2, :workshop, title: 'Workshop', chapter: chapters.sample) - - Rails.logger.info "Creating a lot of old workshops..." - past_workshops_since_n_months = 5 * 12 - past_workshops_one_per_n_months = 6 - past_workshops_count = past_workshops_since_n_months/past_workshops_one_per_n_months - - past_workshops = past_workshops_count.times.map do |n| - Fabricate(:workshop, title: 'Workshop', + Rails.logger.info 'Creating 100 future workshops...' + future_workshops = [] + 100.times do + months_ahead = rand(1..12) + start = Time.zone.now + months_ahead.months + rand(0..28).days + rand(0..23).hours + workshop = Fabricate(:workshop, + title: 'Workshop', chapter: chapters.sample, - date_and_time: Time.zone.now - past_workshops_since_n_months.months + (past_workshops_one_per_n_months * n).months) + date_and_time: start, + ends_at: start + 3.hours) + future_workshops << workshop end - Rails.logger.info "Creating events..." + future_workshops.first(10) + + Rails.logger.info 'Creating events...' events = 20.times.map do |n| Fabricate(:event, name: 'Event', chapters: chapters.sample(4), date_and_time: Time.zone.now + 2.months - n.months) end - Rails.logger.info "Creating meetings..." + Rails.logger.info 'Creating meetings...' 20.times.map do |n| Fabricate(:meeting, name: 'Meeting', date_and_time: Time.zone.now + 1.month - n.months) end - Rails.logger.info "Creating coaches..." - coaches = 50.times.map { Fabricate(:coach, groups: Group.coaches.order('RANDOM()').limit(2)) } + Rails.logger.info 'Creating coaches...' + coaches_group = Group.coaches.to_a + students_group = Group.students.to_a + coaches = 500.times.map { Fabricate(:coach, groups: coaches_group.sample(2)) } tutorials = Fabricate.times(20, :tutorial) 30.times { Fabricate(:feedback_request, workshop: past_workshops.sample) } 20.times { Fabricate(:feedback, tutorial: tutorials.sample, coach: coaches.sample) } 10.times { Fabricate(:testimonial, member: coaches.sample) } - job_titles = [ - 'Software Engineer', - 'Software Developer', - 'Front-end Developer', - 'Back-end Developer', - 'Full-stack Developer' - ] - - job_companies = %w[ - ACME - Globex - Soylent - Initech - Umbrella - Wonka - ] + Rails.logger.info 'Creating students...' + students = 500.times.map { Fabricate(:student, groups: students_group.sample(2)) } - Rails.logger.info "Creating students..." - students = 50.times.map { Fabricate(:student, groups: Group.students.order('RANDOM()').limit(2)) } - - Rails.logger.info "Creating event invitations..." - 10.times do |n| + Rails.logger.info 'Creating event invitations...' + 10.times do |_n| Fabricate(:invitation, member: students.sample, event: events.sample) Fabricate(:coach_invitation, member: coaches.sample, event: events.sample) end - Rails.logger.info "Creating attended by coach workshop invitations..." - coaches.sample(15).each do |coach| - past_workshops.sample(3).each do |workshop| - Fabricate(:attended_coach, workshop: workshop, member: coach) + Rails.logger.info 'Creating workshop invitations for past workshops...' + past_workshops.each do |workshop| + invitation_count = rand(8..15) + invitation_count.times do + Fabricate(:coach_workshop_invitation, member: coaches.sample, workshop: workshop) + rescue StandardError + nil + end + invitation_count.times do + Fabricate(:student_workshop_invitation, member: students.sample, workshop: workshop) + rescue StandardError + nil end end - Rails.logger.info "Creating workshop invitations..." - 30.times do |n| - Fabricate(:coach_workshop_invitation, member: coaches.sample, workshop: workshops.sample) rescue nil - Fabricate(:student_workshop_invitation, member: students.sample, workshop: workshops.sample) rescue nil + Rails.logger.info 'Creating workshop invitations for future workshops...' + future_workshops.each do |workshop| + invitation_count = rand(10..20) + invitation_count.times do + Fabricate(:coach_workshop_invitation, member: coaches.sample, workshop: workshop) + rescue StandardError + nil + end + invitation_count.times do + Fabricate(:student_workshop_invitation, member: students.sample, workshop: workshop) + rescue StandardError + nil + end end Rails.logger.info '..done!' rescue Exception => e From 32060e31c094471cda4e80b5ee7ac663a97418ae Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sat, 21 Feb 2026 15:25:03 +0100 Subject: [PATCH 2/5] feat(seeds): add attended workshop invitations for coaches --- db/seeds.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/db/seeds.rb b/db/seeds.rb index 3f89a3c3e..79945d7d9 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -104,6 +104,13 @@ rescue StandardError nil end + + # Create some attended invitations so coaches appear on wall_of_fame + rand(3..8).times do + Fabricate(:attended_coach, member: coaches.sample, workshop: workshop) + rescue StandardError + nil + end end Rails.logger.info 'Creating workshop invitations for future workshops...' From 45685641c5b8d0b39940d309ea62dcdd71146c2b Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sat, 21 Feb 2026 15:28:17 +0100 Subject: [PATCH 3/5] feat(seeds): add skill tags to coaches --- db/seeds.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/db/seeds.rb b/db/seeds.rb index 79945d7d9..19b7e9d3a 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -82,6 +82,27 @@ 20.times { Fabricate(:feedback, tutorial: tutorials.sample, coach: coaches.sample) } 10.times { Fabricate(:testimonial, member: coaches.sample) } + # Add skills to some coaches using acts_as_taggable_on + skill_lists = [ + 'javascript, ruby, rails', + 'python, django, sql', + 'react, typescript, node', + 'java, spring, kotlin', + 'css, html, bootstrap', + 'go, kubernetes, docker', + 'c++, rust, systems', + 'swift, ios, android', + 'php, laravel, mysql', + 'git, linux, devops' + ] + + coaches.each do |coach| + next if rand > 0.7 # 70% of coaches get skills + + coach.tag_list.add(skill_lists.sample(rand(1..4))) + coach.save(validate: false) + end + Rails.logger.info 'Creating students...' students = 500.times.map { Fabricate(:student, groups: students_group.sample(2)) } From 35a177776368fcf3c6c00a3ca7502cdf63fea052 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sat, 21 Feb 2026 15:48:40 +0100 Subject: [PATCH 4/5] feat(seeds): populate coaches page with attended invitations and skills - Add attended workshop invitations for coaches (wall_of_fame query requires attended: true) - Use skill_list instead of tag_list (acts_as_taggable_on :skills) - Avoid duplicate invitations by tracking existing invitees per workshop - ~70% of coaches get random skill tags from predefined list --- db/seeds.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 19b7e9d3a..e1ada1da5 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -99,7 +99,7 @@ coaches.each do |coach| next if rand > 0.7 # 70% of coaches get skills - coach.tag_list.add(skill_lists.sample(rand(1..4))) + coach.skill_list.add(skill_lists.sample(rand(1..4)).split(', ')) coach.save(validate: false) end @@ -127,8 +127,15 @@ end # Create some attended invitations so coaches appear on wall_of_fame + # Track which coaches already have invitations to avoid duplicates + existing_invitees = WorkshopInvitation.where(workshop: workshop, role: 'Coach').pluck(:member_id) + available_coaches = coaches.reject { |c| existing_invitees.include?(c.id) } rand(3..8).times do - Fabricate(:attended_coach, member: coaches.sample, workshop: workshop) + break if available_coaches.empty? + + coach = available_coaches.sample + available_coaches.delete(coach) + Fabricate(:attended_coach, member: coach, workshop: workshop) rescue StandardError nil end From a5e5fdff29831e55371b93415cd725fb6b115997 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sat, 21 Feb 2026 17:07:40 +0100 Subject: [PATCH 5/5] fix(seeds): generate workshops within 3 months, not 12 --- db/seeds.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/seeds.rb b/db/seeds.rb index e1ada1da5..015802a11 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -47,7 +47,7 @@ Rails.logger.info 'Creating 100 future workshops...' future_workshops = [] 100.times do - months_ahead = rand(1..12) + months_ahead = rand(1..3) start = Time.zone.now + months_ahead.months + rand(0..28).days + rand(0..23).hours workshop = Fabricate(:workshop, title: 'Workshop',