Skip to content

Commit

Permalink
Anonymise users background job
Browse files Browse the repository at this point in the history
This gives us a simple background job which anonymises users who have
been deactivated for more than 5 years. We introduce the sidekiq-scheduler
gem for the purpose of scheduling jobs in a cron-like manner.

(Also worth noting that the sidekiq_scheduler_spec basically just checks for
typos in the Sidekiq schedule config, in case we ever need to tweak that
again or add additional jobs.)
  • Loading branch information
benshimmin committed Feb 3, 2025
1 parent 171073e commit aa62706
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 0 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ gem "wicked"
gem "strip_attributes"
gem "breadcrumbs_on_rails"
gem "sprockets-rails"
gem "sidekiq-scheduler"

# Authentication
gem "devise"
Expand Down
13 changes: 13 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ GEM
encryptor (3.0.0)
erubi (1.13.1)
erubis (2.7.0)
et-orbi (1.2.11)
tzinfo
factory_bot (6.5.0)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.4)
Expand All @@ -182,6 +184,9 @@ GEM
ffi (1.17.0-x86_64-darwin)
ffi (1.17.0-x86_64-linux)
foreman (0.88.1)
fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
globalid (1.2.1)
activesupport (>= 6.1)
govuk_design_system_formbuilder (5.8.0)
Expand Down Expand Up @@ -332,6 +337,7 @@ GEM
rspec-expectations (~> 3.12)
rspec-mocks (~> 3.12)
rspec-support (~> 3.12)
raabro (1.4.0)
racc (1.8.1)
rack (2.2.10)
rack-attack (6.7.0)
Expand Down Expand Up @@ -455,6 +461,8 @@ GEM
ruby_parser (3.19.1)
sexp_processor (~> 4.16)
rubyzip (2.4.1)
rufus-scheduler (3.9.2)
fugit (~> 1.1, >= 1.11.1)
selenium-webdriver (4.28.0)
base64 (~> 0.2)
logger (~> 1.4)
Expand All @@ -469,6 +477,10 @@ GEM
logger
rack (>= 2.2.4)
redis-client (>= 0.22.2)
sidekiq-scheduler (5.0.6)
rufus-scheduler (~> 3.2)
sidekiq (>= 6, < 8)
tilt (>= 1.4.0, < 3)
simplecov (0.22.0)
docile (~> 1.1)
simplecov-html (~> 0.11)
Expand Down Expand Up @@ -616,6 +628,7 @@ DEPENDENCIES
selenium-webdriver
shoulda-matchers
sidekiq (~> 7)
sidekiq-scheduler
simplecov (~> 0.22.0)
simplecov-lcov (~> 0.8.0)
sprockets-rails
Expand Down
9 changes: 9 additions & 0 deletions app/jobs/anonymise_deactivated_users_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class AnonymiseDeactivatedUsersJob
include Sidekiq::Job

def perform
User.deactivated.where("deactivated_at < ?", 5.years.ago).each do |user|
AnonymiseUser.new(user:).call
end
end
end
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "sidekiq/web"
require "sidekiq-scheduler/web"

Rails.application.routes.draw do
devise_scope :user do
Expand Down
5 changes: 5 additions & 0 deletions config/sidekiq.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
:queues:
- default
- mailers
:scheduler:
:schedule:
anonymise_deactivated_users:
cron: '0 0 * * 0' # Every Sunday at midnight
class: AnonymiseDeactivatedUsersJob
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 40. Use sidekiq-scheduler for scheduled jobs

Date: 2025-02-03

## Status

Accepted

## Context

We needed a mechanism for running scheduled background jobs in order to
anonymise users who have been inactive for more than five years. There is no
mechanism currently in place in RODA to handle scheduled background jobs, but
we do have Sidekiq already available to us for running jobs in the background.

## Decision

We have decided to use [sidekiq-scheduler][1] as a lightweight scheduled job
solution. A small amount of research suggested that this gem offered a decent
implementation with a standard and well-understood interface (the time-honoured
cron syntax combined with Sidekiq's jobs (__ workers)). We also briefly
explored [sidekiq-cron][2] which does almost exactly the same thing, but had
slightly worse documentation.

## Consequences

With the addition of this gem, we have a small amount of configuration overhead
and, of course, one additional dependency. Neither of these is particularly
onerous. We now benefit from having a standardised solution for our current
and future scheduled background job requirements.

[1]: https://github.com/sidekiq-scheduler/sidekiq-scheduler
[2]: https://github.com/sidekiq-cron/sidekiq-cron
58 changes: 58 additions & 0 deletions spec/jobs/anonymise_deactivated_users_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
require "rails_helper"
require "sidekiq/testing"
Sidekiq::Testing.fake!

RSpec.describe AnonymiseDeactivatedUsersJob, type: :job do
describe "the job" do
subject(:job) { AnonymiseDeactivatedUsersJob.perform_async }

it "is enqueued" do
expect { job }.to change(AnonymiseDeactivatedUsersJob.jobs, :size).by(1)
end

it "is drained" do
job
AnonymiseDeactivatedUsersJob.drain
expect(AnonymiseDeactivatedUsersJob.jobs.size).to eq 0
end
end

describe "#perform" do
let(:the_recent_past) { 2.years.ago }
let(:the_distant_past) { 6.years.ago }

it "anonymises a user who has been inactive for more than 5 years" do
create(:beis_user, deactivated_at: the_distant_past)

described_class.new.perform

expect(User.first.anonymised_at).not_to eq nil
end

it "does not anonymise a user who has been inactive for less than 5 years" do
create(:beis_user, deactivated_at: the_recent_past)

described_class.new.perform

expect(User.first.anonymised_at).to eq nil
end

it "anonymises a set of users who were deactivated in the distant past" do
5.times { create(:beis_user, deactivated_at: the_distant_past) }

described_class.new.perform

expect(User.deactivated.count).to eq 0
expect(User.where.not(anonymised_at: nil).count).to eq 5
end

it "does not anonymise a set of users who were deactivated in the recent past" do
5.times { create(:beis_user, deactivated_at: the_recent_past) }

described_class.new.perform

expect(User.deactivated.count).to eq 5
expect(User.where.not(anonymised_at: nil).count).to eq 0
end
end
end
34 changes: 34 additions & 0 deletions spec/sidekiq_scheduler_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require "rails_helper"
require "fugit"

RSpec.describe "sidekiq-scheduler" do
sidekiq_file = File.join(Rails.root, "config", "sidekiq.yml")
schedule = YAML.load_file(sidekiq_file)[:scheduler][:schedule]

describe "cron syntax" do
schedule.each do |k, v|
cron = v["cron"]
it "#{k} has correct cron syntax" do
expect { Fugit.do_parse(cron) }.not_to raise_error
end
end
end

describe "job classes" do
schedule.each do |k, v|
klass = v["class"]
it "#{k} has #{klass} class in /jobs" do
expect { klass.constantize }.not_to raise_error
end
end
end

describe "job names" do
schedule.each do |k, v|
klass = v["class"]
it "#{k} has correct name" do
expect(klass.underscore).to start_with(k)
end
end
end
end

0 comments on commit aa62706

Please sign in to comment.