I created a Ruby gem called Actor. It helps you harmonize your application’s service objects.
Service objects are a way of making your web applications grow in complexity while keeping your controllers and models thin. For that you’d create classes that represent your actions, for example, CreateComment
, PlaceOrder
, DestroyUser
, etc.
At the start, these actions might hold very little code and look a lot like a regular controller. But as your app grows, you’ll realize that something simple like creating a comment can turn into a serie of actions:
These actions deserve a way of being represented as first-class citizens in your application. That’s where actor comes into play!
With the Actor gem, your business logic is represented by a class that starts with a verb. It declares what arguments it accepts and what argument it returns. Here’s an example of creating a comment in a Ruby app:
# app/actors/create_comment.rb
class CreateComment < Actor
input :author
input :text
output :comment
def call
self.comment = Comment.create(author: author, text: text)
end
end
Now, if placing a comment takes more steps, here’s how it could look like thanks to the way Actor chains the output of the previous actors into the next when using play
:
class PlaceComment < Actor
play CreateComment,
CheckCommentSpam,
NotifyUserOfComment,
UpdateCommentCounters,
ClearCommentCacheKey,
NotifyNewCommentOnSlack
end
If an actor along the way encounters an error, it can call fail!
which halts the chain and allows previous successful actors a chance to rollback their changes.
Actor doesn’t depend on Rails, but Rails controllers are a good example of where you’d use your actors:
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
authorize
result = PlaceComment.result(author: current_user, text: params[:text])
if result.success?
redirect_to comment_path(result.comment)
else
redirect_to :back, alert: result.error
end
end
end
Now your controller is only responsible for calling an actor, testing its success and deciding how to handle it. Your business logic can now be tested by itself and be called from a console, a job, or an another controller.
Actor is similar to the Interactor gem that I have been using on a number of different projects at Cults and KissKissBankBank and love the way it has helped give a common interface for all our services.
If you’re coming from Interactor, know that Actor:
foo
vs context.foo
, self.foo =
vs context.foo =
, fail!
vs context.fail!
.< Actor
vs having to include Interactor
and include Interactor::Organizer
.play
.call
and result
instead of call!
and call
. This way, the default is to raise an error and failures are not hidden away because you forgot to use !
.input
and output
.All this led me to create my own version, which has been smoothly running in production at Cults.
Install it by adding the following lines to your application’s Gemfile:
# Composable service objects
gem 'service_actor'
Read more about the gem on Actor’s readme on GitHub. Don’t hesitate to send your questions, star the project, submit ideas as issues.
est un développeur web vivant à Paris — Contact — Archives
Textes et contenus sous licence Creative Commons.