Skip to content

ruby sdk dev guide #3560

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ab4b4dc
base
jsundai Apr 30, 2025
407c802
CI: Automatic .md and .mdx formatting
github-actions[bot] Apr 30, 2025
ed1f4a1
getting started new pg
jsundai May 4, 2025
1efaf54
adding
jsundai May 4, 2025
af13969
adding banner and more structure
jsundai May 4, 2025
47b3ffb
brainstorm
jsundai May 4, 2025
7d65188
Merge branch 'ruby-dev-guide' of github.com:temporalio/documentation …
jsundai May 4, 2025
8fadb32
CI: Automatic .md and .mdx formatting
github-actions[bot] May 4, 2025
7a0101c
edits
jsundai May 4, 2025
067aec6
getting started edits
jsundai May 14, 2025
a6ce286
Merge branch 'ruby-dev-guide' of github.com:temporalio/documentation …
jsundai May 14, 2025
e11b9b3
CI: Automatic .md and .mdx formatting
github-actions[bot] May 14, 2025
aae34a2
edits
jsundai May 26, 2025
0814e91
Merge branch 'ruby-dev-guide' of github.com:temporalio/documentation …
jsundai May 26, 2025
2560071
CI: Automatic .md and .mdx formatting
github-actions[bot] May 26, 2025
d72d1ca
aligning
jsundai May 26, 2025
ae57b68
more edits
jsundai May 26, 2025
006fa98
Merge branch 'ruby-dev-guide' of github.com:temporalio/documentation …
jsundai May 27, 2025
686cac6
CI: Automatic .md and .mdx formatting
github-actions[bot] May 27, 2025
93d181c
resolving links
jsundai May 27, 2025
74f5b21
Merge branch 'ruby-dev-guide' of github.com:temporalio/documentation …
jsundai May 27, 2025
2ad4424
filling in set up your local environment
jsundai May 28, 2025
d12139c
CI: Automatic .md and .mdx formatting
github-actions[bot] May 28, 2025
5cea6be
new next steps cta
jsundai May 28, 2025
f1e70c0
polish
jsundai May 28, 2025
94b1654
CI: Automatic .md and .mdx formatting
github-actions[bot] May 28, 2025
6a04089
resolving links
jsundai May 28, 2025
43a2d61
Merge branch 'ruby-dev-guide' of github.com:temporalio/documentation …
jsundai May 28, 2025
73f961a
Merge branch 'main' into ruby-dev-guide
jsundai May 28, 2025
0bb36ea
CI: Automatic .md and .mdx formatting
github-actions[bot] May 28, 2025
55fd8ff
capitalization
jsundai May 28, 2025
32685e2
Merge branch 'ruby-dev-guide' of github.com:temporalio/documentation …
jsundai May 28, 2025
c50f39a
rename file name
jsundai May 28, 2025
8f682ce
edits
jsundai May 28, 2025
5da69a8
Ruby implementation details
cretz Jun 4, 2025
e219333
CI: Automatic .md and .mdx formatting
github-actions[bot] Jun 6, 2025
f61efa6
Merge branch 'main' into ruby-dev-guide
jsundai Jun 9, 2025
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
61 changes: 61 additions & 0 deletions docs/develop/ruby/asynchronous-activity.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
id: asynchronous-activity
title: Asynchronous Activity completion - Ruby SDK
sidebar_label: Asynchronous Activity completion
description: Learn how to asynchronously complete an Activity in Temporal using the Ruby SDK. Follow simple steps to allow an Activity Function to return without the Activity Execution completing.
toc_max_heading_level: 4
keywords:
- asynchronous activity completion
- temporal
- activity function
- task token
- workflow id
- activity id
- temporal client
- external system completion
- activity execution
- complete
- fail
- cancel
- get_async_activity_handle
- ruby examples
tags:
- Activities
- Ruby SDK
- Temporal SDKs
---

## How to asynchronously complete an Activity {#asynchronous-activity-completion}

This page describes how to asynchronously complete an Activity.

