Smarter Rails Services with Active Model Modules
Tags: rails, serviceobject, activerecord, ruby
Published: 2017-12-01 16:41:28

---
title: Smarter Rails Services with Active Model Modules
published: true
description: When MVC is not enough, super charge your service object with Active Model.
tags: Rails, service object, active record, ruby
---

## MVC is Not Enough!

We're familiar with the MVC (Model-View-Controller) pattern that Rails offers us––our models map to database tables and wrap our data in objects; controllers receive requests for data and serve up data to the views; views present the data. A common analogy is that of a restaurant––the models are the food, the controller is the waiter taking your order and brining it to you and the view is the beautifully decorated table where you consume your meal. 

MVC is a powerful pattern for designing web applications. It helps us put code in its place and build well-architected systems. But we know that MVC doesn't provide a framework for *all* of the responsibilities and functions of a large-scale web app. 

For example, let's say you have a web app for a successful online store where you sell, I don't know, let's say something fun like time travel devices.

![](http://www.thegreatcodeadventure.com/content/images/2017/04/Screen-Shot-2017-04-15-at-9.51.28-AM.png)<span style="font-size: 12px">[source](http://www.themindrobber.co.uk/police-box/TARDISes.png)</span>

When someone "checks out" and enacts a purchase, there is quite a bit of work your app has to complete. You'll need to: 

* Identify the user who is making the purchase
* Identify and validate their payment method
* Enact or complete the purchase
* Create and send the user a confirmation/receipt email

Let's take it a step further and say that our purchase creation actually occurs via an API endpoint: `/api/purchases`. So the end result of our purchase handling will involve serializing some data. Now our list of responsibilities also includes:

* Serialize completed purchase data.

That is a lot of responsibility! Where does all of that business logic rightly belong?

Not in the controller, whose only responsibility it is to receive a request and fetch data. Not in the model, whose only job iswrapping data from the database, and certainly not in the view, whose responsibility it is to present data to the user. 

Enter, the service object. 

### Service Objects to the Rescue 

A service object is a simple PORO class (plain old ruby object) designed to wrap up complex business logic in your Rails application. We can call on a service within our controller so that our `PurchasesController` will look super sexy:

```ruby
# app/controllers/purchases_controller.rb
class PurchasesController < ApplicationController
  def create
    PurchaseHandler.execute(purchase_params, current_user)
  end
end
```

I know, beautiful, right? What happens though when our service classes need to get a little smarter, a little less "plain"?

Think about the list of responsibilities for our purchase handling above. That list includes *validating* some data (did we find the correct user? does this user have valid payment methods associated to their account?), *serializing* some data (to be returned via our API endpoint) and enacting some post-processing *after* the purchase is enacted (sending the confirmation/invoice email). 

### Sometimes a PORO Service Just Won't Cut It

While it is absolutely possible to handle all of these responsibilities in a PORO, it is also true that Rails already offers a powerful set of tools for some of these exact *validating, serializing, post-processing* scenarios. Does any of that sound familiar (I'll give you hint, read the title of this post)...Active Model!

Active Model provides us with tools for validation, serialization and callbacks to fire certain methods _after_ a particular method has been called. We're most familiar with the Active Record tool box via the code made available to us by inheriting our models from `ActiveRecord::Base`. You might guess that that's what we'll do with our `PurchaseHandler` service. You'd be wrong. 

We *don't* want *all* of the tools that Active Record provides––we don't want to persist instances of our `PurchaseHandler` class to our database, and many of Active Record's modules deal with that interaction. Our service class is not a model. It is still very much a service whose job it is to wrap up business logic for enacting a purchase. 

Instead, we will pick and choose the specific Active Model modules that offer the tools we're interested in, include those modules in our service class, and leverage the code provided by them to meet our specific needs. 

Let's get started and super charge our service!

![](http://www.thegreatcodeadventure.com/content/images/2017/12/Screen-Shot-2017-12-01-at-11.29.13-AM.png)

### Defining the `PurchaseHandler` Service 

First things first, let's map out the basic structure of our service class. Then we'll tackle including our Active Model modules. 

Our service's API is super-simple. It looks like this:

```ruby
PurchaseHandler.execute(purchase_params, user_id)
```

Where `purchase_params` looks something like this:

```
{
  products: [
    {id: 1, name: "Tardis", quantity: 1},
    {id: 2, name: "De Lorean", quantity: 2}
  ],
  payment_method: {
    type: "credit_card",
    last_four_digits: "1111"
  }
}
```

So, our `PurchaseHandler` class will expose the following method:

```ruby
# app/services
class PurchaseHandler
  def self.execute(params, user_id)
    # do the things
  end
end
```

Active Model modules will give us access to validation, serialization and callback hooks, all of which are available on _instances_ of a class. So, our `.execute` class method will need to initialize an _instance_ of `PurchaseHandler`. 

I'm a fan of keeping the public API really simple––one class method––and using the `.tap` method to keep that one public-facing class method really clean. 

```ruby
#app/services
class PurchaseHandler
  def self.execute(params, user_id)
    self.new(params, user_id).tap(&:create_purchase)
  end
end
```

The `#tap` method is really neat––it yields the instance that it was called on to a block, and returns the instance that it was called on after the block runs. This means that our `.execute` method will return our `handler` instance, which we can then serialize in the controller (more on that later). 

Now that we have the beginnings of our service class built out, let's start pulling in the Active Record tools we need to validate the handler. 

### Active Model Validations

What kind of validations does our service class need? Let's say that before we try to process or complete a purchase, we want to validate that:

* The given user exists.
* The user has a valid payment method that matches the given payment method.
* The products to be purchases are in-stock in the requested quantities. 

If any of these validations fail, we want to add an error to the handler instance's collection of errors. 

The `ActiveModel::Validations` module will give us access to Active Model validation methods *and*, via it's own inclusion of the `ActiveModel::Errors` module, it will give us access to an `.errors` attr_accessor and object. 

We'll use the `#initialize` method to set up the data we need to perform our purchase: 

* Assigning the user
* Assigning the purchase method (i.e. the user's credit card)
* Assigning the products to be purchased

And we'll run our validations on this data *after* the `#initialize` method runs. (Does that smell like callbacks? Yes!)

Let's build out our validations one-by-one. Then we'll write the code to call our validations with the help of Active Model's callbacks. 

First, we want to validate that we were able to find a user with the given ID. 

```ruby
# app/services
class PurchaseHandler
  include ActiveModel::Validations
  validates :user, presence: true
  
  def self.execute(params, user_id)
    self.new(params, user_id).tap(&:create_purchase)
  end

  def initialize(params, user_id)
    @user = User.find_by(user_id)
  end
end
```

Next up, we'll want to validate that we were able to find a credit card for that user that matches the given card. *Note: Our credit-card-finding code is a little basic. We're simply using the user's ID and the last four digits of the card, included in the params. Keep in mind this is just a simplified example*. 

```ruby
# app/services
class PurchaseHandler
  include ActiveModel::Validations
  validates :user, presence: true
  validates :credit_card, presence: true
  validates :products, presence: true

  attr_reader :user,:payment_method, :product_params, 
    :products, :purchase

  def self.execute(params, user_id)
    self.new(params, user_id).tap(&:create_purchase)
  end

  def initialize(params, user_id)
    @user = User.find_by(user_id)
    @payment_method = CreditCard.find_by(
      user_id: user_id, 
      last_four_cc: params[:last_four_cc]
    )
  end
end
```

Lastly, we'll grab the products to be purchased:

```ruby
#app/services
class PurchaseHandler
  include ActiveModel::Validations
  validates :user, presence: true
  validates :credit_card, presence: true
  validates :products, presence: true
  
  attr_reader :user, :credit_card, :product_params, :products, :purchase

  def self.execute(params, user_id)
    self.new(params, user_id).tap(&:create_purchase)
  end

  def initialize(params, user_id)
    @user           = User.find_by(user_id)
    @payment_method = CreditCard.find_by(
      user_id: user_id, 
      last_four_cc: params[:last_four_cc]
    )
    @product_params = params[:products]
    @products       = assign_products
  end

  def assign_products
    Product.where(id: product_params.pluck(:id))
  end
end
```

One last thing we need to think about validating. We need to know more than just whether or not the products exist in the database, but whether or not we have enough of each product in-stock to accommodate the order. For this, we'll build a custom validator. 

#### Building a Custom Validator

Our custom validator will be called `ProductQuantityValidator` and we'll utilize it in our service class via the following line:

```ruby
#app/services
class PurchaseHandler
  include ActiveModel::Validations
  ...
  validates_with ProductQuantityValidator
```

We'll define our custom validator in `app/validators/`

```ruby
# app/validators
class ProductQuantityValidator < ActiveModel::Validator
  def validate(record)
    record.product_params.each do |product_data|
      if product_data[:quantity] > products.find_by(id: product_data[:id])[:quantity]
        record.errors.add :base, "Not enough of product #{product_data[:id]} in stock."
     end
    end
  end
```

Here's how it works:

When we we invoke our handler's validations (coming soon, I promise), the `validates_with` method is called. This in turn initializes the custom validator, and calls `validate` on it, with an argument of the handler instance that invoked `validates_with` in the first place. 

Our custom validator looks up the quantity of each selected product for the given record (our handler instance) and adds an error to the record (our handler) if we don't have enough of that product in stock.

Now that we've built our validations, let's write the code to invoke them. 

#### Invoking Validations with the Help of Active Record Callbacks

In our normal Rails models that inherit from `ActiveRecord::Base` and map to database tables, Active Record calls our validations when the record is saved via the `.create`, `#save` or `#update` methods. 

Our service class, as you may recall, does _not_ map to a database table and does not implement a `#save` method. 

It is possible for us to manually fire the validations by calling the `#valid?` method that `ActiveModel::Validations` exposes. We _could_ do so in the `#initialize` method. 

```ruby
# app/services
class PurchaseHandler
  include ActiveModel::Validations
  validates :user, presence: true
  validates :credit_card, presence: true
  validates :products, presence: true
  validates_with ProductQuantityValidator
  ...

  def initialize(params, user_id)
    @user           = User.find_by(user_id)
    @payment_method = CreditCard.find_by(
      user_id: user_id, 
      last_four_cc: params[:last_four_cc]
    )
    @product_params = params[:products]
    @products       = assign_products
    valid?
  end
```

Let's think about this for a moment though. What is the job of the `#initialize` method? Ruby's `#initialize` method is automatically invoked by calling `Klass.new`, and it's job is to build the instance of our class. It feels just slightly outside the wheelhouse of `#initialize` to not only _build_ the instance, but _also validate it_. I consider this to be a violation of the Single Responsibility Principle. 

Instead, we want our validations to run automatically for us _after the `#initialize` method executes_. 

If only there was a way for us to fire certain methods automatically at a certain point in an object's lifecycle...

Just kidding. Of course there is! Active Model's callbacks will allow us to do just that.  

#### Defining Custom Callbacks

First, we'll include the `ActiveModel::Callbacks` module in our service.

```ruby
# app/services
class PurchaseHandler
  include ActiveModel::Validations
  include ActiveModel::Callbacks
  ...
```

Next up, we'll tell our class to enable callbacks for our `#initialize` method.

```ruby
# app/services
class PurchaseHandler
  include ActiveModel::Validations
  include ActiveModel::Callbacks

  define_model_callbacks :initialize, only: [:after]
  ...
```

The `#define_model_callbacks` method define a list of methods that Active Record will attach callbacks to. 

Now, we can define our callbacks like this:

```ruby
# app/services
class PurchaseHandler
  include ActiveModel::Validations
  include ActiveModel::Callbacks

  define_model_callbacks :initialize, only: [:after]
  after_initialize :valid?
  ...
```

Here, we tell our class to fire the `#valid?` (an `Active::Model::Validations` method) after our `#initialize` method. 

Lastly, in order for our custom callbacks to actually get invoked, we need call the `#run_callbacks` method (courtesy of `ActiveModel::Callbacks`), and wrap the content of our method in a block. 

```ruby
# app/services
class PurchaseHandler
  include ActiveModel::Validations
  include ActiveModel::Callbacks

  define_model_callbacks :initialize, only: [:after]
  after_initialize :valid?
  ...
  def initialize(params, user_id)
    run_callbacks do 
      @user           = User.find_by(user_id)
      @payment_method = CreditCard.find_by(
        user_id: user_id, 
        last_four_cc: params[:last_four_cc]
      )
      @product_params = params[:products]
      @products       = assign_products
    end
  end
```

And that's it!

#### Generating the Purchase

Now that we're properly validating our service object, we're ready to actually enact the purchase. Generating a fake purchase for our fictitious online-store that sells (very real) time traveling devices is (unfortunately) *not* the focus on this post. So, we'll just assume we have an *additional* service class, `PurchaseGenerator`, that we'll call on in the `#create_purchase` method and we won't worry about its implementation. 

```ruby
#app/services
class PurchaseHandler
  include ActiveModel::Validations
  include ActiveModel::Callbacks

  define_model_callbacks :initialize, only: [:after]
  after_initialize :valid?
  ...
  def create_purchase
    PurchaseGenerator.generate(self)
  end
```

That was easy! We're so good at programming. 

Okay, now that we can create purchases, we need to utilize our custom callbacks one more time in order to take care of the post-processing––generating an invoice and sending an email. 

### Defining Custom `after` Callbacks

Why should this occur in post-processing anyway? Well, we could put that logic in the (not coded here) `PurchaseGenerator` class, or even in some helper methods on the `Purchase` model itself. That creates a level of dependency that we don't find acceptable. Coupling purchase generation with invoice creation and email sending means that _every time you ever create a purchase_ you are generating an invoice and email _every time_. What if you are creating a purchase for a test or administrative user? What if you are manually creating a purchase to give a freebie to a valued customer (The Doctor _is_ constantly buying Tardis replacement parts)? It is certainly possible for a situation to arise in which you do _not_ want to enact both functionalities. Keeping our code nice and modular allows it to be flexible and reusable, not to mention **just gorgeous**. 

Now that we're convinced what a great idea this is, let's build a custom callback to fire _after_ the `#create_purchase` method to handle invoicing and email confirmations. We'll define two private helper methods to do that. We'll also use the `run_callbacks` method to wrap the contents of `#create_purchase` so that our callbacks will fire. 

```ruby
#app/services
class PurchaseHandler
  include ActiveModel::Validations
  include ActiveModel::Callbacks

  define_model_callbacks :initialize, only: [:after]
  define_model_callbacks :create_purchase, only: [:after]

  after_initialize :valid?
  after_create_purchase :create_invoice
  after_create_purchase :notify_user
  ...
  def create_purchase
    run_callbacks do 
      @purchase = PurchaseGenerator.generate(self)
    end
  end

  private

  def create_invoice
    Invoice.create(@purchase)
  end

  def notify_user
    UserPurchaseNotifier.send(@purchase)
  end
```

### Serializing Our Service Object

We're almost done super-charging our service object. Last but not least, we want to make it possible to leverage `ActiveModel::Serializer` to serialize our object so that we can respond to the `/api/purchases` request with a nice, tidy JSON package. 

The only thing we need to add to our model is the inclusion of the `ActiveModel::Serialization` module:

```ruby
# app/services
class PurchaseHandler
  include ActiveModel::Validations
  include ActiveModel::Callbacks
  include ActiveModel::Serialization
```

Now we can define a custom serializer, `PurchaseHandlerSerializer`, and use it like this:

```ruby
class PurchasesController < ApplicationController
  def create
    handler = PurchaseHandler.execute(purchase_params, user)
    render json: handler, serializer: PurchaseHandlerSerializer
  end
end
```

Our custom serializer will be simple, it will just pluck out some of the attributes we want to serialize:

```ruby
# app/serializers
class PurchaseHandlerSerializer < ActiveModel::Serializer
  attributes :purchase, :products
end
```

And that's it!

Our final `PurchaseHandler` service looks something like this:

```ruby
class PurchaseHandler
  include ActiveModel::Validations
  include ActiveModel::Callbacks
  include ActiveModel::Serialization
  
  define_model_callbacks :initialize, only: [:after]
  define_model_callbacks :create_purchase, only: [:after]

  after_initialize :valid?
  after_create_purchase :create_invoice
  after_create_purchase :notify_user
  
  attr_reader :payment_method, :purchase, :products, :user, :product_params

  def self.execute(params, user_id)
    self.new(params, user_id).tap(&:create_purchase)
  end
  
  def initialize(params, user_id)
    run_callbacks do 
      @user           = User.find_by(user_id)
      @payment_method = CreditCard.find_by(
        user_id: user_id, 
        last_four_cc: params[:last_four_cc]
      )
      @product_params = params[:products]
      @products       = assign_products
    end
  end

  def create_purchase
    @purchase = PurchaseGenerator.generate(self)
  end

  private

  def create_invoice
    Invoice.create(purchase)
  end

  def notify_user
    UserPurchaseNotifier.send(purchase)
  end
```

### Conclusion

This has been a brief introduction to some of the more commonly useful modules but I encourage you to delve deep into the Active Model tool box.

Active Model is a powerful and flexible tool. It's functionality extends far beyond the simple "inherit your models from `ActiveRecord::Base` that we're used to seeing on our Rails apps.  

MVC is a guideline. It's not written in stone. The main idea is that we _don't_ want to muddy our models, views or controllers with business logic. Instead, we want to create additional objects to handle additional responsibilities. A PORO service is a great place to start, but it isn't always enough. To super-charge our service objects, we can turn to the robust functionality of Active Model.