Bubbling up errors in service objects

Hey all,

I’m creating service objects to handle business logic that does not belong in ActiveRecord models neither should it be in the controllers. I am seeking opinions on how to bubble back errors from a service object back to the controller calling it without using any gems. For example, this would be in my controller:

class SessionsController < ApplicationController
  def create
    result = AuthenticateUser.call(session_params)

    if result.success?
      session[:user_token] = result.token
      redirect_to root_path
    else
      flash.now[:message] = t(result.error)
      render :new
    end
  end
end

For the sake of brevity, I have left out the private method that constructs the session_params.
I would like the service object (AuthenticateUser) to be able to return an error message and also to check if it successful or not. I appreciate suggestions on how to construct a module to handle errors which can bubble back up to the caller of the object.

Cheers

One option is to make use of ActiveModel in your service object. This allows you to treat the class in a very similar way to an ActiveRecord model, and take advantage of custom validations, the errors collection, etc.

class AuthenticateUser
  include ActiveModel::Model
  attr_accessor :username, :password
  attr_reader :token

  validate :check_credentials

  def save
    @token = ... # do something
  end

  private

  def check_credentials
    unless username == "foo" && password == "bar"
      errors.add(:base, "Invalid credentials")
    end
  end
end

The controller would then become:

class SessionsController < ApplicationController
  def create
    result = AuthenticateUser.new(
      username: session_params.fetch(:username),
      password: session_params.fetch(:password)
    )

    if result.save
      session[:user_token] = result.token
      redirect_to root_path
    else
      flash.now[:message] = t(result.errors[:base])
      render :new
    end
  end
end

Although it’s a little out-of-date, the book Growing Rails Applications covers this approach in good detail.

A downside is that the service class is coupled to Rails and can’t be tested in isolation.

1 Like