DEV Community

Leonardo Fragozo
Leonardo Fragozo

Posted on

๐Ÿ“˜ Refactoring with Service Objects in Ruby on Rails

Image description

This tutorial documents two refactors applied in the ClinicSync project, a medical clinic management application developed as part of my MBA in Ruby on Rails. The goal was to apply architectural best practices using Service Objects to improve code readability, maintainability, and scalability.

What are Service Objects?

Service Objects are simple classes used to organize business logic that doesn't belong directly in models, controllers, or views.
They are a way to apply the Single Responsibility Principle (SRP) in practice, giving each part of the system a clearly defined role.


When to Use a Service Object

Use a Service Object when:

  • The logic is too complex to stay in the controller
  • The functionality doesn't naturally belong to a model
  • You need to reuse the same logic in multiple places
  • You want cleaner, more testable code

Basic Structure of a Service Object

class ExampleService
  def self.call(params)
    new(params).call
  end

  def initialize(params)
    @params = params
  end

  def call
    # main logic here
  end
end
Enter fullscreen mode Exit fullscreen mode

Refactor 1: AuthorizeUser Service

Goal:

Remove repeated user type verification logic from the controllers.

Before:

def require_admin
  unless user_signed_in? && current_user.is_a?(Admin)
    redirect_to root_path, alert: "You are not authorized to access this page."
  end
end
Enter fullscreen mode Exit fullscreen mode

After:

def require_admin
  unless user_signed_in? && AuthorizeUser.call(current_user, Admin)
    redirect_to root_path, alert: "You are not authorized to access this page."
  end
end
Enter fullscreen mode Exit fullscreen mode

Service Created:

class AuthorizeUser
  def self.call(user, user_type)
    user.is_a?(user_type)
  end
end
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • DRY logic
  • Easier to maintain
  • Cleaner and reusable code

Refactor 2: AuthenticateUser Service

Goal:

Remove user authentication logic from SessionsController.

Before:

if (user = User.authenticate_by(params.permit(:email_address, :password)))
  start_new_session_for user
  redirect_to home_path_for(user), notice: "Logged in successfully."
else
  redirect_to new_session_path, alert: "Try another email address or password."
end
Enter fullscreen mode Exit fullscreen mode

After:

user = AuthenticateUser.call(params[:email_address], params[:password])

if user
  start_new_session_for user
  redirect_to home_path_for(user), notice: "Logged in successfully."
else
  redirect_to new_session_path, alert: "Try another email address or password."
end
Enter fullscreen mode Exit fullscreen mode

Service Created:

class AuthenticateUser
  def self.call(email_address, password)
    User.authenticate_by(email_address: email_address, password: password)
  end
end
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Cleaner controller
  • Easy to extend for social login, 2FA, etc.
  • Clear separation of concerns

Conclusion

These two small Service Objects made the project:

  • Cleaner
  • More testable
  • Ready to scale

Service Objects are a powerful way to apply Clean Code and prepare your Rails application for future growth.


Written by: [Leonardo Quadros Fragozo]

Top comments (0)