diff --git a/app/jobs/remove_activities_job.rb b/app/jobs/remove_activities_job.rb new file mode 100644 index 0000000000..14f7a8c612 --- /dev/null +++ b/app/jobs/remove_activities_job.rb @@ -0,0 +1,43 @@ +class RemoveActivitiesJob < ApplicationJob + # permanently remove activities that match all of the following criteria: + # - status is 'removed' + # - updated_at is more than 1 month ago + # - one of the following is true: + # - draft is true (never published) + # - series_memberships is empty and less then 25 submissions and latest submission is more than 1 month ago + # + # Destroy is called on each activity individually to ensure that callbacks are run + # This means the activity will be removed from any series, evaluations it is a member of + # and any submissions will be removed + queue_as :cleaning + + def perform + ContentPage.where(status: 'removed').where('updated_at < ?', 1.month.ago).find_each do |activity| + if activity.draft? || activity.series_memberships.empty? + # destroy series memberships first explicitly, as they are dependent: :restrict_with_error + activity.series_memberships.destroy_all + + activity.destroy + end + end + + Exercise.where(status: 'removed').where('updated_at < ?', 1.month.ago).find_each do |activity| + unless activity.draft? + next if activity.series_memberships.present? + next if activity.submissions.count >= 25 + next if activity.submissions.present? && activity.submissions.reorder(:created_at).last.created_at > 1.month.ago + end + + # destroy submissions first explicitly, as they are dependent: :restrict_with_error + activity.submissions.destroy_all + + # destroy series memberships first explicitly, as they are dependent: :restrict_with_error + activity.series_memberships.destroy_all + + activity.destroy + end + + # rerun this job in 1 month + RemoveActivitiesJob.set(wait: 1.month).perform_later + end +end diff --git a/test/jobs/remove_activities_job_test.rb b/test/jobs/remove_activities_job_test.rb new file mode 100644 index 0000000000..9f283fbf58 --- /dev/null +++ b/test/jobs/remove_activities_job_test.rb @@ -0,0 +1,105 @@ +require 'test_helper' + +class RemoveActivitiesJobTest < ActiveJob::TestCase + test 'should remove "removed" draft activities' do + c = create :content_page + create :activity_read_state, activity: c + c.update status: :removed, draft: true, updated_at: 2.months.ago + e = create :exercise + create :submission, exercise: e + e.update status: :removed, draft: true, updated_at: 2.months.ago + s = create :series + s.activities << c + s.activities << e + + assert_difference 'ContentPage.count', -1 do + assert_difference 'Exercise.count', -1 do + RemoveActivitiesJob.perform_now + end + end + end + + test 'should remove "removed" activities with no series memberships and no submissions' do + create :content_page, status: :removed, draft: false, updated_at: 2.months.ago + create :exercise, status: :removed, draft: false, updated_at: 2.months.ago + + assert_difference 'ContentPage.count', -1 do + assert_difference 'Exercise.count', -1 do + RemoveActivitiesJob.perform_now + end + end + end + + test 'should not remove "removed" activities with series memberships' do + c = create :content_page, status: :removed, draft: false, updated_at: 2.months.ago + e = create :exercise, status: :removed, draft: false, updated_at: 2.months.ago + s = create :series + s.activities << c + s.activities << e + + assert_no_difference 'ContentPage.count' do + assert_no_difference 'Exercise.count' do + RemoveActivitiesJob.perform_now + end + end + end + + test 'should not remove "removed" activities with more than 25 submissions' do + e = create :exercise, status: :removed, draft: false, updated_at: 2.months.ago + create_list :submission, 26, exercise: e + + assert_no_difference 'Exercise.count' do + RemoveActivitiesJob.perform_now + end + end + + test 'should remove "removed" activities with less than 25 submissions and last submission more than 1 month ago' do + e = create :exercise, status: :removed, draft: false, updated_at: 2.months.ago + create_list :submission, 21, exercise: e, created_at: 2.months.ago + + assert_difference 'Submission.count', -21 do + assert_difference 'Exercise.count', -1 do + RemoveActivitiesJob.perform_now + end + end + end + + test 'should not remove "removed" activities with less than 25 submissions and last submission less than 1 month ago' do + e = create :exercise, status: :removed, draft: false, updated_at: 2.months.ago + create_list :submission, 5, exercise: e, created_at: 2.months.ago + create_list :submission, 3, exercise: e, created_at: 2.weeks.ago + + assert_no_difference 'Submission.count' do + assert_no_difference 'Exercise.count' do + RemoveActivitiesJob.perform_now + end + end + end + + test 'should not removed non removed activities' do + create :exercise, updated_at: 2.months.ago + create :content_page, updated_at: 2.months.ago + create :exercise, draft: true, updated_at: 2.months.ago + create :content_page, draft: true, updated_at: 2.months.ago + + assert_no_difference 'Exercise.count' do + assert_no_difference 'ContentPage.count' do + RemoveActivitiesJob.perform_now + end + end + end + + test 'should not remove activities updated less than 1 month ago' do + create :exercise, status: :removed, draft: true, updated_at: 2.weeks.ago + + assert_no_difference 'Exercise.count' do + RemoveActivitiesJob.perform_now + end + end + + test 'should reschedule itself' do + assert_enqueued_with(job: RemoveActivitiesJob) do + RemoveActivitiesJob.perform_now + end + end +end