From b64f858f8ce57804be98a506ee685d6a01ea86ec Mon Sep 17 00:00:00 2001 From: "jonathan.kerr" <3410350+jonodrew@users.noreply.github.com> Date: Thu, 29 Jan 2026 11:53:01 +0000 Subject: [PATCH 01/10] Add Active Job adapter This starts us down the path of using Active Job, which is the preferred mechanism for jobs in Rails 7.1. Signed-off-by: jonathan.kerr <3410350+jonodrew@users.noreply.github.com> --- config/application.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/application.rb b/config/application.rb index a8af5e442..e84652355 100644 --- a/config/application.rb +++ b/config/application.rb @@ -33,6 +33,9 @@ class Application < Rails::Application config.active_record.belongs_to_required_by_default = true + # let's start using Active Job! + config.active_job.queue_adapter = :delayed_job + if ENV["RAILS_LOG_TO_STDOUT"].present? $stdout.sync = true config.rails_semantic_logger.add_file_appender = false From 919b3f1f9526ed8f78b2699327d6980a1ee26f6b Mon Sep 17 00:00:00 2001 From: "jonathan.kerr" <3410350+jonodrew@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:21:21 +0000 Subject: [PATCH 02/10] Add chaser email copy I've taken the copy from Kimberly and put it into HTML, as we do with the rest of our emails Signed-off-by: jonathan.kerr <3410350+jonodrew@users.noreply.github.com> --- .../three_month_chaser.html.haml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 app/views/member_mailer/three_month_chaser.html.haml diff --git a/app/views/member_mailer/three_month_chaser.html.haml b/app/views/member_mailer/three_month_chaser.html.haml new file mode 100644 index 000000000..93fcfde68 --- /dev/null +++ b/app/views/member_mailer/three_month_chaser.html.haml @@ -0,0 +1,21 @@ +%h1 Hi #{@member.name}, + +%p + We’ve noticed you haven’t been to a codebar workshop in a little while, and we just wanted to check in. We know life gets busy, but we’d love to understand how things are going for you and whether there’s anything we can do to make it easier or more valuable for you to join again. +%p + If you have a minute, could you please share your thoughts in this short form? 👉 https://forms.gle/tEETvC3zYP9mcLar7 + +%p + Or, if you’re thinking about coming back soon, we’ve got some great upcoming workshops and events you might like to join 👉https://codebar.io/events/ + +%p + Your feedback really helps us make codebar more welcoming and useful for everyone in our community 💜 + +%p + We’d love to see you again soon! + +%p + #{"-- "} +%br +Warmly, +The Codebar Team From b89384b7150e8f764391ddc0aa6e7a975b0b6097 Mon Sep 17 00:00:00 2001 From: "jonathan.kerr" <3410350+jonodrew@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:01:49 +0000 Subject: [PATCH 03/10] Add EmailDelivery concern and test This adds an additional method to the MemberMailer class. It's currently called #chaser and it sends...the chaser email. In the next commit I'll call it from a Job, or maybe a Service that's called from a Job Signed-off-by: jonathan.kerr <3410350+jonodrew@users.noreply.github.com> --- app/mailers/concerns/email_delivery.rb | 19 +++++++++++++++++++ app/mailers/member_mailer.rb | 11 +++++++++++ app/models/member_email_delivery.rb | 3 +++ spec/mailers/member_mailer_spec.rb | 17 +++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 app/mailers/concerns/email_delivery.rb create mode 100644 app/models/member_email_delivery.rb diff --git a/app/mailers/concerns/email_delivery.rb b/app/mailers/concerns/email_delivery.rb new file mode 100644 index 000000000..f39bf581a --- /dev/null +++ b/app/mailers/concerns/email_delivery.rb @@ -0,0 +1,19 @@ +module EmailDelivery + extend ActiveSupport::Concern + + private + + def log_sent_email + member = params[:member] + return unless member + + MemberEmailDelivery.create!( + member: member, + subject: mail.subject, + body: mail.body.to_s, + to: mail.to, + cc: mail.cc, + bcc: mail.bcc + ) + end +end diff --git a/app/mailers/member_mailer.rb b/app/mailers/member_mailer.rb index ebc5e4850..eca12b3b4 100644 --- a/app/mailers/member_mailer.rb +++ b/app/mailers/member_mailer.rb @@ -1,5 +1,16 @@ class MemberMailer < ApplicationMailer include EmailHeaderHelper + include EmailDelivery + + after_deliver :log_sent_mail , only: [:chaser] + + def chaser + @member = params[:member] + subject = "It’s been a while, how are you doing? ♥️" + mail mail_args(@member, subject, 'hello@codebar.io', 'hello@codebar.io') do |format| + format.html {render 'three_month_chaser'} + end + end def welcome(member) if member.student? diff --git a/app/models/member_email_delivery.rb b/app/models/member_email_delivery.rb new file mode 100644 index 000000000..f6d836129 --- /dev/null +++ b/app/models/member_email_delivery.rb @@ -0,0 +1,3 @@ +class MemberEmailDelivery < ApplicationRecord + belongs_to :member, polymorphic: true, optional: true +end diff --git a/spec/mailers/member_mailer_spec.rb b/spec/mailers/member_mailer_spec.rb index b09ed32f6..77c6566ff 100644 --- a/spec/mailers/member_mailer_spec.rb +++ b/spec/mailers/member_mailer_spec.rb @@ -115,4 +115,21 @@ end.to change { ActionMailer::Base.deliveries.count }.by 1 end end + + describe "#chaser" do + it "logs the sent email" do + expect do + MemberMailer + .with(member: member) + .chaser + .deliver_now + end.to change(MemberEmailDelivery, :count).by(1) + + log = MemberEmailDelivery.last! + + expect(log.member).to eq(member) + expect(log.subject).to eq("It’s been a while, how are you doing? ♥️") + expect(log.to).to eq([member.email]) + end + end end From ce30d68e1c0d482aca21b175ed60b9f03e935d60 Mon Sep 17 00:00:00 2001 From: "jonathan.kerr" <3410350+jonodrew@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:03:27 +0000 Subject: [PATCH 04/10] Update test runner to use the queue This is the setup required for using ActiveJob and its queue, and checking if things were enqueued. Signed-off-by: jonathan.kerr <3410350+jonodrew@users.noreply.github.com> --- config/environments/test.rb | 5 ++++- spec/spec_helper.rb | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/config/environments/test.rb b/config/environments/test.rb index bcf11a2e7..b01ef5e72 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,4 +1,5 @@ -require 'active_support/core_ext/integer/time' +require "active_support/core_ext/integer/time" +require "active_job/test_helper" # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that @@ -54,6 +55,8 @@ # Raise error when a before_action's only/except options reference missing actions. config.action_controller.raise_on_missing_callback_actions = true + config.active_job.queue_adapter = :test + # Fake omniauth for testing OmniAuth.config.test_mode = true diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3ca44fcb8..f889aaebd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -57,6 +57,7 @@ def self.branch_coverage? ActiveRecord::Migration.check_all_pending! if defined?(ActiveRecord::Migration) RSpec.configure do |config| + config.include ActiveJob::TestHelper config.include ApplicationHelper config.include LoginHelpers config.include ActiveSupport::Testing::TimeHelpers @@ -82,6 +83,9 @@ def self.branch_coverage? to_return(status: 200, body: '{"status":"active","segments":[]}', headers: { 'Content-Type' => 'application/json' }) DatabaseCleaner.strategy = :transaction + + clear_enqueued_jobs + clear_performed_jobs end # Driver is using an external browser with an app From 5e2c5618c8fa97cd842efe84d37ef439b9fedbc7 Mon Sep 17 00:00:00 2001 From: "jonathan.kerr" <3410350+jonodrew@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:04:05 +0000 Subject: [PATCH 05/10] Add :member_email_deliveries relation to Member This was an oversight when creating the related table, and fixes it Signed-off-by: jonathan.kerr <3410350+jonodrew@users.noreply.github.com> --- app/models/member.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/member.rb b/app/models/member.rb index 2b50cd1de..13f7eddae 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -22,6 +22,7 @@ class Member < ApplicationRecord has_many :chapters, -> { distinct }, through: :groups has_many :announcements, -> { distinct }, through: :groups has_many :meeting_invitations + has_many :member_email_deliveries validates :auth_services, presence: true validates :name, :surname, :email, :about_you, presence: true, if: :can_log_in? From 026782b864ea395a5779e5b7823a2f4eef4b9a29 Mon Sep 17 00:00:00 2001 From: "jonathan.kerr" <3410350+jonodrew@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:05:40 +0000 Subject: [PATCH 06/10] Create Job and Service for sending the chaser This outlines a single test for sending the chaser email. It also creates the relevant Job and Service. When users haven't attended a workshop they were invited to in the last three months _and_ haven't already been chased, they'll be sent a follow up email Signed-off-by: jonathan.kerr <3410350+jonodrew@users.noreply.github.com> --- app/jobs/send_three_month_email_job.rb | 9 ++++ app/services/three_month_email_service.rb | 26 +++++++++++ .../member_email_delivery_fabricator.rb | 6 +++ .../three_month_email_service_spec.rb | 43 +++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 app/jobs/send_three_month_email_job.rb create mode 100644 app/services/three_month_email_service.rb create mode 100644 spec/fabricators/member_email_delivery_fabricator.rb create mode 100644 spec/services/three_month_email_service_spec.rb diff --git a/app/jobs/send_three_month_email_job.rb b/app/jobs/send_three_month_email_job.rb new file mode 100644 index 000000000..29c250500 --- /dev/null +++ b/app/jobs/send_three_month_email_job.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class SendThreeMonthEmailJob < ApplicationJob + queue_as :default + + def perform + ThreeMonthEmailService.send_chaser + end +end diff --git a/app/services/three_month_email_service.rb b/app/services/three_month_email_service.rb new file mode 100644 index 000000000..50ee4ecba --- /dev/null +++ b/app/services/three_month_email_service.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class ThreeMonthEmailService + def self.send_chaser +recent_attendees = Member.joins(:workshop_invitations) + .merge( + WorkshopInvitation.attended.to_students + .joins(:workshop) + .where('workshops.date_and_time >= ?', 3.months.ago.beginning_of_day) + ) + .distinct + +members = Member.not_banned + .accepted_toc + .joins(:groups) + .merge(Group.students) + .left_joins(:member_email_deliveries) + .where(member_email_deliveries: { id: nil }) + .where.not(id: recent_attendees.select(:id)) + .distinct + return if members.empty? + members.find_each do |member| + MemberMailer.with(member: member).chaser.deliver_later + end + end +end diff --git a/spec/fabricators/member_email_delivery_fabricator.rb b/spec/fabricators/member_email_delivery_fabricator.rb new file mode 100644 index 000000000..f633b7a35 --- /dev/null +++ b/spec/fabricators/member_email_delivery_fabricator.rb @@ -0,0 +1,6 @@ +Fabricator(:member_email_delivery) do + member(fabricator: :member) + subject("Chaser") + body("Lorem ipsum") + to(["test_email@address"]) +end diff --git a/spec/services/three_month_email_service_spec.rb b/spec/services/three_month_email_service_spec.rb new file mode 100644 index 000000000..3d2b9b6b7 --- /dev/null +++ b/spec/services/three_month_email_service_spec.rb @@ -0,0 +1,43 @@ +RSpec.describe ThreeMonthEmailService, type: :service do + describe "#send_chaser" do + subject(:call) { described_class.send_chaser } + + let!(:eligible_member) { Fabricate(:member) } + let!(:emailed_member) { Fabricate(:member) } + let!(:old_invite_member) { Fabricate(:member) } + + before do + # Eligible: recent invite, no email delivery + Fabricate( + :workshop_invitation, + member: eligible_member, + created_at: 3.months.ago, + attended: false + ) + + # Already emailed: recent invite, but has email delivery + Fabricate( + :workshop_invitation, + member: emailed_member, + created_at: 2.months.ago + ) + Fabricate( + :member_email_delivery, + member: emailed_member + ) + + # Old invite: more than 3 months ago + Fabricate( + :workshop_invitation, + member: old_invite_member, + created_at: 4.months.ago + ) + end + + it "enqueues chaser emails only for eligible members" do + expect { + call + }.to have_enqueued_mail(MemberMailer, :chaser).once + end + end +end From 12d31f8ca093b4f847569eb171e944f98624ce13 Mon Sep 17 00:00:00 2001 From: "jonathan.kerr" <3410350+jonodrew@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:31:40 +0000 Subject: [PATCH 07/10] Create rake task for running this Job To note - the first time we run this, there's likely to be a massive spike in emails. To set this in Heroku, we'll need to log into the interface and call it as `rake chaser:three_months` Signed-off-by: jonathan.kerr <3410350+jonodrew@users.noreply.github.com> --- lib/tasks/chaser.rake | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 lib/tasks/chaser.rake diff --git a/lib/tasks/chaser.rake b/lib/tasks/chaser.rake new file mode 100644 index 000000000..d06134aa5 --- /dev/null +++ b/lib/tasks/chaser.rake @@ -0,0 +1,7 @@ +namespace :chaser do + desc "Send emails to users who've not attended in a while" + + task three_months: :environment do + SendThreeMonthEmailJob.perform_later + end +end From b5787b61cde37e34c331aae13f3bd3f5c0d1b9e2 Mon Sep 17 00:00:00 2001 From: Michelle Steele Date: Tue, 10 Mar 2026 18:04:44 +0000 Subject: [PATCH 08/10] Fix ThreeMonthEmailService query to check member selection for chaser emails and pass spec --- app/mailers/member_mailer.rb | 2 +- app/services/three_month_email_service.rb | 30 +++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/mailers/member_mailer.rb b/app/mailers/member_mailer.rb index eca12b3b4..fa1c8425f 100644 --- a/app/mailers/member_mailer.rb +++ b/app/mailers/member_mailer.rb @@ -2,7 +2,7 @@ class MemberMailer < ApplicationMailer include EmailHeaderHelper include EmailDelivery - after_deliver :log_sent_mail , only: [:chaser] + after_deliver :log_sent_email, only: [:chaser] def chaser @member = params[:member] diff --git a/app/services/three_month_email_service.rb b/app/services/three_month_email_service.rb index 50ee4ecba..66fe388aa 100644 --- a/app/services/three_month_email_service.rb +++ b/app/services/three_month_email_service.rb @@ -2,23 +2,23 @@ class ThreeMonthEmailService def self.send_chaser -recent_attendees = Member.joins(:workshop_invitations) - .merge( - WorkshopInvitation.attended.to_students - .joins(:workshop) - .where('workshops.date_and_time >= ?', 3.months.ago.beginning_of_day) - ) - .distinct + cutoff = 3.months.ago.beginning_of_day + recent_attendee_ids = WorkshopInvitation.to_students + .attended + .joins(:workshop) + .where('workshops.date_and_time >= ?', cutoff) + .select(:member_id) -members = Member.not_banned - .accepted_toc - .joins(:groups) - .merge(Group.students) - .left_joins(:member_email_deliveries) - .where(member_email_deliveries: { id: nil }) - .where.not(id: recent_attendees.select(:id)) - .distinct + members = Member.not_banned + .accepted_toc + .joins(:groups) + .merge(Group.students) + .left_joins(:member_email_deliveries) + .where(member_email_deliveries: { id: nil }) + .where.not(id: recent_attendee_ids) + .distinct return if members.empty? + members.find_each do |member| MemberMailer.with(member: member).chaser.deliver_later end From f2ffaa1cec6ab3568d2aeb8e559f69064f151dab Mon Sep 17 00:00:00 2001 From: Michelle Steele Date: Tue, 10 Mar 2026 18:34:47 +0000 Subject: [PATCH 09/10] Add more chaser scenarios to the member selection --- .../three_month_email_service_spec.rb | 164 +++++++++++++++--- 1 file changed, 144 insertions(+), 20 deletions(-) diff --git a/spec/services/three_month_email_service_spec.rb b/spec/services/three_month_email_service_spec.rb index 3d2b9b6b7..7eb209cda 100644 --- a/spec/services/three_month_email_service_spec.rb +++ b/spec/services/three_month_email_service_spec.rb @@ -2,42 +2,166 @@ describe "#send_chaser" do subject(:call) { described_class.send_chaser } - let!(:eligible_member) { Fabricate(:member) } - let!(:emailed_member) { Fabricate(:member) } - let!(:old_invite_member) { Fabricate(:member) } + let(:chapter) { Fabricate(:chapter) } + let(:students_group) { Fabricate(:group, name: "Students", chapter: chapter) } + let(:coaches_group) { Fabricate(:group, name: "Coaches", chapter: chapter) } - before do - # Eligible: recent invite, no email delivery + let!(:eligible_student) do + member = Fabricate(:member) + Fabricate(:subscription, member: member, group: students_group) + member + end + + let!(:already_emailed_student) do + member = Fabricate(:member) + Fabricate(:subscription, member: member, group: students_group) + Fabricate(:member_email_delivery, member: member) + member + end + + let!(:student_with_recent_attendance) do + member = Fabricate(:member) + Fabricate(:subscription, member: member, group: students_group) Fabricate( :workshop_invitation, - member: eligible_member, - created_at: 3.months.ago, - attended: false + member: member, + workshop: Fabricate(:workshop, chapter: chapter, date_and_time: 1.month.ago), + role: "Student", + attended: true ) + member + end - # Already emailed: recent invite, but has email delivery + let!(:student_with_old_attendance) do + member = Fabricate(:member) + Fabricate(:subscription, member: member, group: students_group) Fabricate( :workshop_invitation, - member: emailed_member, - created_at: 2.months.ago + member: member, + workshop: Fabricate(:workshop, chapter: chapter, date_and_time: 4.months.ago), + role: "Student", + attended: true ) + member + end + + let!(:coach_member) do + member = Fabricate(:member) + Fabricate(:subscription, member: member, group: coaches_group) + member + end + + let!(:unsubscribed_member) { Fabricate(:member) } + let!(:banned_student) do + member = Fabricate(:banned_member) + Fabricate(:subscription, member: member, group: students_group) + member + end + let!(:student_without_toc) do + member = Fabricate(:member_without_toc) + Fabricate(:subscription, member: member, group: students_group) + member + end + + it "emails only students who have not attended in the last 3 months and were not emailed before" do + expect { perform_enqueued_jobs { call } }.to change(MemberEmailDelivery, :count).by(2) + + expect(MemberEmailDelivery.where(member: eligible_student)).to exist + expect(MemberEmailDelivery.where(member: student_with_old_attendance)).to exist + end + + it "does not email a member already present in member_email_deliveries" do + expect { perform_enqueued_jobs { call } } + .not_to change { MemberEmailDelivery.where(member: already_emailed_student).count } + end + + it "does not email students with a recent attended workshop" do + expect { perform_enqueued_jobs { call } } + .not_to change { MemberEmailDelivery.where(member: student_with_recent_attendance).count } + end + + it "does not email members without a student subscription" do + perform_enqueued_jobs { call } + + expect(MemberEmailDelivery.where(member: coach_member)).to be_empty + expect(MemberEmailDelivery.where(member: unsubscribed_member)).to be_empty + end + + it "does not email banned students or students without accepted terms" do + perform_enqueued_jobs { call } + + expect(MemberEmailDelivery.where(member: banned_student)).to be_empty + expect(MemberEmailDelivery.where(member: student_without_toc)).to be_empty + end + + it "sends only one chaser for a member with multiple student subscriptions" do + member = Fabricate(:member) + other_chapter = Fabricate(:chapter) + other_students_group = Fabricate(:group, name: "Students", chapter: other_chapter) + Fabricate(:subscription, member: member, group: students_group) + Fabricate(:subscription, member: member, group: other_students_group) + + perform_enqueued_jobs { call } + + expect(MemberEmailDelivery.where(member: member).count).to eq(1) + end + + it "sends only one chaser for a member with multiple qualifying old attendances" do + member = Fabricate(:member) + Fabricate(:subscription, member: member, group: students_group) Fabricate( - :member_email_delivery, - member: emailed_member + :workshop_invitation, + member: member, + workshop: Fabricate(:workshop, chapter: chapter, date_and_time: 5.months.ago), + role: "Student", + attended: true ) + Fabricate( + :workshop_invitation, + member: member, + workshop: Fabricate(:workshop, chapter: chapter, date_and_time: 4.months.ago), + role: "Student", + attended: true + ) + + perform_enqueued_jobs { call } - # Old invite: more than 3 months ago + expect(MemberEmailDelivery.where(member: member).count).to eq(1) + end + + it "does not send chasers when there are no eligible members" do + Fabricate( + :workshop_invitation, + member: eligible_student, + workshop: Fabricate(:workshop, chapter: chapter, date_and_time: 1.month.ago), + role: "Student", + attended: true + ) Fabricate( :workshop_invitation, - member: old_invite_member, - created_at: 4.months.ago + member: student_with_old_attendance, + workshop: Fabricate(:workshop, chapter: chapter, date_and_time: 1.month.ago), + role: "Student", + attended: true ) + + expect { perform_enqueued_jobs { call } }.not_to change(MemberEmailDelivery, :count) end - it "enqueues chaser emails only for eligible members" do - expect { - call - }.to have_enqueued_mail(MemberMailer, :chaser).once + it "emails a student member who has recent attendance only as a coach" do + member = Fabricate(:member) + Fabricate(:subscription, member: member, group: students_group) + Fabricate( + :workshop_invitation, + member: member, + workshop: Fabricate(:workshop, chapter: chapter, date_and_time: 1.month.ago), + role: "Coach", + attended: true + ) + + perform_enqueued_jobs { call } + + expect(MemberEmailDelivery.where(member: member)).to exist end end end From 6c154c101dfd514b1b85b1b50f92eecd147d2dc2 Mon Sep 17 00:00:00 2001 From: Michelle Steele Date: Tue, 17 Mar 2026 18:11:06 +0000 Subject: [PATCH 10/10] Exclude students with no attendance in the past year from chaser email Narrows the chaser target to members who attended between 3 months and 1 year ago, filtering out long-inactive students who are unlikely to re-engage. Co-Authored-By: Claude Opus 4.6 (1M context) --- app/services/three_month_email_service.rb | 13 ++++++-- .../three_month_email_service_spec.rb | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/app/services/three_month_email_service.rb b/app/services/three_month_email_service.rb index 66fe388aa..20d3c1585 100644 --- a/app/services/three_month_email_service.rb +++ b/app/services/three_month_email_service.rb @@ -2,13 +2,21 @@ class ThreeMonthEmailService def self.send_chaser - cutoff = 3.months.ago.beginning_of_day + three_month_cutoff = 3.months.ago.beginning_of_day + one_year_cutoff = 1.year.ago.beginning_of_day + recent_attendee_ids = WorkshopInvitation.to_students .attended .joins(:workshop) - .where('workshops.date_and_time >= ?', cutoff) + .where('workshops.date_and_time >= ?', three_month_cutoff) .select(:member_id) + past_year_attendee_ids = WorkshopInvitation.to_students + .attended + .joins(:workshop) + .where('workshops.date_and_time >= ?', one_year_cutoff) + .select(:member_id) + members = Member.not_banned .accepted_toc .joins(:groups) @@ -16,6 +24,7 @@ def self.send_chaser .left_joins(:member_email_deliveries) .where(member_email_deliveries: { id: nil }) .where.not(id: recent_attendee_ids) + .where(id: past_year_attendee_ids) .distinct return if members.empty? diff --git a/spec/services/three_month_email_service_spec.rb b/spec/services/three_month_email_service_spec.rb index 7eb209cda..4117bb819 100644 --- a/spec/services/three_month_email_service_spec.rb +++ b/spec/services/three_month_email_service_spec.rb @@ -9,6 +9,13 @@ let!(:eligible_student) do member = Fabricate(:member) Fabricate(:subscription, member: member, group: students_group) + Fabricate( + :workshop_invitation, + member: member, + workshop: Fabricate(:workshop, chapter: chapter, date_and_time: 6.months.ago), + role: "Student", + attended: true + ) member end @@ -63,6 +70,19 @@ member end + let!(:student_with_very_old_attendance) do + member = Fabricate(:member) + Fabricate(:subscription, member: member, group: students_group) + Fabricate( + :workshop_invitation, + member: member, + workshop: Fabricate(:workshop, chapter: chapter, date_and_time: 14.months.ago), + role: "Student", + attended: true + ) + member + end + it "emails only students who have not attended in the last 3 months and were not emailed before" do expect { perform_enqueued_jobs { call } }.to change(MemberEmailDelivery, :count).by(2) @@ -94,6 +114,12 @@ expect(MemberEmailDelivery.where(member: student_without_toc)).to be_empty end + it "does not email students who have not attended in the past year" do + perform_enqueued_jobs { call } + + expect(MemberEmailDelivery.where(member: student_with_very_old_attendance)).to be_empty + end + it "sends only one chaser for a member with multiple student subscriptions" do member = Fabricate(:member) other_chapter = Fabricate(:chapter) @@ -151,6 +177,13 @@ it "emails a student member who has recent attendance only as a coach" do member = Fabricate(:member) Fabricate(:subscription, member: member, group: students_group) + Fabricate( + :workshop_invitation, + member: member, + workshop: Fabricate(:workshop, chapter: chapter, date_and_time: 6.months.ago), + role: "Student", + attended: true + ) Fabricate( :workshop_invitation, member: member,