[Asynchronous Activity Completion](/activity-execution#asynchronous-activity-completion) enables the Activity Function to return without the Activity Execution completing.

There are three steps to follow:

1. The Activity provides the external system with identifying information needed to complete the Activity Execution.
Identifying information can be a [Task Token](/activity-execution#task-token), or a combination of Namespace, Workflow Id, and Activity Id.
2. The Activity Function completes in a way that identifies it as waiting to be completed by an external system.
3. The Temporal Client is used to Heartbeat and complete the Activity.

To mark an Activity as completing asynchronously, do the following inside the Activity.

```ruby
# Capture token for later completion
captured_token = Temporalio::Activity::Context.current.info.task_token

# Raise a special exception that says an activity will be completed somewhere else
raise Temporalio::Activity::CompleteAsyncError
```

To update an Activity outside the Activity, use the [async_activity_handle](https://ruby.temporal.io/Temporalio/Client.html#async_activity_handle-instance_method) method on the client to get the handle of the Activity.

```ruby
handle = my_client.async_activity_handle(captured_token)
```

Then, on that handle, you can use `heartbeat`, `complete`, `fail`, or `report_cancellation` methods to update the Activity.

```ruby
handle.complete('completion value')
```
67 changes: 67 additions & 0 deletions docs/develop/ruby/child-workflows.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
id: child-workflows
title: Child Workflows - Ruby SDK
sidebar_label: Child Workflows
description: Learn how to start a Child Workflow Execution and set a Parent Close Policy using Temporal Ruby SDK.
keywords:
- start child workflow execution
- temporal ruby sdk
- parent close policy
- child workflow
- child workflow options
- signal child workflow
- ruby examples
tags:
- Workflows
- Ruby SDK
- Temporal SDKs
- Child Workflows
---

This page shows how to do the following:

- [Start a Child Workflow Execution](#child-workflows) using the Ruby SDK
- [Set a Parent Close Policy](#parent-close-policy) using the Ruby SDK

## Start a Child Workflow Execution {#child-workflows}

**How to start a Child Workflow Execution using the Temporal Ruby SDK**

A [Child Workflow Execution](/child-workflows) is a Workflow Execution that is scheduled from within another Workflow using a Child Workflow API.

When using a Child Workflow API, Child Workflow related Events ([StartChildWorkflowExecutionInitiated](/references/events#startchildworkflowexecutioninitiated), [ChildWorkflowExecutionStarted](/references/events#childworkflowexecutionstarted), [ChildWorkflowExecutionCompleted](/references/events#childworkflowexecutioncompleted), etc...) are logged in the Workflow Execution Event History.

Always block progress until the [ChildWorkflowExecutionStarted](/references/events#childworkflowexecutionstarted) Event is logged to the Event History to ensure the Child Workflow Execution has started.
After that, Child Workflow Executions may be abandoned using the _Abandon_ [Parent Close Policy](/parent-close-policy) set in the Child Workflow Options.

To spawn a Child Workflow Execution in Ruby, use the `execute_child_workflow` method which starts the Child Workflow and waits for completion or
use the `start_child_workflow` method to start a Child Workflow and return its handle.
This is useful if you want to do something after it has only started, or to get the Workflow/Run ID, or to be able to signal it while running.

:::note

`execute_child_workflow` is a helper method for `start_child_workflow(...).result`.

:::

```ruby
Temporalio::Workflow.execute_child_workflow(MyChildWorkflow, 'my-workflow-arg')
```

## Set a Parent Close Policy {#parent-close-policy}

**How to set a Parent Close Policy using the Temporal Ruby SDK**

A [Parent Close Policy](/parent-close-policy) determines what happens to a Child Workflow Execution if its Parent changes to a Closed status (Completed, Failed, or Timed Out).

The default Parent Close Policy option is set to terminate the Child Workflow Execution.

Set the `parent_close_policy` parameter for `execute_child_workflow` or `start_child_workflow` to specify the behavior of the Child Workflow when the Parent Workflow closes.

```ruby
Temporalio::Workflow.execute_child_workflow(
MyChildWorkflow,
'my-workflow-arg',
parent_close_policy: Temporalio::Workflow::ParentClosePolicy::ABANDON
)
```
52 changes: 52 additions & 0 deletions docs/develop/ruby/continue-as-new.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
id: continue-as-new
title: Continue-As-New - Ruby SDK
sidebar_label: Continue-As-New
description: Learn how to use Continue-As-New with the Temporal Ruby SDK to manage Workflow Event Histories, ensuring optimal performance by starting new Executions seamlessly.
keywords:
- sdk
- ruby
- continue-as-new
tags:
- Workflows
- Ruby SDK
- Temporal SDKs
- continue-as-new
---

This page describes how to Continue-As-New using the Temporal Ruby SDK.

[Continue-As-New](/workflow-execution/continue-as-new) enables a Workflow Execution to close successfully and create a new Workflow Execution in a single atomic operation if the number of Events in the Event History is becoming too large.
The Workflow Execution spawned from the use of Continue-As-New has the same Workflow Id, a new Run Id, and a fresh Event History and is passed all the appropriate parameters.

:::caution

As a precautionary measure, the Workflow Execution's Event History is limited to [51,200 Events](https://github.com/temporalio/temporal/blob/e3496b1c51bfaaae8142b78e4032cc791de8a76f/service/history/configs/config.go#L382) or [50 MB](https://github.com/temporalio/temporal/blob/e3496b1c51bfaaae8142b78e4032cc791de8a76f/service/history/configs/config.go#L380) and will warn you after 10,240 Events or 10 MB.

:::

To prevent a Workflow Execution Event History from exceeding this limit and failing, use Continue-As-New to start a new Workflow Execution with a fresh Event History.

A very large Event History can adversely affect the performance of a Workflow Execution.
For example, in the case of a Workflow Worker failure, the full Event History must be pulled from the Temporal Service and given to another Worker via a Workflow Task.
If the Event history is very large, it may take some time to load it.

The Continue-As-New feature enables developers to complete the current Workflow Execution and start a new one atomically.

The new Workflow Execution has the same Workflow Id, but a different Run Id, and has its own Event History.

## Continue-As-New in Ruby {#continue-as-new}

To Continue-As-New in Ruby, raise a `Temporalio::Workflow::ContinueAsNewError` from inside your Workflow, which will stop the Workflow immediately and Continue-As-New.

```ruby
raise Temporalio::Workflow::ContinueAsNewError.new('my-new-arg')
```

:::warning Using Continue-as-New and Updates

- Temporal _does not_ support Continue-as-New functionality within Update handlers.
- Complete all handlers _before_ using Continue-as-New.
- Use Continue-as-New from your main Workflow Definition method, just as you would complete or fail a Workflow Execution.

:::
177 changes: 177 additions & 0 deletions docs/develop/ruby/converters-and-encryption.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
---
id: converters-and-encryption
title: Converters and encryption - Ruby SDK
sidebar_label: Converters and encryption
description: Learn how to use a custom Payload Codec and Converter in the Ruby SDK to modify Temporal Data Conversion behavior, including examples for encryption and formatting.
keywords:
- sdk
- ruby
- data encryption
- codec
- converter
tags:
- Ruby SDK
- Temporal SDKs
- Security
- Codec Server
- Data Converters
- Encryption
---

import { CaptionedImage } from '@site/src/components';

Temporal's security model is designed around client-side encryption of Payloads.
A client may encrypt Payloads before sending them to the server, and decrypt them after receiving them from the server.
This provides a high degree of confidentiality because the Temporal Server itself has absolutely no knowledge of the actual data.
It also gives implementers more power and more freedom regarding which client is able to read which data -- they can control access with keys, algorithms, or other security measures.

A Temporal developer adds client-side encryption of Payloads by providing a Custom Payload Codec to its Client.
Depending on business needs, a complete implementation of Payload Encryption may involve selecting appropriate encryption algorithms, managing encryption keys, restricting a subset of their users from viewing payload output, or a combination of these.

The server itself never adds encryption over Payloads.
Therefore, unless client-side encryption is implemented, Payload data will be persisted in non-encrypted form to the data store, and any Client that can make requests to a Temporal namespace (including the Temporal UI and CLI) will be able to read Payloads contained in Workflows.
When working with sensitive data, you should always implement Payload encryption.

## Custom Payload Codec {#custom-payload-codec}

**How to use a custom Payload Codec using the Ruby SDK**

Custom Data Converters can change the default Temporal Data Conversion behavior by adding hooks, sending payloads to external storage, or performing different encoding steps.
If you only need to change the encoding performed on your payloads -- by adding compression or encryption -- you can override the default Data Converter to use a new `PayloadCodec`.

The Payload Codec needs to extend `Temporalio::Converters::PayloadCodec` and implement `encode` and `decode` methods.
These should convert the given payloads as needed into new payloads, using the `"encoding"` metadata field.
Do not mutate the existing payloads.
Here is an example of an encryption codec that just uses base64 in each direction:

```ruby
class Base64Codec < Temporalio::Converters::PayloadCodec
def encode(payloads)
payloads.map do |p|
Temporalio::Api::Common::V1::Payload.new(
# Set our specific encoding. We may also want to add a key ID in here for use by
# the decode side
metadata: { 'encoding' => 'binary/my-payload-encoding' },
data: Base64.strict_encode64(p.to_proto)
)
end
end

def decode(payloads)
payloads.map do |p|
# Ignore if it doesn't have our expected encoding
next p unless p.metadata['encoding'] == 'binary/my-payload-encoding'

Temporalio::Api::Common::V1::Payload.decode(
Base64.strict_decode64(p.data)
)
end
end
end
```

**Set Data Converter to use custom Payload Codec**

When creating a client, the default `DataConverter` can be updated with the payload codec like so:

```ruby
my_client = Temporalio::Client.connect(
'localhost:7233',
'my-namespace',
data_converter: Temporalio::Converters::DataConverter.new(payload_codec: Base64Codec.new)
)
```

- Data **encoding** is performed by the client using the converters and codecs provided by Temporal or your custom implementation when passing input to the Temporal Cluster. For example, plain text input is usually serialized into a JSON object, and can then be compressed or encrypted.
- Data **decoding** may be performed by your application logic during your Workflows or Activities as necessary, but decoded Workflow results are never persisted back to the Temporal Cluster.
Instead, they are stored encoded on the Cluster, and you need to provide an additional parameter when using the [temporal workflow show](/cli/workflow#show) command or when browsing the Web UI to view output.

<!-- TODO: For reference, see the [Encryption](https://github.com/temporalio/samples-ruby/tree/main/encryption) sample. -->

### Using a Codec Server

A Codec Server is an HTTP server that uses your custom Codec logic to decode your data remotely.
The Codec Server is independent of the Temporal Cluster and decodes your encrypted payloads through predefined endpoints.
You create, operate, and manage access to your Codec Server in your own environment.
The Temporal CLI and the Web UI in turn provide built-in hooks to call the Codec Server to decode encrypted payloads on demand.
Refer to the [Codec Server](/production-deployment/data-encryption) documentation for information on how to design and deploy a Codec Server.

## Payload conversion

Temporal SDKs provide a default [Payload Converter](/payload-converter) that can be customized to convert a custom data type to [Payload](/dataconversion#payload) and back.

### Conversion sequence {#conversion-sequence}

The order in which your encoding Payload Converters are applied depend on the order given to the Data Converter.
You can set multiple encoding Payload Converters to run your conversions.
When the Data Converter receives a value for conversion, it passes through each Payload Converter in sequence until the converter that handles the data type does the conversion.

Payload Converters can be customized independently of a Payload Codec.
Temporal's Converter architecture looks like this:

<CaptionedImage
src="/img/info/converter-architecture.png"
title="Temporal converter architecture"
/>

### Supported Data Types {#supported-data-types}

Data converters are used to convert raw Temporal payloads to/from actual Ruby types.
A custom data converter can be set via the `data_converter` keyword argument when creating a client. Data converters are a combination of payload converters, payload codecs, and failure converters.
Payload converters convert Ruby values to/from serialized bytes. Payload codecs convert bytes to bytes (e.g. for compression or encryption). Failure converters convert exceptions to/from serialized failures.

Data converters are in the `Temporalio::Converters` module.
The default data converter uses a default payload converter, which supports the following types:

- `nil`
- "bytes" (i.e. `String` with `Encoding::ASCII_8BIT` encoding)
- `Google::Protobuf::MessageExts` instances
- [JSON module](https://docs.ruby-lang.org/en/master/JSON.html) for everything else

This means that normal Ruby objects will use `JSON.generate` when serializing and `JSON.parse` when deserializing (with `create_additions: true` set by default).
So a Ruby object will often appear as a hash when deserialized.
Also, hashes that are passed in with symbol keys end up with string keys when deserialized.
While "JSON Additions" are supported, it is not cross-SDK-language compatible since this is a Ruby-specific construct.

The default payload converter is a collection of "encoding payload converters".
On serialize, each encoding converter will be tried in order until one accepts (default falls through to the JSON one).
The encoding converter sets an `encoding` metadata value which is used to know which converter to use on deserialize.
Custom encoding converters can be created, or even the entire payload converter can be replaced with a different implementation.

**NOTE:** For ActiveRecord, or other general/ORM models that are used for a different purpose, it is not recommended to try to reuse them as Temporal models.
Eventually model purposes diverge and models for a Temporal workflows/activities should be specific to their use for clarity and compatibility reasons.
Also many Ruby ORMs do many lazy things and therefore provide unclear serialization semantics.
Instead, consider having models specific for workflows/activities and translate to/from existing models as needed.
See the next section on how to do this with ActiveModel objects.

#### ActiveModel {#active-model}

By default, ActiveModel objects do not natively support the `JSON` module.
A mixin can be created to add this support for ActiveModel, for example:

```ruby
module ActiveModelJSONSupport
extend ActiveSupport::Concern
include ActiveModel::Serializers::JSON

included do
def as_json(*)
super.merge(::JSON.create_id => self.class.name)
end

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

def self.json_create(object)
object = object.dup
object.delete(::JSON.create_id)
new(**object.symbolize_keys)
end
end
end
```

Now if `include ActiveModelJSONSupport` is present on any ActiveModel class, on serialization `to_json` will be used which will use `as_json` which calls the super `as_json` but also includes the fully qualified class name as the JSON
`create_id` key.
On deserialization, Ruby JSON then uses this key to know what class to call `json_create` on.
Loading