Module: Servus::Extensions::Async::Call

Defined in:
lib/servus/extensions/async/call.rb

Overview

Provides asynchronous service execution via ActiveJob.

This module extends Base with the #call_async method and, for every service, generates a named ActiveJob subclass so background runners (Sidekiq, GoodJob, delayed_job, …) display a meaningful per-service job name instead of one generic class for every invocation.

For Treasury::TransferGold::Service the generated job is Treasury::TransferGold::ServiceJob — a sibling constant in the service's parent namespace, subclassing Job.

See Also:

Instance Method Summary collapse

Instance Method Details

#async(queue: nil, priority: nil) { ... } ⇒ Class<Servus::Extensions::Async::Job>

Configures the service's named job class (#servus_job_class), exposing the underlying ActiveJob mechanics on a per-service basis.

Provides keyword shortcuts for the two most common options (+queue+ and priority) and an optional block, evaluated in the job class's context, for the full ActiveJob surface (+retry_on+, discard_on, callbacks, etc.).

Settings declared here are class-level defaults for the job. Options passed inline to #call_async (e.g. queue:, wait:) are layered on top of them per enqueue via ActiveJob::Base.set, so inline options win.

Examples:

Queue and priority

class Payments::Charge::Service < Servus::Base
  async queue: :critical, priority: 10
end

Full ActiveJob configuration via a block

class Payments::Charge::Service < Servus::Base
  async do
    retry_on Net::OpenTimeout, wait: 5.seconds, attempts: 3
    discard_on ActiveJob::DeserializationError
  end
end

Parameters:

  • queue (Symbol, String, nil) (defaults to: nil)

    the queue to route the job to (+queue_as+)

  • priority (Integer, nil) (defaults to: nil)

    the job priority (adapter-dependent)

Yields:

  • evaluated in the job class context for full ActiveJob configuration

Returns:

See Also:



119
120
121
122
123
124
125
# File 'lib/servus/extensions/async/call.rb', line 119

def async(queue: nil, priority: nil, &block)
  job = servus_job_class
  job.queue_as(queue)     if queue
  job.priority = priority if priority
  job.class_eval(&block)  if block
  job
end

#call_async(**args) ⇒ void

Note:

Only available when ActiveJob is loaded (typically in Rails applications)

This method returns an undefined value.

Enqueues the service for asynchronous execution via ActiveJob.

This method schedules the service to run in a background job, supporting all standard ActiveJob options for scheduling, queue routing, and priority.

Service arguments are passed as keyword arguments alongside job configuration. Job-specific options are extracted and the remaining arguments are passed to the service's initialize method.

The service's own named job class (#servus_job_class) is enqueued — the class itself identifies the service, so only the service arguments are serialized as the job payload.

Examples:

Basic async execution

Services::SendEmail::Service.call_async(
  user_id: 123,
  template: :welcome
)

With delay

Services::SendReminder::Service.call_async(
  wait: 1.day,
  user_id: 123
)

With queue and priority

Services::ProcessPayment::Service.call_async(
  queue: :critical,
  priority: 10,
  order_id: 456
)

With custom job options

Services::GenerateReport::Service.call_async(
  wait_until: Date.tomorrow.beginning_of_day,
  job_options: { tags: ['reports', 'daily'] },
  report_type: :sales
)

Parameters:

  • args (Hash)

    combined service arguments and job configuration options

Options Hash (**args):

  • :wait (ActiveSupport::Duration)

    delay before execution (e.g., 5.minutes)

  • :wait_until (Time)

    specific time to execute (e.g., 2.hours.from_now)

  • :queue (Symbol, String)

    queue name (e.g., :low_priority)

  • :priority (Integer)

    job priority (adapter-dependent)

  • :job_options (Hash)

    additional ActiveJob options

Raises:

See Also:



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/servus/extensions/async/call.rb', line 72

def call_async(**args)
  # Extract ActiveJob configuration options
  job_options = args.slice(:wait, :wait_until, :queue, :priority)
  job_options.merge!(args.delete(:job_options) || {}) # merge custom job options
  job_options.compact!

  # Remove special keys that shouldn't be passed to the service
  args.except!(:wait, :wait_until, :queue, :priority, :job_options)

  # The named job class identifies the service — only args are serialized.
  job = job_options.any? ? servus_job_class.set(**job_options) : servus_job_class
  job.perform_later(**args)
rescue StandardError => e
  raise Errors::JobEnqueueError, "Failed to enqueue async job for #{self}: #{e.message}"
end

#inherited(subclass) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Eagerly generates the named job class when a service subclass is defined.

Anonymous subclasses (+Class.new+) have no name yet, so their job is generated lazily by #servus_job_class instead.

Parameters:

  • subclass (Class<Servus::Base>)

    the newly defined service



149
150
151
152
# File 'lib/servus/extensions/async/call.rb', line 149

def inherited(subclass)
  super
  subclass.servus_job_class if subclass.name
end

#servus_job_classClass<Servus::Extensions::Async::Job>

Returns the named ActiveJob class for this service, generating and memoizing it on first access.

For named services the class is normally created eagerly by the #inherited hook when the service is defined; this accessor covers anonymous services (e.g. Class.new(Servus::Base) in tests) whose name only becomes available later, and guarantees a class exists whenever one is needed.

Returns:

See Also:



137
138
139
# File 'lib/servus/extensions/async/call.rb', line 137

def servus_job_class
  @servus_job_class ||= build_servus_job_class
end