Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-12, macos-latest]
os: [ubuntu-latest, macos-13, macos-latest]
# Earliest and latest supported
rubyVersion: ["3.1", "3.3"]
rubyVersion: ["3.2", "3.4"]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
Expand Down
46 changes: 44 additions & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,46 @@
AllCops:
NewCops: enable
TargetRubyVersion: 3.1
SuggestExtensions: false
TargetRubyVersion: 3.2
SuggestExtensions: false

# Don't need super for activities or workflows
Lint/MissingSuper:
AllowedParentClasses:
- Temporalio::Activity::Definition
- Temporalio::Workflow::Definition

# The default is too small and triggers simply setting lots of values on a proto
Metrics/AbcSize:
Max: 200

# The default is too small
Metrics/BlockLength:
Max: 100

# The default is too small
Metrics/ClassLength:
Max: 1000

# The default is too small
Metrics/CyclomaticComplexity:
Max: 100

# The default is too small
Metrics/MethodLength:
Max: 100

# The default is too small
Metrics/ModuleLength:
Max: 1000

# The default is too small
Metrics/PerceivedComplexity:
Max: 40

# Don't need API docs for samples
Style/Documentation:
Enabled: false

# Don't need API docs for samples
Style/DocumentationMethod:
Enabled: false
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

source 'https://rubygems.org'

gem 'temporalio'
gem 'temporalio', path: '../temporal-sdk-ruby/temporalio'

group :development do
gem 'minitest'
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ until the SDK is marked stable.

Prerequisites:

