simplymadeapps/simple_scheduler

View on GitHub
lib/simple_scheduler/future_job.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
# frozen_string_literal: true

module SimpleScheduler
  # Active Job class that wraps the scheduled job and determines if the job
  # should still be run based on the scheduled time and when the job expires.
  class FutureJob < ActiveJob::Base
    # An error class that is raised if a job does not run because the run time is
    # too late when compared to the scheduled run time.
    # @!attribute run_time
    #   @return [Time] The actual run time
    # @!attribute scheduled_time
    #   @return [Time] The scheduled run time
    # @!attribute task
    #   @return [SimpleScheduler::Task] The expired task
    class Expired < StandardError
      attr_accessor :run_time, :scheduled_time, :task
    end

    rescue_from Expired, with: :handle_expired_task

    # Perform the future job as defined by the task.
    # @param task_params [Hash] The params from the scheduled task
    # @param scheduled_time [Integer] The epoch time for when the job was scheduled to be run
    def perform(task_params, scheduled_time)
      @task = Task.new(task_params)
      @scheduled_time = Time.at(scheduled_time).in_time_zone(@task.time_zone)
      raise Expired if expired?

      queue_task
    end

    # Delete all future jobs created by Simple Scheduler from the `Sidekiq::ScheduledSet`.
    def self.delete_all
      Task.scheduled_set.each do |job|
        job.delete if job.display_class == "SimpleScheduler::FutureJob"
      end
    end

    private

    # The duration between the scheduled run time and actual run time that
    # will cause the job to expire. Expired jobs will not be executed.
    # @return [ActiveSupport::Duration]
    def expire_duration
      split_duration = @task.expires_after.split(".")
      duration = split_duration[0].to_i
      duration_units = split_duration[1]
      duration.send(duration_units)
    end

    # Returns whether or not the job has expired based on the time
    # between the scheduled run time and the current time.
    # @return [Boolean]
    def expired?
      return false if @task.expires_after.blank?

      expire_duration.from_now(@scheduled_time) < Time.now.in_time_zone(@task.time_zone)
    end

    # Handle the expired task by passing the task and run time information
    # to a block that can be creating in a Rails initializer file.
    def handle_expired_task(exception)
      exception.run_time = Time.now.in_time_zone(@task.time_zone)
      exception.scheduled_time = @scheduled_time
      exception.task = @task

      SimpleScheduler.expired_task_blocks.each do |block|
        block.call(exception)
      end
    end

    # The name of the method used to queue the task's job or worker.
    # @return [Symbol]
    def perform_method
      if @task.job_class.included_modules.include?(Sidekiq::Worker)
        :perform_async
      else
        :perform_later
      end
    end

    # Queue the task with the scheduled time if the job allows.
    def queue_task
      if @task.job_class.instance_method(:perform).arity.zero?
        @task.job_class.send(perform_method)
      else
        @task.job_class.send(perform_method, @scheduled_time.to_i)
      end
    end
  end
end