Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c52d376
Add all Ruby SDK reference files (11 files, ~2100 lines)
donald-pinckney Mar 16, 2026
0dcac1e
Fix alignment issues in Ruby reference files
donald-pinckney Mar 16, 2026
dfcdc82
Fix correctness issues in Ruby reference files
donald-pinckney Mar 16, 2026
ef5d989
Add Ruby to all language references in SKILL.md and core files
donald-pinckney Mar 16, 2026
2e936b2
Merge branch 'main' into add-ruby-support
donald-pinckney May 26, 2026
f9c81ee
Apply suggestions from code review
donald-pinckney May 26, 2026
c3181b8
Apply suggestions from code review
donald-pinckney May 26, 2026
5312401
Apply suggestion from @chris-olszewski
donald-pinckney May 26, 2026
734a347
copy over sample code
donald-pinckney May 26, 2026
ce0d434
Remove useless section, mention Mutex
donald-pinckney May 27, 2026
d051bf3
cleanup mutex mentions
donald-pinckney May 27, 2026
b22eb20
Clean up transitive NDE section
donald-pinckney May 28, 2026
b0630d7
Menial changes to align to python structure
donald-pinckney May 28, 2026
afc00ef
Add Workflow Init section to Ruby advanced-features
donald-pinckney May 28, 2026
d3b0c3c
Document graceful_shutdown_period in Ruby Worker Tuning
donald-pinckney May 28, 2026
76a4f02
Propagate cancellation in Ruby activity-error handling
donald-pinckney May 28, 2026
41c6f0e
Align Ruby Workflow Failure section to Python
donald-pinckney May 28, 2026
adcb0d4
Add logger configuration to Ruby observability
donald-pinckney May 28, 2026
1269e03
Make Ruby Saga compensations cancellation-proof
donald-pinckney May 28, 2026
b08606a
Document patched() memoization caveat in Ruby versioning
donald-pinckney May 28, 2026
d0d5f4b
Add default versioning behavior to Ruby worker versioning
donald-pinckney May 28, 2026
649d296
Fix worker versioning config API names in Ruby docs
donald-pinckney May 28, 2026
dc1994b
Fix worker concurrency config in Ruby Worker Tuning
donald-pinckney May 28, 2026
5fe7a95
Align Ruby Workflow Init title with Python
donald-pinckney May 28, 2026
2c4004a
Structure Ruby Metrics to match Python
donald-pinckney May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add all Ruby SDK reference files (11 files, ~2100 lines)
Created complete Ruby reference documentation covering:
- ruby.md: Overview, quick start, key concepts, file organization
- patterns.md: Signals, queries, updates, child workflows, saga, cancellation, etc.
- determinism.md: Illegal call tracing, safe alternatives table
- determinism-protection.md: TracePoint, durable fiber scheduler, customization
- versioning.md: Patching API, type versioning, worker versioning
- testing.md: WorkflowEnvironment, mocking, replay, activity testing
- error-handling.md: ApplicationError, retries, timeouts, workflow failure
- data-handling.md: Data converter, ActiveModel, hints, search attributes
- observability.md: Logging, metrics, best practices
- gotchas.md: Common mistakes, illegal call tracing issues
- advanced-features.md: Schedules, async completion, worker tuning, Rails

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
  • Loading branch information
donald-pinckney and claude committed Mar 16, 2026
commit c52d376dc28429e211e6dcab3924dc1b7bf58256
191 changes: 191 additions & 0 deletions references/ruby/advanced-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# Ruby SDK Advanced Features

## Schedules

Create recurring workflow executions with `Temporalio::Client::Schedule`.

```ruby
require 'temporalio/client'

# Create a schedule
schedule_id = 'daily-report'
client.create_schedule(
schedule_id,
Temporalio::Client::Schedule.new(
action: Temporalio::Client::Schedule::Action::StartWorkflow.new(
DailyReportWorkflow,
id: 'daily-report',
task_queue: 'reports'
),
spec: Temporalio::Client::Schedule::Spec.new(
intervals: [
Temporalio::Client::Schedule::Spec::Interval.new(every: 86_400) # 1 day in seconds
]
)
)
)

# Manage schedules
handle = client.schedule_handle(schedule_id)
handle.pause(note: 'Maintenance window')
handle.unpause
handle.trigger
handle.delete
```

## Async Activity Completion

