Actor Action Object in Rails Projects
- Learn how procedural code can be used in conjunction with object-oriented paradigm in your Rails projects
I was reading the 'Problem Restatement' chapter from the book 'The Thinker's Toolkit' by Morgan D. Jones. He was explaining how double negation is not natural to our mind. The double negation was also discussed by Steve McConnell in his Code Complete 2 book in the context of code. Then I came across the section 'Active Voice Facilitates', where he states:
A statement in passive voice takes longer to process than one in active voice. Because the mind's basic linguistic programming interprets and forms sentences in terms of actor-action-object, not object-action-actor. When children first begin speaking, they say, 'Tom throws the ball' not 'The ball is thrown by Tom'. Of course, children quickly learn to make sense of passive voice, but to do so, they first have to mentally rearrange the sentence to identify the actor, the action and the object. By the time we're adults, our minds have become adept at forming and interpreting complicated statements in passive voice. Nevertheless, our mental linguistic machinery, which is hardwired into the brain, doesn't change. When the object in a statement comes first, the mind must still set the object aside until the actor and action become known. The slight difficulty we have with passive voice probably stems from the mind's built-in propensity to view the world in terms of cause-and-effect relationships. Thus, the mind is programmed to see cause first and effect second. But passive voice puts the effect ahead of cause, so the mind has to reverse their order to interpret the statement.
---- The Thinkers's Toolkit by Morgan D. Jones
Actor Action Object
So the question is :
How can we represent the Actor-Action-Object in our Rails projects?
You can see how I structure my code in Striped Rails project. The folder structure looks like this : app/actors/customer/use_cases. In this folder the use cases are listed as files with names :
add_new_credit_card.rb guest_checkout.rb one_click_checkout.rb register_for_an_account.rb
and so on. This represents the Actor-Action part of the Actor-Action-Object structure. The Action part,
add_new_credit_card.rb is not an object. It is a method. The source code looks like this:
module Actors module Customer module UseCases def self.add_new_credit_card(user, stripe_token) card = StripeGateway.add_new_credit_card(user.stripe_customer_id, stripe_token) # Update our database with last 4 digits, expiration date and month. StripeCustomer.save_credit_card_details(card, user) card end end end end
This is consistent with the fact that Action in Actor-Action-Object represents the verb, the actual use case name. The actor is interacting with our system, the use case name represents that interaction. The Action part encapsulates all the objects that are required to satisfy the use case. Here is the guest_checkout.rb:
module Actors module Customer module UseCases def self.guest_checkout(product_id, stripe_token, user) amount = Product.price_in_cents_for(product_id) customer = StripeGateway.save_credit_card(stripe_token) charge = StripeGateway.charge(amount, customer.id) StripeCustomer.save_credit_card_and_stripe_customer_id(customer, user) StripeCustomer.create_payment(product_id, user.id, charge.id) end end end end
The use case delegates all the work to appropriate classes or objects. It does not do any work, it knows how to co-ordinate the work that needs to be done by different classes or objects. So the Object part of our Actor-Action-Object structure is usually many.
The controller calls these methods, for instance, subscription_controller create action does this:
@success = Actors::Customer::UseCases.subscribe_to_a_plan(current_user, params[:stripeToken], params[:plan_name])
Service Objects in the Wild
The use cases are named with the name of the corresponding use case. The file name is not a class name but a verb. Each use case is encapsulated as a stateless method in a module. It is not an object. So you cannot say my implementation uses a 'Service Object' because it is not an object. It does not have any state.
It resembles a service object in the sense, it follows the accept input, perform work, return result structure. It is also similar to a 'service' in the sense, it does describe system interactions. It usually will involve more than one object in our application.
I don't name classes with verbs, looks like developers are not following the basics of OO design. They try to fit square peg in a round hole. Here is an example Service Objects in Rails where he names an object as CreateInvoice. This is wrong, not just naming wise, you don't need to create an object in order to just invoke call on that object. You could easily turn that into a stateless method. Why unnecessarily create objects when you don't need them?
This post Gourmet Service Objects says Service Object does only one thing. My use case handler does more than just one thing. It interacts with external systems, updates the database, send emails etc.
This article here Rails Controllers and Services uses Service Object for credit card processing. I would use the Gateway Object (An object that encapsulates access to an external system or resource) as discussed by Martin Fowler in his Patterns of Enterprise Application Architecture book.
When you find yourself violating the basic principles of OO design, it is a sign that your design needs improvement. There is no need to hide the use cases in your system. Make them explicit. Use cases are procedural in nature. It will cut across objects in different layers of your system. We don't have to represent them as objects. There seems to be lot of confusion about Service Objects because developers are not following the industry standard terminology in the Ruby community.
We can represent the Actor-Action-Object in our Rails project to make the code easy to read. We can easily find out the use cases supported by the Rails project simply by looking at the use_cases folder. It also allows us to implement the
Tell Don't Ask principle easily. I don't agree with Eric Evans about the need for Service Object, especially in web applications. I believe Actor-Action-Object results in a better design. Because a developer's mental model maps well to the system when you use Actor-Action-Object Pattern.