Here's a quick and simple pattern that comes in handy when building simple service objects in Ruby applications. They can be used anywhere in the system to model a command but are commonly found at the boundary between the application domain and the UI layer. In the Rails world, this generally means the "in the controller". You may also know them as interactors.

The premise

The easiest way to explain the premise is with examples so let's have a quick look at a simple Rails controller example before and after extracting the service object.

Imagine we have a website that allows a user to sign in using a username and password. A simple sign_in action for our authentication controller might look something like this

# Simplified controller. In the real world we'd want to do
# things like sanitize params and encrypt passwords
class AuthenticationController < ApplicationController
	def sign_in
    	username = params[:username]
        password = params[:password]

        if User.exists?(username: username)
        	@user = User.find(username: username)
            if @user.password == password
            	session[:current_user] = @user
            	flash[:success] = 'You are now signed in'
                redirect_to :success
			else
            	flash[:error] = 'Login failed'
                redirect_to :login
			end
        else
        	flash[:error] = 'Login failed'
            redirect_to :login
        end
    end
end

Now let's jump straight to the good stuff and have a look at what this might look like once we've implemented our service object

class AuthenticationController < ApplicationController
	def sign_in
    	username = params[:username]
        password = params[:password]
        
        UserAuthenticator.new(username, password).authenticate do |response|
        	response.on(:success) do |user|
            	session[:current_user] = user
            	flash[:success] = 'You are now signed in'
            	redirect_to :success
            end
            
            response.on(:unknown_user, :invalid_password) do
            	flash[:error] = 'Login failed'
                redirect_to :error
            end
        end
    end
end

Why is this good?

So what makes the second example better than the first? There are a few reasons.

Separation of concerns

The most important reason that this is better in my opinion is that we have now separated the details of how we handle authentication from how we interact with the user. If we look at the controller code we can see that everything that remains is now specific to the controller. We fetch some values from params. We feed them to the authenticator and based on the response we store the user in the session, set a flash and perform a redirect. This is all web layer stuff and is now separate from the nuts and bolts of how we actually perform the authentication. This in turn then yields (no pun intended) a number of other benefits

Isolated change

If we want to change the way we interact with the user during the authentication process, the changes are limited to the controller. If we want to change how we actually perform the authentication the changes are limited to the authenticator. These two things are likely to change for different reasons. For example our UX guys might decide that we should show different messages and redirect to different actions depending on the type of error. This would require only changing the controller code. Conversely we may decide that we want to move our application to a service-oriented architecture and authenticate our users against an external REST API. In which case it is just our authenticator that needs to change, the controller remains the same. We don't know but the point is that by isolating the different areas of concern we have prepared ourself better for changes in the future.

Easier to reuse

In the first example, the logic of how to perform an authentication is embedded in the controller. If we wanted to reuse this logic elsewhere it would be tricky to do so. By separating out the business logic (how to perform the authentication) from the web layer, not only is it easier to reuse this elsewhere in the web layer but if we wanted to reuse it in other ways, for example in a CLI or as part of a script, it is easy to do so.

Easier to test

Again by separating the concerns we have made it easier to test the authentication logic separately to the controller. This makes it easier to write simpler yet more thorough tests of each part. We can also avoid hitting the persistence layer for our controller tests and loading the full Rails stack for our authenticator tests which helps keep our tests running nice and quick.

How it works

Now that we've seen what we are trying to achieve let's look at the code that makes it happen. Here is an example of what the UserAuthenticator in our second example might look like

class UserAuthenticator
	def initialize(username, password)
    	@username = username
        @password = password
    end

	def authenticate(username, password)
    	yield Response.new(:unknown_user) and return if unknown_user?
        yield Response.new(:invalid_password) and return if invalid_password?
        yield Response.new(:success, user)
    end

private
	attr_reader :username, :password

	def unknown_user?
    	!User.exists?(username: username)
	end
    
    def invalid_password?
    	user.password != password
    end
    
    def user
    	@user ||= User.find(username: username)
    end

    class Response
    	def initialize(result, *args)
        	@result = result
            @args = args
        end
        
    	def on *outcome
      		yield(*args) if outcome.include?(result)
    	end

    private
    	attr_reader :result, :args
    end
end

So actually we have two classes here. The UserAuthenticator itself and then an inner Response class that represents the response to be returned. Clearly it is more code than the first example, however both of these classes are tiny and simple. It is easy to see what criteria we are using for each response and because they are so simple, it is easy to see what is happening at each step. In practice, the response class is so generic you may wish to extract it and reuse it across several service objects.

I've pushed simple implementations of the service object and the response object up to Github. If the above example still seems complex to you, have a look at those as they are even simpler. You can also find examples of how to test them.