Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
A

actor

> 后端框架
Open source

Composable Ruby service objects

775 stars0 likes0 views
WebsiteGitHub

About

Composable Ruby service objects

ServiceActor

This Ruby gem lets you move your application logic into small composable service objects. It is a lightweight framework that helps you keep your models and controllers thin.

Contents

  • Installation
  • Usage
    • Inputs
    • Outputs
    • Fail
  • Play actors in a sequence
    • Rollback
    • Inline actors
    • Play conditions
    • Input aliases
  • Input options
    • Defaults
    • Allow nil
    • Conditions
    • Types
    • Custom input errors
    • Custom type validations
  • Testing
  • FAQ
  • Thanks
  • Contributing
  • License

Installation

Add the gem to your application’s Gemfile by executing:

bundle add service_actor

Extensions

  • Rails generators: service_actor-rails
  • TTY prompts: service_actor-promptable

Usage

Actors are single-purpose actions in your application that represent your business logic. They start with a verb, inherit from Actor and implement a call method.

# app/actors/send_notification.rb
class SendNotification  

When called, an actor returns a result. Reading and writing to this result allows actors to accept and return multiple arguments. Let’s find out how to do that and then we’ll see how to chain multiple actors together.

Inputs

To accept arguments, use input to create a method named after this input:

class GreetUser  "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():

class BuildGreeting  "Have a wonderful day, Fred!"

Fail

To stop the execution and mark an actor as having failed, use fail!:

class UpdateUser  [!WARNING]
> If you specify the type option for output fields, it will not be enforced for
> failed actors.
> As a result, their output might not match the specified type.

## Play actors in a sequence

To help you create actors that are small, single-responsibility actions, an
actor can use `play` to call other actors:

```rb
class PlaceOrder  [!TIP]
> Rollback is only called on the _previous_ actors in `play` and is not called on
> the failing actor itself. Actors should be kept to a single purpose and not have
> anything to clean up if they call `fail!`.

### Inline actors

For small work or preparing the result set for the next actors, you can create
inline actors by using lambdas. Each lambda has access to the shared result. For
example:

```rb
class PayOrder  actor { actor.order.currency ||= "EUR" },
       CreatePayment,
       UpdateOrderBalance,
       -> actor { Logger.info("Order #{actor.order.id} paid") }
end

You can also call instance methods. For example:

class PayOrder  actor { actor.order.amount > 42 }
  play CreatePayment, unless: -> actor { actor.order.currency == "USD" }
end

Input aliases

You can use alias_input to transform the output of an actor into the input of the next actors.

class PlaceComment  { "wonderful" }
  input :length_of_time, default: -> { ["day", "week", "month"].sample }
  input :article,
        default: -> actor { actor.adjective.match?(/^[aeiou]/) ? "an" : "a" }

  output :greeting

  def call
    self.greeting = "Have #{article} #{adjective} #{length_of_time}, #{name}!"
  end
end

actor = BuildGreeting.call(name: "Jim")
actor.greeting # => "Have a wonderful week, Jim!"

actor = BuildGreeting.call(name: "Siobhan", adjective: "elegant")
actor.greeting # => "Have an elegant week, Siobhan!"

While lambdas are the preferred way to specify defaults, you can also provide a default value without using lambdas by using an immutable object.

# frozen_string_literal: true

class ExampleActor  { Registry::DEFAULT_OPTIONS }

  def call
    options[:names] = nil
  end
end

Allow nil

By default inputs accept nil values. To raise an error instead:

class UpdateUser  user { user.admin? }
        }

  # …
end

This will raise an argument error if any of the given lambdas returns a falsey value.

Types

Sometimes it can help to have a quick way of making sure we didn’t mess up our inputs.

For that you can use the type option and giving a class or an array of possible classes. If the input or output doesn’t match these types, an error is raised.

class UpdateUser  user { user.admin? },
            message: "The user is not an administrator"
          }
        }

  # ...
end

You can also use incoming arguments when shaping your error text:

class UpdateUser 
  See examples of custom messages on all input arguments

  #### Inclusion

  ```ruby
  class Pay  provider { PROVIDERS.include?(provider) },
              message: (lambda do |value:, **|
                "The specified provider \"#{value}\" was not found."
              end)
            }
          }
  end

Type

class ReduceOrderAmount 

### Custom type validations

This gem provides a minimal API for checking the types of `input` and `output`
values:

- A direct class match: `input :age, type: Integer`
- A choice between classes: `output :height, type: [Integer, Float]`

More complex type checks are outside the scope of this gem. However, type
checking is performed using Ruby’s `===` method.
This means you can define a custom class with a `===` method to implement your
own type logic.

For example, to define a “positive integer” type, you can create a custom class:

```ruby
class PositiveInteger
class  #
AgeActor.call(age: -42)
# ServiceActor::ArgumentError: The "age" input on "AgeActor" must be of type
# "PositiveInteger" but was "Integer" (ServiceActor::ArgumentError)

This approach also allows you to define adapters for third-party validation gems, providing the flexibility to integrate custom type checks.

See more examples.

Testing

In your application, add automated testing to your actors as you would do to any other part of your applications.

You will find that cutting your business logic into single purpose actors will make it easier for you to test your application.

FAQ

Howtos and frequently asked questions can be found on the wiki.

Thanks

This gem is influenced by (and compatible with) Interactor.

Thank you to the wonderful contributors.

Thank you to @nicoolas25, @AnneSottise & @williampollet for the early thoughts and feedback on this gem.

Photo by Lloyd Dirks.

Contributing

See CONTRIBUTING.md.

License

The gem is available as open source under the terms of the MIT License.

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Rubyruby-on-railsservice-objects

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category后端框架
PricingOpen source

> Related tools

N
Node.js
基于 V8 的 JavaScript 运行时
D
Django
Python 高级 Web 框架
S
Spring Boot
Java 生态主流微服务框架