activejob-temporal
Temporal-powered adapter for Rails ActiveJob.
This gem is under active development. Expect breaking changes until v1.0.0.
What It Does
activejob-temporal runs Rails ActiveJob work through Temporal workflows and activities. It is intended for jobs where durable execution, crash recovery, long-running retries, cancellation visibility, and Temporal UI history matter more than queue simplicity.
Use a traditional ActiveJob backend when the work is short, simple, and does not justify operating Temporal.
Requirements
- Ruby >= 4.0
- Rails 8.1 through ActiveJob 8.1
- Temporal cluster, either self-hosted or Temporal Cloud
CI validates Ruby 4.0.0 and the latest Ruby 4.0 patch against Rails 8.1. Temporal Ruby SDK 1.4.x compatibility is contract-tested against 1.4.0 and the latest 1.4.x release.
Install
gem "activejob-temporal"
bundle install
Then configure ActiveJob:
# config/application.rb or config/environments/production.rb
config.active_job.queue_adapter = :temporal
Quick Start
Create a Temporal initializer:
# config/initializers/activejob_temporal.rb
ActiveJob::Temporal.configure do |config|
config.target = ENV.fetch("ACTIVEJOB_TEMPORAL_TARGET", "127.0.0.1:7233")
config.namespace = ENV.fetch("ACTIVEJOB_TEMPORAL_NAMESPACE", "default")
config.task_queue_prefix = ENV.fetch("ACTIVEJOB_TEMPORAL_TASK_QUEUE_PREFIX", nil)
config.default_activity_timeout = 15.minutes
config.default_retry_initial_interval = 30.seconds
config.default_retry_backoff = 2.0
config.default_retry_max_attempts = 1
end
Register the built-in search attributes once per Temporal cluster:
tctl admin cluster add-search-attributes \
--name ajClass --type Keyword \
--name ajQueue --type Keyword \
--name ajJobId --type Keyword \
--name ajEnqueuedAt --type Datetime \
--name ajTenantId --type Int \
--name ajTags --type KeywordList
Temporal Cloud deployments may use the temporal CLI instead of tctl; keep the same attribute names and types.
Define and enqueue normal ActiveJob jobs:
class SendInvoiceJob < ApplicationJob
queue_as :billing
retry_on SomeTransientError, wait: 60.seconds, attempts: 5
discard_on SomeFatalError
def perform(invoice_id)
invoice = Invoice.find(invoice_id)
InvoiceMailer.invoice_ready(invoice).deliver_now
end
end
SendInvoiceJob.perform_later(invoice.id)
SendInvoiceJob.set(wait: 5.minutes).perform_later(invoice.id)
SendInvoiceJob.set(tags: [:urgent, :customer_123]).perform_later(invoice.id)
Start a worker for every task queue you use:
ACTIVEJOB_TEMPORAL_TARGET=localhost:7233 \
ACTIVEJOB_TEMPORAL_NAMESPACE=default \
ACTIVEJOB_TEMPORAL_TASK_QUEUE=billing \
bundle exec temporal-worker
Open Temporal UI and look for workflows named ajwf:SendInvoiceJob:<job_id>.
Common Capabilities
| Need | API | Detailed guide |
|---|---|---|
| Delay a single job | MyJob.set(wait: 5.minutes).perform_later(...) |
Usage Patterns |
| Register recurring cron work | schedule cron: "0 2 * * *" and create_temporal_schedule |
Recurring Jobs |
| Enqueue only when work exists | perform_later_if(condition, *args) |
Usage Patterns |
| Enqueue many prepared jobs | ActiveJob::Temporal.enqueue_batch(jobs) |
Usage Patterns |
| Run sequential jobs in one workflow | set(chain: [NextJob]) |
Usage Patterns |
| Start child ActiveJob workflows | set(child_workflows: [ChildJob]) |
Usage Patterns |
| Call external Temporal activities or workflows | ActiveJob::Temporal.activity(...), ActiveJob::Temporal.workflow(...) |
Usage Patterns |
| Wait for separately enqueued jobs | set(depends_on: parent_job) |
Usage Patterns |
| Map ActiveJob retries to Temporal | retry_on, discard_on |
Retry Policy Guide |
| Park exhausted failures | config.dead_letter_queue = "failed_jobs" |
Configuration Reference |
| Tune activity timeouts | temporal_options start_to_close_timeout: ... |
Usage Patterns |
| Add throughput limits | rate_limit 100, per: :second |
Configuration Reference |
| Cancel or inspect jobs | cancel, cancel_all, status, running? |
Usage Patterns |
| Pause, resume, query, or update workflow state | signal, query, update |
Usage Patterns |
| Add runtime middleware | config.add_middleware MiddlewareClass |
Middleware |
| Expose Prometheus metrics | config.observability.use :prometheus |
Metrics Guide |
| Encrypt job payloads | encrypt_payload = true |
Configuration Reference |
| Store large payloads externally | payload_storage_adapter = MyPayloadStorage.new |
Configuration Reference |
Baseline behavior also includes transaction-aware enqueueing through ActiveJob, GlobalID-compatible argument serialization, structured JSON logs, searchable set(tags:) metadata, and JSON payloads with opt-in MessagePack or Marshal envelopes.
Configuration
The full configuration surface lives in Configuration Reference. The machine-readable schema is docs/config_schema.yaml.
The most common settings are:
ActiveJob::Temporal.configure do |config|
config.target = "temporal.example.com:7233"
config.namespace = "production"
config.task_queue_prefix = "rails-"
config.priority_task_queues = { 10 => "high_priority", 90 => "low_priority" }
config.default_activity_timeout = 30.seconds
config.default_retry_initial_interval = 10.seconds
config.default_retry_backoff = 1.5
config.default_retry_max_attempts = 5
end
Workers can also read environment variables such as ACTIVEJOB_TEMPORAL_TARGET, ACTIVEJOB_TEMPORAL_NAMESPACE, ACTIVEJOB_TEMPORAL_TASK_QUEUE, ACTIVEJOB_TEMPORAL_MAX_CONCURRENT_ACTIVITIES, ACTIVEJOB_TEMPORAL_METRICS_PORT, and TLS certificate settings. See Worker Setup for the worker-focused list.
Documentation
Start with docs/README.md for the complete documentation map.
High-use guides:
- Usage Patterns
- Configuration Reference
- Worker Setup
- Troubleshooting
- Performance Tuning
- Comparison Guide
- Security
See examples/basic_rails_app for a Docker Compose Rails app with Temporal, Temporal UI, search attribute setup, workers, seeded GlobalID records, and tests.
Contributing
Install dependencies and run the focused local checks:
rvm 4.0.3 do bundle install
rvm 4.0.3 do bundle exec rake spec:unit
rvm 4.0.3 do bundle exec rubocop
rvm 4.0.3 do bundle exec rake build
For changes near configured mutation subjects, also run:
rvm 4.0.3 do bundle exec rake mutation
Keep docs updated when behavior changes. The scoped mutation task runs on Ruby 4. Mutant 0.16 may warn about an older parser dependency; keep using the Ruby 4 toolchain and treat parser failures on new Ruby syntax as a mutation tooling limitation.
Bug reports and feature requests belong in GitHub issues.
License
MIT. See LICENSE.
Versioning
This project follows Semantic Versioning. See CHANGELOG for release history.
Current development version: 0.1.0. Release commits must be tagged before publishing to RubyGems.