March 15, 2019
Once an application reaches certain size, questions about architecture begin to appear. Rails follows a Model View Controller organization and basic rules exist for clean code:
And it raises the first question: Where do I put all that code?
Service objects can be a class or a module in Ruby that perform an action and can help take out logic from other areas of the MVC structure. For a simple example, let’s say we have a controller like this:
class PostsController < ApplicationController
def create
@title = params[:title]
@content = params[:content]
@post = Post.new(title: @title, content: @content)
if @post.save
flash.notice = 'Post saved'
render @post
else
flash.alert = flash_error_message(@post)
redirect_to :new
end
end
end
Extracting some of this into a service object is easy once you understand the design pattern.
services
folder in the Rails’ app
foldercreate_post.rb
CreatePost
class/moduleUsing a module approach I created a service that looks very much like a factory design pattern:
module CreatePost
class << self
def execute(params)
title = params[:title]
content = params[:content]
post = Post.new(title: title, content: content)
end
end
end
Which in turn made the controller a lot more manageable:
class PostsController < ApplicationController
def create
@post = CreatePost.execute(params)
if @post.save
flash.notice = 'Post saved'
render @post
else
flash.alert = flash_error_message(@post)
redirect_to :new
end
end
end
In some cases we need to store instance variables and other methods, if so, we use classes. Using a class, our code could be rewritten as:
class CreatePost
def initialize(params)
@title = params[:title]
@content = params[:content]
end
def call
Post.new(title: @title, content: @content)
end
end
The code of the controller would be:
class PostsController < ApplicationController
def create
@post = CreatePost.new(params).call
if @post.save
flash.notice = 'Post saved'
render @post
else
flash.alert = flash_error_message(@post)
redirect_to :new
end
end
end
When we start using services our services
folder tends to grow a lot. We can manage this growth by creating a modular structure using folders and modules.
The services
folder can reflect the variety of service objects and it’s different uses in our app. We group them in namespaces using Ruby modules.
module Post
module Build
def self.call(params)
title = params[:title]
content = params[:content]
Post.new(title: title, content: content)
end
end
end
To achieve this we have to place them in folders that reflects our module structure to let Rails load them.
services/post/build.rb
services/post/update.rb
services/comments/build.rb
...
This way our use of service objects can scale with the growth of our app.
Crafted by Wilbur Suero, a Software Engineer, who is passionate about building innovative and impactful solutions that drive business growth and operational excellence.