For activities that complete asynchronously (e.g., human tasks, external callbacks).

```ruby
class RequestApproval < Temporalio::Activity::Definition
def execute(request_id)
# Get task token for async completion
task_token = Temporalio::Activity::Context.current.info.task_token

# Store task token for later completion (e.g., in database)
store_task_token(request_id, task_token)

# Signal that this activity completes asynchronously
Temporalio::Activity::Context.current.raise_complete_async
end
end

# Later, complete the activity from another process
client = Temporalio::Client.connect('localhost:7233')
task_token = get_task_token(request_id)
handle = client.async_activity_handle(task_token: task_token)

if approved
handle.complete('approved')
else
handle.fail(Temporalio::Error::ApplicationError.new('Rejected'))
end
```

If you configure a `heartbeat_timeout:` on the activity, the external completer is responsible for sending heartbeats via the async handle. If you do NOT set a `heartbeat_timeout`, no heartbeats are required.

## Worker Tuning

Configure worker performance settings.

```ruby
worker = Temporalio::Worker.new(
client: client,
task_queue: 'my-queue',
workflows: [MyWorkflow],
activities: [MyActivity],
max_concurrent_workflow_tasks: 100,
max_concurrent_activities: 100
)
worker.run
```

## Workflow Failure Exception Types

Control which exceptions cause workflow failure vs workflow task failure (which Temporal retries automatically).

### Per-Workflow Configuration

```ruby
class MyWorkflow < Temporalio::Workflow::Definition
# Class method approach
def self.workflow_failure_exception_type
MyCustomError
end

def execute
raise MyCustomError, 'This fails the workflow, not just the task'
end
end
```

### Worker-Level Configuration

```ruby
Temporalio::Worker.new(
client: client,
task_queue: 'my-queue',
workflows: [MyWorkflow],
workflow_failure_exception_types: [MyCustomError]
)
```

**Tips:**
- Set to `[Exception]` in tests so any unhandled exception fails the workflow immediately rather than retrying the workflow task forever. Surfaces bugs faster.
- Include `Temporalio::Workflow::NondeterminismError` to fail the workflow instead of leaving it in a retrying state on non-determinism errors.

## Activity Concurrency and Executors

Ruby uses `Temporalio::Worker::ActivityExecutor::ThreadPool` by default. Activities run in a thread pool.

```ruby
# Default: activities run in thread pool
worker = Temporalio::Worker.new(
client: client,
task_queue: 'my-queue',
workflows: [MyWorkflow],
activities: [MyActivity],
activity_executors: {
default: Temporalio::Worker::ActivityExecutor::ThreadPool.new(max_threads: 20)
}
)
```

Fiber-based execution is also possible for IO-bound activities using Ruby's fiber scheduler.

## Rails Integration

### ActiveRecord Considerations

Never pass ActiveRecord models directly to Temporal workflows or activities. Serialize to plain data structures.

```ruby
# BAD - Passing AR model
client.execute_workflow(
ProcessOrderWorkflow,
Order.find(42), # Don't pass AR objects!
id: 'order-42',
task_queue: 'orders'
)

# GOOD - Pass serializable data
client.execute_workflow(
ProcessOrderWorkflow,
{ id: 42, total: order.total, status: order.status },
id: 'order-42',
task_queue: 'orders'
)
```

### Zeitwerk and Autoloading

Rails uses Zeitwerk for autoloading. Workflow and activity classes must be loadable by Zeitwerk or explicitly required.
Comment thread
donald-pinckney marked this conversation as resolved.
Outdated

```ruby
Comment thread
donald-pinckney marked this conversation as resolved.
# In config/initializers/temporal.rb or similar
# Eager load Temporal classes so they're available to the worker
Rails.application.config.after_initialize do
Dir[Rails.root.join('app/workflows/**/*.rb')].each { |f| require f }
Dir[Rails.root.join('app/activities/**/*.rb')].each { |f| require f }
end
Comment thread
donald-pinckney marked this conversation as resolved.
Outdated
```

### Forking Considerations

If using a forking server (Puma, Unicorn), workers must be created **after** the fork. Connections established before fork are not safe to share across processes.

