Turning Controller Actions into Services

Posted on 09 Nov 2012 by Eric Oestrich

This post was originally published on the SmartLogic Blog.

SRP keeps your code from behaving like this.

When you're developing an API, controller actions have a tendency to get large and inefficient. You can keep chopping controller actions into smaller pieces, but turning them into services may be where you want to go. The main reason for this is because you can get validation of the data before you use it to perform the service, making code more reliable.

What is a service and when would you need to make one?

A service is a class that interacts with multiple models. If a method on a model is going to access more than itself and maybe one neighbor then it should be pulled out into a service. Single Responsibility Principle (SRP) essentially states that a class should do just one thing. With this, we're making our class do just one thing: create an order. That way, we don't have a "fat model" that violates SRP.

Here's an example app on github to supplement the code below.

app/controllers/orders_controller.rb
class OrdersController < ApplicationController
  def create
    service = OrderCreationService.new(current_user, params[:order])
    service.perform

    if service.successful?
      respond_with service.order
    else
      respond_with service.errors, :status => 422
    end
  end
end
app/services/ordercreationservice.rb
class OrderCreationService
  include ActiveModel::Validations

  validates :name, :presence => true

  attr_reader :user, :params, :order, :name

  delegate :as_json, :to => :order

  def initialize(user, params)
    @user = user
    @params = params

    params.each do |param, value|
      instance_variable_set("@#{param}", value) if respond_to?(param)
    end
  end

  def perform
    return unless valid?

    # This can be done with only one line, or you can go nuts and make
    # this much more expansive if your desired functionality demands it
    @order = user.orders.create(params)
  end

  def successful?
    valid? && order.persisted?
  end
end

Here's how this gives you the nice ability of having validations around the parameters your API takes:

$ curl -X POST http://localhost:3000/orders
{ 'errors': { 'name': ["can't be blank"] } }

With this method, your API development results in cleaner, more reliable code that will be easier for both internal developers and external developers to work with. Also, be sure to test via RspecApiDocumentation and Raddocs.

For more like this, follow SmartLogic on LinkedIn or like us on Facebook.
Image Source

comments powered by Disqus
Eric Oestrich
I am:
All posts
Creative Commons License
This site's content is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License unless otherwise specified. Code on this site is licensed under the MIT License unless otherwise specified.