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 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 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 Service Created: class AuthorizeUser def self.call(user, user_type) user.is_a?(user_type) end end 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 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 Service Created: class AuthenticateUser def self.call(email_address, password) User.authenticate_by(email_address: email_address, password: password) end end 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]

May 1, 2025 - 16:30
 0

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

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

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

Service Created:

class AuthorizeUser
  def self.call(user, user_type)
    user.is_a?(user_type)
  end
end

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

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

Service Created:

class AuthenticateUser
  def self.call(email_address, password)
    User.authenticate_by(email_address: email_address, password: password)
  end
end

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]