```ruby
# In Puma config (puma.rb)
on_worker_boot do
Comment thread
donald-pinckney marked this conversation as resolved.
Outdated
# Create Temporal client and worker AFTER fork
client = Temporalio::Client.connect('localhost:7233')
worker = Temporalio::Worker.new(
client: client,
task_queue: 'my-queue',
workflows: [MyWorkflow],
activities: [MyActivity]
)
Thread.new { worker.run }
end
```
192 changes: 192 additions & 0 deletions references/ruby/data-handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Ruby SDK Data Handling

## Overview

Data converters serialize and deserialize workflow/activity inputs and outputs. The `Temporalio::Converters` module provides the conversion pipeline.

## Default Data Converter

The default converter handles types in this order:

1. `nil` - null payload
2. Bytes - `String` with `ASCII_8BIT` encoding
3. Protobuf - objects implementing `Google::Protobuf::MessageExts`
4. JSON - everything else, via Ruby's `JSON` module

Note: symbol keys become strings on deserialization. `create_additions: true` by default.

## ActiveModel Integration

Use the `ActiveModelJSONSupport` mixin for custom model classes:

```ruby
module ActiveModelJSONSupport
def as_json(_options = {})
{ JSON.create_id => self.class.name }.merge(instance_variables.each_with_object({}) do |var, hash|
hash[var.to_s.delete('@')] = instance_variable_get(var)
end)
end

def to_json(*args)
as_json.to_json(*args)
end

def self.included(base)
base.define_method(:json_create) do |hash|
obj = base.new
hash.each do |key, value|
next if key == JSON.create_id
obj.instance_variable_set("@#{key}", value)
end
obj
end
end
end

class MyModel
include ActiveModelJSONSupport
attr_accessor :name, :value

def initialize(name: nil, value: nil)
@name = name
@value = value
end
end
```

## Custom Data Converter

```ruby
converter = Temporalio::Converters::DataConverter.new(
payload_converter: my_payload_converter,
payload_codec: my_payload_codec,
failure_converter: my_failure_converter
)

client = Temporalio::Client.connect(
'localhost:7233',
'default',
data_converter: converter
)
```

## Converter Hints

Ruby-specific feature for guiding deserialization to the correct type:

```ruby
class MyWorkflow
workflow_arg_hint MyClass
workflow_result_hint MyClass

workflow_update :my_update, arg_hints: [MyClass]

def execute(input)
# input is deserialized as MyClass
end
end
```

Custom converters use these hints to know the target deserialization type.

## Payload Encryption

Implement a `PayloadCodec` with `encode` and `decode`:

```ruby
class EncryptionCodec
def encode(payloads)
payloads.map { |p| encrypt(p) }
end

def decode(payloads)
payloads.map { |p| decrypt(p) }
end

private

def encrypt(payload)
# encryption logic
end

def decrypt(payload)
# decryption logic
end
end

converter = Temporalio::Converters::DataConverter.new(
payload_codec: EncryptionCodec.new
)
```

## Search Attributes

Define a search attribute key:

```ruby
key = Temporalio::SearchAttributes::Key.new(
'CustomerId',
Temporalio::SearchAttributes::IndexedValueType::KEYWORD
)
```

Set at workflow start:

```ruby
client.start_workflow(
MyWorkflow,
'arg',
id: 'wf-1',
task_queue: 'my-queue',
search_attributes: Temporalio::SearchAttributes.new({ key => 'customer-123' })
)
```

Upsert from a workflow:

```ruby
Temporalio::Workflow.upsert_search_attributes({ key => 'new-value' })
```

Query workflows:

```ruby
client.list_workflows(filter: "CustomerId = 'customer-123'")
```

## Workflow Memo

Set at workflow start:

```ruby
client.start_workflow(
MyWorkflow,
'arg',
id: 'wf-1',
task_queue: 'my-queue',
memo: { 'region' => 'us-east', 'priority' => 'high' }
)
```

Read from within a workflow:

```ruby
region = Temporalio::Workflow.memo['region']
```

## Deterministic APIs for Values

Use these instead of standard Ruby equivalents inside workflows:

```ruby
Temporalio::Workflow.uuid # deterministic UUID
Temporalio::Workflow.random # deterministic random number
Temporalio::Workflow.now # deterministic current time
```

## Best Practices

- Use dedicated model classes for Temporal data, not ActiveRecord models.
- Keep payloads small; store large data externally and pass references.
- Encrypt sensitive data with a `PayloadCodec`.
- Use `Temporalio::Workflow.uuid`, `.random`, and `.now` inside workflows for determinism.
Loading