* Ruby 3.1+
* Ruby 3.2+
* Local Temporal server running (can [install CLI](https://docs.temporal.io/cli#install) then
[run a dev server](https://docs.temporal.io/cli#start-dev-server))
* `bundle install` run in the root

## Samples

<!-- Keep this list in alphabetical order -->
* [activity_simple](activity_simple) - Simple workflow that calls two activities.
* [activity_worker](activity_worker) - Use Ruby activities from a workflow in another language.
* [context_propagation](context_propagation) - Use interceptors to propagate thread/fiber local data from clients
through workflows/activities.
* [message_passing_simple](message_passing_simple) - Simple workflow that accepts signals, queries, and updates.

## Development

Expand Down
23 changes: 23 additions & 0 deletions activity_simple/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Activity Simple

This sample shows calling a couple of simple activities from a simple workflow.

To run, first see [README.md](../README.md) for prerequisites. Then, in another terminal, start the Ruby worker
from this directory:

bundle exec ruby worker.rb

Finally in another terminal, use the Ruby client to the workflow from this directory:

bundle exec ruby starter.rb

The Ruby code will invoke the workflow which will execute two activities and return. The output of the final command
should be:

```
Executing workflow
Workflow result: some-db-value from table some-db-table <appended-value>
```

There is also a [test](../test/activity_simple/my_workflow_test.rb) that demonstrates mocking an activity during the
test.
33 changes: 33 additions & 0 deletions activity_simple/my_activities.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

require 'temporalio/activity'

module ActivitySimple
module MyActivities
# Fake database client
class MyDatabaseClient
def select_value(table)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably worth adding a short sleep here to emphasize that we're faking I/O (as you do below in the message-passing sample)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meh, not really needed that much (the async concepts of whether a sleep is present or not is different in Ruby and doesn't really mean anything) and wasn't done at https://github.com/temporalio/samples-dotnet/blob/main/src/ActivitySimple/MyActivities.cs either where it doesn't matter there either. But I may add this.

"some-db-value from table #{table}"
end
end

# Stateful activity that is created only once by worker creation code
class SelectFromDatabase < Temporalio::Activity::Definition
def initialize(db_client)
@db_client = db_client
end

def execute(table)
@db_client.select_value(table)
end
end

# Stateless activity that is passed as class to worker creation code,
# thereby instantiating every attempt
class AppendSuffix < Temporalio::Activity::Definition
def execute(append_to)
"#{append_to} <appended-value>"
end
end
end
end
29 changes: 29 additions & 0 deletions activity_simple/my_workflow.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

require 'temporalio/workflow'
require_relative 'my_activities'

module ActivitySimple
class MyWorkflow < Temporalio::Workflow::Definition
def execute

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please forgive reviewing API in the samples PR.

I would have reached for run instead of execute -- that has consistency with other SDKs, as well as threading APIs such as those of Java and Python etc. Is there a reason to do with the Ruby ecosystem for using execute?

Copy link
Member Author

@cretz cretz Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please forgive reviewing API in the samples PR.

No prob, this is a good place. And I am happy to see others digging in to Ruby.

Based on some ChatGPT ecosystem querying, it did seem execute was more preferred for background-job-like things. There is also perform. It is also what is used in the past Ruby SDK implementation and the Coinbase Ruby SDK implementation with success/familiarity. We use "run" as the attribute/annotation/decorator name, but in those other SDKs you can choose any method name though our samples often choose "run". But for Ruby, this is an actual abstract method and if made sense to match what was already there in ecosystem and existing Temporal Ruby.

# Run an activity that needs some state like a database connection
result1 = Temporalio::Workflow.execute_activity(
MyActivities::SelectFromDatabase,
'some-db-table',
start_to_close_timeout: 5 * 60 # 5 minutes
)
Temporalio::Workflow.logger.info("Activity result 1: #{result1}")

# Run a stateless activity (note no difference on the caller side)
result2 = Temporalio::Workflow.execute_activity(
MyActivities::AppendSuffix,
result1,
start_to_close_timeout: 5 * 60
)
Temporalio::Workflow.logger.info("Activity result 2: #{result2}")

# We'll go ahead and return this result
result2
end
end
end
16 changes: 16 additions & 0 deletions activity_simple/starter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

require 'temporalio/client'
require_relative 'my_workflow'

# Create a client
client = Temporalio::Client.connect('localhost:7233', 'default')

# Run workflow
puts 'Executing workflow'
result = client.execute_workflow(
ActivitySimple::MyWorkflow,
id: 'activity-simple-sample-workflow-id',
task_queue: 'activity-simple-sample'
)
puts "Workflow result: #{result}"
32 changes: 32 additions & 0 deletions activity_simple/worker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

require_relative 'my_activities'
require_relative 'my_workflow'
require 'logger'
require 'temporalio/client'
require 'temporalio/worker'

# Create a Temporal client
client = Temporalio::Client.connect(
'localhost:7233',
'default',
logger: Logger.new($stdout, level: Logger::INFO)
)

# Use an instance for the stateful DB activity, other activity we will pass
# in as class meaning it is instantiated each attempt
db_client = ActivitySimple::MyActivities::MyDatabaseClient.new
select_from_db_activity = ActivitySimple::MyActivities::SelectFromDatabase.new(db_client)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't .new just shorthand for .new()? But then both would be instances which contradicts what you say above -- my ruby understanding must be failing me.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ignore I see, only one of those is actually an activity.


# Create worker with the activities and workflow
worker = Temporalio::Worker.new(
client:,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this syntax shorthand for client: client, a bit like TS/Rust?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

task_queue: 'activity-simple-sample',
activities: [select_from_db_activity, ActivitySimple::MyActivities::AppendSuffix],
workflows: [ActivitySimple::MyWorkflow],
workflow_executor: Temporalio::Worker::WorkflowExecutor::ThreadPool.default
)

# Run the worker until SIGINT
puts 'Starting worker (ctrl+c to exit)'
worker.run(shutdown_signals: ['SIGINT'])
5 changes: 3 additions & 2 deletions activity_worker/activity_worker.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative 'activity'
require_relative 'say_hello_activity'
require 'temporalio/client'
require 'temporalio/worker'

Expand All @@ -14,7 +14,8 @@
# By providing the class to the activity, it will be instantiated for every
# attempt. If we provide an instance (e.g. SayHelloActivity.new), the same
# instance is reused.
activities: [ActivityWorker::SayHelloActivity]
activities: [ActivityWorker::SayHelloActivity],
workflow_executor: Temporalio::Worker::WorkflowExecutor::ThreadPool.default
)

# Run the worker until SIGINT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

module ActivityWorker
# Activity is a class with execute implemented
class SayHelloActivity < Temporalio::Activity
class SayHelloActivity < Temporalio::Activity::Definition
def execute(name)
"Hello, #{name}!"
end
Expand Down
16 changes: 16 additions & 0 deletions context_propagation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Context Propagation

This sample shows how a thread/fiber local can be propagated through workflows and activities using an interceptor.

To run, first see [README.md](../README.md) for prerequisites. Then, in another terminal, start the Ruby worker
from this directory:

bundle exec ruby worker.rb

Finally in another terminal, use the Ruby client to the workflow from this directory:

bundle exec ruby starter.rb

The Ruby code will invoke the workflow which will execute an activity and return. Note the log output from the worker
that contains logs on which user is calling the workflow/activity, information which we set as thread local on the
client and was automatically propagated through to the workflow and activity.
Loading
Loading