Welcome back! By now, you have learned how to set up a Rails app, create controllers, and manage views. In our last lesson, we discussed service objects and how they help keep the business logic separate from controllers. When solving the previous practices, you may have noticed that in some cases we declared the data in many functions of the service instead of making it more structural. Today, we're taking this a step further by looking into dependency management for service objects in Rails. Effective dependency management ensures that our services are modular, testable, and easier to maintain.
In this lesson, we'll explore how to manage dependencies within service objects. Specifically, we’ll look at how to use dependency injection to make your services more flexible and easier to test. Using our SandwichService
example, you'll learn the specifics of constructing services with dependencies and see how this design improves code maintainability.
Here’s a preview of the SandwichService
class we'll be diving into:
Ruby1class SandwichService 2 def initialize(bread: Bread.new, cheese: Cheese.new, grill: Grill.new) 3 @bread = bread 4 @cheese = cheese 5 @grill = grill 6 end 7 8 def make_sandwich 9 @grill.turn_on 10 bread_name = @bread.get_name 11 cheese_name = @cheese.get_name 12 @grill.turn_off 13 "Sandwich with #{bread_name} and #{cheese_name}" 14 end 15end
This SandwichService
class illustrates a well-architected service object. It receives its dependencies (Bread
, Cheese
, and Grill
) through initialization, promoting flexibility and easier testing.
To understand how the SandwichService
works in context, here is the complete code along with all the necessary files.
Sandwich Service Object
app/services/sandwich_service.rb
Ruby1class SandwichService 2 def initialize(bread: Bread.new, cheese: Cheese.new, grill: Grill.new) 3 @bread = bread 4 @cheese = cheese 5 @grill = grill 6 end 7 8 def make_sandwich 9 @grill.turn_on 10 bread_name = @bread.get_name 11 cheese_name = @cheese.get_name 12 @grill.turn_off 13 "Sandwich with #{bread_name} and #{cheese_name}" 14 end 15end
Bread Dependency
app/services/bread.rb
Ruby1class Bread 2 def get_name 3 "Whole Wheat" 4 end 5end
Cheese Dependency
app/services/cheese.rb
Ruby1class Cheese 2 def get_name 3 "Cheddar" 4 end 5end
Grill Dependency
app/services/grill.rb
Ruby1class Grill 2 def turn_on 3 puts 'Grill is on' 4 end 5 6 def turn_off 7 puts 'Grill is off' 8 end 9end
Usage_example (Controller or any place you use the service)
app/controllers/sandwiches_controller.rb
Ruby1class SandwichesController < ApplicationController 2 def create 3 sandwich_service = SandwichService.new 4 @sandwich = sandwich_service.make_sandwich 5 render plain: @sandwich 6 end 7end
Routes
config/routes.rb
Ruby1Rails.application.routes.draw do 2 resources :sandwiches, only: [:create] 3end
Understanding dependency management in Rails is crucial for building scalable and maintainable applications. Good dependency management ensures that service objects are single-responsibility and can be reused across different parts of your application. It also makes the codebase easier to test, as dependencies can be mocked or stubbed during testing.
Benefits include:
- Modularity: Keeping services modular makes it easier to update parts of your application without affecting others.
- Testability: Injecting dependencies allows you to replace real objects with mocks or stubs, making unit tests more reliable and faster.
- Maintainability: Clear separation of concerns and well-defined dependencies improve the readability and maintainability of your code.
By mastering dependency management for service objects, you will be able to write cleaner, more efficient, and easier-to-maintain code. Exciting, right? Let’s start the practice section and put these principles into action.