diff --git a/lib/temporal/activity.rb b/lib/temporal/activity.rb index a3a726af..d5524a0a 100644 --- a/lib/temporal/activity.rb +++ b/lib/temporal/activity.rb @@ -1,4 +1,5 @@ require 'temporal/activity/workflow_convenience_methods' +require 'temporal/callable' require 'temporal/concerns/executable' require 'temporal/errors' @@ -9,7 +10,9 @@ class Activity def self.execute_in_context(context, input) activity = new(context) - activity.execute(*input) + callable = Temporal::Callable.new(method: activity.method(:execute)) + + callable.call(input) end def initialize(context) diff --git a/lib/temporal/callable.rb b/lib/temporal/callable.rb new file mode 100644 index 00000000..3bec64fd --- /dev/null +++ b/lib/temporal/callable.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Temporal + class Callable + def initialize(method:) + @method = method + end + + def call(input) + if input.is_a?(Array) && input.last.instance_of?(Hash) + *args, kwargs = input + + @method.call(*args, **kwargs) + else + @method.call(*input) + end + end + end +end diff --git a/lib/temporal/workflow.rb b/lib/temporal/workflow.rb index 3b5dcfe6..c135a19c 100644 --- a/lib/temporal/workflow.rb +++ b/lib/temporal/workflow.rb @@ -1,3 +1,4 @@ +require 'temporal/callable' require 'temporal/concerns/executable' require 'temporal/workflow/convenience_methods' require 'temporal/thread_local_context' @@ -13,7 +14,9 @@ def self.execute_in_context(context, input) Temporal::ThreadLocalContext.set(context) workflow = new(context) - result = workflow.execute(*input) + callable = Temporal::Callable.new(method: workflow.method(:execute)) + + result = callable.call(input) context.complete(result) unless context.completed? rescue StandardError, ScriptError => error diff --git a/spec/unit/lib/temporal/activity_spec.rb b/spec/unit/lib/temporal/activity_spec.rb index 47a0e8b0..10b32677 100644 --- a/spec/unit/lib/temporal/activity_spec.rb +++ b/spec/unit/lib/temporal/activity_spec.rb @@ -4,15 +4,28 @@ describe Temporal::Activity do it_behaves_like 'an executable' + class ArgsActivity < Temporal::Activity + def execute(a) + 'args result' + end + end + + class KwargsActivity < Temporal::Activity + def execute(a, b:, c:) + 'kwargs result' + end + end + subject { described_class.new(context) } let(:context) { instance_double('Temporal::Activity::Context') } describe '.execute_in_context' do + subject { ArgsActivity.new(context) } + let(:input) { ['test'] } before do allow(described_class).to receive(:new).and_return(subject) - allow(subject).to receive(:execute).and_return('result') end it 'passes the context' do @@ -22,13 +35,41 @@ end it 'calls #execute' do - described_class.execute_in_context(context, input) + expect(subject).to receive(:execute).with(*input) - expect(subject).to have_received(:execute).with(*input) + described_class.execute_in_context(context, input) end it 'returns #execute result' do - expect(described_class.execute_in_context(context, input)).to eq('result') + expect(described_class.execute_in_context(context, input)).to eq('args result') + end + + context 'when using keyword arguments' do + subject { KwargsActivity.new(context) } + + let(:input) { ['test', { b: 'b', c: 'c' }] } + + it 'passes the context' do + described_class.execute_in_context(context, input) + + expect(described_class).to have_received(:new).with(context) + end + + it 'calls #execute' do + expect(subject).to receive(:execute).with('test', b: 'b', c: 'c') + + described_class.execute_in_context(context, input) + end + + it 'does not raise an ArgumentError' do + expect { + described_class.execute_in_context(context, input) + }.not_to raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 1; required keywords: b, c)') + end + + it 'returns #execute result' do + expect(described_class.execute_in_context(context, input)).to eq('kwargs result') + end end end diff --git a/spec/unit/lib/temporal/workflow_spec.rb b/spec/unit/lib/temporal/workflow_spec.rb index b8f6af5f..41014aae 100644 --- a/spec/unit/lib/temporal/workflow_spec.rb +++ b/spec/unit/lib/temporal/workflow_spec.rb @@ -1,6 +1,78 @@ require 'temporal/workflow' +require 'temporal/workflow/context' require 'shared_examples/an_executable' describe Temporal::Workflow do it_behaves_like 'an executable' + + class ArgsWorkflow < Temporal::Workflow + def execute(a) + 'args result' + end + end + + class KwargsWorkflow < Temporal::Workflow + def execute(a, b:, c:) + 'kwargs result' + end + end + + subject { described_class.new(ctx) } + let(:ctx) { instance_double('Temporal::Workflow::Context') } + + before do + allow(ctx).to receive(:completed?).and_return(true) + end + + describe '.execute_in_context' do + subject { ArgsWorkflow.new(ctx) } + + let(:input) { ['test'] } + + before do + allow(described_class).to receive(:new).and_return(subject) + end + + it 'passes the context' do + described_class.execute_in_context(ctx, input) + + expect(described_class).to have_received(:new).with(ctx) + end + + it 'calls #execute' do + expect(subject).to receive(:execute).with(*input) + + described_class.execute_in_context(ctx, input) + end + + context 'when using keyword arguments' do + subject { KwargsWorkflow.new(ctx) } + + let(:input) { ['test', { b: 'b', c: 'c' }] } + + it 'passes the context' do + described_class.execute_in_context(ctx, input) + + expect(described_class).to have_received(:new).with(ctx) + end + + it 'calls #execute' do + expect(subject).to receive(:execute).with('test', b: 'b', c: 'c') + + described_class.execute_in_context(ctx, input) + end + + it 'does not raise an ArgumentError' do + expect { + described_class.execute_in_context(ctx, input) + }.not_to raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 1; required keywords: b, c)') + end + end + end + + describe '#execute' do + it 'is not implemented on a superclass' do + expect { subject.execute }.to raise_error(NotImplementedError) + end + end end