Skip to content
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

Add .value() class method that gives the return value of actor's #call #152

Merged
merged 12 commits into from
Apr 12, 2024
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## unreleased

Features:
- Add support for `value()` method on actors (#152)

## v3.8.1

Fix:
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,22 @@ actor.greeting # => "Have a wonderful day!"
actor.greeting? # => true
```

If you only have one value you want from an actor, you can skip defining an
output by making it the return value of `.call()` and calling your actor with
`.value()`:

```rb
class BuildGreeting < Actor
input :name

def call
"Have a wonderful day, #{name}!"
end
end

BuildGreeting.value(name: "Fred") # => "Have a wonderful day, Fred!"
```

### Fail

To stop the execution and mark an actor as having failed, use `fail!`:
Expand Down Expand Up @@ -183,6 +199,9 @@ Calling this actor will now call every actor along the way. Inputs and outputs
will go from one actor to the next, all sharing the same result set until it is
finally returned.

If you use `.value()` to call this actor, it will give the return value of
the final actor in the play chain.

### Rollback

When using `play`, if an actor calls `fail!`, the following actors will not be
Expand Down
1 change: 1 addition & 0 deletions lib/service_actor/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def included(base)
base.include(ServiceActor::Checkable)
base.include(ServiceActor::Defaultable)
base.include(ServiceActor::Failable)
base.include(ServiceActor::Valuable)
end
end
end
3 changes: 2 additions & 1 deletion lib/service_actor/checkable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ def _call
self.service_actor_argument_errors = []

service_actor_checks_for(:input)
super
return_val = super
service_actor_checks_for(:output)
return_val
end

private
Expand Down
2 changes: 2 additions & 0 deletions lib/service_actor/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def rollback; end
# This method is used internally to override behavior on call. Overriding
# `call` instead would mean that end-users have to call `super` in their
# actors.
# When overriding and calling `super`, make sure the final value is the return
# value of `super` (see e.g. ServiceActor::Checkable).
# :nodoc:
def _call
call
Expand Down
17 changes: 12 additions & 5 deletions lib/service_actor/playable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,17 @@ def define_alias_input(actor, new_input, original_input)

module PrependedMethods
def call
default_output = nil

self.class.play_actors.each do |options|
next unless callable_actor?(options)

options[:actors].each { |actor| play_actor(actor) }
options[:actors].each do |actor|
default_output = play_actor(actor)
end
end

default_output
rescue self.class.failure_class
rollback
raise
Expand Down Expand Up @@ -95,17 +101,18 @@ def play_service_actor(actor)
return unless actor.ancestors.include?(ServiceActor::Core)

actor = actor.new(result)
actor._call
call_output = actor._call

(@played_actors ||= []).unshift(actor)

call_output
end

def play_method(actor)
return unless actor.is_a?(Symbol)

send(actor)

true
# return truthy method value, or true as fallback
send(actor) || true
end

def play_interactor(actor)
Expand Down
10 changes: 9 additions & 1 deletion lib/service_actor/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def initialize(data = {})
end

def to_h
data
filter_default_output(data)
end

def inspect
Expand Down Expand Up @@ -103,6 +103,14 @@ def deconstruct_keys(keys)

attr_reader :data

# Key `_default_output` is an internal datum used by actor class
# method `.valuable`. Don't expose it with the rest of the result.
def filter_default_output(h)
# using `filter` instead of `except` to maintain Ruby 2.7 compatibility
# update once support for 2.7 is dropped
h.filter { |k| k != :_default_output }
llhhaa marked this conversation as resolved.
Show resolved Hide resolved
end

def respond_to_missing?(method_name, _include_private = false)
return true if method_name.end_with?("=")
if method_name.end_with?("?") &&
Expand Down
36 changes: 36 additions & 0 deletions lib/service_actor/valuable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

# Adds the `value` method to actors. This allows you to call `.value` and get
# back the return value of that actor's `call` method.
#
# In the case of play actors, it will return the value of the final actor's
# `call` method in the chain.
#
# class MyActor < Actor
# def call
# "foo"
# end
# end
#
# > MyActor.value
# => "foo"
module ServiceActor::Valuable
class << self
def included(base)
base.extend(ClassMethods)
base.prepend(PrependedMethods)
end
end

module ClassMethods
def value(result = nil, **arguments)
call(result, **arguments)[:_default_output]
end
end

module PrependedMethods
def _call
result[:_default_output] = super
end
end
end
Loading