How to Refactor Your Rails App With Service Objects

How to Refactor Your Rails App With Service Objects

As Rails applications grow in complexity, it becomes crucial to maintain clean, readable, and maintainable code. One effective approach to achieve this is through refactoring your codebase using service objects. Service objects help extract complex business logic from models or controllers into separate classes, promoting better organization, testability, and reusability. In this article, we will explore the process of refactoring a Rails application with service objects, providing a practical example to demonstrate their benefits.

Identifying the Problem: To begin the refactoring process, start by identifying a section of code that contains complex logic or violates the Single Responsibility Principle (SRP). Let’s assume we have a Rails application with a UserController that handles user creation, validation, and notification.

Extracting a Service Object: To refactor this code, extract the complex logic related to user creation and notification into a separate service object. Create a new file called user_creation_service.rb under the app/services directory. Here’s an example of what the service object might look like:

#app/services/user_creation_service.rb

class UserCreationService

  def initialize(user_params)

    @user_params = user_params

  end

  def create_user

    user = User.new(@user_params)

    if user.save

      send_notification(user)

      true

    else

      false

    end

  end

  private

  def send_notification(user)

    # Logic for sending a notification to the user

  end

end

Updating the UserController: In the UserController, replace the complex logic with a call to the service object. Here’s an example of how it could be updated:

#app/controllers/users_controller.rb

class UsersController < ApplicationController

  def create

    user_service = UserCreationService.new(user_params)

    if user_service.create_user

      redirect_to root_path, notice: ‘User created successfully!’

    else

      render :new

    end

  end

  private

  def user_params

    params.require(:user).permit(:name, :email, :password)

  end

end

Testing the Service Object: Service objects can be easily tested in isolation, ensuring the correctness of the extracted logic. Write tests for the UserCreationService class, mocking dependencies as needed. For example:

#spec/services/user_creation_service_spec.rb

require ‘rails_helper’

RSpec.describe UserCreationService do

  describe ‘#create_user’ do

    it ‘creates a user and sends a notification’ do

      user_params = { name: ‘John Doe’, email: ‘[email protected]’, password: ‘password’ }

      user_service = UserCreationService.new(user_params)

      expect(user_service.create_user).to be true

      expect(Notification).to have_received(:send).with(instance_of(User))

    end

    it ‘returns false if user creation fails’ do

      # Test failure case

    end

  end

end

Conclusion: 

By refactoring your Rails application with service objects, you can effectively separate complex business logic, improve code organization, and enhance testability. The practical example provided demonstrates the process of extracting user creation and notification logic into a service object, resulting in cleaner and more maintainable code. Embrace the power of service objects to streamline your Rails app and enjoy the benefits of a modular and scalable codebase.



Related Posts

Leave a Comment

Your email address will not be published. Required fields are marked *

en_USEnglish