Skip to content

Commit

Permalink
Allow specifying recurring tasks just with a "command"
Browse files Browse the repository at this point in the history
No need to have a class, can specify this using just a command for which
we'd provide a default RecurringtJob class that will just eval the command.
  • Loading branch information
rosa committed Sep 9, 2024
1 parent 8d47ed4 commit 90f63c8
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 9 deletions.
9 changes: 9 additions & 0 deletions app/jobs/solid_queue/recurring_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class SolidQueue::RecurringJob < ActiveJob::Base
queue_as :solid_queue_recurring

def perform(command)
eval(command)
end
end
19 changes: 16 additions & 3 deletions app/models/solid_queue/recurring_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ class RecurringTask < Record
serialize :arguments, coder: Arguments, default: []

validate :supported_schedule
validate :ensure_command_or_class_present
validate :existing_job_class

scope :static, -> { where(static: true) }

mattr_accessor :default_job_class
self.default_job_class = RecurringJob

class << self
def wrap(args)
args.is_a?(self) ? args : from_configuration(args.first, **args.second)
Expand All @@ -20,6 +24,7 @@ def from_configuration(key, **options)
new \
key: key,
class_name: options[:class],
command: options[:command],
arguments: options[:args],
schedule: options[:schedule],
queue_name: options[:queue].presence,
Expand Down Expand Up @@ -85,8 +90,14 @@ def supported_schedule
end
end

def ensure_command_or_class_present
unless command.present? || class_name.present?
errors.add :base, :command_and_class_blank, message: "either command or class_name must be present"
end
end

def existing_job_class
unless job_class.present?
if class_name.present? && job_class.nil?
errors.add :class_name, :undefined, message: "doesn't correspond to an existing class"
end
end
Expand All @@ -113,7 +124,9 @@ def perform_later
end

def arguments_with_kwargs
if arguments.last.is_a?(Hash)
if class_name.nil?
command
elsif arguments.last.is_a?(Hash)
arguments[0...-1] + [ Hash.ruby2_keywords_hash(arguments.last) ]
else
arguments
Expand All @@ -126,7 +139,7 @@ def parsed_schedule
end

def job_class
@job_class ||= class_name&.safe_constantize
@job_class ||= class_name.present? ? class_name.safe_constantize : self.class.default_job_class
end

def enqueue_options
Expand Down
29 changes: 24 additions & 5 deletions test/models/solid_queue/recurring_task_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,17 @@ def perform
assert_not SolidQueue::RecurringTask.new(key: "task-id", class_name: "SolidQueue::RecurringTaskTest::JobWithoutArguments").valid?
end

test "undefined job class" do
test "valid and invalid job class and command" do
# Command
assert recurring_task_with(command: "puts '¡hola!'").valid?
# Class
assert recurring_task_with(class_name: "JobWithPriority").valid?

# Invalid class name
assert_not recurring_task_with(class_name: "UnknownJob").valid?

# Empty class name
assert_not SolidQueue::RecurringTask.new(key: "task-id", schedule: "every minute").valid?
# Empty class name and command
assert_not recurring_task_with(key: "task-id", schedule: "every minute").valid?
end

test "task with custom queue and priority" do
Expand Down Expand Up @@ -154,6 +160,13 @@ def perform
assert_equal 4, job.priority
end

test "task configured with a command" do
task = recurring_task_with(command: "JobBuffer.add('from_a_command')")
enqueue_and_assert_performed_with_result(task, "from_a_command")

assert_equal "SolidQueue::RecurringJob", SolidQueue::Job.last.class_name
end

private
def enqueue_and_assert_performed_with_result(task, result)
assert_difference [ -> { SolidQueue::Job.count }, -> { SolidQueue::ReadyExecution.count } ], +1 do
Expand All @@ -170,7 +183,13 @@ def enqueue_and_assert_performed_with_result(task, result)
assert_equal result, JobBuffer.last_value
end

def recurring_task_with(class_name:, **options)
SolidQueue::RecurringTask.from_configuration("task-id", class: "SolidQueue::RecurringTaskTest::#{class_name}", **options.with_defaults(schedule: "every hour"))
def recurring_task_with(class_name: nil, **options)
options = options.dup.with_defaults(schedule: "every hour")

if class_name.present?
options[:class] = "SolidQueue::RecurringTaskTest::#{class_name}"
end

SolidQueue::RecurringTask.from_configuration("task-id", **options)
end
end
2 changes: 1 addition & 1 deletion test/unit/scheduler_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class SchedulerTest < ActiveSupport::TestCase
scheduler.stop
end

test "run more than one instance of the dispatcher with recurring tasks" do
test "run more than one instance of the scheduler with recurring tasks" do
recurring_tasks = { example_task: { class: "AddToBufferJob", schedule: "every second", args: 42 } }
schedulers = 2.times.collect do
SolidQueue::Scheduler.new(recurring_tasks: recurring_tasks)
Expand Down

0 comments on commit 90f63c8

Please sign in to comment.