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.
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.