Skip to content

Instantly share code, notes, and snippets.

@bhavik2936
Created May 13, 2024 14:24
Show Gist options
  • Save bhavik2936/57997d916a20b66c3d511c637c9775e7 to your computer and use it in GitHub Desktop.
Save bhavik2936/57997d916a20b66c3d511c637c9775e7 to your computer and use it in GitHub Desktop.
Demonstration of rake task for resetting the password_changed_at attribute with a random timestamp ranging from 60 to 90 days ago
namespace :cleanup do
desc 'Update users\' password_changed_at timestamp between 60 and 90 days old'
# Execute the rake task as cleanup:update_user_password_changed_at_timestamp
task update_user_password_changed_at_timestamp: :environment do
logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
Client.each_tenant do |client|
logger.info "Queueing UpdateUserPasswordChangedAtTimestampJob for #{client.subdomain}"
UpdateUserPasswordChangedAtTimestampJob.perform_async(client.subdomain)
end
end
end
require 'rails_helper'
describe 'cleanup rake tasks' do
context 'cleanup:update_user_password_changed_at_timestamp', rake: 'cleanup:update_user_password_changed_at_timestamp' do
let(:task) { Rake::Task['cleanup:update_user_password_changed_at_timestamp'] }
it 'should enqueue cleanup fix job for all clients' do
Sidekiq::Testing.fake! do
expect {
task.invoke
}.to change(UpdateUserPasswordChangedAtTimestampJob.jobs, :size).by(Client.count)
end
end
end
end
module TaggedLogHelper
def tagged_logger
return @logger unless @logger.nil?
tags = Array.wrap(@logger_tags || self.class)
@logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)).tagged(*tags)
end
def log(message = nil, additional_tags = nil, level = Logger::INFO)
level = log_level_lookup(level)
tagged_logger.tagged(*additional_tags).log(level, message)
end
private
def log_level_lookup(level)
return level if level.is_a?(Integer)
levels = {
'debug' => Logger::DEBUG,
'info' => Logger::INFO,
'warn' => Logger::WARN,
'error' => Logger::ERROR,
'fatal' => Logger::FATAL
}
levels[level] || Logger::UNKNOWN
end
end
class UpdateUserPasswordChangedAtTimestampJob
include TaggedLogHelper
include Sidekiq::Worker
sidekiq_options queue: 'bulk', retry: false
MIN_DAY_LIMIT = 60
MAX_DAY_LIMIT = 90
def perform(tenant)
Client.switch(tenant) do
log "Updating users' password_changed_at attribute for #{tenant}"
start = Time.now
update_password_changed_at_attribute
runtime = Time.now - start
log "Took #{runtime} seconds to update users' password_changed_at attribute for #{tenant}"
end
end
# Fetch number of users having password_changed_at attribute older than 90 days
def self.users_with_expired_password_attribute
User.where(password_changed_at: ...(DateTime.now - MAX_DAY_LIMIT.days))
.count
end
private
def update_password_changed_at_attribute
User.where(password_changed_at: ...(DateTime.now - MAX_DAY_LIMIT.days))
.in_batches.update_all(sql_to_update_password_changed_at_attribute)
end
# SQL to update password_changed_at with random timestamp falls between 60 and 90 days
def sql_to_update_password_changed_at_attribute
<<-SQL.squish
password_changed_at = CURRENT_TIMESTAMP -
MAKE_INTERVAL(days => FLOOR((RANDOM() * (#{MAX_DAY_LIMIT} - #{MIN_DAY_LIMIT}) + #{MIN_DAY_LIMIT}))::int)
SQL
end
end
require 'rails_helper'
describe UpdateUserPasswordChangedAtTimestampJob do
subject { described_class.perform_async(Client.current!.subdomain) }
context 'enqueuing the job' do
it 'sends job to sidekiq' do
Sidekiq::Testing.fake! do
expect {
subject
}.to change(described_class.jobs, :size).by(1)
end
end
end
context '#perform' do
context 'when password has been changed recently' do
let!(:user) { create :user, password_changed_at: DateTime.now - 10.days }
it 'should not update password_changed_at attribute' do
expect { subject }.not_to change(user, :password_changed_at)
end
end
context 'when password_changed_at attribute is over 90 days' do
let(:old_date) { Date.new(2019, 12, 25) }
let!(:users) { create_list :user, 10, password_changed_at: old_date }
it 'should change password_changed_at attribute for all users' do
expect { subject }.to change(described_class, :users_with_expired_password_count).from(10).to(0)
end
it 'should randomly assign password_changed_at attribute between range of 60 to 90 days old' do
subject
password_changed_at_attributes = User.distinct.pluck(:password_changed_at)
expect(password_changed_at_attributes.length).to be > 1
expect(password_changed_at_attributes).to all(be_between(DateTime.now - described_class::MAX_DAY_LIMIT, DateTime.now - described_class::MIN_DAY_LIMIT))
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment