Cancelable jobs for Rails active jobs

24 January 2022

In my current project we have a lot of ActiveJobs most of them only need to run in production, that’s why I wrote this Rails concern. This concern allows you to skip certain jobs in development.

Below is an example .yml file we have to determine what jobs run in development.

module Cancelable
  extend ActiveSupport::Concern

  included do
    around_perform do |job, perform|
      perform.call unless Cancelable.allowed?

      if Cancelable.allowed?
        file_name = "jobs/#{job.class.name.underscore}.rb"
        constant_name = Cancelable.setting_name(job.class)

        setting = Setting.find_or_create_by(name: constant_name) do |new_setting|
          new_setting.value = Cancelable.default_setting_for(constant_name, default: true)
          new_setting.description = "Automatically created in #{file_name}"
        end

        if setting.value
          perform.call
        else
          Rails.logger.info "♦ Job #{job.class} is disabled (#{Rails.application.routes.url_helpers.admin_setting_url(setting)})"
        end
      end
    end
  end

  def self.allowed?
    Rails.env.development?
  end

  def self.setting_name(clazz)
    clazz.name
  end

  def self.default_setting_for(constant_name, default:)
    value = YAML.load_file("config/default_jobs.yml").dig(constant_name)
    value.nil? ? default : value
  end
end

This is how to use it:

class MyJob < ApplicationJob
  include Cancelable

  def perform
    # do something
  end
end

The RSpec test:

class CancelableTestJob < ApplicationJob
  def perform = CheckIn.do!("Test", reporting_interval: 5.minutes, grace_period: 3.minutes)
end

describe Cancelable do
  subject(:test_job) { CancelableTestJob.new }

  before { allow(CheckIn).to receive(:do!) }

  context "when cancelable is not active" do
    before { allow(Cancelable).to receive(:allowed?).and_return(false) }

    it "can not be canceled with a default config" do
      allow(Cancelable).to receive(:default_setting_for).with("CancelableTestJob", default: anything).and_return(false)

      test_job.perform_now

      expect(CheckIn).to have_received(:do!)
      expect(Setting).to have(0).settings
    end
  end

  context "when cancelable is active" do
    before { allow(Cancelable).to receive(:allowed?).and_return(true) }

    it "is executed and creates a new setting with a true value" do
      test_job.perform_now

      expect(CheckIn).to have_received(:do!)
      expect(Setting).to have(1).setting
      expect(Setting.first.name).to eq "CancelableTestJob"
      expect(Setting.first.value).to eq true
    end

    it "can be canceled with a default config" do
      allow(Cancelable).to receive(:default_setting_for).with("CancelableTestJob", default: anything).and_return(false)

      test_job.perform_now

      expect(CheckIn).not_to have_received(:do!)
      expect(Setting).to have(1).setting
      expect(Setting.first.name).to eq "CancelableTestJob"
      expect(Setting.first.value).to eq false
    end

    it "can be allowed with a default config" do
      allow(Cancelable).to receive(:default_setting_for).with("CancelableTestJob", default: anything).and_return(true)

      test_job.perform_now

      expect(CheckIn).to have_received(:do!)
      expect(Setting).to have(1).setting
      expect(Setting.first.name).to eq "CancelableTestJob"
      expect(Setting.first.value).to eq true
    end

    describe ".setting_name" do
      it "returns the name of a setting" do
        expect(Cancelable.setting_name(Tesla::DetectPluggedInJob)).to eq "Tesla::DetectPluggedInJob"
      end
    end
  end
end

This is our config:

DataSource::GridImbalanceJob: false
DataSource::GridFrequencyJob: false
DataSource::SolarDataJob: false
DataSource::SolarLogJob: false
ElaadSubmitMeasurementsJob: false
ImportEfluxSessionsJob: false
Jankees van Woezik profile picture

Hello, I'm Jankees van Woezik

Like this post? Follow me at @jankeesvw on Twitter